From ee98026cc5c0d45bdbdfe2e477e0c7f810a81c4e Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Sun, 12 Jul 2026 15:55:18 +0530 Subject: [PATCH] first commit --- ')) | 0 .editorconfig | 9 + .gitignore | 9 + .prettierignore | 17 + .prettierrc.json | 9 + .publish/ai/README.md | 146 + .publish/ai/package.json | 26 + .publish/authz/README.md | 151 + .publish/authz/package.json | 26 + .publish/cli/README.md | 200 + .publish/cli/package.json | 45 + .publish/compiler/README.md | 165 + .publish/compiler/package.json | 26 + .publish/core/README.md | 381 ++ .publish/core/package.json | 34 + .publish/csr/README.md | 178 + .publish/csr/package.json | 29 + .publish/db/README.md | 228 + .publish/db/package.json | 50 + .publish/dev-server/README.md | 287 ++ .publish/dev-server/package.json | 44 + .publish/encryption/README.md | 80 + .publish/encryption/package.json | 26 + .publish/i18n/README.md | 167 + .publish/i18n/package.json | 29 + .publish/jwt/README.md | 132 + .publish/jwt/package.json | 26 + .publish/mobile/README.md | 24 + .publish/mobile/package.json | 29 + .publish/native/README.md | 17 + .publish/native/package.json | 34 + .publish/oauth/README.md | 196 + .publish/oauth/package.json | 26 + .publish/pubsub/README.md | 129 + .publish/pubsub/package.json | 30 + .publish/queue/README.md | 164 + .publish/queue/package.json | 26 + .publish/reactive/README.md | 99 + .publish/reactive/package.json | 26 + .publish/router/README.md | 174 + .publish/router/package.json | 30 + .publish/ssr/README.md | 105 + .publish/ssr/package.json | 29 + .publish/styles/README.md | 227 + .publish/styles/package.json | 29 + .publish/test/README.md | 150 + .publish/test/package.json | 26 + .publish/tracking/README.md | 128 + .publish/tracking/package.json | 26 + .publish/ui/README.md | 156 + .publish/ui/components/alert.wrn | 16 + .publish/ui/components/avatar.wrn | 11 + .publish/ui/components/badge.wrn | 11 + .publish/ui/components/button.wrn | 14 + .publish/ui/components/card.wrn | 9 + .publish/ui/components/checkbox.wrn | 14 + .publish/ui/components/container.wrn | 9 + .publish/ui/components/disclosure.wrn | 13 + .publish/ui/components/divider.wrn | 9 + .publish/ui/components/file-upload.wrn | 21 + .publish/ui/components/grid.wrn | 11 + .publish/ui/components/hstack.wrn | 12 + .publish/ui/components/input.wrn | 13 + .publish/ui/components/progress.wrn | 11 + .publish/ui/components/radio.wrn | 15 + .publish/ui/components/select.wrn | 10 + .publish/ui/components/skeleton.wrn | 11 + .publish/ui/components/spacer.wrn | 9 + .publish/ui/components/spinner.wrn | 9 + .publish/ui/components/stack.wrn | 10 + .publish/ui/components/switch.wrn | 15 + .publish/ui/components/table.wrn | 12 + .publish/ui/components/tag.wrn | 11 + .publish/ui/components/textarea.wrn | 12 + .publish/ui/components/theme-toggle.wrn | 11 + .publish/ui/components/tooltip.wrn | 10 + .publish/ui/package.json | 32 + .publish/ui/ui.css | 525 +++ .publish/uploader/README.md | 123 + .publish/uploader/package.json | 29 + .publish/validation/README.md | 179 + .publish/validation/package.json | 26 + .vscode/settings.json | 46 + README.md | 1506 +++++++ bun.lock | 801 ++++ docker-compose.yml | 32 + docs/GUIDE.md | 1387 ++++++ docs/PUBLISHING.md | 99 + .../2026-07-02-database-system-design.md | 172 + .../2026-07-02-wire-ui-foundation-design.md | 102 + editors/vscode/.vscode/launch.json | 16 + editors/vscode/.vscode/tasks.json | 26 + editors/vscode/.vscodeignore | 10 + editors/vscode/CHANGELOG.md | 6 + editors/vscode/LICENSE | 21 + editors/vscode/README.md | 51 + editors/vscode/SUPPORT.md | 7 + editors/vscode/icons/wrn.svg | 7 + editors/vscode/language-configuration.json | 46 + editors/vscode/package-lock.json | 3811 +++++++++++++++++ editors/vscode/package.json | 154 + editors/vscode/snippets/wrn.json | 114 + editors/vscode/src/compiler.cjs | 1282 ++++++ editors/vscode/src/extension.js | 235 + .../syntaxes/wrn.injection.tmLanguage.json | 20 + editors/vscode/syntaxes/wrn.tmLanguage.json | 306 ++ editors/vscode/test/validate.mjs | 62 + editors/vscode/wrnexus-0.1.0.vsix | Bin 0 -> 22754 bytes eslint.config.js | 58 + examples/basic-app/.editorconfig | 9 + examples/basic-app/.env | 2 + examples/basic-app/.env.uat | 3 + examples/basic-app/.prettierignore | 5 + examples/basic-app/.prettierrc.json | 9 + examples/basic-app/README.md | 38 + examples/basic-app/app/api/echo.ts | 13 + examples/basic-app/app/api/hello.ts | 7 + examples/basic-app/app/api/login.ts | 24 + examples/basic-app/app/api/logout.ts | 8 + examples/basic-app/app/api/me.ts | 11 + examples/basic-app/app/api/users/csr.ts | 8 + examples/basic-app/app/api/users/ssr.ts | 8 + examples/basic-app/app/components/counter.wrn | 20 + .../basic-app/app/db/migrations/0001_init.sql | 19 + .../app/db/migrations/0002_seed_users.sql | 7 + .../app/db/migrations/0003_add_password.sql | 5 + examples/basic-app/app/db/queries.gen.ts | 23 + examples/basic-app/app/db/queries/users.sql | 14 + examples/basic-app/app/db/schema.ts | 35 + examples/basic-app/app/db/seed.ts | 23 + examples/basic-app/app/env.ts | 14 + examples/basic-app/app/example.test.ts | 76 + examples/basic-app/app/layouts/auth.wrn | 11 + examples/basic-app/app/layouts/dashboard.wrn | 22 + examples/basic-app/app/layouts/public.wrn | 27 + examples/basic-app/app/locales/en.json | 16 + examples/basic-app/app/locales/es.json | 16 + examples/basic-app/app/middleware/auth.ts | 16 + examples/basic-app/app/middleware/logger.ts | 6 + .../basic-app/app/middleware/ratelimit.ts | 16 + examples/basic-app/app/pages/about.wrn | 15 + examples/basic-app/app/pages/chat.wrn | 58 + examples/basic-app/app/pages/dashboard.wrn | 34 + examples/basic-app/app/pages/hello.wrn | 130 + examples/basic-app/app/pages/index.wrn | 58 + examples/basic-app/app/pages/login.wrn | 34 + examples/basic-app/app/pages/reactive.wrn | 29 + examples/basic-app/app/pages/ui.wrn | 85 + examples/basic-app/app/realtime/chat.ts | 28 + examples/basic-app/app/routes.gen.ts | 31 + examples/basic-app/app/schemas/login.ts | 7 + examples/basic-app/app/styles/global.css | 170 + examples/basic-app/eslint.config.js | 37 + examples/basic-app/package.json | 31 + examples/basic-app/public/robots.txt | 2 + examples/basic-app/public/site.webmanifest | 6 + examples/basic-app/tsconfig.json | 7 + examples/basic-app/wrnexus.config.ts | 109 + llms.txt | 259 ++ package.json | 43 + packages/ai/README.md | 146 + packages/ai/package.json | 11 + packages/ai/src/index.ts | 247 ++ packages/ai/test/ai.test.ts | 56 + packages/authz/README.md | 151 + packages/authz/package.json | 10 + packages/authz/src/index.ts | 130 + packages/authz/test/authz.test.ts | 80 + packages/cli/README.md | 200 + packages/cli/package.json | 25 + packages/cli/src/ai-guide.ts | 275 ++ packages/cli/src/build.ts | 306 ++ packages/cli/src/create.ts | 391 ++ packages/cli/src/db.ts | 244 ++ packages/cli/src/dev.ts | 83 + packages/cli/src/docker.ts | 93 + packages/cli/src/doctor.ts | 68 + packages/cli/src/eject.ts | 37 + packages/cli/src/generate.ts | 127 + packages/cli/src/index.ts | 190 + packages/cli/src/mobile-command.ts | 209 + packages/cli/src/mobile.ts | 267 ++ packages/cli/src/native-command.ts | 101 + packages/cli/src/profiles.ts | 31 + packages/cli/src/routes.ts | 17 + packages/cli/src/test.ts | 26 + packages/cli/src/update.ts | 216 + packages/cli/src/workspace.ts | 218 + packages/cli/test/docker.test.ts | 30 + packages/cli/test/doctor.test.ts | 21 + packages/cli/test/generate.test.ts | 34 + packages/cli/test/mobile.test.ts | 100 + packages/compiler/README.md | 165 + packages/compiler/VISION.md | 62 + packages/compiler/package.json | 9 + packages/compiler/src/codegen.ts | 862 ++++ packages/compiler/src/index.ts | 61 + packages/compiler/src/native-codegen.ts | 233 + packages/compiler/src/parser.ts | 622 +++ packages/compiler/src/tokenizer.ts | 184 + packages/compiler/test/compiler.test.ts | 136 + packages/compiler/test/native-codegen.test.ts | 61 + packages/core/README.md | 381 ++ packages/core/package.json | 11 + packages/core/src/auth.ts | 95 + packages/core/src/cache.ts | 145 + packages/core/src/context.ts | 119 + packages/core/src/csrf.ts | 62 + packages/core/src/errors.ts | 220 + packages/core/src/headers.ts | 420 ++ packages/core/src/index.ts | 106 + packages/core/src/jsx-dev-runtime.ts | 2 + packages/core/src/jsx-runtime.ts | 175 + packages/core/src/logging.ts | 62 + packages/core/src/ratelimit.ts | 126 + packages/core/src/realtime.ts | 409 ++ packages/core/src/security.ts | 53 + packages/core/src/storage.ts | 389 ++ packages/core/src/stream.ts | 93 + packages/core/src/uploads.ts | 78 + packages/core/test/auth.test.ts | 93 + packages/core/test/csrf.test.ts | 28 + packages/core/test/headers.test.ts | 66 + packages/core/test/jsx-security.test.ts | 13 + packages/core/test/middleware.test.ts | 292 ++ packages/core/test/realtime-bridge.test.ts | 63 + packages/core/test/realtime.test.ts | 161 + packages/core/test/stream.test.ts | 38 + packages/csr/README.md | 178 + packages/csr/package.json | 12 + packages/csr/src/index.ts | 31 + packages/csr/src/nav-runtime.ts | 129 + packages/csr/src/reactive-runtime.ts | 577 +++ packages/csr/src/realtime-runtime.ts | 188 + packages/csr/test/nav.test.ts | 93 + packages/csr/test/reactive.test.ts | 149 + packages/csr/test/realtime.test.ts | 123 + packages/db/README.md | 228 + packages/db/package.json | 16 + packages/db/src/adapters/bunsql.ts | 69 + packages/db/src/adapters/mongo.ts | 120 + packages/db/src/adapters/mysql.ts | 1 + packages/db/src/adapters/postgres.ts | 1 + packages/db/src/adapters/sqlite.ts | 60 + packages/db/src/client.ts | 61 + packages/db/src/connect.ts | 42 + packages/db/src/driver.ts | 86 + packages/db/src/generate.ts | 232 + packages/db/src/index.ts | 29 + packages/db/src/migrate.ts | 164 + packages/db/src/query.ts | 145 + packages/db/src/schema.ts | 129 + packages/db/src/session-store.ts | 48 + packages/db/src/sql.ts | 68 + packages/db/test/db.test.ts | 195 + packages/db/test/live-db.test.ts | 77 + packages/db/test/registry.test.ts | 47 + packages/dev-server/README.md | 287 ++ packages/dev-server/package.json | 24 + packages/dev-server/src/adapters/node.ts | 113 + packages/dev-server/src/assets.ts | 118 + packages/dev-server/src/gateway.ts | 340 ++ packages/dev-server/src/hmr.ts | 56 + packages/dev-server/src/index.ts | 259 ++ packages/dev-server/src/pipeline.ts | 111 + packages/dev-server/src/prod.ts | 369 ++ packages/dev-server/src/public.ts | 127 + packages/dev-server/src/realtime-bus.ts | 18 + packages/dev-server/src/runtime.ts | 1422 ++++++ packages/dev-server/src/serve-entry.ts | 61 + packages/dev-server/src/watch.ts | 75 + packages/dev-server/test/hmr.test.ts | 10 + .../dev-server/test/native-runtime.test.ts | 10 + packages/dev-server/test/node-adapter.test.ts | 97 + packages/dev-server/test/props.test.ts | 18 + packages/dev-server/test/public.test.ts | 22 + packages/encryption/README.md | 80 + packages/encryption/package.json | 10 + packages/encryption/src/index.ts | 140 + packages/encryption/test/encryption.test.ts | 58 + packages/i18n/README.md | 167 + packages/i18n/package.json | 13 + packages/i18n/src/format.ts | 52 + packages/i18n/src/index.ts | 172 + packages/i18n/test/format.test.ts | 35 + packages/i18n/test/i18n.test.ts | 40 + packages/jwt/README.md | 132 + packages/jwt/package.json | 10 + packages/jwt/src/index.ts | 185 + packages/jwt/test/jwt.test.ts | 65 + packages/mobile/README.md | 24 + packages/mobile/package.json | 12 + packages/mobile/src/index.ts | 91 + packages/mobile/test/mobile.test.ts | 61 + packages/native/README.md | 17 + packages/native/package.json | 11 + packages/native/src/browser.ts | 121 + packages/native/src/index.ts | 24 + packages/native/src/mobile.ts | 69 + packages/native/src/registry.ts | 106 + packages/native/src/types.ts | 25 + packages/native/test/native.test.ts | 88 + packages/oauth/README.md | 196 + packages/oauth/package.json | 10 + packages/oauth/src/index.ts | 243 ++ packages/oauth/test/oauth.test.ts | 95 + packages/pubsub/README.md | 129 + packages/pubsub/package.json | 11 + packages/pubsub/src/index.ts | 63 + packages/pubsub/src/redis.ts | 182 + packages/pubsub/src/resp.ts | 92 + packages/pubsub/test/pubsub.test.ts | 37 + packages/pubsub/test/redis-driver.test.ts | 33 + packages/pubsub/test/resp.test.ts | 51 + packages/queue/README.md | 164 + packages/queue/package.json | 10 + packages/queue/src/index.ts | 149 + packages/queue/test/queue.test.ts | 106 + packages/reactive/README.md | 99 + packages/reactive/package.json | 9 + packages/reactive/src/index.ts | 5 + packages/reactive/src/signal.ts | 53 + packages/reactive/test/signal.test.ts | 17 + packages/router/README.md | 174 + packages/router/package.json | 13 + packages/router/src/index.ts | 200 + packages/router/src/match.ts | 81 + packages/router/src/routes-gen.ts | 46 + packages/router/src/scan.ts | 59 + packages/router/test/routes-gen.test.ts | 44 + packages/ssr/README.md | 105 + packages/ssr/package.json | 12 + packages/ssr/src/index.ts | 175 + packages/ssr/test/ssr.test.ts | 24 + packages/styles/README.md | 227 + packages/styles/package.json | 12 + packages/styles/src/config.ts | 291 ++ packages/styles/src/fonts.ts | 185 + packages/styles/src/index.ts | 85 + packages/styles/src/styles.ts | 67 + packages/styles/src/theme.ts | 149 + packages/styles/test/config.test.ts | 68 + packages/styles/test/fonts.test.ts | 22 + packages/styles/test/theme.test.ts | 42 + packages/test/README.md | 150 + packages/test/package.json | 10 + packages/test/src/index.ts | 177 + packages/test/test/test-utils.test.ts | 32 + packages/tracking/README.md | 128 + packages/tracking/package.json | 10 + packages/tracking/src/index.ts | 95 + packages/tracking/test/tracking.test.ts | 61 + packages/ui/README.md | 156 + packages/ui/components/alert.wrn | 16 + packages/ui/components/avatar.wrn | 11 + packages/ui/components/badge.wrn | 11 + packages/ui/components/button.wrn | 14 + packages/ui/components/card.wrn | 9 + packages/ui/components/checkbox.wrn | 14 + packages/ui/components/container.wrn | 9 + packages/ui/components/disclosure.wrn | 13 + packages/ui/components/divider.wrn | 9 + packages/ui/components/file-upload.wrn | 21 + packages/ui/components/grid.wrn | 11 + packages/ui/components/hstack.wrn | 12 + packages/ui/components/input.wrn | 13 + packages/ui/components/progress.wrn | 11 + packages/ui/components/radio.wrn | 15 + packages/ui/components/select.wrn | 10 + packages/ui/components/skeleton.wrn | 11 + packages/ui/components/spacer.wrn | 9 + packages/ui/components/spinner.wrn | 9 + packages/ui/components/stack.wrn | 10 + packages/ui/components/switch.wrn | 15 + packages/ui/components/table.wrn | 12 + packages/ui/components/tag.wrn | 11 + packages/ui/components/textarea.wrn | 12 + packages/ui/components/theme-toggle.wrn | 11 + packages/ui/components/tooltip.wrn | 10 + packages/ui/package.json | 14 + packages/ui/src/index.ts | 48 + packages/ui/test/ui.test.ts | 10 + packages/ui/ui.css | 525 +++ packages/uploader/README.md | 123 + packages/uploader/package.json | 13 + packages/uploader/src/adapters/local.ts | 55 + packages/uploader/src/adapters/s3.ts | 102 + packages/uploader/src/client.ts | 56 + packages/uploader/src/driver.ts | 88 + packages/uploader/src/index.ts | 62 + packages/uploader/src/mime.ts | 81 + packages/uploader/src/runtime.ts | 202 + packages/uploader/src/sigv4.ts | 109 + packages/uploader/src/upload.ts | 227 + packages/uploader/test/s3.test.ts | 42 + packages/uploader/test/uploader.test.ts | 36 + packages/validation/README.md | 179 + packages/validation/package.json | 10 + packages/validation/src/index.ts | 375 ++ packages/validation/src/runtime.ts | 174 + packages/validation/test/validation.test.ts | 162 + scripts/publish-packages.ts | 233 + scripts/test-live-db.sh | 27 + tsconfig.json | 44 + 404 files changed, 44522 insertions(+) create mode 100644 ')) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 .publish/ai/README.md create mode 100644 .publish/ai/package.json create mode 100644 .publish/authz/README.md create mode 100644 .publish/authz/package.json create mode 100644 .publish/cli/README.md create mode 100644 .publish/cli/package.json create mode 100644 .publish/compiler/README.md create mode 100644 .publish/compiler/package.json create mode 100644 .publish/core/README.md create mode 100644 .publish/core/package.json create mode 100644 .publish/csr/README.md create mode 100644 .publish/csr/package.json create mode 100644 .publish/db/README.md create mode 100644 .publish/db/package.json create mode 100644 .publish/dev-server/README.md create mode 100644 .publish/dev-server/package.json create mode 100644 .publish/encryption/README.md create mode 100644 .publish/encryption/package.json create mode 100644 .publish/i18n/README.md create mode 100644 .publish/i18n/package.json create mode 100644 .publish/jwt/README.md create mode 100644 .publish/jwt/package.json create mode 100644 .publish/mobile/README.md create mode 100644 .publish/mobile/package.json create mode 100644 .publish/native/README.md create mode 100644 .publish/native/package.json create mode 100644 .publish/oauth/README.md create mode 100644 .publish/oauth/package.json create mode 100644 .publish/pubsub/README.md create mode 100644 .publish/pubsub/package.json create mode 100644 .publish/queue/README.md create mode 100644 .publish/queue/package.json create mode 100644 .publish/reactive/README.md create mode 100644 .publish/reactive/package.json create mode 100644 .publish/router/README.md create mode 100644 .publish/router/package.json create mode 100644 .publish/ssr/README.md create mode 100644 .publish/ssr/package.json create mode 100644 .publish/styles/README.md create mode 100644 .publish/styles/package.json create mode 100644 .publish/test/README.md create mode 100644 .publish/test/package.json create mode 100644 .publish/tracking/README.md create mode 100644 .publish/tracking/package.json create mode 100644 .publish/ui/README.md create mode 100644 .publish/ui/components/alert.wrn create mode 100644 .publish/ui/components/avatar.wrn create mode 100644 .publish/ui/components/badge.wrn create mode 100644 .publish/ui/components/button.wrn create mode 100644 .publish/ui/components/card.wrn create mode 100644 .publish/ui/components/checkbox.wrn create mode 100644 .publish/ui/components/container.wrn create mode 100644 .publish/ui/components/disclosure.wrn create mode 100644 .publish/ui/components/divider.wrn create mode 100644 .publish/ui/components/file-upload.wrn create mode 100644 .publish/ui/components/grid.wrn create mode 100644 .publish/ui/components/hstack.wrn create mode 100644 .publish/ui/components/input.wrn create mode 100644 .publish/ui/components/progress.wrn create mode 100644 .publish/ui/components/radio.wrn create mode 100644 .publish/ui/components/select.wrn create mode 100644 .publish/ui/components/skeleton.wrn create mode 100644 .publish/ui/components/spacer.wrn create mode 100644 .publish/ui/components/spinner.wrn create mode 100644 .publish/ui/components/stack.wrn create mode 100644 .publish/ui/components/switch.wrn create mode 100644 .publish/ui/components/table.wrn create mode 100644 .publish/ui/components/tag.wrn create mode 100644 .publish/ui/components/textarea.wrn create mode 100644 .publish/ui/components/theme-toggle.wrn create mode 100644 .publish/ui/components/tooltip.wrn create mode 100644 .publish/ui/package.json create mode 100644 .publish/ui/ui.css create mode 100644 .publish/uploader/README.md create mode 100644 .publish/uploader/package.json create mode 100644 .publish/validation/README.md create mode 100644 .publish/validation/package.json create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 bun.lock create mode 100644 docker-compose.yml create mode 100644 docs/GUIDE.md create mode 100644 docs/PUBLISHING.md create mode 100644 docs/plans/2026-07-02-database-system-design.md create mode 100644 docs/plans/2026-07-02-wire-ui-foundation-design.md create mode 100644 editors/vscode/.vscode/launch.json create mode 100644 editors/vscode/.vscode/tasks.json create mode 100644 editors/vscode/.vscodeignore create mode 100644 editors/vscode/CHANGELOG.md create mode 100644 editors/vscode/LICENSE create mode 100644 editors/vscode/README.md create mode 100644 editors/vscode/SUPPORT.md create mode 100644 editors/vscode/icons/wrn.svg create mode 100644 editors/vscode/language-configuration.json create mode 100644 editors/vscode/package-lock.json create mode 100644 editors/vscode/package.json create mode 100644 editors/vscode/snippets/wrn.json create mode 100644 editors/vscode/src/compiler.cjs create mode 100644 editors/vscode/src/extension.js create mode 100644 editors/vscode/syntaxes/wrn.injection.tmLanguage.json create mode 100644 editors/vscode/syntaxes/wrn.tmLanguage.json create mode 100644 editors/vscode/test/validate.mjs create mode 100644 editors/vscode/wrnexus-0.1.0.vsix create mode 100644 eslint.config.js create mode 100644 examples/basic-app/.editorconfig create mode 100644 examples/basic-app/.env create mode 100644 examples/basic-app/.env.uat create mode 100644 examples/basic-app/.prettierignore create mode 100644 examples/basic-app/.prettierrc.json create mode 100644 examples/basic-app/README.md create mode 100644 examples/basic-app/app/api/echo.ts create mode 100644 examples/basic-app/app/api/hello.ts create mode 100644 examples/basic-app/app/api/login.ts create mode 100644 examples/basic-app/app/api/logout.ts create mode 100644 examples/basic-app/app/api/me.ts create mode 100644 examples/basic-app/app/api/users/csr.ts create mode 100644 examples/basic-app/app/api/users/ssr.ts create mode 100644 examples/basic-app/app/components/counter.wrn create mode 100644 examples/basic-app/app/db/migrations/0001_init.sql create mode 100644 examples/basic-app/app/db/migrations/0002_seed_users.sql create mode 100644 examples/basic-app/app/db/migrations/0003_add_password.sql create mode 100644 examples/basic-app/app/db/queries.gen.ts create mode 100644 examples/basic-app/app/db/queries/users.sql create mode 100644 examples/basic-app/app/db/schema.ts create mode 100644 examples/basic-app/app/db/seed.ts create mode 100644 examples/basic-app/app/env.ts create mode 100644 examples/basic-app/app/example.test.ts create mode 100644 examples/basic-app/app/layouts/auth.wrn create mode 100644 examples/basic-app/app/layouts/dashboard.wrn create mode 100644 examples/basic-app/app/layouts/public.wrn create mode 100644 examples/basic-app/app/locales/en.json create mode 100644 examples/basic-app/app/locales/es.json create mode 100644 examples/basic-app/app/middleware/auth.ts create mode 100644 examples/basic-app/app/middleware/logger.ts create mode 100644 examples/basic-app/app/middleware/ratelimit.ts create mode 100644 examples/basic-app/app/pages/about.wrn create mode 100644 examples/basic-app/app/pages/chat.wrn create mode 100644 examples/basic-app/app/pages/dashboard.wrn create mode 100644 examples/basic-app/app/pages/hello.wrn create mode 100644 examples/basic-app/app/pages/index.wrn create mode 100644 examples/basic-app/app/pages/login.wrn create mode 100644 examples/basic-app/app/pages/reactive.wrn create mode 100644 examples/basic-app/app/pages/ui.wrn create mode 100644 examples/basic-app/app/realtime/chat.ts create mode 100644 examples/basic-app/app/routes.gen.ts create mode 100644 examples/basic-app/app/schemas/login.ts create mode 100644 examples/basic-app/app/styles/global.css create mode 100644 examples/basic-app/eslint.config.js create mode 100644 examples/basic-app/package.json create mode 100644 examples/basic-app/public/robots.txt create mode 100644 examples/basic-app/public/site.webmanifest create mode 100644 examples/basic-app/tsconfig.json create mode 100644 examples/basic-app/wrnexus.config.ts create mode 100644 llms.txt create mode 100644 package.json create mode 100644 packages/ai/README.md create mode 100644 packages/ai/package.json create mode 100644 packages/ai/src/index.ts create mode 100644 packages/ai/test/ai.test.ts create mode 100644 packages/authz/README.md create mode 100644 packages/authz/package.json create mode 100644 packages/authz/src/index.ts create mode 100644 packages/authz/test/authz.test.ts create mode 100644 packages/cli/README.md create mode 100644 packages/cli/package.json create mode 100644 packages/cli/src/ai-guide.ts create mode 100644 packages/cli/src/build.ts create mode 100644 packages/cli/src/create.ts create mode 100644 packages/cli/src/db.ts create mode 100644 packages/cli/src/dev.ts create mode 100644 packages/cli/src/docker.ts create mode 100644 packages/cli/src/doctor.ts create mode 100644 packages/cli/src/eject.ts create mode 100644 packages/cli/src/generate.ts create mode 100644 packages/cli/src/index.ts create mode 100644 packages/cli/src/mobile-command.ts create mode 100644 packages/cli/src/mobile.ts create mode 100644 packages/cli/src/native-command.ts create mode 100644 packages/cli/src/profiles.ts create mode 100644 packages/cli/src/routes.ts create mode 100644 packages/cli/src/test.ts create mode 100644 packages/cli/src/update.ts create mode 100644 packages/cli/src/workspace.ts create mode 100644 packages/cli/test/docker.test.ts create mode 100644 packages/cli/test/doctor.test.ts create mode 100644 packages/cli/test/generate.test.ts create mode 100644 packages/cli/test/mobile.test.ts create mode 100644 packages/compiler/README.md create mode 100644 packages/compiler/VISION.md create mode 100644 packages/compiler/package.json create mode 100644 packages/compiler/src/codegen.ts create mode 100644 packages/compiler/src/index.ts create mode 100644 packages/compiler/src/native-codegen.ts create mode 100644 packages/compiler/src/parser.ts create mode 100644 packages/compiler/src/tokenizer.ts create mode 100644 packages/compiler/test/compiler.test.ts create mode 100644 packages/compiler/test/native-codegen.test.ts create mode 100644 packages/core/README.md create mode 100644 packages/core/package.json create mode 100644 packages/core/src/auth.ts create mode 100644 packages/core/src/cache.ts create mode 100644 packages/core/src/context.ts create mode 100644 packages/core/src/csrf.ts create mode 100644 packages/core/src/errors.ts create mode 100644 packages/core/src/headers.ts create mode 100644 packages/core/src/index.ts create mode 100644 packages/core/src/jsx-dev-runtime.ts create mode 100644 packages/core/src/jsx-runtime.ts create mode 100644 packages/core/src/logging.ts create mode 100644 packages/core/src/ratelimit.ts create mode 100644 packages/core/src/realtime.ts create mode 100644 packages/core/src/security.ts create mode 100644 packages/core/src/storage.ts create mode 100644 packages/core/src/stream.ts create mode 100644 packages/core/src/uploads.ts create mode 100644 packages/core/test/auth.test.ts create mode 100644 packages/core/test/csrf.test.ts create mode 100644 packages/core/test/headers.test.ts create mode 100644 packages/core/test/jsx-security.test.ts create mode 100644 packages/core/test/middleware.test.ts create mode 100644 packages/core/test/realtime-bridge.test.ts create mode 100644 packages/core/test/realtime.test.ts create mode 100644 packages/core/test/stream.test.ts create mode 100644 packages/csr/README.md create mode 100644 packages/csr/package.json create mode 100644 packages/csr/src/index.ts create mode 100644 packages/csr/src/nav-runtime.ts create mode 100644 packages/csr/src/reactive-runtime.ts create mode 100644 packages/csr/src/realtime-runtime.ts create mode 100644 packages/csr/test/nav.test.ts create mode 100644 packages/csr/test/reactive.test.ts create mode 100644 packages/csr/test/realtime.test.ts create mode 100644 packages/db/README.md create mode 100644 packages/db/package.json create mode 100644 packages/db/src/adapters/bunsql.ts create mode 100644 packages/db/src/adapters/mongo.ts create mode 100644 packages/db/src/adapters/mysql.ts create mode 100644 packages/db/src/adapters/postgres.ts create mode 100644 packages/db/src/adapters/sqlite.ts create mode 100644 packages/db/src/client.ts create mode 100644 packages/db/src/connect.ts create mode 100644 packages/db/src/driver.ts create mode 100644 packages/db/src/generate.ts create mode 100644 packages/db/src/index.ts create mode 100644 packages/db/src/migrate.ts create mode 100644 packages/db/src/query.ts create mode 100644 packages/db/src/schema.ts create mode 100644 packages/db/src/session-store.ts create mode 100644 packages/db/src/sql.ts create mode 100644 packages/db/test/db.test.ts create mode 100644 packages/db/test/live-db.test.ts create mode 100644 packages/db/test/registry.test.ts create mode 100644 packages/dev-server/README.md create mode 100644 packages/dev-server/package.json create mode 100644 packages/dev-server/src/adapters/node.ts create mode 100644 packages/dev-server/src/assets.ts create mode 100644 packages/dev-server/src/gateway.ts create mode 100644 packages/dev-server/src/hmr.ts create mode 100644 packages/dev-server/src/index.ts create mode 100644 packages/dev-server/src/pipeline.ts create mode 100644 packages/dev-server/src/prod.ts create mode 100644 packages/dev-server/src/public.ts create mode 100644 packages/dev-server/src/realtime-bus.ts create mode 100644 packages/dev-server/src/runtime.ts create mode 100644 packages/dev-server/src/serve-entry.ts create mode 100644 packages/dev-server/src/watch.ts create mode 100644 packages/dev-server/test/hmr.test.ts create mode 100644 packages/dev-server/test/native-runtime.test.ts create mode 100644 packages/dev-server/test/node-adapter.test.ts create mode 100644 packages/dev-server/test/props.test.ts create mode 100644 packages/dev-server/test/public.test.ts create mode 100644 packages/encryption/README.md create mode 100644 packages/encryption/package.json create mode 100644 packages/encryption/src/index.ts create mode 100644 packages/encryption/test/encryption.test.ts create mode 100644 packages/i18n/README.md create mode 100644 packages/i18n/package.json create mode 100644 packages/i18n/src/format.ts create mode 100644 packages/i18n/src/index.ts create mode 100644 packages/i18n/test/format.test.ts create mode 100644 packages/i18n/test/i18n.test.ts create mode 100644 packages/jwt/README.md create mode 100644 packages/jwt/package.json create mode 100644 packages/jwt/src/index.ts create mode 100644 packages/jwt/test/jwt.test.ts create mode 100644 packages/mobile/README.md create mode 100644 packages/mobile/package.json create mode 100644 packages/mobile/src/index.ts create mode 100644 packages/mobile/test/mobile.test.ts create mode 100644 packages/native/README.md create mode 100644 packages/native/package.json create mode 100644 packages/native/src/browser.ts create mode 100644 packages/native/src/index.ts create mode 100644 packages/native/src/mobile.ts create mode 100644 packages/native/src/registry.ts create mode 100644 packages/native/src/types.ts create mode 100644 packages/native/test/native.test.ts create mode 100644 packages/oauth/README.md create mode 100644 packages/oauth/package.json create mode 100644 packages/oauth/src/index.ts create mode 100644 packages/oauth/test/oauth.test.ts create mode 100644 packages/pubsub/README.md create mode 100644 packages/pubsub/package.json create mode 100644 packages/pubsub/src/index.ts create mode 100644 packages/pubsub/src/redis.ts create mode 100644 packages/pubsub/src/resp.ts create mode 100644 packages/pubsub/test/pubsub.test.ts create mode 100644 packages/pubsub/test/redis-driver.test.ts create mode 100644 packages/pubsub/test/resp.test.ts create mode 100644 packages/queue/README.md create mode 100644 packages/queue/package.json create mode 100644 packages/queue/src/index.ts create mode 100644 packages/queue/test/queue.test.ts create mode 100644 packages/reactive/README.md create mode 100644 packages/reactive/package.json create mode 100644 packages/reactive/src/index.ts create mode 100644 packages/reactive/src/signal.ts create mode 100644 packages/reactive/test/signal.test.ts create mode 100644 packages/router/README.md create mode 100644 packages/router/package.json create mode 100644 packages/router/src/index.ts create mode 100644 packages/router/src/match.ts create mode 100644 packages/router/src/routes-gen.ts create mode 100644 packages/router/src/scan.ts create mode 100644 packages/router/test/routes-gen.test.ts create mode 100644 packages/ssr/README.md create mode 100644 packages/ssr/package.json create mode 100644 packages/ssr/src/index.ts create mode 100644 packages/ssr/test/ssr.test.ts create mode 100644 packages/styles/README.md create mode 100644 packages/styles/package.json create mode 100644 packages/styles/src/config.ts create mode 100644 packages/styles/src/fonts.ts create mode 100644 packages/styles/src/index.ts create mode 100644 packages/styles/src/styles.ts create mode 100644 packages/styles/src/theme.ts create mode 100644 packages/styles/test/config.test.ts create mode 100644 packages/styles/test/fonts.test.ts create mode 100644 packages/styles/test/theme.test.ts create mode 100644 packages/test/README.md create mode 100644 packages/test/package.json create mode 100644 packages/test/src/index.ts create mode 100644 packages/test/test/test-utils.test.ts create mode 100644 packages/tracking/README.md create mode 100644 packages/tracking/package.json create mode 100644 packages/tracking/src/index.ts create mode 100644 packages/tracking/test/tracking.test.ts create mode 100644 packages/ui/README.md create mode 100644 packages/ui/components/alert.wrn create mode 100644 packages/ui/components/avatar.wrn create mode 100644 packages/ui/components/badge.wrn create mode 100644 packages/ui/components/button.wrn create mode 100644 packages/ui/components/card.wrn create mode 100644 packages/ui/components/checkbox.wrn create mode 100644 packages/ui/components/container.wrn create mode 100644 packages/ui/components/disclosure.wrn create mode 100644 packages/ui/components/divider.wrn create mode 100644 packages/ui/components/file-upload.wrn create mode 100644 packages/ui/components/grid.wrn create mode 100644 packages/ui/components/hstack.wrn create mode 100644 packages/ui/components/input.wrn create mode 100644 packages/ui/components/progress.wrn create mode 100644 packages/ui/components/radio.wrn create mode 100644 packages/ui/components/select.wrn create mode 100644 packages/ui/components/skeleton.wrn create mode 100644 packages/ui/components/spacer.wrn create mode 100644 packages/ui/components/spinner.wrn create mode 100644 packages/ui/components/stack.wrn create mode 100644 packages/ui/components/switch.wrn create mode 100644 packages/ui/components/table.wrn create mode 100644 packages/ui/components/tag.wrn create mode 100644 packages/ui/components/textarea.wrn create mode 100644 packages/ui/components/theme-toggle.wrn create mode 100644 packages/ui/components/tooltip.wrn create mode 100644 packages/ui/package.json create mode 100644 packages/ui/src/index.ts create mode 100644 packages/ui/test/ui.test.ts create mode 100644 packages/ui/ui.css create mode 100644 packages/uploader/README.md create mode 100644 packages/uploader/package.json create mode 100644 packages/uploader/src/adapters/local.ts create mode 100644 packages/uploader/src/adapters/s3.ts create mode 100644 packages/uploader/src/client.ts create mode 100644 packages/uploader/src/driver.ts create mode 100644 packages/uploader/src/index.ts create mode 100644 packages/uploader/src/mime.ts create mode 100644 packages/uploader/src/runtime.ts create mode 100644 packages/uploader/src/sigv4.ts create mode 100644 packages/uploader/src/upload.ts create mode 100644 packages/uploader/test/s3.test.ts create mode 100644 packages/uploader/test/uploader.test.ts create mode 100644 packages/validation/README.md create mode 100644 packages/validation/package.json create mode 100644 packages/validation/src/index.ts create mode 100644 packages/validation/src/runtime.ts create mode 100644 packages/validation/test/validation.test.ts create mode 100644 scripts/publish-packages.ts create mode 100644 scripts/test-live-db.sh create mode 100644 tsconfig.json diff --git a/')) b/')) new file mode 100644 index 00000000..e69de29b diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..86a63dc0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2dedf281 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +dist/ +.wrnexus/ +*.log +*.db +*.db-shm +*.db-wal +.DS_Store +bun.lockb diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..b38548d9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,17 @@ +node_modules/ +dist/ +**/dist/ +.publish/ +**/.wirefw/ +coverage/ +bun.lock +bun.lockb +*.log + +# Generated code (queries.gen.ts, routes.gen.ts, etc.) +**/*.gen.ts + +# Bundled .wire compiler for the VS Code extension (generated) +editors/vscode/src/compiler.cjs +*.svg +**/.vscodeignore diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..32474fc7 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "endOfLine": "lf" +} diff --git a/.publish/ai/README.md b/.publish/ai/README.md new file mode 100644 index 00000000..bf3ca85f --- /dev/null +++ b/.publish/ai/README.md @@ -0,0 +1,146 @@ +# @wrnexus/ai + +> A tiny, zero-dependency Claude (Anthropic) client for WrNexus apps — generate and stream text with Claude from any server-side code. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/ai` is a thin, dependency-free wrapper over the Anthropic **Messages API**, +built on `fetch` (Bun-native, no SDK). Use it in API routes, jobs, or middleware to +call Claude. It defaults to the most capable model, **`claude-opus-4-8`**, reads your +key from `ANTHROPIC_API_KEY`, and supports both one-shot generation and streaming. + +## Installation + +```bash +bun add @wrnexus/ai +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +Set your key in the environment (e.g. `.env`): + +``` +ANTHROPIC_API_KEY=sk-ant-... +``` + +## API + +### `createAI(config?)` + +Creates a client. The key is read at call time, so it's safe to create at import. + +```ts +import { createAI } from "@wrnexus/ai"; +const ai = createAI(); // or createAI({ apiKey, model, maxTokens, baseURL, version }) +``` + +`AIConfig` fields (all optional): + +| Field | Default | Description | +| ----------- | --------------------------- | -------------------------- | +| `apiKey` | `ANTHROPIC_API_KEY` | Anthropic API key | +| `model` | `"claude-opus-4-8"` | Model id | +| `maxTokens` | `4096` | Default max output tokens | +| `baseURL` | `https://api.anthropic.com` | API base URL | +| `version` | `"2023-06-01"` | `anthropic-version` header | + +### `ai.generate(prompt, opts?): Promise` + +One-shot text generation. `prompt` is a string or a `Message[]` history. + +```ts +const text = await ai.generate("Write a haiku about Bun."); + +const reply = await ai.generate( + [ + { role: "user", content: "My name is Ada." }, + { role: "assistant", content: "Hi Ada!" }, + { role: "user", content: "What's my name?" }, + ], + { system: "You are concise." }, +); +``` + +### `ai.stream(prompt, opts?): AsyncGenerator` + +Yields text deltas as they arrive. + +```ts +for await (const chunk of ai.stream("Tell me a story.")) { + process.stdout.write(chunk); +} +``` + +### `ai.streamResponse(prompt, opts?): Response` + +Returns a streaming `text/plain` `Response` — drop it straight into an API route. + +```ts +// app/api/chat.ts +import { createAI } from "@wrnexus/ai"; +const ai = createAI(); + +export const POST = async (ctx) => { + const { prompt } = await ctx.req.json(); + return ai.streamResponse(prompt); +}; +``` + +### `GenerateOptions` + +| Option | Type | Description | +| ----------- | ------------------------------------------------- | ---------------------------------------------------- | +| `system` | `string` | System prompt | +| `model` | `string` | Override the model for this call | +| `maxTokens` | `number` | Override max output tokens | +| `thinking` | `boolean` | Enable adaptive extended thinking (deeper reasoning) | +| `effort` | `"low" \| "medium" \| "high" \| "xhigh" \| "max"` | Reasoning effort / token spend | +| `messages` | `Message[]` | Full history — supersedes `prompt` | +| `signal` | `AbortSignal` | Cancel the request | + +> `temperature` / `top_p` are intentionally **not** exposed — the current Claude +> models reject them (400). Steer output with prompting instead. + +### `AIError` + +Thrown on non-2xx responses or a model refusal. Carries `.status` and `.type` +(e.g. `"authentication_error"`, `"rate_limit_error"`, `"refusal"`). + +```ts +import { AIError } from "@wrnexus/ai"; +try { + await ai.generate("..."); +} catch (e) { + if (e instanceof AIError && e.type === "rate_limit_error") { + /* back off */ + } +} +``` + +## Usage + +```ts +// app/api/summarize.ts — summarize posted text +import { createAI } from "@wrnexus/ai"; +const ai = createAI(); + +export const POST = async (ctx) => { + const { text } = await ctx.req.json().catch(() => ({})); + if (!text) return Response.json({ error: "Provide 'text'." }, { status: 400 }); + const summary = await ai.generate(`Summarize in one sentence:\n\n${text}`, { + system: "You are a precise summarizer.", + }); + return Response.json({ summary }); +}; +``` + +## Requirements / Notes + +- **Bun-only.** Uses `fetch`, `ReadableStream`, `TextDecoder`/`TextEncoder`, and + reads `ANTHROPIC_API_KEY` from `Bun.env` (falls back to `process.env`). +- **Zero dependencies** — no `@anthropic-ai/sdk`; talks to the Messages API directly. +- Defaults to `claude-opus-4-8`. Pass `{ model }` for a different model (e.g. + `"claude-sonnet-5"` for speed/cost, `"claude-haiku-4-5"` for the fastest). diff --git a/.publish/ai/package.json b/.publish/ai/package.json new file mode 100644 index 00000000..1dcecef9 --- /dev/null +++ b/.publish/ai/package.json @@ -0,0 +1,26 @@ +{ + "name": "@wrnexus/ai", + "version": "0.2.12", + "type": "module", + "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", + "license": "MIT", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "engines": { + "bun": ">=1.1.0" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "restricted" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ] +} diff --git a/.publish/authz/README.md b/.publish/authz/README.md new file mode 100644 index 00000000..f0102ea5 --- /dev/null +++ b/.publish/authz/README.md @@ -0,0 +1,151 @@ +# @wrnexus/authz + +> Composable authorization for WrNexus — role-based (RBAC), policy-based (PBAC), and attribute-based (ABAC) access control that reduces to a boolean check plus an `authorize()` guard. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/authz` is a small, server-side authorization toolkit. It gives you three +interchangeable models — RBAC (roles → permissions), PBAC (policy predicates), and +ABAC (attribute matchers) — that all collapse to a `boolean | Promise` decision. +Wrap any decision in a `Middleware` guard (`authorize`, `requireRole`, `requirePermission`) +to protect WrNexus routes. Reach for it whenever a route or action needs to be gated on who +the user is, what roles they hold, or attributes of the user and the resource. It plugs into +`@wrnexus/core` by reading `ctx.user` as the authorization subject. + +## Installation + +```bash +bun add @wrnexus/authz +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +## API + +The package has a single entry point (`@wrnexus/authz`) exporting the following. + +### Types + +| Symbol | Description | +| ---------------------------------- | --------------------------------------------------------------------------------------------- | +| `Subject` | The authorized principal: `{ id?: string; roles?: string[]; [attribute: string]: unknown }`. | +| `Rbac` | An RBAC checker: `{ can(subject, permission): boolean; permissionsFor(roles): Set }`. | +| `Policy` | A predicate `(subject: S, resource?: R) => boolean \| Promise`. | + +### RBAC + +#### `defineRbac(roles: Record): Rbac` + +Builds an RBAC checker from a role → permissions map. Supported permission forms: + +- `"*"` — grants every permission. +- `"ns:*"` — namespace wildcard (e.g. `"post:*"` grants `"post:write"`). +- `"role:"` — inherits all permissions of another role (resolved recursively, cycle-safe). + +The returned `Rbac` provides: + +- `can(subject, permission)` — `true` if any of `subject.roles` grants `permission` (honouring `*` and namespace wildcards). Returns `false` when the subject has no roles. +- `permissionsFor(roles)` — the resolved `Set` of all permissions granted to a set of roles. + +#### `hasRole(subject: Subject | undefined, ...required: string[]): boolean` + +`true` if the subject holds **all** of the given roles. + +### PBAC / ABAC combinators + +- `any(...policies: Policy[]): Policy` — allow if **any** policy passes (OR); awaits async policies. +- `all(...policies: Policy[]): Policy` — allow only if **all** policies pass (AND); awaits async policies. +- `attr(name: string, match: unknown | ((value: unknown) => boolean)): Policy` — ABAC helper that allows when `subject[name]` equals `match`, or when `match` is a function, when `match(value)` is truthy. + +### Guards (middleware) + +Each guard returns a `@wrnexus/core` `Middleware`. A denied request short-circuits with +`Response.json({ ok: false, error: "Forbidden" }, { status: 403 })`. + +- `authorize(policy: (ctx: Context) => boolean | Promise): Middleware` — runs `policy` against the request `Context`; calls `next()` when it resolves truthy, otherwise returns 403. +- `requireRole(...roles: string[]): Middleware` — allows when `ctx.user` holds **any** of the listed roles. +- `requirePermission(rbac: Rbac, permission: string): Middleware` — allows when `rbac.can(ctx.user, permission)` is `true`. + +## Usage + +### RBAC + +```ts +import { defineRbac, hasRole } from "@wrnexus/authz"; + +const rbac = defineRbac({ + admin: ["*"], + editor: ["post:read", "post:write"], + viewer: ["post:read"], + // role inheritance: lead gets everything an editor has, plus post:publish + lead: ["role:editor", "post:publish"], +}); + +const user = { id: "u1", roles: ["editor"] }; + +rbac.can(user, "post:write"); // true +rbac.can(user, "post:delete"); // false +rbac.permissionsFor(["lead"]); // Set { "post:read", "post:write", "post:publish" } +hasRole(user, "editor"); // true +``` + +### Guarding routes + +```ts +import { authorize, requireRole, requirePermission, defineRbac } from "@wrnexus/authz"; + +const rbac = defineRbac({ admin: ["*"], editor: ["post:read", "post:write"] }); + +// Only admins or editors +app.get("/dashboard", requireRole("admin", "editor"), handler); + +// Requires a specific permission +app.post("/posts", requirePermission(rbac, "post:write"), handler); + +// Arbitrary policy over the request context +app.delete( + "/posts/:id", + authorize((ctx) => hasRole(ctx.user, "admin")), + handler, +); +``` + +### PBAC / ABAC policies + +```ts +import { any, all, attr, authorize, type Policy } from "@wrnexus/authz"; + +interface User { + id: string; + department?: string; + roles?: string[]; +} +interface Post { + authorId: string; +} + +// Ownership policy (subject + resource) +const ownsPost: Policy = (u, post) => u.id === post?.authorId; + +// ABAC: attribute equality, or a predicate +const inEngineering = attr("department", "engineering"); +const isVerified = attr("verified", (v) => v === true); + +// Compose: allow if the user owns the post OR is in engineering AND verified +const canEdit = any(ownsPost, all(inEngineering, isVerified)); + +app.put( + "/posts/:id", + authorize((ctx) => canEdit(ctx.user as User, loadPost(ctx))), + handler, +); +``` + +## Requirements / Notes + +- **Bun-only** — like the rest of WrNexus, this package targets the Bun runtime; Node is not supported. +- Works with [`@wrnexus/core`](../core) — the guards return `Middleware` and read the subject from `ctx.user` on the request `Context`. Both types are imported from `@wrnexus/core`. +- Policy combinators (`any`, `all`) and `authorize` are async-aware, so policies may return a `Promise` (e.g. for a database ownership check). diff --git a/.publish/authz/package.json b/.publish/authz/package.json new file mode 100644 index 00000000..3ee14ef1 --- /dev/null +++ b/.publish/authz/package.json @@ -0,0 +1,26 @@ +{ + "name": "@wrnexus/authz", + "version": "0.2.12", + "type": "module", + "description": "@wrnexus/authz — part of the WrNexus framework.", + "license": "MIT", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "engines": { + "bun": ">=1.1.0" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "restricted" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ] +} diff --git a/.publish/cli/README.md b/.publish/cli/README.md new file mode 100644 index 00000000..191520b1 --- /dev/null +++ b/.publish/cli/README.md @@ -0,0 +1,200 @@ +# @wrnexus/cli + +> The `wrnexus` command-line tool that scaffolds, runs, builds, tests, and manages WrNexus apps. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/cli` provides the `wrnexus` executable — the single entry point for developing a WrNexus app. It runs the HMR dev server, produces a self-contained production build, scaffolds apps/pages/components, drives database migrations, regenerates typed routes and queries, runs tests, and manages configuration profiles. It also scaffolds multi-app monorepos and serves them behind a domain-routing gateway. This is a CLI/build-time package (it shells out to the Bun binary for the dev child and tests) and it also exports the workspace config types via a subpath. + +## Installation + +```bash +bun add @wrnexus/cli +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +Once installed, invoke it from an app directory: + +```bash +bunx wrnexus dev +# or add scripts: "dev": "wrnexus dev .", "build": "wrnexus build ." +``` + +## Commands + +Every command accepts an optional `[app-dir]` (defaults to `.`). Commands that read config or `.env` also accept `--profile=` (see [Profiles](#profiles)). + +| Command | Purpose | +| ------------------------------------- | ---------------------------------------------------------------------- | +| `wrnexus dev [app-dir] [--port=3000]` | Start the development server with live reload / HMR. | +| `wrnexus build [app-dir]` | Build a self-contained production server bundle + assets into `dist/`. | +| `wrnexus create ` | Scaffold a new single app from an inline template. | +| `wrnexus workspace ` | Scaffold a monorepo (`apps/*` + shared `packages/*`). | +| `wrnexus gateway [--port=3000]` | Serve every workspace app behind one port, routed by domain. | +| `wrnexus generate ` | Scaffold a `page` \| `component` \| `api` \| `schema`. | +| `wrnexus generate routes` | Regenerate the typed routes file (`app/routes.gen.ts`). | +| `wrnexus generate docker` | Scaffold `Dockerfile`, `.dockerignore`, and `docker-compose.yml`. | +| `wrnexus generate mobile` | Scaffold a Capacitor shell for iOS and Android. | +| `wrnexus mobile add ` | Install Capacitor plugins and sync native projects. | +| `wrnexus eject ` | Copy Wire UI component `.wrn` sources into `app/components/`. | +| `wrnexus db ` | Database migrations and tooling (see [db](#wrnexus-db)). | +| `wrnexus test [app-dir] [--watch]` | Run the app's tests via `bun test` (defaults to the `test` profile). | +| `wrnexus profiles [app-dir]` | List config profiles and their `.env` files, marking the active one. | +| `wrnexus help` | Print usage. | + +`wrnexus g` is an alias for `wrnexus generate`. + +### `wrnexus dev` + +Supervises a child dev-server process (from `@wrnexus/dev-server`). The child owns file watching and HMR: CSS and client-island edits update the live page over a WebSocket with no restart; when a server module changes, the child exits with a restart code and the supervisor respawns it (the browser reconnects and morphs in the new HTML). On startup it regenerates typed DB queries and typed routes (best effort). Use `--port=` to change the port (default `3000`). + +```bash +wrnexus dev . --port=8080 +``` + +### `wrnexus build` + +Emits into `/dist/`: + +- `server.js` — a single, minified, self-contained Bun server with a **static** manifest of every page / api / realtime / middleware / component / layout module (no runtime filesystem scan or on-the-fly bundling). +- `reactive.js`, `theme.css`, `theme.js`, `ui.css`, and (if present) `styles.css` — hashed, minified browser assets. +- `public/` — copied verbatim. + +Before bundling, it regenerates typed queries for the default and every named database. Run the output with: + +```bash +bun dist/server.js # PORT env var optional +``` + +### `wrnexus create` + +Scaffolds a new app from an inline (dependency-free) template — `package.json`, config, and starter `app/` files. Run `wrnexus dev` in the new directory to start. + +```bash +wrnexus create my-app +``` + +### `wrnexus generate` + +Scaffolds a single file from a template, refusing to overwrite an existing file. Types (with aliases): `page`/`p`, `component`/`c`, `api`/`a`, `schema`/`s`. Nested names create nested paths. + +```bash +wrnexus generate page about # app/pages/about.wrn +wrnexus generate component user-card # app/components/user-card.wrn +wrnexus generate api users/list # app/api/users/list.ts +wrnexus generate schema signup # app/schemas/signup.ts +wrnexus generate routes # regenerate app/routes.gen.ts +wrnexus generate docker # Dockerfile + compose + .dockerignore +wrnexus generate mobile --mode=webview --app-id=com.example.app --app-name="Example" --url=https://app.example.com +wrnexus generate mobile --mode=native +``` + +The mobile generator creates a separate `mobile/` package and reads +`config.mobile.mode`. `webview` creates a Capacitor shell that renders the hosted +WrNexus application. `native` creates a WebView-free Expo/React Native app whose +screens call the shared backend through `mobile/src/wrnexus.ts`. Native screens +do not render `.wrn` HTML. In either mode, run `bun install` in `mobile/`; iOS +device builds require macOS and Xcode. + +Install official or community Capacitor plugins through the root CLI: + +```bash +wrnexus mobile add @capacitor/camera @capacitor/haptics +wrnexus mobile sync +wrnexus mobile assets # generate native icons from config.mobile.icon +``` + +In native mode, `mobile add` runs `expo install` and `mobile sync` runs Expo +prebuild. In WebView mode they retain the Capacitor install/sync behavior. +`wrnexus mobile compile` maps portable `app/pages/**/*.wrn` pages to Expo Router +TSX routes. Native `bun run start` invokes this compilation automatically. + +Browser code can access installed plugins through the SSR-safe +`@wrnexus/mobile` bridge. The command adds each plugin to both the WrNexus app +(JavaScript proxy) and `mobile/` (native synchronization). + +`wrnexus mobile sync` also configures Android so only true network failures use +the local connection-error screen. HTTP errors such as 404 and 500 keep their +WrNexus response pages. + +### `wrnexus eject` + +Copies a Wire UI component's `.wrn` source out of `@wrnexus/ui` into `app/components/`, so the app owns and can edit it (the app copy shadows the library one by name). Run with no names to list available components. It skips components that already exist in the app. + +```bash +wrnexus eject button card modal +``` + +### `wrnexus db` + +Database migrations and tooling. Without a flag, commands target the **default** database (`db` in `wrnexus.config.ts`, files under `app/db/`). Pass `--db=` to target a named database (`databases.`, files under `app/db//`). + +| Subcommand | Purpose | +| ------------------------------- | ----------------------------------------------------------------------------------- | +| `db new [--from-models]` | Scaffold a migration; `--from-models` derives it from the TS models in `schema.ts`. | +| `db migrate` | Apply all pending migrations. | +| `db rollback` | Revert the last applied migration. | +| `db status` | List applied / pending migrations. | +| `db generate` | Regenerate typed queries (`queries/*.sql` → `queries.gen.ts`). | +| `db seed` | Run the database's `seed.ts` (default export / `seed` function). | +| `db studio [table]` | Inspect tables — list row counts, or dump the first 50 rows of one table. | + +```bash +wrnexus db new create_users --from-models +wrnexus db migrate +wrnexus db studio users +wrnexus db status --db=analytics +``` + +### `wrnexus workspace` and `wrnexus gateway` + +`workspace ` scaffolds a monorepo: several WrNexus apps under `apps/*` and shared libraries under `packages/*`, plus a `wrnexus.workspace.ts` that maps each app to the domains it serves. `gateway` runs every app behind one port and routes by `Host` header, with optional per-app auth and gateway-wide security (trusted hosts, rate limit, security headers, access log). + +```bash +wrnexus workspace acme +wrnexus gateway --port=3000 +``` + +### `wrnexus test` + +Runs the app's tests with `bun test`. Defaults to the `test` profile (config + `.env.test`). Pass `--watch` to re-run on change; extra flags pass straight through to `bun test`. + +```bash +wrnexus test . --watch +``` + +## Profiles + +Pass `--profile=` to `dev`, `build`, `db` (or set `WRNEXUS_PROFILE`) to select a config profile. The CLI publishes `WRNEXUS_PROFILE` so config loaders and the dev child pick it up, and loads that profile's `.env` cascade (`.env`, `.env.local`, `.env.`, `.env..local`) into `process.env`. + +```bash +wrnexus dev --profile=uat +wrnexus profiles # ● development (config, .env.development) + # ○ production + # ○ uat (config, .env.uat) +``` + +## Subpath exports + +`@wrnexus/cli/workspace` exposes the workspace configuration types used by `wrnexus.workspace.ts`: + +```ts +import type { WorkspaceConfig, WorkspaceApp } from "@wrnexus/cli/workspace"; + +const config: WorkspaceConfig = { + security: { trustedHostsOnly: true, headers: true, accessLog: true }, + apps: [{ name: "web", dir: "apps/web", domains: ["localhost", "web.localhost"] }], +}; + +export default config; +``` + +## Requirements / Notes + +- **Bun-only.** The CLI runs on Bun, spawns the Bun binary for the dev child and `bun test`, and the production build uses `Bun.build`. Node is not supported. +- Orchestrates the rest of the framework: `@wrnexus/dev-server` (dev/prod server + gateway), `@wrnexus/router` (route + typed-routes codegen), `@wrnexus/compiler` (`.wrn` → `.ts`), `@wrnexus/db` (migrations, typed queries), `@wrnexus/styles` (config, profiles, `.env`, themes, styles), `@wrnexus/ui` (ejectable Wire UI components), `@wrnexus/validation`, `@wrnexus/csr`, and `@wrnexus/i18n`. +- Reads `wrnexus.config.ts` for `db` / `databases`, `theme`, `styles`, `seo`, `security`, `i18n`, and `profiles`, and `wrnexus.workspace.ts` for the gateway. diff --git a/.publish/cli/package.json b/.publish/cli/package.json new file mode 100644 index 00000000..b4be9883 --- /dev/null +++ b/.publish/cli/package.json @@ -0,0 +1,45 @@ +{ + "name": "@wrnexus/cli", + "version": "0.2.12", + "type": "module", + "description": "@wrnexus/cli — part of the WrNexus framework.", + "license": "MIT", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "engines": { + "bun": ">=1.1.0" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "restricted" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./workspace": { + "types": "./dist/workspace.d.ts", + "import": "./dist/workspace.js" + } + }, + "bin": { + "wrnexus": "./dist/index.js" + }, + "dependencies": { + "@wrnexus/core": "^0.2.12", + "@wrnexus/router": "^0.2.12", + "@wrnexus/csr": "^0.2.12", + "@wrnexus/compiler": "^0.2.12", + "@wrnexus/styles": "^0.2.12", + "@wrnexus/dev-server": "^0.2.12", + "@wrnexus/ui": "^0.2.12", + "@wrnexus/validation": "^0.2.12", + "@wrnexus/i18n": "^0.2.12", + "@wrnexus/db": "^0.2.12" + }, + "files": [ + "dist" + ] +} diff --git a/.publish/compiler/README.md b/.publish/compiler/README.md new file mode 100644 index 00000000..463a0d7f --- /dev/null +++ b/.publish/compiler/README.md @@ -0,0 +1,165 @@ +# @wrnexus/compiler + +> Compiler for the `.wrn` language — tokenizes, parses, and lowers `.wrn` page and component files to TypeScript. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/compiler` turns `.wrn` source into TypeScript that targets the framework's runtime primitives. A `.wrn` file declares either a `page` (a route) or a `component` (a reusable, prop-driven fragment) with blocks for `state`, `view` (plain HTML), `seo`, `style`, `functions`, `api`, `ssr`/`client` data bindings, and `realtime` websocket handlers. The pipeline is `source → Lexer → parse() → PageAst → generate() → TypeScript`. It is a build/server-side library — the WrNexus dev loader calls it to compile `.wrn` files on the fly, surfacing `ParseError` as a readable error page. + +## Installation + +```bash +bun add @wrnexus/compiler +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +## API + +All exports come from the package root (`@wrnexus/compiler`). + +### `compileWireFile(source: string): string` + +Compile `.wrn` source to a TypeScript module string. Throws `ParseError` on invalid input. The output is prefixed with a `// compiled from .wrn` comment. + +### `compile(source: string): CompileResult` + +Richer entry point that returns the generated code, the AST, and any diagnostics. + +```ts +interface CompileResult { + code: string; + ast: PageAst; + diagnostics: string[]; +} +``` + +On a `ParseError` it pushes the message into `diagnostics` and re-throws. + +### `parse(source: string): PageAst` + +Run the lexer + recursive-descent parser and return the AST. Throws `ParseError` (lexer `LexError`s are caught and rethrown as `ParseError`). + +### `generate(ast: PageAst): string` + +Lower a `PageAst` to TypeScript. `page` ASTs become a default-export page component (plus `meta`, optional `layout`, `__wrnexusApi`/method handlers, `websocket`, and SSR/CSR data bindings); `component` ASTs become a module exporting `render(props)` and `__wrnexusComponent`. + +### `Lexer` + +On-demand lexer for `.wrn`. Yields structural tokens and exposes raw-span readers for the parser. + +```ts +class Lexer { + pos: number; + constructor(src: string); + next(): Token; // consume next structural token + peek(): Token; // look ahead without consuming + readPath(): string; // route path, e.g. /users/[id] + readToLineEnd(): string; // rest of line (state/prop initializers) + readBalancedBraces(): string; // inner text of a { ... } block, string-aware +} +``` + +`Token` is `{ type: TokenType; value: string; pos: number }`, where `TokenType` is one of `ident`, `string`, `lbrace`, `rbrace`, `lparen`, `rparen`, `at`, `eq`, `comma`, `eof`. + +### Errors + +| Class | Thrown by | Meaning | +| ------------ | ------------------------------------------------- | --------------------------------------------------------------- | +| `ParseError` | `parse`, `compile`, `compileWireFile`, `generate` | Invalid `.wrn` grammar or (rewrapped) lex failure. | +| `LexError` | `Lexer` | Unexpected character / unterminated string / unbalanced braces. | + +### AST types + +Exported type-only symbols describing the parsed tree: + +| Type | Description | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PageAst` | Root node: `kind` (`"page" \| "component"`), `name`, optional `layout`, `props`, `states`, `seo`, `view`, `styles`, `functions`, `dataApis`, `modeFunctions`, `apis`, `realtimes`. | +| `ViewNode` | `{ type: "text"; value }` or `{ type: "element"; tag; attrs; children }`. | +| `Attr` | `{ name; value; event; boolean? }` — `event` marks `@event` bindings. | +| `StateDecl` | `{ name; expr }` — a `state x = ` declaration. | +| `SeoBlock` | `Record` from the `seo { ... }` block. | +| `ApiBlock` | `{ method; path; body }` — a top-level `api METHOD /path { ... }`. | +| `DataApiBlock` | `{ mode; name; method; path; body }` — an `api` inside an `ssr`/`client` block. | +| `DataMode` | `"ssr" \| "client"`. | +| `ModeFunctionsBlock` | `{ mode; body }` — a `functions { ... }` inside an `ssr`/`client` block. | +| `RealtimeBlock` | `{ name; handlers }` — a `realtime { on evt(args) { ... } }` block. | + +## Usage + +Compile a page: + +```ts +import { compileWireFile } from "@wrnexus/compiler"; + +const ts = compileWireFile(` +page Home { + state count = 0 + seo { title = "Home" description = "Welcome" } + view { + + } +} +`); +// ts is a TypeScript module: exports `meta`, and a default page component +// returning an HTML string, wrapped in a data-scope for the reactive runtime. +``` + +Inspect the AST and diagnostics: + +```ts +import { compile, ParseError } from "@wrnexus/compiler"; + +try { + const { code, ast, diagnostics } = compile(source); + console.log(ast.kind, ast.name, ast.states.length); +} catch (err) { + if (err instanceof ParseError) console.error(err.message); +} +``` + +Drive the parse/codegen stages directly: + +```ts +import { parse, generate } from "@wrnexus/compiler"; + +const ast = parse(componentSource); // ast.kind === "component" +const module = generate(ast); // exports render(props) + __wrnexusComponent +``` + +Use the lexer standalone: + +```ts +import { Lexer } from "@wrnexus/compiler"; + +const lx = new Lexer("page Home {"); +lx.next(); // { type: "ident", value: "page", pos: 0 } +lx.next(); // { type: "ident", value: "Home", pos: 5 } +lx.next(); // { type: "lbrace", value: "{", pos: 10 } +``` + +## The `.wrn` language (as parsed) + +A file opens with `page ` or `component ` followed by a `{ ... }` body containing zero or more members: + +- `layout = ""` — selects `app/layouts/.wrn` (pages only). +- `props { name = ... }` — component props; each default's type drives coercion. +- `state = ` — reactive state seeded from a raw JS expression. +- `view { }` — plain HTML with `{expr}` interpolation, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and ``. +- `seo { key = "value" ... }` — metadata merged into the generated `meta`. +- `style { }` — inlined page/component stylesheet (repeatable). +- `functions { }` — shared server-side helpers (repeatable). +- `api { }` — route handler, lowered to a `METHOD` export (repeatable). +- `ssr { ... }` / `client { ... }` — data blocks holding `api { ... }` bindings and their own `functions { ... }`. +- `realtime { on () { } ... }` — websocket handlers, lowered to a `websocket` export. + +`view` markup is parsed by a lenient dedicated HTML parser (`parseHtmlView`); HTML void elements (`
`, ``, …) take no closing tag. Line comments (`//`) are skipped by the lexer. + +## Requirements / Notes + +- Pure TypeScript with no runtime dependencies; runs under **Bun** as part of the WrNexus toolchain (Node is not supported). +- Generated modules target WrNexus runtime primitives (`data-scope`, `data-text`, `data-on-*`, `data-for`, `data-component`, `__wrnexus*`/`__wire*` helpers) — consume the output within a WrNexus app, e.g. via `@wrnexus/core`'s dev loader. diff --git a/.publish/compiler/package.json b/.publish/compiler/package.json new file mode 100644 index 00000000..2f1712a4 --- /dev/null +++ b/.publish/compiler/package.json @@ -0,0 +1,26 @@ +{ + "name": "@wrnexus/compiler", + "version": "0.2.12", + "type": "module", + "description": "@wrnexus/compiler — part of the WrNexus framework.", + "license": "MIT", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "engines": { + "bun": ">=1.1.0" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "restricted" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ] +} diff --git a/.publish/core/README.md b/.publish/core/README.md new file mode 100644 index 00000000..e338b6b5 --- /dev/null +++ b/.publish/core/README.md @@ -0,0 +1,381 @@ +# @wrnexus/core + +> The framework core: the request `Context`, middleware contract, and the security, session, caching, streaming, realtime, and JSX primitives every other WrNexus package builds on. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/core` is the shared foundation of WrNexus. It defines the `Context` +object that flows through every middleware, page, and API route, plus the +`Middleware`/`Next` contract they implement. On top of that it ships the +building blocks a real app needs: cookie-backed sessions, password auth, CSRF +protection, rate limiting, request logging, HTTP + in-memory caching, file +uploads, streaming/SSE responses, WebSocket "rooms", security headers/CORS, and +a server-side JSX runtime that renders to HTML strings. Everything here is +**server-side** and Bun-native (it uses `Bun.password`, `Bun.write`, the +web-standard `Request`/`Response`, and `crypto`). You depend on it directly and +transitively through the rest of the framework. + +## Installation + +```bash +bun add @wrnexus/core +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +## API + +### Context & middleware — `@wrnexus/core` + +The `Context` (`ctx`) is the single value passed to middleware and handlers. + +| Export | Kind | Description | +| ------------------------------ | ---- | ------------------------------------------------------------------------------------------------------------------------ | +| `Context` | type | Per-request object: `req`, `url`, `lang`, `t`, `params`, `locals`, `user?`, `ip?`, `cookies`, `session`, `localStorage`. | +| `Next` | type | `() => Promise \| Response` — invokes the next middleware/handler. | +| `Middleware` | type | `(ctx, next) => Promise \| Response`. Return `next()` to continue, or a `Response` to short-circuit. | +| `createContext(req, url)` | fn | Build a fresh `Context` for an incoming request (wires up cookies, session, localStorage snapshot). | +| `withContextHeaders(ctx, res)` | fn | Apply accumulated headers (e.g. `Set-Cookie`) from the context onto a response. | +| `PageComponent` | type | `(ctx) => string \| Promise` — a page module's default export. | +| `PageMeta` / `SeoConfig` | type | `` metadata: `title`, `description`, `canonical`, `robots`, `image`, `twitterCard`, `themeColor`, … | +| `TFunction` | type | `(key, params?) => string` — translate a key for `ctx.lang`, interpolating `{param}` placeholders. | + +Key `Context` fields: + +- `ctx.locals` — per-request scratch space for passing values between middleware. +- `ctx.user` — the authenticated user (populated by `sessionAuth`/`logIn`), or `null`. +- `ctx.ip` — the direct socket peer IP (not spoofable via headers). +- `ctx.cookies` / `ctx.session` / `ctx.localStorage` — see **Storage** below. + +### Authentication — `@wrnexus/core` + +Passwords are hashed with argon2id via `Bun.password`; sessions ride the +cookie-backed `SessionStore`. + +| Export | Signature | Notes | +| -------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `hashPassword(password)` | `(string) => Promise` | argon2id hash to store. | +| `verifyPassword(password, hash)` | `(string, string) => Promise` | Constant-safe; returns `false` on bad/empty hash. | +| `logIn(ctx, user)` | `(Context, U) => void` | Regenerates the session id (fixation defense), stores the user, sets `ctx.user`. | +| `logOut(ctx)` | `(Context) => void` | Clears the session and `ctx.user`. | +| `getUser(ctx)` | `(Context) => U \| null` | Current user from `ctx.user`, falling back to the session. | +| `sessionAuth()` | `() => Middleware` | Hydrates `ctx.user` from the session each request. Register early. | +| `requireAuth(options?)` | `(RequireAuthOptions?) => Middleware` | Guard: API/fetch requests get `401 JSON`, page navigations get `302` to `loginPath` (default `/login`) with `?next=`. | +| `SESSION_USER_KEY` | `"user"` | Session key holding the user. | + +`RequireAuthOptions`: `{ loginPath?: string }`. + +### CSRF — `@wrnexus/core` + +Double-submit cookie pattern: a readable `wire-csrf` cookie is echoed in an +`x-csrf-token` header on unsafe requests. + +| Export | Signature | Notes | +| ----------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| `csrfToken(ctx)` | `(Context) => string` | Ensures the CSRF cookie exists and returns its token. | +| `verifyCsrf(ctx)` | `(Context) => boolean` | Safe methods (GET/HEAD/OPTIONS) pass; otherwise header/`ctx.locals._csrf` must match the cookie (constant-time). | +| `csrfProtection()` | `() => Middleware` | 403s unsafe requests with a missing/mismatched token. | +| `CSRF_COOKIE` / `CSRF_HEADER` | `"wire-csrf"` / `"x-csrf-token"` | Cookie & header names. | + +### Rate limiting — `@wrnexus/core` + +Fixed-window limiter that returns `429` with `Retry-After` and emits +`RateLimit-Limit`/`-Remaining`/`-Reset` headers. + +| Export | Signature | Notes | +| --------------------- | ----------------------------------- | ---------------------------------------------------------------------- | +| `rateLimit(options?)` | `(RateLimitOptions?) => Middleware` | Main middleware. | +| `peerKey(ctx)` | `(Context) => string` | Non-spoofable key from `ctx.ip` (default). | +| `proxyKey(ctx)` | `(Context) => string` | Trusts `x-forwarded-for`/`x-real-ip`. Use only behind a trusted proxy. | +| `defaultKey` | — | **Deprecated** alias of `proxyKey`. | + +`RateLimitOptions`: `windowMs` (default `60_000`), `max` (default `60`), +`key`, `trustProxy` (default `false` → keys on `peerKey`; `true` → `proxyKey`), +`message`, `headers` (default `true`), `store`. + +`RateLimitStore` is pluggable — implement `hit(key, windowMs, now) => Bucket | Promise` +(a `Bucket` is `{ count, resetAt }`) to back limits with Redis/SQL across +instances. The default store is process-local memory. + +### Request logging — `@wrnexus/core` + +| Export | Signature | Notes | +| ------------------------- | --------------------------------------- | -------------------------------------------------------------------------------- | +| `requestLogger(options?)` | `(RequestLoggerOptions?) => Middleware` | One record per request with a request id (stored on `ctx.locals[requestIdKey]`). | + +`RequestLoggerOptions`: `format` (`"pretty"` default \| `"json"`), `sink(line, record)` +(default `console.log`), `requestIdKey` (default `"requestId"`), `now`. +`RequestRecord` = `{ time, id, method, path, status, durationMs }`. + +### Caching — `@wrnexus/core` + +| Export | Kind | Notes | +| -------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `TTLCache` | class | In-memory TTL cache: `get`, `set`, `getOrLoad(key, loader, ttlMs?)`, `delete`, `clear`, `size`. Constructor takes a default `ttlMs` (60s). | +| `cacheControl(options)` | fn | Build a `Cache-Control` value from `CacheControlOptions`. | +| `withCacheControl(res, options)` | fn | Apply `Cache-Control` to a response. | +| `etag(body, weak?)` | fn | Stable quoted FNV-1a ETag (weak by default). | +| `notModified(req, tag)` | fn | `true` when `If-None-Match` matches — send a `304`. | + +`CacheControlOptions`: `maxAge`, `sMaxAge`, `private`, `noStore`, `noCache`, +`staleWhileRevalidate`, `immutable`. + +### File uploads — `@wrnexus/core` + +Bun parses `multipart/form-data` via `Request.formData()`; these helpers +validate and persist the resulting `File`s. + +| Export | Signature | Notes | +| --------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `collectUploads(form)` | `(FormData) => { field, file }[]` | Every non-empty `File` in a parsed form. | +| `saveUpload(file, options)` | `(File, SaveUploadOptions) => Promise` | Validates size/type, sanitizes the name, writes via `Bun.write`. Throws `UploadError`. | +| `sanitizeFilename(name)` | `(string) => string` | Strips separators, traversal, control/illegal chars; caps at 255. | +| `UploadError` | class | Thrown on rejected uploads. | + +`SaveUploadOptions`: `dir` (required), `maxBytes`, `allowedTypes` (MIME types +like `"image/png"` and/or extensions like `".png"`), `filename(file)`. +`SavedUpload` = `{ path, filename, size, type }`. + +### Streaming & SSE — `@wrnexus/core` + +| Export | Signature | Notes | +| ------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `streamResponse(source, init?)` | `(Iterable\|AsyncIterable, StreamResponseInit?) => Response` | Streaming `Response` from a chunk source (basis for streaming SSR). | +| `sse(source)` | `(Iterable\|AsyncIterable) => Response` | `text/event-stream` response. | + +`StreamResponseInit`: `status`, `headers`, `contentType` (default +`"text/html; charset=utf-8"`). `ServerSentEvent`: `{ data, event?, id?, retry? }`. + +### Realtime rooms — `@wrnexus/core` + +WebSocket rooms. A file in `app/realtime/` exports +`default defineRoom({ ... })` and is served at `ws://host/realtime/`. + +| Export | Signature | Notes | +| --------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------- | +| `defineRoom(handlers)` | `(RoomHandlers) => RoomDefinition` | Define a room. Export the result as `default`. | +| `isRoomDefinition(value)` | `(unknown) => boolean` | Type guard for a room definition. | +| `createRealtimeRegistry()` | `() => RealtimeRegistry` | Server-side connection manager mapping sockets ↔ rooms. | +| `bridgeRealtime(registry, bus, topic?)` | `(RealtimeRegistry, RealtimeBus, string?) => () => void` | Bridge broadcasts/`toUser` sends across processes via a pub/sub bus. | + +`RoomHandlers`: `authorize(info) => boolean` (gate before accept — return +`false` to reject with 403), `onConnect(client)`, `onMessage(client, message)` +(JSON auto-parsed), `onLeave(client)`. A handler receives a `RoomClient` with +`id`, `user`, `query`, `data`, `room`, and `send` / `broadcast` / +`to(id)` / `toUser(user)` / `close`. The `Room` API adds `state`, `clients()`, +`count()`, and `broadcast`. `RealtimeBus` is structurally satisfied by +`@wrnexus/pubsub`. Legacy `RealtimeHandler`/`RealtimeSocket` raw handlers are +still exported. Connection-targeted sends (`send`, `to(id)`) stay local; room +broadcasts and `toUser` cross the bridge. + +### Error pages — `@wrnexus/core` + +| Export | Signature | Notes | +| ------------------------------ | -------------------------------- | ----------------------------------------------------- | +| `renderError(err, mode)` | `(unknown, Mode) => Response` | Dev page (with stack) or generic prod page by `mode`. | +| `renderDevError(err, status?)` | `(unknown, number?) => Response` | Readable HTML error page including the stack trace. | +| `renderProdError(status?)` | `(number?) => Response` | Generic page that never leaks file paths. | +| `renderNotFound()` | `() => Response` | Simple 404 page. | + +`Mode` = `"development" | "production"`. + +### Security headers & CORS — `@wrnexus/core` + +| Export | Signature | Notes | +| -------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `withSecurityHeaders(req, res, mode, security?, nonce?)` | → `Response` | Applies CORS + CSP, HSTS, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`, COOP, Trusted Types, and `extraHeaders`. | +| `createCorsPreflightResponse(req, security?)` | → `Response \| null` | Builds a `204`/`403` preflight response for CORS `OPTIONS` requests. | +| `isWebSocketOriginAllowed(req, security?)` | → `boolean` | Guards WS upgrades against cross-site hijacking (allows same-origin, configured CORS origins, and non-browser clients). | + +Config types: `SecurityConfig` (top-level), `CorsConfig`/`CorsOrigin`, +`ContentSecurityPolicyConfig`/`CspDirectiveValue`, `HstsConfig`, +`TrustedTypesConfig`, `PermissionsPolicyConfig`. WrNexus applies sensible +defaults (self-only CSP, `frame-ancestors 'none'`, restrictive Permissions-Policy, +HSTS in production, Trusted Types in production); each is individually +overridable or disable-able via `false`. + +### Storage: cookies, sessions, localStorage — `@wrnexus/core` + +These back the `ctx.cookies`, `ctx.session`, and `ctx.localStorage` fields. + +| Export | Kind | Notes | +| --------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `setSessionBackend(backend)` | fn | Swap the **sync** session persistence backend (`SessionBackend`) — e.g. `bun:sqlite`. Default is process-local memory. Call once at startup. | +| `loadSession(backend, options?)` | fn → `Middleware` | Back `ctx.session` with an **async** store (`AsyncSessionBackend`: `load`/`save`/`destroy`) — loads before the request, saves after. `options.ttlMs` default 24h. | +| `CookieStore` | type | `get`/`getAll`/`has`/`set(name, value, opts?)`/`delete`/`headers`. | +| `SessionStore` | type | `id`/`get`/`getAll`/`set`/`delete`/`regenerate`/`clear`. | +| `LocalStorageSnapshot` | type | Read-only view of the browser's localStorage sent via header for CSR bindings. | +| `CookieOptions` | type | `path`, `domain`, `maxAge`, `expires`, `httpOnly`, `secure`, `sameSite`. | +| `SessionEntry` / `SessionBackend` / `AsyncSessionBackend` | types | Session persistence contracts. | + +### Low-level security helpers — `@wrnexus/core` + +| Export | Signature | Notes | +| ----------------------------- | --------------------- | --------------------------------------------------- | +| `escapeHtml(value)` | `(string) => string` | Escape for HTML text/attributes. | +| `isSafeIslandName(name)` | `(string) => boolean` | Allow only a conservative `[A-Za-z0-9_-]+` charset. | +| `isSafeRequestPath(pathname)` | `(string) => boolean` | Reject NULs, `..` traversal, and backslashes. | + +### JSX runtime — `@wrnexus/core`, `@wrnexus/core/jsx-runtime`, `@wrnexus/core/jsx-dev-runtime` + +A server-side JSX runtime that renders to HTML **strings** (no virtual DOM). +Point `tsconfig`'s `jsxImportSource` at `@wrnexus/core`. + +| Export | Kind | Notes | +| ------------------------------------------ | ------ | --------------------------------------------------------------------------------------- | +| `jsx` / `jsxs` | fn | The runtime factory (TypeScript calls these automatically). Returns an `Html` instance. | +| `Fragment` | symbol | JSX fragment marker. | +| `Html` | class | Wraps a raw, already-safe HTML string (`toString()` returns it). | +| `mustache(expr)` | fn | Emit a `{{expr}}` placeholder (tagged-template or string form) for the client binder. | +| `JSXComponent` / `JSXProps` / `Renderable` | types | Component signature and renderable value types. | + +Values interpolated as children are HTML-escaped unless they are an `Html` +instance; use `dangerouslySetInnerHTML={{ __html }}` for trusted markup. Void +elements render without a closing tag; `className`→`class`, `htmlFor`→`for`, and +`style` objects are serialized to CSS text. + +The subpath exports map to the runtime TypeScript's JSX transform expects: + +```jsonc +// tsconfig.json +{ + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "@wrnexus/core", + }, +} +``` + +## Usage + +### A minimal middleware chain + +```ts +import { + createContext, + withContextHeaders, + sessionAuth, + requireAuth, + requestLogger, + rateLimit, + csrfProtection, + type Middleware, +} from "@wrnexus/core"; + +const chain: Middleware[] = [ + requestLogger({ format: "json" }), + rateLimit({ max: 100, windowMs: 60_000 }), + csrfProtection(), + sessionAuth(), + requireAuth({ loginPath: "/login" }), +]; +``` + +### Password auth + +```ts +import { hashPassword, verifyPassword, logIn, getUser } from "@wrnexus/core"; + +// Registration +const passwordHash = await hashPassword(form.password); + +// Login +if (await verifyPassword(form.password, user.passwordHash)) { + logIn(ctx, { id: user.id, email: user.email }); +} + +const current = getUser<{ id: string }>(ctx); // or null +``` + +### HTTP caching with ETags + +```ts +import { etag, notModified, withCacheControl } from "@wrnexus/core"; + +const body = JSON.stringify(data); +const tag = etag(body); +if (notModified(ctx.req, tag)) { + return new Response(null, { status: 304, headers: { ETag: tag } }); +} +const res = new Response(body, { headers: { ETag: tag, "content-type": "application/json" } }); +return withCacheControl(res, { maxAge: 60, staleWhileRevalidate: 300 }); +``` + +### Streaming SSE + +```ts +import { sse } from "@wrnexus/core"; + +async function* ticks() { + for (let n = 0; ; n++) { + yield { event: "tick", data: String(n) }; + await Bun.sleep(1000); + } +} +export default (ctx) => sse(ticks()); +``` + +### A realtime room + +```ts +// app/realtime/chat.ts +import { defineRoom } from "@wrnexus/core"; + +export default defineRoom({ + authorize: (info) => !!info.user, // require auth + onConnect(client) { + client.user = client.query.user; + client.room.broadcast({ type: "join", id: client.id }); + }, + onMessage(client, msg) { + client.broadcast({ type: "say", from: client.id, text: msg.text }); + }, +}); +``` + +Scale it across processes: + +```ts +import { createRealtimeRegistry, bridgeRealtime } from "@wrnexus/core"; +import { createPubSub } from "@wrnexus/pubsub"; +import { redisDriver } from "@wrnexus/pubsub/redis"; + +const registry = createRealtimeRegistry(); +bridgeRealtime(registry, createPubSub(redisDriver(process.env.REDIS_URL))); +``` + +### JSX rendering + +```tsx +import { Html } from "@wrnexus/core"; + +function Card({ title, body }: { title: string; body: string }) { + return ( +
+

