@warlock.js/core 5.34.3 → 5.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -0
- package/esm/cli/cli-commands.manager.mjs +1 -1
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/commands/add.command.mjs +1 -0
- package/esm/cli/commands/add.command.mjs.map +1 -1
- package/esm/cli/commands/build.command.mjs +30 -3
- package/esm/cli/commands/build.command.mjs.map +1 -1
- package/esm/cli/commands/check/check-command.action.mjs +1 -0
- package/esm/cli/commands/check/check-command.action.mjs.map +1 -1
- package/esm/cli/commands/check.command.mjs +5 -0
- package/esm/cli/commands/check.command.mjs.map +1 -1
- package/esm/cli/commands/explain/explain-command.action.mjs +21 -0
- package/esm/cli/commands/explain/explain-command.action.mjs.map +1 -1
- package/esm/cli/commands/explain.command.mjs +14 -4
- package/esm/cli/commands/explain.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generate.command.mjs +19 -1
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/endpoint.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/endpoint.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/web.generator.mjs +169 -0
- package/esm/cli/commands/generate/generators/web.generator.mjs.map +1 -0
- package/esm/cli/commands/generate/templates/web-stubs.mjs +231 -0
- package/esm/cli/commands/generate/templates/web-stubs.mjs.map +1 -0
- package/esm/cli/commands/inspect/inspect-command.action.mjs +4 -3
- package/esm/cli/commands/inspect/inspect-command.action.mjs.map +1 -1
- package/esm/cli/commands/inspect.command.mjs +13 -8
- package/esm/cli/commands/inspect.command.mjs.map +1 -1
- package/esm/cli/commands/mcp/mcp-command.action.mjs +1 -1
- package/esm/cli/commands/mcp/mcp-command.action.mjs.map +1 -1
- package/esm/config/config-loader.d.mts.map +1 -1
- package/esm/config/config-loader.mjs +2 -0
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/config/load-config-files.mjs +4 -0
- package/esm/config/load-config-files.mjs.map +1 -1
- package/esm/dev-server/file-event-handler.mjs +11 -1
- package/esm/dev-server/file-event-handler.mjs.map +1 -1
- package/esm/dev-server/file-manager.d.mts +1 -1
- package/esm/dev-server/file-manager.d.mts.map +1 -1
- package/esm/dev-server/file-manager.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +16 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs +13 -4
- package/esm/dev-server/files-watcher.mjs.map +1 -1
- package/esm/dev-server/loader/loader-port-acks.mjs +21 -9
- package/esm/dev-server/loader/loader-port-acks.mjs.map +1 -1
- package/esm/dev-server/loader/register-loader.mjs +31 -12
- package/esm/dev-server/loader/register-loader.mjs.map +1 -1
- package/esm/dev-server/module-loader.mjs +15 -1
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs +1 -1
- package/esm/dev-server/special-file-patterns.mjs +4 -1
- package/esm/dev-server/special-file-patterns.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +1 -1
- package/esm/e2e/cdp.d.mts.map +1 -1
- package/esm/e2e/cdp.mjs +9 -2
- package/esm/e2e/cdp.mjs.map +1 -1
- package/esm/e2e/index.d.mts +2 -2
- package/esm/e2e/page.d.mts +14 -1
- package/esm/e2e/page.d.mts.map +1 -1
- package/esm/e2e/page.mjs +81 -8
- package/esm/e2e/page.mjs.map +1 -1
- package/esm/e2e/types.d.mts +49 -0
- package/esm/e2e/types.d.mts.map +1 -1
- package/esm/e2e/types.mjs.map +1 -1
- package/esm/errors/error-codes.d.mts +72 -0
- package/esm/errors/error-codes.d.mts.map +1 -1
- package/esm/errors/error-codes.mjs +72 -0
- package/esm/errors/error-codes.mjs.map +1 -1
- package/esm/errors/index.d.mts +2 -2
- package/esm/errors/index.mjs +2 -2
- package/esm/generations/add-command.action.mjs +2 -0
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/design-system-payload.mjs +53 -0
- package/esm/generations/design-system-payload.mjs.map +1 -0
- package/esm/generations/features/design-system.feature.mjs +38 -0
- package/esm/generations/features/design-system.feature.mjs.map +1 -0
- package/esm/generations/features/index.mjs +3 -1
- package/esm/generations/features/index.mjs.map +1 -1
- package/esm/generations/features/notifications.feature.mjs +13 -2
- package/esm/generations/features/notifications.feature.mjs.map +1 -1
- package/esm/generations/features/test.feature.mjs +48 -13
- package/esm/generations/features/test.feature.mjs.map +1 -1
- package/esm/generations/features/web.feature.mjs +3 -3
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/generations/stubs.mjs +9 -5
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/error-mapper.d.mts +1 -1
- package/esm/http/error-mapper.mjs +3 -3
- package/esm/http/error-mapper.mjs.map +1 -1
- package/esm/http/error-response.d.mts +3 -0
- package/esm/http/error-response.d.mts.map +1 -1
- package/esm/http/error-response.mjs +8 -2
- package/esm/http/error-response.mjs.map +1 -1
- package/esm/http/errors/errors.d.mts +8 -0
- package/esm/http/errors/errors.d.mts.map +1 -1
- package/esm/http/errors/errors.mjs +13 -1
- package/esm/http/errors/errors.mjs.map +1 -1
- package/esm/http/index.d.mts +3 -2
- package/esm/http/index.mjs +3 -2
- package/esm/http/list-endpoint.d.mts +35 -0
- package/esm/http/list-endpoint.d.mts.map +1 -0
- package/esm/http/list-endpoint.mjs +28 -0
- package/esm/http/list-endpoint.mjs.map +1 -0
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +12 -2
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/server.mjs +1 -1
- package/esm/index.d.mts +10 -6
- package/esm/index.mjs +5 -2
- package/esm/localization/index.mjs +3 -1
- package/esm/localization/translation-usage-graph.d.mts +49 -0
- package/esm/localization/translation-usage-graph.d.mts.map +1 -0
- package/esm/localization/translation-usage-graph.mjs +128 -0
- package/esm/localization/translation-usage-graph.mjs.map +1 -0
- package/esm/localization/translation-usage.d.mts +85 -0
- package/esm/localization/translation-usage.d.mts.map +1 -0
- package/esm/localization/translation-usage.mjs +307 -0
- package/esm/localization/translation-usage.mjs.map +1 -0
- package/esm/openapi/security.mjs +12 -4
- package/esm/openapi/security.mjs.map +1 -1
- package/esm/openapi/types.d.mts +2 -0
- package/esm/openapi/types.d.mts.map +1 -1
- package/esm/production/build-budgets.d.mts +64 -0
- package/esm/production/build-budgets.d.mts.map +1 -0
- package/esm/production/build-budgets.mjs +109 -0
- package/esm/production/build-budgets.mjs.map +1 -0
- package/esm/production/production-builder.mjs +13 -1
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/resolve-build-config.d.mts +1 -1
- package/esm/production/resolve-build-config.d.mts.map +1 -1
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/production/route-registration-snapshot.mjs +5 -3
- package/esm/production/route-registration-snapshot.mjs.map +1 -1
- package/esm/restful/restful.d.mts +1 -1
- package/esm/router/router.d.mts +2 -0
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +4 -1
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +19 -0
- package/esm/router/types.d.mts.map +1 -1
- package/esm/tests/fakes/storage-fake.d.mts +3 -1
- package/esm/tests/fakes/storage-fake.d.mts.map +1 -1
- package/esm/tests/fakes/storage-fake.mjs +3 -3
- package/esm/tests/fakes/storage-fake.mjs.map +1 -1
- package/esm/tests/fakes/test-fakes.d.mts +3 -1
- package/esm/tests/fakes/test-fakes.d.mts.map +1 -1
- package/esm/tests/fakes/test-fakes.mjs +3 -2
- package/esm/tests/fakes/test-fakes.mjs.map +1 -1
- package/esm/tests/index.d.mts +4 -2
- package/esm/tests/index.mjs +3 -1
- package/esm/tests/load-unit-config.mjs +24 -0
- package/esm/tests/load-unit-config.mjs.map +1 -0
- package/esm/tests/response-contract.d.mts +32 -0
- package/esm/tests/response-contract.d.mts.map +1 -0
- package/esm/tests/response-contract.mjs +116 -0
- package/esm/tests/response-contract.mjs.map +1 -0
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +10 -2
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/test-lifecycle-state.mjs.map +1 -1
- package/esm/tests/test-locales.mjs +33 -0
- package/esm/tests/test-locales.mjs.map +1 -0
- package/esm/tests/test-runtime-scope.d.mts +15 -0
- package/esm/tests/test-runtime-scope.d.mts.map +1 -0
- package/esm/tests/test-runtime-scope.mjs +80 -0
- package/esm/tests/test-runtime-scope.mjs.map +1 -0
- package/esm/tests/vitest-setup.d.mts +13 -3
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs +146 -18
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/tooling/check/affected-files.d.mts +5 -0
- package/esm/tooling/check/affected-files.d.mts.map +1 -0
- package/esm/tooling/check/affected-files.mjs +56 -0
- package/esm/tooling/check/affected-files.mjs.map +1 -0
- package/esm/tooling/check/api-route-references.d.mts +16 -0
- package/esm/tooling/check/api-route-references.d.mts.map +1 -0
- package/esm/tooling/check/api-route-references.mjs +146 -0
- package/esm/tooling/check/api-route-references.mjs.map +1 -0
- package/esm/tooling/check/changed-files.d.mts +2 -0
- package/esm/tooling/check/changed-files.d.mts.map +1 -1
- package/esm/tooling/check/changed-files.mjs +2 -2
- package/esm/tooling/check/changed-files.mjs.map +1 -1
- package/esm/tooling/check/conventions-step.d.mts +7 -3
- package/esm/tooling/check/conventions-step.d.mts.map +1 -1
- package/esm/tooling/check/conventions-step.mjs +59 -8
- package/esm/tooling/check/conventions-step.mjs.map +1 -1
- package/esm/tooling/check/fixes.d.mts +8 -0
- package/esm/tooling/check/fixes.d.mts.map +1 -0
- package/esm/tooling/check/fixes.mjs +31 -0
- package/esm/tooling/check/fixes.mjs.map +1 -0
- package/esm/tooling/check/index.d.mts +12 -0
- package/esm/tooling/check/index.mjs +3 -2
- package/esm/tooling/check/run-check.d.mts +2 -0
- package/esm/tooling/check/run-check.d.mts.map +1 -1
- package/esm/tooling/check/run-check.mjs +23 -5
- package/esm/tooling/check/run-check.mjs.map +1 -1
- package/esm/tooling/check/run-tests.mjs +1 -1
- package/esm/tooling/check/source-ast.d.mts +1 -0
- package/esm/tooling/check/translation-conventions.mjs +32 -0
- package/esm/tooling/check/translation-conventions.mjs.map +1 -0
- package/esm/tooling/check/web-conventions.d.mts +10 -0
- package/esm/tooling/check/web-conventions.d.mts.map +1 -0
- package/esm/tooling/check/web-conventions.mjs +126 -0
- package/esm/tooling/check/web-conventions.mjs.map +1 -0
- package/esm/tooling/index.d.mts +7 -2
- package/esm/tooling/index.mjs +4 -2
- package/esm/tooling/inspect/collect-files.mjs +172 -0
- package/esm/tooling/inspect/collect-files.mjs.map +1 -0
- package/esm/tooling/inspect/collect-pages.mjs +3 -2
- package/esm/tooling/inspect/collect-pages.mjs.map +1 -1
- package/esm/tooling/inspect/collect-routes.mjs +1 -1
- package/esm/tooling/inspect/collect-routes.mjs.map +1 -1
- package/esm/tooling/inspect/explain-file.d.mts +12 -0
- package/esm/tooling/inspect/explain-file.d.mts.map +1 -0
- package/esm/tooling/inspect/explain-file.mjs +45 -0
- package/esm/tooling/inspect/explain-file.mjs.map +1 -0
- package/esm/tooling/inspect/index.d.mts +3 -2
- package/esm/tooling/inspect/index.mjs +2 -1
- package/esm/tooling/inspect/inspect-app.d.mts.map +1 -1
- package/esm/tooling/inspect/inspect-app.mjs +9 -3
- package/esm/tooling/inspect/inspect-app.mjs.map +1 -1
- package/esm/tooling/inspect/inspect-report.schema.mjs +34 -1
- package/esm/tooling/inspect/inspect-report.schema.mjs.map +1 -1
- package/esm/tooling/inspect/inspect-report.types.d.mts +17 -1
- package/esm/tooling/inspect/inspect-report.types.d.mts.map +1 -1
- package/esm/tooling/inspect/inspect-report.types.mjs +2 -1
- package/esm/tooling/inspect/inspect-report.types.mjs.map +1 -1
- package/esm/tooling/mcp/mcp-tools.d.mts.map +1 -1
- package/esm/tooling/mcp/mcp-tools.mjs +8 -2
- package/esm/tooling/mcp/mcp-tools.mjs.map +1 -1
- package/esm/tooling/problem.d.mts +4 -0
- package/esm/tooling/problem.d.mts.map +1 -1
- package/esm/tooling/problem.mjs +1 -0
- package/esm/tooling/problem.mjs.map +1 -1
- package/esm/tooling/syntax/ast.d.mts +2 -0
- package/esm/tooling/typecheck/run-typecheck.mjs +5 -1
- package/esm/tooling/typecheck/run-typecheck.mjs.map +1 -1
- package/esm/upgrade/codemods/index.mjs +2 -0
- package/esm/upgrade/codemods/index.mjs.map +1 -1
- package/esm/upgrade/codemods/vite-vitest.mjs +170 -0
- package/esm/upgrade/codemods/vite-vitest.mjs.map +1 -0
- package/esm/upgrade/notes.mjs +4 -0
- package/esm/upgrade/notes.mjs.map +1 -1
- package/esm/vite/index.d.mts +3 -1
- package/esm/vite/index.mjs +3 -1
- package/esm/vite/test-setup-reporter.d.mts +16 -0
- package/esm/vite/test-setup-reporter.d.mts.map +1 -0
- package/esm/vite/test-setup-reporter.mjs +55 -0
- package/esm/vite/test-setup-reporter.mjs.map +1 -0
- package/esm/vite/test-workers.d.mts +5 -0
- package/esm/vite/test-workers.d.mts.map +1 -0
- package/esm/vite/test-workers.mjs +38 -0
- package/esm/vite/test-workers.mjs.map +1 -0
- package/esm/warlock-config/index.d.mts +2 -1
- package/esm/warlock-config/types.d.mts +3 -0
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/llms-full.txt +874 -9
- package/llms.txt +2 -0
- package/package.json +15 -14
- package/schemas/inspect-report.schema.json +47 -1
- package/skills/build-budgets/SKILL.md +51 -0
- package/skills/build-restful/SKILL.md +56 -0
- package/skills/configure-app/SKILL.md +7 -0
- package/skills/create-module/SKILL.md +9 -0
- package/skills/generate-web-ui/SKILL.md +71 -0
- package/skills/index.md +2 -2
- package/skills/register-route/SKILL.md +13 -0
- package/skills/run-app/SKILL.md +1 -1
- package/skills/send-mail/SKILL.md +25 -0
- package/skills/send-response/SKILL.md +50 -0
- package/skills/test-browser/SKILL.md +53 -0
- package/skills/test-http/SKILL.md +91 -0
- package/skills/test-service/SKILL.md +161 -3
- package/skills/upgrade-from-5-29/SKILL.md +109 -0
- package/skills/use-localization/SKILL.md +25 -0
- package/skills/warlock-check/SKILL.md +75 -1
- package/skills/warlock-inspect/SKILL.md +45 -2
- package/skills/warlock-mcp/SKILL.md +4 -0
- package/skills/write-cli-command/SKILL.md +18 -0
- package/esm/production/build-app-production.mjs +0 -14
- package/esm/production/build-app-production.mjs.map +0 -1
package/llms.txt
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- [Package orientation](@warlock.js/core/skills/index.md): Warlock application runtime: `router`, `Request`, `Response`, `config`, `env`, `sendMail` and `defineConfig`. Use for "add an API route", controllers, middleware, CLI commands, config, uploads, mail, sockets, OpenAPI, testing or upgrades. Not this package → @warlock.js/web for React SSR pages; @warlock.js/cascade for database queries; @warlock.js/seal for standalone schemas; @warlock.js/auth for login.
|
|
10
10
|
- [add-connector](@warlock.js/core/skills/add-connector/SKILL.md): Extend Warlock with a `BaseConnector` subclass and register its instance in `warlock.config.ts > connectors`. Covers runtime lifecycle (`boot`, `start`, `shutdown`, priority, phase, watched files) and the optional static `build.generate` / `build.emit` contribution used by `warlock build`. Triggers: `BaseConnector`, `connectors`, `ConnectorLifecyclePhase`, `ConnectorBuildContribution`, `build.generate`, `build.emit`; "add a queue worker", "wire a subsystem into bootstrap", "contribute files to warlock build", "control startup ordering", "graceful shutdown hook". Skip: app context accessors — the `use-app-context` topic; general config shape — the `configure-app` topic; competing pattern: hand-rolled process signal blocks, NestJS lifecycle hooks.
|
|
11
11
|
- [benchmark-code](@warlock.js/core/skills/benchmark-code/SKILL.md): Wrap a function with `measure(name, fn, options?)` to time it and classify the latency — onComplete/onError/onFinish hooks, `latencyRange` thresholds, `BenchmarkProfiler` for percentiles, `BenchmarkSnapshots` for raw captures. Triggers: `measure`, `BenchmarkProfiler`, `BenchmarkSnapshots`, `BenchmarkChannel`, `ConsoleChannel`, `latencyRange`, `shouldBenchmarkError`; "time this operation", "profile a slow service", "emit p50/p95/p99 metrics", "classify latency against thresholds"; typical import `import { measure, BenchmarkProfiler } from "@warlock.js/core"`. Skip: retry composition — the `retry-operation` topic; benchmark config wiring — the `configure-app` topic; competing libs `prom-client`, `pino`, `perf_hooks`, `console.time`.
|
|
12
|
+
- [build-budgets](@warlock.js/core/skills/build-budgets/SKILL.md): Configure build.budgets in warlock.config.ts, inspect per-page gzip JS/CSS and deferred assets, compare the last build, and enforce limits with warlock build --json. Use for client bundle size regressions and CI transfer-size gates. For field metrics use web measure-web-vitals.
|
|
12
13
|
- [build-restful](@warlock.js/core/skills/build-restful/SKILL.md): Generate standard CRUD endpoints — via `router.route(path).list().show().create().update().destroy()` chain or the `Restful` base class. Pick the chain by default; reach for `Restful` when you want repository-bound defaults. Triggers: `router.route`, `Restful`, `router.restfulResource`, `RouteResource`, `.crud`, `.nest`, `beforeCreate`, `onCreate`; "build a CRUD API", "register list/show/create/update/destroy", "repository-bound default handlers", "override a single REST action"; typical import `import { router, Restful } from "@warlock.js/core"`. Skip: wider router surface — the `register-route` topic; per-action controllers — the `create-controller` topic; wire mapping — the `define-resource` topic; competing pattern: hand-rolled controllers, `@nestjs/swagger` decorator-driven CRUD.
|
|
13
14
|
- [build-url](@warlock.js/core/skills/build-url/SKILL.md): HTTP URL helpers — `url`, `publicUrl`, `assetsUrl`, `uploadsUrl`, anchored at `app.baseUrl`. Use to render `src` / `href` / API URLs in resources and responses. `setBaseUrl` is wired by the HTTP connector from `config.get("app.baseUrl")`. Triggers: `url`, `publicUrl`, `assetsUrl`, `uploadsUrl`, `setBaseUrl`, `BASE_URL`; "render an avatar src URL", "absolute download link", "embed asset URL in email", "URL helpers vs path helpers"; typical import `import { url, publicUrl, uploadsUrl } from "@warlock.js/core"`. Skip: filesystem paths — the `resolve-path` topic; signed CDN URLs — the `store-file` topic; resource output — the `define-resource` topic; competing patterns: hand-rolled `${baseUrl}/...` template strings.
|
|
14
15
|
- [configure-app](@warlock.js/core/skills/configure-app/SKILL.md): Configure a Warlock app — the two layers (`warlock.config.ts` for framework-level wiring, `src/config/*.ts` for subsystems), `.env` + `env()`, the `config()` getter for runtime reads, and `app.publicUrl`/`PUBLIC_APP_URL` (the app's public origin). Triggers: `defineConfig`, `config.get`, `config.key`, `env`, `ConfigRegistry`, `HttpConfigurations`, `AppConfigurations`, `publicUrl`, `PUBLIC_APP_URL`, `getPublicUrl`; "add a new config file", "warlock.config.ts vs src/config", "read env values", "runtime config lookup", "app public origin/URL"; typical import `import { defineConfig, config, env } from "@warlock.js/core"`. Skip: cache driver registration — the `cache-basics` topic of the `warlock-js-cache` skill; mail config — the `send-mail` topic; storage config — the `store-file` topic; sitemap refusal on a missing origin — the `generate-sitemap` topic of the `warlock-js-web` skill; competing libs `dotenv` direct, `convict`, `node-config`.
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
- [define-resource](@warlock.js/core/skills/define-resource/SKILL.md): Map model fields to wire-shape via `defineResource()` or `Resource` subclasses. Output-only — never put business logic, hydration, or reconciliation in a resource. Also covers the typed output: `ResourceOutput<typeof R>`, `defineResource<Out>()`, `Serialized<T, W>` and `ModelResourceRegistry`. Triggers: `defineResource`, `Resource`, `RegisterResource`, `toJSON`, `ResourceOutput`, `Serialized`, `ModelResourceRegistry`, `"self"`, `"localized"`, `"uploadsUrl"`; "shape an API response", "type a resource's output", "what does the client receive", "nest related resources", "rename a field on output", "self-referential tree resource"; typical import `import { defineResource } from "@warlock.js/core"`. Skip: localized columns — the `use-localization` topic; URL casting — the `build-url` topic; controller side — the `create-controller` topic; competing libs `@nestjs/swagger` `@ApiProperty`, `class-transformer`, hand-rolled DTO mappers.
|
|
18
19
|
- [encrypt-data](@warlock.js/core/skills/encrypt-data/SKILL.md): Reversible AES-256-GCM `encrypt` / `decrypt` for secrets you need to read back; one-way HMAC-SHA256 `hmacHash` for deterministic fingerprints (lookup/dedup of encrypted columns). Keys come from `src/config/encryption.ts`. Triggers: `encrypt`, `decrypt`, `hmacHash`, `EncryptionConfigurations`, `APP_ENCRYPTION_KEY`, `APP_HMAC_KEY`; "store an API key reversibly", "fingerprint an encrypted column for lookup", "AES-256-GCM secret", "HMAC-SHA256 dedup key"; typical import `import { encrypt, decrypt, hmacHash } from "@warlock.js/core"`. Skip: password hashing — the `hash-password` topic; config wiring — the `configure-app` topic; competing libs Node `crypto` direct, `crypto-js`, `libsodium-wrappers`.
|
|
19
20
|
- [generate-openapi](@warlock.js/core/skills/generate-openapi/SKILL.md): Generate an OpenAPI 3.1 document from the registered routes with `warlock generate.openapi` (default `storage/openapi/openapi.json`), write a Postman collection from it with `warlock generate.postman` (default `storage/postman/collection.json`), and read it live in the dev-only API docs page at `/__warlock/docs`. Documents paths, path/query parameters, request bodies from Seal validation, responses from `handler.responseSchema`, 401/422 and bearer/cookie security from `authMiddleware()`. Triggers: `generate.openapi`, `openApiGeneratorCommand`, `buildOpenApiDocument`, `getDevelopmentOpenApiDocument`, `generate.postman`, `openApiToPostmanCollection`, `getDevelopmentPostmanCollection`, `responseSchema`, `--include-pages`, `/__warlock/docs`; "OpenAPI", "Swagger", "API docs", "import my API into Postman or Insomnia", "document my routes". Skip: declaring response types on a handler — the `create-controller` topic; the devtools dashboard — the `warlock-js-devtools` skill; listing routes — the `warlock-routes` topic.
|
|
21
|
+
- [generate-web-ui](@warlock.js/core/skills/generate-web-ui/SKILL.md): Generate web pages, schema-backed forms and CRUD UI with warlock generate page, form and crud-ui. Includes setup companions, design-system controls, useSubmitForm, optional guarded endpoints and module CRUD prerequisites.
|
|
20
22
|
- [hash-password](@warlock.js/core/skills/hash-password/SKILL.md): One-way bcrypt password hashing — `hashPassword` / `verifyPassword`, plus the declarative `useHashedPassword()` schema transformer that auto-hashes a model's password field on save. Salt rounds come from `src/config/encryption.ts`. Triggers: `hashPassword`, `verifyPassword`, `useHashedPassword`, `password.salt`, `bcryptjs`; "hash a user password", "verify login credentials", "auto-hash on save", "rotate a password"; typical import `import { hashPassword, verifyPassword } from "@warlock.js/core"`. Skip: reversible secrets — the `encrypt-data` topic; the other transformers — the `use-model-transformers` topic; config wiring — the `configure-app` topic; competing libs `bcrypt` native, `argon2`, `scrypt`.
|
|
21
23
|
- [health-checks](@warlock.js/core/skills/health-checks/SKILL.md): Built-in liveness (`/health`) and readiness (`/ready`) endpoints plus graceful HTTP request draining for zero-downtime deploys — the `health` registry (`health.addCheck`/`removeCheck`), the `http.health.*` and `http.gracefulShutdown.*` config, and how readiness ties into `Application.isShuttingDown`. Triggers: `health`, `health.addCheck`, `health.removeCheck`, `HealthCheck`, `/health`, `/ready`, `http.health`, `http.gracefulShutdown`, `forceCloseConnections`, "liveness probe", "readiness probe", "graceful shutdown", "drain in-flight requests", "zero-downtime deploy", "kubernetes health check", "503 until ready"; typical import `import { health } from "@warlock.js/core"`. Skip: the `Application.onShutdown` / `onceBooted` lifecycle hooks — the `use-app-context` topic; maintenance-mode 503s — the `use-middleware` topic; connector lifecycle — the `add-connector` topic; competing libs `@fastify/under-pressure`, `terminus`, hand-rolled `/health` controllers.
|
|
22
24
|
- [lower-stage3-decorators](@warlock.js/core/skills/lower-stage3-decorators/SKILL.md): Vite/Vitest plugin `lowerStage3Decorators()` that lowers TC39 Stage-3 (native) decorators with esbuild before oxc / the SSR rewrite mangles them — so `@RegisterModel`-decorated Cascade models load under Vitest 4 / Vite 8. Triggers: `lowerStage3Decorators`, "SyntaxError: Invalid or unexpected token" on a decorated class, "@(0, __vite_ssr_import__.X)()", "decorator crashes vitest", "models won't load in tests", "Vite 8 oxc decorators"; typical import `import { lowerStage3Decorators } from "@warlock.js/core/vite"` in `vite.config.ts` / `vitest.config.ts`. Skip: writing the tests themselves — the `test-service` topic / the `test-http` topic; runtime migrations — the `write-migration` topic of the `warlock-js-cascade` skill.
|
package/package.json
CHANGED
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"@mongez/slug": "^1.0.7",
|
|
26
26
|
"@mongez/supportive-is": "^3.0.0",
|
|
27
27
|
"@mongez/time-wizard": "^1.0.6",
|
|
28
|
-
"@warlock.js/cache": "5.
|
|
29
|
-
"@warlock.js/cascade": "5.
|
|
30
|
-
"@warlock.js/context": "5.
|
|
31
|
-
"@warlock.js/fs": "5.
|
|
32
|
-
"@warlock.js/logger": "5.
|
|
33
|
-
"@warlock.js/seal": "5.
|
|
28
|
+
"@warlock.js/cache": "5.36.0",
|
|
29
|
+
"@warlock.js/cascade": "5.36.0",
|
|
30
|
+
"@warlock.js/context": "5.36.0",
|
|
31
|
+
"@warlock.js/fs": "5.36.0",
|
|
32
|
+
"@warlock.js/logger": "5.36.0",
|
|
33
|
+
"@warlock.js/seal": "5.36.0",
|
|
34
34
|
"bcryptjs": "^3.0.3",
|
|
35
35
|
"chokidar": "^5.0.0",
|
|
36
36
|
"dayjs": "^1.11.19",
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"mime": "^4.1.0",
|
|
47
47
|
"oxc-parser": "0.154.0",
|
|
48
48
|
"pluralize-esm": "^9.0.5",
|
|
49
|
-
"typescript": "7.0.2"
|
|
49
|
+
"typescript": "7.0.2",
|
|
50
|
+
"axe-core": "4.14.0"
|
|
50
51
|
},
|
|
51
52
|
"peerDependencies": {
|
|
52
53
|
"@napi-rs/keyring": "^2.1.1",
|
|
@@ -55,12 +56,12 @@
|
|
|
55
56
|
"@aws-sdk/lib-storage": "^3.955.0",
|
|
56
57
|
"@aws-sdk/s3-request-presigner": "^3.955.0",
|
|
57
58
|
"@react-email/render": "^2.0.5",
|
|
58
|
-
"@warlock.js/access": "5.
|
|
59
|
-
"@warlock.js/ai": "5.
|
|
60
|
-
"@warlock.js/ai-tools": "5.
|
|
61
|
-
"@warlock.js/devtools": "5.
|
|
62
|
-
"@warlock.js/herald": "5.
|
|
63
|
-
"@warlock.js/notifications": "5.
|
|
59
|
+
"@warlock.js/access": "5.36.0",
|
|
60
|
+
"@warlock.js/ai": "5.36.0",
|
|
61
|
+
"@warlock.js/ai-tools": "5.36.0",
|
|
62
|
+
"@warlock.js/devtools": "5.36.0",
|
|
63
|
+
"@warlock.js/herald": "5.36.0",
|
|
64
|
+
"@warlock.js/notifications": "5.36.0",
|
|
64
65
|
"nodemailer": "^8.0.5",
|
|
65
66
|
"react": "^19.2.3",
|
|
66
67
|
"react-dom": "^19.2.3",
|
|
@@ -145,7 +146,7 @@
|
|
|
145
146
|
"engines": {
|
|
146
147
|
"node": ">=22.15"
|
|
147
148
|
},
|
|
148
|
-
"version": "5.
|
|
149
|
+
"version": "5.36.0",
|
|
149
150
|
"type": "module",
|
|
150
151
|
"main": "./esm/index.mjs",
|
|
151
152
|
"module": "./esm/index.mjs",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"pages",
|
|
48
48
|
"jobs",
|
|
49
49
|
"schedules",
|
|
50
|
-
"packages"
|
|
50
|
+
"packages",
|
|
51
|
+
"files"
|
|
51
52
|
]
|
|
52
53
|
}
|
|
53
54
|
},
|
|
@@ -549,6 +550,51 @@
|
|
|
549
550
|
}
|
|
550
551
|
}
|
|
551
552
|
}
|
|
553
|
+
},
|
|
554
|
+
"files": {
|
|
555
|
+
"type": "array",
|
|
556
|
+
"items": {
|
|
557
|
+
"type": "object",
|
|
558
|
+
"required": ["file", "module", "role", "exports", "imports", "importedBy"],
|
|
559
|
+
"properties": {
|
|
560
|
+
"file": {
|
|
561
|
+
"type": "string"
|
|
562
|
+
},
|
|
563
|
+
"module": {
|
|
564
|
+
"type": "string"
|
|
565
|
+
},
|
|
566
|
+
"role": {
|
|
567
|
+
"type": "string"
|
|
568
|
+
},
|
|
569
|
+
"exports": {
|
|
570
|
+
"type": "array",
|
|
571
|
+
"items": {
|
|
572
|
+
"type": "string"
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
"importedBy": {
|
|
576
|
+
"type": "array",
|
|
577
|
+
"items": {
|
|
578
|
+
"type": "string"
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
"imports": {
|
|
582
|
+
"type": "array",
|
|
583
|
+
"items": {
|
|
584
|
+
"type": "object",
|
|
585
|
+
"required": ["specifier", "file"],
|
|
586
|
+
"properties": {
|
|
587
|
+
"specifier": {
|
|
588
|
+
"type": "string"
|
|
589
|
+
},
|
|
590
|
+
"file": {
|
|
591
|
+
"type": ["string", "null"]
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
552
598
|
}
|
|
553
599
|
}
|
|
554
600
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-budgets
|
|
3
|
+
description: "Configure build.budgets in warlock.config.ts, inspect per-page gzip JS/CSS and deferred assets, compare the last build, and enforce limits with warlock build --json. Use for client bundle size regressions and CI transfer-size gates. For field metrics use web measure-web-vitals."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Client build budgets
|
|
7
|
+
|
|
8
|
+
```ts title="warlock.config.ts"
|
|
9
|
+
import { defineConfig } from "@warlock.js/core";
|
|
10
|
+
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
build: {
|
|
13
|
+
budgets: {
|
|
14
|
+
page: { js: "150kb", css: "50kb" },
|
|
15
|
+
pages: { "/admin/**": { js: "220kb" } },
|
|
16
|
+
mode: "error",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Run `npx warlock build` for the sorted table, or `npx warlock build --json`
|
|
23
|
+
for `{ success, budgets, error? }`. Progress goes to stderr in JSON mode.
|
|
24
|
+
`budgets` contains `version: 1`, `compression: "gzip"`, `pages` and
|
|
25
|
+
`violations`. API-only builds report an empty page list.
|
|
26
|
+
|
|
27
|
+
Page limits cover the hydration entry, page, layouts, root, setup registrations
|
|
28
|
+
and error boundary, plus their transitive static imports. Files are counted once
|
|
29
|
+
per page and gzipped individually. Dynamic imports beyond those required roots
|
|
30
|
+
are reported separately and do not consume the budget. Shared files count in
|
|
31
|
+
each page's first visit. This measures emitted JS/CSS, not HTML, fonts or images.
|
|
32
|
+
|
|
33
|
+
`pages` keys match route paths with `*` (within a segment), `**` (across
|
|
34
|
+
segments), or `?` (one non-slash character). Defaults apply first; matching rules
|
|
35
|
+
merge in insertion order and the last supplied field wins. Unsupported glob
|
|
36
|
+
syntax fails with an actionable error. Units are case-insensitive `b`, `kb` and
|
|
37
|
+
`mb`, with 1024 bytes per kb; equality passes. Omitted fields are unlimited.
|
|
38
|
+
|
|
39
|
+
Existing apps without budgets receive a report only. Configured budgets default
|
|
40
|
+
to `mode: "error"`; `"warn"` logs violations and completes. New project templates
|
|
41
|
+
configure 150kb JS / 50kb CSS. A failure names the page, limit, actual size and
|
|
42
|
+
the ten largest rendered modules. Those module sizes are Rollup attribution
|
|
43
|
+
estimates in uncompressed rendered bytes, not gzip shares; compression is not
|
|
44
|
+
additive across modules inside a file.
|
|
45
|
+
|
|
46
|
+
The successful build stores `build-budgets.json` in `build.outdir` (normally
|
|
47
|
+
`dist`). Preserve that directory before rebuilding to obtain deltas. A failed
|
|
48
|
+
build rolls back to the previous artifact and baseline. JSON output still
|
|
49
|
+
contains its attempted report. Changing output directories or deleting dist
|
|
50
|
+
starts a fresh baseline; new routes display no delta. Vite's private
|
|
51
|
+
`client/.vite/budget-modules.json` contains the full module attribution.
|
|
@@ -266,3 +266,59 @@ router.restfulResource("/orders", new OrdersRestful());
|
|
|
266
266
|
- The `create-controller` topic — what the chain's handlers look like.
|
|
267
267
|
- The `define-resource` topic — wire shape for the records the chain returns.
|
|
268
268
|
- The `warlock-conventions` topic — controller/service/repository layering rules.
|
|
269
|
+
|
|
270
|
+
## Bounded list endpoints
|
|
271
|
+
|
|
272
|
+
Use `listEndpoint(request, listCallback, options)` from `@warlock.js/core` to
|
|
273
|
+
produce `{ <pluralKey>: data, pagination }` from a page-based repository list.
|
|
274
|
+
The callback receives repository options, with sorting translated into the
|
|
275
|
+
repository's `[column, direction]` tuple. Wrap repository methods in an arrow
|
|
276
|
+
to preserve their receiver. Both `list` and `listCached` work.
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { listEndpoint, type Request, type RequestHandler } from "@warlock.js/core";
|
|
280
|
+
import { v, type Infer } from "@warlock.js/seal";
|
|
281
|
+
import { productsRepository } from "../repositories/products.repository";
|
|
282
|
+
|
|
283
|
+
const querySchema = v.object({
|
|
284
|
+
page: v.int().min(1).default(1),
|
|
285
|
+
limit: v.int().min(1).default(20),
|
|
286
|
+
orderBy: v.literal("createdAt", "name").optional(),
|
|
287
|
+
orderDir: v.literal("asc", "desc").optional(),
|
|
288
|
+
status: v.literal("active", "archived").optional(),
|
|
289
|
+
});
|
|
290
|
+
type Query = Infer<typeof querySchema>;
|
|
291
|
+
|
|
292
|
+
export const listProducts: RequestHandler<Request<Query>> = async ({ request, response }) =>
|
|
293
|
+
response.success(
|
|
294
|
+
await listEndpoint(request, (options) => productsRepository.listCached(options), {
|
|
295
|
+
pluralKey: "products",
|
|
296
|
+
maxLimit: 100,
|
|
297
|
+
orderBy: ["createdAt", "name"],
|
|
298
|
+
filters: ["status"],
|
|
299
|
+
}),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
listProducts.validation = { schema: querySchema, validating: ["query"] };
|
|
303
|
+
// Attach responseSchema with your ProductResource and pagination fields too.
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`request.validated()` must already contain schema-validated query values. Seal
|
|
307
|
+
handles coercion and filter validation; the helper does not read raw query or
|
|
308
|
+
body values. Configure the repository's `filterBy` for the chosen filter fields.
|
|
309
|
+
Only explicitly listed `filters` are forwarded; pagination and sort controls
|
|
310
|
+
cannot be declared as filters in the typed API. Filters remain flat repository
|
|
311
|
+
options, rather than a nested `filters` object.
|
|
312
|
+
|
|
313
|
+
Defaults are page 1, limit 20, maxLimit 100. The helper clamps a positive integer
|
|
314
|
+
limit to maxLimit (use `.max(100)` in the query schema instead if you prefer a
|
|
315
|
+
422 for excessive requests). Invalid page/limit or unlisted sort columns throw
|
|
316
|
+
`AppError("invalidListQuery", 422, ...)`. Sort directions are restricted to
|
|
317
|
+
`asc`/`desc`, defaulting to `asc` when a column is provided. With no column,
|
|
318
|
+
repository default ordering applies. Invalid helper configuration throws a
|
|
319
|
+
`TypeError`. The plural key must be nonempty and cannot be `pagination`.
|
|
320
|
+
|
|
321
|
+
The helper forces page pagination and preserves the repository's pagination
|
|
322
|
+
metadata without recalculating it. Cursor and unpaginated endpoints need their
|
|
323
|
+
own controller. Add authorization constraints inside the callback using trusted
|
|
324
|
+
server values; never accept them as client filters.
|
|
@@ -448,3 +448,10 @@ export default {
|
|
|
448
448
|
The synchronous formatter receives `HttpErrorResponseContext`: `{ status: number, body: unknown, requestId: string }`. Narrow `body` before reading fields; it is the parsed, serialized JSON body. Return a JSON-serializable object or `undefined` to keep the original. The formatter changes only the body, preserving the HTTP status and headers except for recalculated content length, a `private, no-store` cache policy, and the correlation header. If it throws or cannot serialize its result, core logs the formatter error and retains the original body.
|
|
449
449
|
|
|
450
450
|
The hook covers status 400–599 with `application/json` or an `application/*+json` content type, including Fastify parsing errors, unmatched-route 404s, validation 422s, rate-limit 429s, and 500s. Warlock responses use the ID already stamped from `request.id`. Before Warlock creates a Request, the formatter receives Fastify's `request.id` and echoes that same value under `http.requestId.header` (default `X-Request-Id`). Setting `http.requestId.enabled: false` suppresses that header; without a stamped Warlock ID, the formatter falls back to Fastify's request ID even for routed responses. Keep request-ID headers enabled when the body must correlate with Warlock request logs. HTML pages, files, raw text, streams, and errors after headers have been sent are not reformatted. With no formatter configured, existing response shapes are unchanged.
|
|
451
|
+
|
|
452
|
+
## Co-located configuration tests
|
|
453
|
+
|
|
454
|
+
`src/config/mail.spec.ts` and `src/config/mail.test.tsx` are never imported as
|
|
455
|
+
configuration. Discovery and reload also exclude `test`, `tests`, `__tests__`,
|
|
456
|
+
`__mocks__`, `__fixtures__`, and `fixtures` directories at any depth. A real
|
|
457
|
+
`src/config/tests.ts` remains ordinary configuration.
|
|
@@ -195,3 +195,12 @@ Generated web starter files match the scaffold’s Oxfmt configuration and its
|
|
|
195
195
|
built-in import sorting.
|
|
196
196
|
After editing generated code, run `npm run format` and `npm run lint:check`.
|
|
197
197
|
Formatting retains unused imports; the linter reports them.
|
|
198
|
+
|
|
199
|
+
## Notification translation defaults
|
|
200
|
+
|
|
201
|
+
After configuring `app.localeCodes`, run `npx warlock add notifications`.
|
|
202
|
+
New notification dictionaries include `notFound` as a string in every configured
|
|
203
|
+
locale. English ("Notification not found") is the explicit fallback for each
|
|
204
|
+
locale without a bundled translation, including locale sets without English.
|
|
205
|
+
Replace fallback text with your translations before publishing. Existing module
|
|
206
|
+
files and existing translation keys are preserved.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-web-ui
|
|
3
|
+
description: "Generate web pages, schema-backed forms and CRUD UI with warlock generate page, form and crud-ui. Includes setup companions, design-system controls, useSubmitForm, optional guarded endpoints and module CRUD prerequisites."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Generate web UI
|
|
7
|
+
|
|
8
|
+
Run these commands from an app with `@warlock.js/web` installed and the app's
|
|
9
|
+
design system in `src/web/design-system/components/`. Controls are default
|
|
10
|
+
exports, imported individually through `web/design-system/components/<name>`;
|
|
11
|
+
they implement Mongez `useFormControl` and accept `name`, `label`, `hint` and
|
|
12
|
+
`error`. Forms import `Form` directly from `@mongez/react-form`.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npx warlock generate page account/settings
|
|
16
|
+
npx warlock generate page dashboard --interactive
|
|
17
|
+
npx warlock generate form contact
|
|
18
|
+
npx warlock generate form feedback --module posts --endpoint
|
|
19
|
+
npx warlock generate crud-ui posts --dry-run --json
|
|
20
|
+
npx warlock generate crud-ui posts
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`page account/settings` creates `src/web/account/settings.page.tsx` and
|
|
24
|
+
`settings.setup.ts`, with `/account/settings`, a named route, a loader and
|
|
25
|
+
`config.metadata`. `--interactive` also creates a counter feature hook under
|
|
26
|
+
`hooks/`; replace that example with the feature's behavior. Executable page
|
|
27
|
+
exports belong in `.setup.ts`, UI in `.page.tsx`, state and effects in hooks.
|
|
28
|
+
|
|
29
|
+
`form contact` creates `src/web/forms/contact-form.tsx`, a props file, a
|
|
30
|
+
`use-contact-form` hook using `useSubmitForm`, and
|
|
31
|
+
`src/shared/schema/contact.schema.ts`. The starter schema has one `name`
|
|
32
|
+
string field; edit that shared schema and the controls together. Its default
|
|
33
|
+
submit target is `/contact`; supply a real endpoint or set the form's `path`.
|
|
34
|
+
With `--module posts`, output lives under `src/web/posts/`, the shared schema
|
|
35
|
+
under `src/shared/schema/posts/`, and the target is `/posts/contact`.
|
|
36
|
+
The module must exist. `--endpoint` requires `--module`, adds a controller
|
|
37
|
+
with `validation` and `responseSchema`, and appends a guarded POST route.
|
|
38
|
+
This initial endpoint validates and echoes the input; replace that body with a
|
|
39
|
+
service call to persist it. It does not create database persistence for you.
|
|
40
|
+
|
|
41
|
+
For CRUD UI, first generate a full module with
|
|
42
|
+
`npx warlock generate module posts`. Define its create/update schemas and
|
|
43
|
+
list query schema, then run `npx warlock generate crud-ui posts`.
|
|
44
|
+
The generator loads the real Seal schemas using the app loader, so their
|
|
45
|
+
imports must resolve. These schema modules must be safe to import on both
|
|
46
|
+
client and server. Plain string fields map to TextInput, password names to
|
|
47
|
+
PasswordInput, email/date/time/date-time formats to the corresponding controls,
|
|
48
|
+
and booleans to CheckboxInput. Unions, nullable fields, enums, numeric fields,
|
|
49
|
+
nested objects and arrays require custom control decisions and are rejected
|
|
50
|
+
before writing output. An empty module schema produces an empty form.
|
|
51
|
+
|
|
52
|
+
CRUD output contains list/create/edit pages and setup companions, separate
|
|
53
|
+
create/update forms, their hooks and props, list/edit/delete hooks, and a
|
|
54
|
+
delete action component. Lists use the existing GET endpoint and its
|
|
55
|
+
`data`/`pagination` contract (`page` and `pages`). Literal fields in
|
|
56
|
+
`listPostsQuerySchema` become filter inputs; `page` is controlled by navigation
|
|
57
|
+
and `limit` remains the endpoint default. Edit reads GET `/posts/:id`, create
|
|
58
|
+
submits POST `/posts`, update uses PUT `/posts/:id`, and delete uses DELETE
|
|
59
|
+
`/posts/:id`. Successful deletion refreshes the list. Mutation forms show
|
|
60
|
+
validation errors, pending state and success feedback.
|
|
61
|
+
|
|
62
|
+
CRUD generation accepts the conventional full module route file, allowing
|
|
63
|
+
formatting and comments. It fails closed for custom routes/guards instead of
|
|
64
|
+
inferring authorization. Pages require `requireUser({ userType: "user" })`,
|
|
65
|
+
matching the starter's `guarded` helper. Configure `web.session` with the auth
|
|
66
|
+
page-session resolver before visiting these pages. Endpoint guards still
|
|
67
|
+
run on every API request; customized authorization requires a hand-written UI.
|
|
68
|
+
|
|
69
|
+
All three commands support `--dry-run --json` through the normal generation
|
|
70
|
+
report. Existing generated files are skipped unless `--force` is explicit.
|
|
71
|
+
Route registration is idempotent and retains existing module content.
|
package/skills/index.md
CHANGED
|
@@ -18,11 +18,11 @@ When the framework is wrong or missing a capability, follow the `report-framewor
|
|
|
18
18
|
|
|
19
19
|
## Topics by area
|
|
20
20
|
|
|
21
|
-
- **Foundations:** warlock-conventions, create-module, make-endpoint, configure-app, use-app-context, resolve-path, build-url, update-packages, lower-stage3-decorators, upgrade-from-5-29
|
|
21
|
+
- **Foundations:** warlock-conventions, create-module, make-endpoint, configure-app, build-budgets, use-app-context, resolve-path, build-url, update-packages, lower-stage3-decorators, upgrade-from-5-29
|
|
22
22
|
- **HTTP & routing:** register-route, warlock-routes, create-controller, use-middleware, write-middleware, validate-input, use-request-locals, build-restful, wire-socket
|
|
23
23
|
- **Responses & resources:** send-response, define-resource, use-model-transformers, use-localization
|
|
24
24
|
- **Domain layers:** write-use-case, use-repository, write-seeder
|
|
25
|
-
- **CLI & running the app:** run-app, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
|
|
25
|
+
- **CLI & running the app:** generate-web-ui, run-app, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
|
|
26
26
|
- **Agent tooling:** warlock-mcp, warlock-trace, warlock-agents-md, warlock-board
|
|
27
27
|
- **Storage, files & mail:** store-file, upload-file, process-image, send-mail
|
|
28
28
|
- **Security & utilities:** hash-password, encrypt-data, store-secrets, retry-operation, benchmark-code, request-memo, request-tracing
|
|
@@ -279,3 +279,16 @@ router.get("/webhooks/status", webhookStatusController, { rateLimit: false });
|
|
|
279
279
|
- The `create-controller` topic — the handler shape, validation, request/response surface.
|
|
280
280
|
- The `send-response` topic — picking the right `response.<helper>()`.
|
|
281
281
|
- The `warlock-conventions` topic — module layout, the `guarded` convention, path aliases.
|
|
282
|
+
|
|
283
|
+
## A named route the browser must never call — `clientVisible: false`
|
|
284
|
+
|
|
285
|
+
A named API route is embedded in a page's browser route table only when that page's client code reaches its name. Mark an endpoint that browser code must never reach with `clientVisible: false`:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
router.post("/internal/reindex", reindexController, {
|
|
289
|
+
name: "internal.reindex",
|
|
290
|
+
clientVisible: false,
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
It is then never embedded in a page, and `warlock check` reports a browser reference to its name as `WL9020` (an error). The server still authorizes the call as for any route.
|
package/skills/run-app/SKILL.md
CHANGED
|
@@ -182,7 +182,7 @@ Omit switches to run TypeScript and installed/configured Oxlint. Set `oxlint: fa
|
|
|
182
182
|
|
|
183
183
|
## `warlock build` — production bundle
|
|
184
184
|
|
|
185
|
-
esbuild bundle of the app down to a single JS file in `dist/`.
|
|
185
|
+
esbuild bundle of the app down to a single JS file in `dist/`. `--json` prints the client budget report; build settings come from `warlock.config.ts > build`. See the `build-budgets` topic for per-page gzip limits and deltas.
|
|
186
186
|
|
|
187
187
|
### `build.*` config knobs
|
|
188
188
|
|
|
@@ -400,3 +400,28 @@ await Mail.to(user.email)
|
|
|
400
400
|
|
|
401
401
|
- The `configure-app` topic — `src/config/mail.ts` shape and `env()` patterns.
|
|
402
402
|
- The `warlock-conventions` topic — where mail-sending services live.
|
|
403
|
+
|
|
404
|
+
## Preview development mail in devtools
|
|
405
|
+
|
|
406
|
+
Run `npx warlock add devtools`, start development, and open `/__warlock` (or your
|
|
407
|
+
configured devtools path). In the mail panel, select a captured message to read
|
|
408
|
+
its recipients, subject, text, and sandboxed HTML preview. Send while the panel's
|
|
409
|
+
collector is attached; captures are held in memory and disappear on restart.
|
|
410
|
+
|
|
411
|
+
Use development mail mode so previews do not deliver real email:
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
import { setMailMode, sendMail } from "@warlock.js/core";
|
|
415
|
+
|
|
416
|
+
setMailMode("development");
|
|
417
|
+
await sendMail({
|
|
418
|
+
to: "reader@example.com",
|
|
419
|
+
subject: "Preview",
|
|
420
|
+
html: "<p>Your code is 123456</p>",
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Development sends emit `mailEvents.onCaptured`, which devtools consumes; console
|
|
425
|
+
logs show metadata rather than sensitive bodies. Devtools is mounted only for
|
|
426
|
+
the development runtime and is not mounted in production. Keep production mail
|
|
427
|
+
mode configured for actual delivery. Test-mode assertions use the test mailbox.
|
|
@@ -508,3 +508,53 @@ export default {
|
|
|
508
508
|
The synchronous formatter receives `HttpErrorResponseContext`: `{ status: number, body: unknown, requestId: string }`. Narrow `body` before reading fields; it is the parsed, serialized JSON body. Return a JSON-serializable object or `undefined` to keep the original. The formatter changes only the body, preserving the HTTP status and headers except for recalculated content length, a `private, no-store` cache policy, and the correlation header. If it throws or cannot serialize its result, core logs the formatter error and retains the original body.
|
|
509
509
|
|
|
510
510
|
The hook covers status 400–599 with `application/json` or an `application/*+json` content type, including Fastify parsing errors, unmatched-route 404s, validation 422s, rate-limit 429s, and 500s. Warlock responses use the ID already stamped from `request.id`. Before Warlock creates a Request, the formatter receives Fastify's `request.id` and echoes that same value under `http.requestId.header` (default `X-Request-Id`). Setting `http.requestId.enabled: false` suppresses that header; without a stamped Warlock ID, the formatter falls back to Fastify's request ID even for routed responses. Keep request-ID headers enabled when the body must correlate with Warlock request logs. HTML pages, files, raw text, streams, and errors after headers have been sent are not reformatted. With no formatter configured, existing response shapes are unchanged.
|
|
511
|
+
|
|
512
|
+
## Typed domain failures
|
|
513
|
+
|
|
514
|
+
`AppError<Details, Code>` is available from `@warlock.js/core` and the lightweight
|
|
515
|
+
`@warlock.js/core/errors` entry. Its constructor is
|
|
516
|
+
`new AppError(code, status, publicMessage, details?)`. Status must be an integer
|
|
517
|
+
from 400 to 599. The message and details are explicitly public, including for
|
|
518
|
+
5xx errors: never put diagnostics, credentials or private records there.
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
import { AppError } from "@warlock.js/core/errors";
|
|
522
|
+
|
|
523
|
+
throw new AppError("quotaExceeded", 429, "Try again later.", { retryAfter: 30 });
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
The default response is
|
|
527
|
+
`{ error: { code: "quotaExceeded", message: "Try again later.", details: { retryAfter: 30 } } }`.
|
|
528
|
+
Absent details are omitted; `false`, `0` and `null` details are preserved. Errors
|
|
529
|
+
retain the private, no-store cache policy, and server failures are logged.
|
|
530
|
+
|
|
531
|
+
`AppError` and `HttpError` are separate `Error` subclasses. Existing `HttpError`
|
|
532
|
+
constructors, inheritance, `payload` and legacy `{ error: message, payload? }`
|
|
533
|
+
bodies remain unchanged. Both bypass `http.errorMapper`; ordinary errors retain
|
|
534
|
+
the existing opaque 500 fallback. Choose `AppError` for new domain failures
|
|
535
|
+
that need stable codes, and keep existing HTTP error subclasses as they are.
|
|
536
|
+
|
|
537
|
+
`http.errorResponse` receives an optional typed `appError` alongside `body`,
|
|
538
|
+
`status` and `requestId`. No per-error mapper is needed:
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
import type { HttpErrorResponseFormatter } from "@warlock.js/core";
|
|
542
|
+
|
|
543
|
+
const errorResponse: HttpErrorResponseFormatter = ({ appError, requestId }) => {
|
|
544
|
+
if (!appError) return undefined; // keep other JSON errors unchanged
|
|
545
|
+
return {
|
|
546
|
+
error: {
|
|
547
|
+
code: appError.code,
|
|
548
|
+
message: appError.publicMessage,
|
|
549
|
+
...(appError.details === undefined ? {} : { details: appError.details }),
|
|
550
|
+
},
|
|
551
|
+
requestId,
|
|
552
|
+
};
|
|
553
|
+
};
|
|
554
|
+
// Set this function as http.errorResponse in your HTTP configuration.
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
The formatter sees the original instance only for thrown `AppError`s handled
|
|
558
|
+
by the request lifecycle. Early Fastify rejections and returned helper responses
|
|
559
|
+
have no `appError`; use `body` and `status` for those cases. Do not spread an
|
|
560
|
+
error instance into the response.
|
|
@@ -118,3 +118,56 @@ Cleanup verifies the unique supervisor command line before stopping only its
|
|
|
118
118
|
recorded process group (Windows: verified PID tree). It retains disposable files
|
|
119
119
|
if ownership verification fails and reports the failure in evidence. Do not add
|
|
120
120
|
process-name or port-based kills to a spec.
|
|
121
|
+
|
|
122
|
+
## Accessibility checks
|
|
123
|
+
|
|
124
|
+
Call `await page.assertAccessible()` after the content is visible and again after
|
|
125
|
+
opening menus, dialogs or validation feedback. The harness reads its pinned local
|
|
126
|
+
axe-core distribution and injects it with CDP `Runtime.evaluate`; it never fetches
|
|
127
|
+
a CDN script or adds axe to the app bundle. A missing dependency, injection error,
|
|
128
|
+
unmatched selector or scan exception fails the step.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
await page.assertAccessible();
|
|
132
|
+
await page.assertAccessible({
|
|
133
|
+
selector: "main",
|
|
134
|
+
tags: ["wcag2a", "wcag2aa", "wcag21aa"],
|
|
135
|
+
allow: [{ id: "color-contrast", selector: ".legacy-caption" }],
|
|
136
|
+
timeoutMs: 30_000,
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Defaults run all enabled axe rules on the document. `tags` uses axe's `runOnly`
|
|
141
|
+
tag filter; `selector` limits the scanned region. Allowances match exact rule IDs
|
|
142
|
+
and, when supplied, an exact single-element target selector. Omitting `selector`
|
|
143
|
+
from an allowance permits that rule everywhere; keep exceptions narrow. Nested
|
|
144
|
+
frame/shadow targets cannot match a simple selector allowance. Allowed nodes stay
|
|
145
|
+
in evidence with `allowed: true`; other nodes of the same rule still fail.
|
|
146
|
+
|
|
147
|
+
Evidence's `accessibility` array contains URL, viewport, timestamp, violations
|
|
148
|
+
with rule IDs and target selectors, and incomplete checks for manual review.
|
|
149
|
+
Incomplete checks do not fail automatically. A passing automated scan does not
|
|
150
|
+
replace keyboard, screen-reader, focus-order or contrast review. See the
|
|
151
|
+
[axe API](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md) for scan
|
|
152
|
+
semantics. Run at both 390 and 1280 pixels with `assertViewport()` as well.
|
|
153
|
+
|
|
154
|
+
## Watched development checks
|
|
155
|
+
|
|
156
|
+
Use an owned app copy with `app.env: { NODE_ENV: "development" }` and
|
|
157
|
+
`app.start: [process.execPath, "node_modules/@warlock.js/core/bin/warlock.js", "dev"]`.
|
|
158
|
+
Do not attach to an existing server. The fixture's `directory` is available in
|
|
159
|
+
`run`; use `node:fs/promises` to edit only files inside that copy.
|
|
160
|
+
|
|
161
|
+
Wait for a visible revision marker after each edit; a successful file write alone
|
|
162
|
+
does not prove watcher invalidation or browser hydration. Exercise several edits,
|
|
163
|
+
including setup-file creation, changes and removal. After each revision, request
|
|
164
|
+
the page again, exercise a hydrated control, check imported `shared` in loaders
|
|
165
|
+
and middleware, and verify concurrent requests keep distinct values. Do not
|
|
166
|
+
allowlist request-store or hydration errors to make these checks pass.
|
|
167
|
+
|
|
168
|
+
For forms beneath layouts, compare server-rendered IDs with the hydrated hook
|
|
169
|
+
values (for example expose the hook value in a click handler), check repeated
|
|
170
|
+
instances, label `for` and ARIA references, then navigate away and back. Comparing
|
|
171
|
+
DOM IDs alone can miss a mismatch: React may retain the server attribute while
|
|
172
|
+
client handlers capture another ID. Capture evidence and screenshots at every
|
|
173
|
+
revision. Do not replace React's `useId()` globally or hide hydration warnings.
|