{title}

+

{body}

+
+ ); +} + +const html: Html = ; +return new Response(html.toString(), { headers: { "content-type": "text/html" } }); +``` + +## Requirements / Notes + +- **Bun-only.** Uses `Bun.password` (argon2id), `Bun.write`, web-standard + `Request`/`Response`/`FormData`/`ReadableStream`, and the global `crypto`. + Node is not supported. +- Session and rate-limit backends default to **process-local memory**. For + multi-instance deployments, swap in a shared backend: `setSessionBackend` (sync, + e.g. `bun:sqlite`) or `loadSession` (async, e.g. Redis) for sessions, a custom + `RateLimitStore` for limits, and `bridgeRealtime` for realtime. +- Works with the rest of the framework: realtime bridging is structurally + compatible with [`@wrnexus/pubsub`](../pubsub); the security, auth, and JSX + primitives here are consumed by the WrNexus server/router packages. +- Subpath exports: `@wrnexus/core/jsx-runtime` and `@wrnexus/core/jsx-dev-runtime` + for TypeScript's automatic JSX transform. diff --git a/.publish/core/package.json b/.publish/core/package.json new file mode 100644 index 00000000..4392a482 --- /dev/null +++ b/.publish/core/package.json @@ -0,0 +1,34 @@ +{ + "name": "@wrnexus/core", + "version": "0.2.12", + "type": "module", + "description": "@wrnexus/core — part of the WrNexus framework.", + "license": "MIT", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "engines": { + "bun": ">=1.1.0" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "restricted" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./jsx-runtime": { + "types": "./dist/jsx-runtime.d.ts", + "import": "./dist/jsx-runtime.js" + }, + "./jsx-dev-runtime": { + "types": "./dist/jsx-dev-runtime.d.ts", + "import": "./dist/jsx-dev-runtime.js" + } + }, + "files": [ + "dist" + ] +} diff --git a/.publish/csr/README.md b/.publish/csr/README.md new file mode 100644 index 00000000..e43b7038 --- /dev/null +++ b/.publish/csr/README.md @@ -0,0 +1,178 @@ +# @wrnexus/csr + +> The browser-side client runtime for WrNexus — generic, self-contained JS that hydrates server-rendered pages with reactivity, client-side navigation, and realtime rooms. + +Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. + +## Overview + +`@wrnexus/csr` holds the three client runtimes that WrNexus serves to the browser. Components are authored as `.wrn` files and rendered on the **server**; this package provides the single, generic runtime that **hydrates** that HTML in the browser — there are no per-component browser bundles. Each runtime is exported as a plain-JS string (no build step, no imports) intended to be served verbatim from a well-known URL: + +- **reactive** at `/__wrnexus/reactive.js` — reactive directives (`data-scope`, `data-text`, `data-for`, …) +- **nav** at `/__wrnexus/nav.js` — SPA-style client navigation with graceful fallback +- **realtime** at `/__wrnexus/realtime.js` — WebSocket "rooms", declarative or programmatic + +The package itself runs on the server (it just returns strings); the strings it returns run in the browser. A dev/prod server (see `@wrnexus/core`) is responsible for actually serving them. + +## Installation + +```bash +bun add @wrnexus/csr +``` + +> Private package — the machine must be authenticated to the `wrnexus` npm org +> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). + +## API + +All exports come from the package root (`@wrnexus/csr`). The runtime source is delivered as strings, so the "API" on the server side is small; the real surface is the browser directives/globals each string installs. + +### Runtime strings + +| Export | Type | Served at | Contents | +| ------------------ | -------- | ------------------------ | ------------------------------ | +| `REACTIVE_RUNTIME` | `string` | `/__wrnexus/reactive.js` | Reactive directive runtime | +| `NAV_RUNTIME` | `string` | `/__wrnexus/nav.js` | Client-side navigation runtime | +| `REALTIME_RUNTIME` | `string` | `/__wrnexus/realtime.js` | Realtime rooms runtime | + +### Accessor functions + +Convenience getters that return the same strings. + +```ts +getReactiveRuntime(): string // → REACTIVE_RUNTIME +getNavRuntime(): string // → NAV_RUNTIME +getRealtimeRuntime(): string // → REALTIME_RUNTIME +``` + +### Browser: reactive directives + +Applied to any subtree containing `data-scope`. Expressions are parsed by a tiny eval-free evaluator, so a strict CSP with no `unsafe-eval` works. + +| Directive | Purpose | +| -------------------------------------------------- | ----------------------------------------------------------------------- | +| `data-scope="count: 0, name: 'x'"` | Declare reactive state on a subtree | +| `data-on-="count++"` | Run a statement in scope on a DOM event | +| `data-text="expr"` | Bind an element's `textContent` to an expression | +| `data-show="expr"` | Toggle visibility (`display`) on truthiness | +| `data-for="item in list"` (opt. `item, i in list`) | Per-item list rendering template | +| `{{expr}}` or `{expr}` | Interpolation inside text nodes and attribute values | +| `data-wrnexus-csr="id"` | Target for a generated CSR fetch binding (fetches `/__wrnexus/csr?...`) | + +Supported expression features: literals, identifiers, member access (`a.b`, `a[b]`), function/method calls, arrays, objects, arithmetic, comparison, equality, logical (`&& ||`), unary (`! - +`), and ternary. Statements support `++`/`--`, assignment operators (`= += -= *= /= %=`), and bare expression/method calls. Rendering is dependency-tracked: a signal change only re-runs the renderers that actually read it. + +Browser globals installed: `window.__wrnexusHydrateScopes(root)` and `window.__wrnexusHydrateCsrFetches(root)` — both idempotent, so re-running after a DOM swap or HMR morph is safe. Both run automatically on `DOMContentLoaded`. + +### Browser: navigation + +Intercepts same-origin `` clicks, fetches the target page, and swaps the `#app` container in place (via `importNode` — not `innerHTML` — so it works under a Trusted-Types CSP), updating history, title, and scroll, then re-hydrates. Cross-origin links, modified clicks, `download`/`data-no-nav`/`rel="external"`/`target` links, non-HTML responses, or a missing `#app` fall back to a full browser navigation. + +- Programmatic navigation: `window.__wrnexusNavigate(url)` +- Emits a `wrnexus:navigated` `CustomEvent` (`detail.url`) after each swap +- Sends `x-wrnexus-nav: 1` on fetches so the server can return the page fragment +- Appends any `/__wrnexus/*` runtime scripts the incoming page needs but the current document lacks + +### Browser: realtime rooms + +Connects to `/realtime/` over WebSocket (`ws`/`wss` chosen from `location.protocol`). Two usage modes. + +Programmatic API via `window.wire`: + +```ts +wire.room(name): Room // open (or reuse) a room connection +wire.bindRooms(root?) // (re)bind declarative [data-room] containers + +interface Room { + name: string; + send(obj: object | string): Room; // JSON-stringifies objects; queues until open + on(type: string, cb): Room; // filter by msg.type; "*" or a fn = all messages + on(cb): Room; + close(): Room; +} +``` + +Internal lifecycle messages are emitted to listeners as `{ type }`: `__open`, `__close`, `__error`, and `__raw` (non-JSON frames, with `data`). Reconnect uses exponential backoff capped at 5s; queued sends flush on reconnect. + +Declarative binding (zero JS) on a `data-room=""` container: + +| Attribute | On | Purpose | +| ------------------------------------ | --------------- | ------------------------------------------------------------------------ | +| `data-room=""` | container | Connect to room `` | +| `data-room-user=""` | container | Identify the connection (`?user=`) | +| `data-room-log` | element | Where incoming messages are appended | +| `