@warlock.js/core 5.24.0 → 5.26.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 +64 -1
- package/bin/warlock.js +0 -0
- package/esm/application/application.d.mts +21 -0
- package/esm/application/application.d.mts.map +1 -1
- package/esm/application/application.mjs +29 -1
- package/esm/application/application.mjs.map +1 -1
- package/esm/application/boot-signal.d.mts +2 -1
- package/esm/application/boot-signal.d.mts.map +1 -1
- package/esm/application/boot-signal.mjs.map +1 -1
- package/esm/application/index.d.mts +1 -0
- package/esm/application/index.mjs +1 -0
- package/esm/application/roles.d.mts +72 -0
- package/esm/application/roles.d.mts.map +1 -0
- package/esm/application/roles.mjs +124 -0
- package/esm/application/roles.mjs.map +1 -0
- package/esm/cli/commands/add.command.mjs.map +1 -1
- package/esm/cli/commands/create-database.command.mjs.map +1 -1
- package/esm/cli/commands/dev-server.command.mjs.map +1 -1
- package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/index.mjs +2 -0
- package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +170 -0
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/doctor-command.action.mjs +7 -2
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
- package/esm/cli/commands/doctor.command.mjs +5 -0
- package/esm/cli/commands/doctor.command.mjs.map +1 -1
- package/esm/cli/commands/drop-tables.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs +1 -12
- package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/path-resolver.mjs.map +1 -1
- package/esm/cli/commands/migrate.command.mjs.map +1 -1
- package/esm/cli/commands/routes.command.mjs.map +1 -1
- package/esm/cli/commands/seed.command.mjs.map +1 -1
- package/esm/cli/commands/start-production.command.mjs +54 -5
- package/esm/cli/commands/start-production.command.mjs.map +1 -1
- package/esm/cli/commands/storage-put.action.mjs.map +1 -1
- package/esm/cli/commands/storage-put.command.mjs.map +1 -1
- package/esm/cli/commands/typings-generator.command.mjs +20 -1
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/cli/commands/update.command.mjs.map +1 -1
- package/esm/cli/commands-loader.mjs.map +1 -1
- package/esm/config/config-loader.d.mts.map +1 -1
- package/esm/config/config-loader.mjs +3 -1
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/database-connector.mjs.map +1 -1
- package/esm/connectors/herald-connector.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +7 -0
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.mjs.map +1 -1
- package/esm/connectors/mail-connector.mjs.map +1 -1
- package/esm/connectors/types.d.mts +8 -0
- package/esm/connectors/types.d.mts.map +1 -1
- package/esm/connectors/types.mjs.map +1 -1
- package/esm/database/create-database-action.mjs.map +1 -1
- package/esm/database/drop-tables-action.mjs.map +1 -1
- package/esm/database/migrate-action.mjs +4 -3
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/models/database-log/database-log.mjs.map +1 -1
- package/esm/database/seed-command-action.mjs +2 -1
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/dev-server/dependency-graph.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +2 -0
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/layer-executor.mjs.map +1 -1
- package/esm/dev-server/legacy-locales-warning.mjs +16 -0
- package/esm/dev-server/legacy-locales-warning.mjs.map +1 -0
- package/esm/dev-server/manifest-manager.mjs.map +1 -1
- package/esm/dev-server/module-loader.mjs +26 -5
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/register-installed-devtools.mjs +47 -0
- package/esm/dev-server/register-installed-devtools.mjs.map +1 -0
- package/esm/dev-server/run-connector-typings.mjs +28 -0
- package/esm/dev-server/run-connector-typings.mjs.map +1 -0
- package/esm/dev-server/special-file-patterns.mjs +5 -3
- package/esm/dev-server/special-file-patterns.mjs.map +1 -1
- package/esm/dev-server/special-files-collector.d.mts +2 -2
- package/esm/dev-server/special-files-collector.d.mts.map +1 -1
- package/esm/dev-server/special-files-collector.mjs +7 -4
- package/esm/dev-server/special-files-collector.mjs.map +1 -1
- package/esm/dev-server/translation-keys-sources.mjs +7 -3
- package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +27 -4
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/encryption/password.mjs.map +1 -1
- package/esm/generations/add-command.action.mjs +3 -1
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/features/access.feature.mjs +38 -18
- package/esm/generations/features/access.feature.mjs.map +1 -1
- package/esm/generations/features/ai-anthropic.feature.mjs.map +1 -1
- package/esm/generations/features/ai-bedrock.feature.mjs.map +1 -1
- package/esm/generations/features/ai-google.feature.mjs.map +1 -1
- package/esm/generations/features/ai-ollama.feature.mjs.map +1 -1
- package/esm/generations/features/ai-openai.feature.mjs.map +1 -1
- package/esm/generations/features/ai-panoptic.feature.mjs.map +1 -1
- package/esm/generations/features/ai-tools.feature.mjs.map +1 -1
- package/esm/generations/features/ai-workspace.feature.mjs.map +1 -1
- package/esm/generations/features/ai.feature.mjs.map +1 -1
- package/esm/generations/features/bull-board.feature.mjs +2 -2
- package/esm/generations/features/bull-board.feature.mjs.map +1 -1
- package/esm/generations/features/devtools.feature.mjs +23 -0
- package/esm/generations/features/devtools.feature.mjs.map +1 -0
- package/esm/generations/features/herald.feature.mjs.map +1 -1
- package/esm/generations/features/image.feature.mjs.map +1 -1
- package/esm/generations/features/index.mjs +2 -0
- package/esm/generations/features/index.mjs.map +1 -1
- package/esm/generations/features/mail.feature.mjs.map +1 -1
- package/esm/generations/features/mongodb.feature.mjs.map +1 -1
- package/esm/generations/features/notifications.feature.mjs.map +1 -1
- package/esm/generations/features/postgres.feature.mjs.map +1 -1
- package/esm/generations/features/react-email.feature.mjs.map +1 -1
- package/esm/generations/features/react.feature.mjs.map +1 -1
- package/esm/generations/features/redis.feature.mjs.map +1 -1
- package/esm/generations/features/s3.feature.mjs.map +1 -1
- package/esm/generations/features/scheduler.feature.mjs.map +1 -1
- package/esm/generations/features/ses.feature.mjs.map +1 -1
- package/esm/generations/features/socket.feature.mjs.map +1 -1
- package/esm/generations/features/test.feature.mjs +6 -2
- package/esm/generations/features/test.feature.mjs.map +1 -1
- package/esm/generations/features/types.mjs.map +1 -1
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/http/build-rate-limit-options.d.mts +8 -2
- package/esm/http/build-rate-limit-options.d.mts.map +1 -1
- package/esm/http/build-rate-limit-options.mjs +1 -0
- package/esm/http/build-rate-limit-options.mjs.map +1 -1
- package/esm/http/database/RequestLog.mjs.map +1 -1
- package/esm/http/errors/errors.d.mts +1 -17
- package/esm/http/errors/errors.d.mts.map +1 -1
- package/esm/http/errors/errors.mjs +1 -20
- package/esm/http/errors/errors.mjs.map +1 -1
- package/esm/http/index.d.mts +4 -3
- package/esm/http/index.mjs +3 -2
- package/esm/http/middleware/inject-request-context.d.mts +1 -1
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +9 -1
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.d.mts.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.mjs +5 -1
- package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
- package/esm/http/request.d.mts +7 -24
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +1 -22
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts +5 -5
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs +4 -2
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server-rebuilt.d.mts +20 -0
- package/esm/http/server-rebuilt.d.mts.map +1 -0
- package/esm/http/server-rebuilt.mjs +21 -0
- package/esm/http/server-rebuilt.mjs.map +1 -0
- package/esm/http/tracing/index.mjs +1 -1
- package/esm/http/tracing/tracing-dispatcher.d.mts +13 -6
- package/esm/http/tracing/tracing-dispatcher.d.mts.map +1 -1
- package/esm/http/tracing/tracing-dispatcher.mjs +46 -14
- package/esm/http/tracing/tracing-dispatcher.mjs.map +1 -1
- package/esm/http/tracing/tracing.type.d.mts +2 -1
- package/esm/http/tracing/tracing.type.d.mts.map +1 -1
- package/esm/http/types.d.mts +9 -0
- package/esm/http/types.d.mts.map +1 -1
- package/esm/image/image.mjs.map +1 -1
- package/esm/index.d.mts +8 -4
- package/esm/index.mjs +8 -3
- package/esm/loader/app-module-importer.d.mts +17 -0
- package/esm/loader/app-module-importer.d.mts.map +1 -0
- package/esm/loader/app-module-importer.mjs +20 -0
- package/esm/loader/app-module-importer.mjs.map +1 -0
- package/esm/localization/index.mjs +4 -0
- package/esm/localization/parse-locale-dictionary.d.mts +23 -0
- package/esm/localization/parse-locale-dictionary.d.mts.map +1 -0
- package/esm/localization/parse-locale-dictionary.mjs +215 -0
- package/esm/localization/parse-locale-dictionary.mjs.map +1 -0
- package/esm/localization/register-locale-dictionary.d.mts +12 -0
- package/esm/localization/register-locale-dictionary.d.mts.map +1 -0
- package/esm/localization/register-locale-dictionary.mjs +58 -0
- package/esm/localization/register-locale-dictionary.mjs.map +1 -0
- package/esm/logger/logger.mjs.map +1 -1
- package/esm/mail/events.d.mts +7 -0
- package/esm/mail/events.d.mts.map +1 -1
- package/esm/mail/events.mjs +7 -0
- package/esm/mail/events.mjs.map +1 -1
- package/esm/mail/send-mail.d.mts.map +1 -1
- package/esm/mail/send-mail.mjs +23 -2
- package/esm/mail/send-mail.mjs.map +1 -1
- package/esm/mail/types.d.mts +5 -1
- package/esm/mail/types.d.mts.map +1 -1
- package/esm/mail/types.mjs.map +1 -1
- package/esm/manifest/manifest-manager.mjs.map +1 -1
- package/esm/production/build-contributions.mjs +19 -3
- package/esm/production/build-contributions.mjs.map +1 -1
- package/esm/production/production-builder.mjs +39 -10
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/production-supervisor.mjs +6 -0
- package/esm/production/production-supervisor.mjs.map +1 -1
- package/esm/production/resolve-build-config.d.mts +1 -1
- package/esm/production/resolve-build-config.mjs +15 -0
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/production/route-registration-child.mjs +14 -2
- package/esm/production/route-registration-child.mjs.map +1 -1
- package/esm/production/route-registration-snapshot.mjs +9 -2
- package/esm/production/route-registration-snapshot.mjs.map +1 -1
- package/esm/resource/resource-field-builder.mjs +1 -1
- package/esm/resource/resource-field-builder.mjs.map +1 -1
- package/esm/resource/resource.d.mts +5 -0
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs +17 -6
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +1 -0
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +10 -1
- package/esm/router/types.d.mts.map +1 -1
- package/esm/tests/index.d.mts +3 -2
- package/esm/tests/index.mjs +3 -2
- package/esm/tests/start-http-development-server.d.mts +10 -0
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +16 -2
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/vitest-setup.d.mts +16 -1
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs +20 -2
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/use-cases/use-case-events.mjs.map +1 -1
- package/esm/utils/is-test-file.mjs +15 -0
- package/esm/utils/is-test-file.mjs.map +1 -0
- package/esm/utils/normalized-path.d.mts +3 -1
- package/esm/utils/normalized-path.d.mts.map +1 -1
- package/esm/utils/normalized-path.mjs +4 -2
- package/esm/utils/normalized-path.mjs.map +1 -1
- package/esm/utils/paths.mjs.map +1 -1
- package/esm/validation/plugins/database-plugin.mjs.map +1 -1
- package/esm/validation/validateAll.mjs +57 -4
- package/esm/validation/validateAll.mjs.map +1 -1
- package/esm/warlock-config/default-configurations.mjs +1 -0
- package/esm/warlock-config/default-configurations.mjs.map +1 -1
- package/esm/warlock-config/types.d.mts +7 -0
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
- package/llms-full.txt +382 -214
- package/llms.txt +1 -1
- package/package.json +23 -19
- package/skills/README.md +1 -1
- package/skills/configure-app/SKILL.md +2 -0
- package/skills/create-controller/SKILL.md +4 -0
- package/skills/define-resource/SKILL.md +4 -0
- package/skills/request-tracing/SKILL.md +38 -15
- package/skills/run-app/SKILL.md +71 -34
- package/skills/send-mail/SKILL.md +55 -25
- package/skills/test-http/SKILL.md +28 -9
- package/skills/test-service/SKILL.md +61 -40
- package/skills/use-localization/SKILL.md +39 -26
- package/skills/warlock-conventions/SKILL.md +26 -18
- package/skills/write-cli-command/SKILL.md +50 -45
package/llms.txt
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
- [update-packages](@warlock.js/core/update-packages/SKILL.md): Keep a project current with `warlock update` — bump every `@warlock.js/*` dependency in package.json to its latest published version (range operator preserved), then run the lockfile-detected package manager install. Also covers the `warlock dev` update notice, its `u` update-and-restart keyboard shortcut, and the `devServer.checkForUpdates` toggle. Triggers: `warlock update`, `--no-install`, `--dry-run`, `--check`, `checkForUpdates`, `fetchLatestVersion`, `isNewerVersion`; "update warlock packages", "upgrade the framework", "is there a new warlock version", "update notice in the dev server", "press u to update", "dev server keyboard shortcut", "update check offline", "bump @warlock.js/* to latest"; typical CLI `warlock update`. Skip: dev/build/start runtime — `@warlock.js/core/run-app/SKILL.md`; writing a custom command — `@warlock.js/core/write-cli-command/SKILL.md`; installing a NEW feature package (auth, mail, storage) — that is `warlock add`; releasing/publishing the framework — workspace release tooling, not this command.
|
|
34
34
|
- [upload-file](@warlock.js/core/upload-file/SKILL.md): Handle multipart file uploads — read via `request.file()` or `request.validated()`, validate with `v.file()`, save via `UploadedFile.save()` or the storage layer, transform images inline. Triggers: `UploadedFile`, `request.file`, `v.file`, `.save`, `.saveAs`, `.resize`, `.format`, `.quality`, `.image`, `.mimeType`, `.maxSize`; "accept a file upload", "validate file size and mime", "save to S3 or local disk", "resize an uploaded image on save"; typical import `import type { UploadedFile, RequestHandler } from "@warlock.js/core"`. Skip: storage drivers + presigned URLs — `@warlock.js/core/store-file/SKILL.md`; image-only transforms — `@warlock.js/core/process-image/SKILL.md`; schema rules — `@warlock.js/core/validate-input/SKILL.md`; competing libs `multer`, `formidable`, `busboy`.
|
|
35
35
|
- [use-app-context](@warlock.js/core/use-app-context/SKILL.md): Read app-wide context — the `Application` static class (env, version, uptime, runtime strategy, boot lifecycle) plus the `app` runtime accessor (live Fastify, socket.io, router, database via the DI container). Triggers: `Application.isProduction`, `Application.environment`, `Application.runtimeStrategy`, `Application.uptime`, `Application.version`, `Application.onceBooted`, `Application.whenBooted`, `Application.isBooted`, `Application.onShutdown`, `Application.isShuttingDown`, `app.http`, `app.socket`, `app.database`, `app.router`; "branch on environment", "reach the live Fastify instance", "framework version in health endpoint", "dev vs production runtime check", "run code once the app is fully booted", "after all connectors started", "app booted hook", "run cleanup before shutdown", "graceful shutdown hook"; typical import `import { Application, app } from "@warlock.js/core"`. Skip: path helpers — `@warlock.js/core/resolve-path/SKILL.md`; connector start order — `@warlock.js/core/add-connector/SKILL.md`; competing patterns: bare `process.env.NODE_ENV`, ad-hoc Fastify imports.
|
|
36
|
-
- [use-localization](@warlock.js/core/use-localization/SKILL.md): Multi-locale translations via `groupedTranslations` (declare keys), `t()`
|
|
36
|
+
- [use-localization](@warlock.js/core/use-localization/SKILL.md): Multi-locale translations via `groupedTranslations` (declare keys), `t()` (look up; always use it in app code, never `request.t()`), `request.getLocaleCode()` (detect locale from headers/query), `getLocalized` (pick the right value from a localized-array column). Triggers: `groupedTranslations`, `t`, `request.t`, `request.trans`, `request.transFrom`, `request.getLocaleCode`, `request.setLocaleCode`, `getLocalized`; "add a translation key", "resolve a localized error message", "detect request locale", "pick the right per-locale column value"; typical import `import { t, getLocalized } from "@warlock.js/core"`. Skip: resource output — `@warlock.js/core/define-resource/SKILL.md`; module scaffold — `@warlock.js/core/create-module/SKILL.md`; competing libs `i18next`, `react-intl`, raw `@mongez/localization`.
|
|
37
37
|
- [use-middleware](@warlock.js/core/use-middleware/SKILL.md): Attach built-in HTTP middleware to routes via the `middleware` namespace from `@warlock.js/core` — rateLimit, concurrencyLimit, maxBodySize, idempotency, maintenance, ipFilter, cache. Plus `X-Request-Id` correlation, wired automatically. Triggers: `middleware.rateLimit`, `middleware.concurrencyLimit`, `middleware.maxBodySize`, `middleware.idempotency`, `middleware.maintenance`, `middleware.ipFilter`, `middleware.cache`, `X-Request-Id`, `Idempotency-Key`; "add rate limiting", "dedupe writes by idempotency key", "cap concurrent requests", "block IPs", "cache a GET response"; typical import `import { middleware } from "@warlock.js/core"`. Skip: author custom middleware — `@warlock.js/core/write-middleware/SKILL.md`; cache singleton — `@warlock.js/cache/cache-basics/SKILL.md`; competing libs `@fastify/rate-limit` direct, `express-rate-limit`, `helmet`.
|
|
38
38
|
- [use-model-transformers](@warlock.js/core/use-model-transformers/SKILL.md): Three schema-side helpers — `useHashedPassword()` (bcrypt on save) attaches via `.addTransformer(...)`; `useComputedSlug(field?, scope?)` (auto-slug from another field) and `useComputedModel(callback)` (arbitrary computed-on-save value) attach via `v.computed(...)`. Triggers: `useHashedPassword`, `useComputedSlug`, `useComputedModel`, `.addTransformer`, `v.computed`, `ComputedCallback`; "auto-hash a password field", "auto-slug from title on save", "derive a value at write time", "declarative model transformers"; typical import `import { useHashedPassword, useComputedSlug } from "@warlock.js/core"`. Skip: bcrypt setup details — `@warlock.js/core/hash-password/SKILL.md`; repository writes — `@warlock.js/core/use-repository/SKILL.md`; output filtering — `@warlock.js/core/define-resource/SKILL.md`; competing patterns: manual `await hashPassword(input)` in services, ORM lifecycle hooks.
|
|
39
39
|
- [use-repository](@warlock.js/core/use-repository/SKILL.md): Subclass `RepositoryManager` for data access — declare `source`, `filterBy`, `defaultOptions`, then call `list()`/`listCached()`/`find()`/`create()`/`update()`/`delete()`, the active/cached/cursor variants, and the `filterBy`-aware aggregates `sum()`/`avg()`/`min()`/`max()`/`groupBy()`/`aggregate()`. Triggers: `RepositoryManager`, `FilterRules`, `RepositoryOptions`, `.list`, `.listCached`, `.find`, `.findCached`, `.create`, `.update`, `.delete`, `.sum`, `.avg`, `.min`, `.max`, `.groupBy`, `.aggregate`, `simpleSelectColumns`; "create a repository", "filter rules for a list endpoint", "cursor vs page pagination", "cached vs uncached read", "sum/avg/group-by with filters"; typical import `import { RepositoryManager } from "@warlock.js/core"`. Skip: cache singleton — `@warlock.js/cache/cache-basics/SKILL.md`; use-case pipelines — `@warlock.js/core/write-use-case/SKILL.md`; wire mapping — `@warlock.js/core/define-resource/SKILL.md`; competing libs `typeorm` Repository, `prisma.client.<model>`, `@nestjs/typeorm`.
|
package/package.json
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
"warlock": "bin/warlock.js"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@fastify/cookie": "^
|
|
12
|
-
"@fastify/cors": "^11.
|
|
13
|
-
"@fastify/http-proxy": "^11.
|
|
14
|
-
"@fastify/multipart": "^
|
|
15
|
-
"@fastify/rate-limit": "^
|
|
16
|
-
"@fastify/static": "^
|
|
11
|
+
"@fastify/cookie": "^11.1.2",
|
|
12
|
+
"@fastify/cors": "^11.3.0",
|
|
13
|
+
"@fastify/http-proxy": "^11.6.3",
|
|
14
|
+
"@fastify/multipart": "^10.1.2",
|
|
15
|
+
"@fastify/rate-limit": "^11.2.0",
|
|
16
|
+
"@fastify/static": "^10.1.5",
|
|
17
17
|
"@mongez/concat-route": "^1.2.1",
|
|
18
18
|
"@mongez/config": "^1.2.2",
|
|
19
19
|
"@mongez/copper": "^2.1.3",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"@mongez/slug": "^1.0.7",
|
|
26
26
|
"@mongez/supportive-is": "^2.1.5",
|
|
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.26.0",
|
|
29
|
+
"@warlock.js/cascade": "5.26.0",
|
|
30
|
+
"@warlock.js/context": "5.26.0",
|
|
31
|
+
"@warlock.js/fs": "5.26.0",
|
|
32
|
+
"@warlock.js/logger": "5.26.0",
|
|
33
|
+
"@warlock.js/seal": "5.26.0",
|
|
34
34
|
"bcryptjs": "^3.0.3",
|
|
35
35
|
"chokidar": "^5.0.0",
|
|
36
36
|
"dayjs": "^1.11.19",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"esbuild": "^0.27.2",
|
|
39
39
|
"eslint": "^9.18.0",
|
|
40
40
|
"fast-glob": "^3.3.3",
|
|
41
|
-
"fastify": "^5.
|
|
42
|
-
"find-my-way": "^9.
|
|
41
|
+
"fastify": "^5.12.5",
|
|
42
|
+
"find-my-way": "^9.9.0",
|
|
43
43
|
"get-tsconfig": "^5.0.0-beta.5",
|
|
44
44
|
"import-meta-resolve": "^4.2.0",
|
|
45
45
|
"mime": "^4.1.0",
|
|
@@ -52,10 +52,11 @@
|
|
|
52
52
|
"@aws-sdk/lib-storage": "^3.955.0",
|
|
53
53
|
"@aws-sdk/s3-request-presigner": "^3.955.0",
|
|
54
54
|
"@react-email/render": "^2.0.5",
|
|
55
|
-
"@warlock.js/access": "5.
|
|
56
|
-
"@warlock.js/ai": "5.
|
|
57
|
-
"@warlock.js/herald": "5.
|
|
58
|
-
"@warlock.js/notifications": "5.
|
|
55
|
+
"@warlock.js/access": "5.26.0",
|
|
56
|
+
"@warlock.js/ai": "5.26.0",
|
|
57
|
+
"@warlock.js/herald": "5.26.0",
|
|
58
|
+
"@warlock.js/notifications": "5.26.0",
|
|
59
|
+
"@warlock.js/devtools": "5.26.0",
|
|
59
60
|
"nodemailer": "^8.0.5",
|
|
60
61
|
"react": "^19.2.3",
|
|
61
62
|
"react-dom": "^19.2.3",
|
|
@@ -104,6 +105,9 @@
|
|
|
104
105
|
},
|
|
105
106
|
"@warlock.js/notifications": {
|
|
106
107
|
"optional": true
|
|
108
|
+
},
|
|
109
|
+
"@warlock.js/devtools": {
|
|
110
|
+
"optional": true
|
|
107
111
|
}
|
|
108
112
|
},
|
|
109
113
|
"repository": {
|
|
@@ -123,7 +127,7 @@
|
|
|
123
127
|
],
|
|
124
128
|
"author": "hassanzohdy",
|
|
125
129
|
"license": "MIT",
|
|
126
|
-
"version": "5.
|
|
130
|
+
"version": "5.26.0",
|
|
127
131
|
"type": "module",
|
|
128
132
|
"main": "./esm/index.mjs",
|
|
129
133
|
"module": "./esm/index.mjs",
|
package/skills/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Each folder holds one `SKILL.md` (an agent-facing how-to for a single task). The
|
|
|
34
34
|
- [use-app-context](use-app-context/SKILL.md) — read app-wide context via the `Application` static class and the `app` runtime accessor.
|
|
35
35
|
- [add-connector](add-connector/SKILL.md) — extend the lifecycle with a `BaseConnector` subclass (`start`/`shutdown`/`watchedFiles`).
|
|
36
36
|
- [wire-socket](wire-socket/SKILL.md) — configure Socket.IO, reach the live server via `getSocketServer()` / `app.socket`.
|
|
37
|
-
- [use-localization](use-localization/SKILL.md) — multi-locale translations: `groupedTranslations`, `t()`
|
|
37
|
+
- [use-localization](use-localization/SKILL.md) — multi-locale translations: `groupedTranslations`, `t()` (always, never `request.t()`), locale resolution.
|
|
38
38
|
|
|
39
39
|
## Use-cases & services
|
|
40
40
|
|
|
@@ -229,6 +229,8 @@ Same shape — TS validates the union; runtime picks the `default` key.
|
|
|
229
229
|
- `socket.adapter` — adapter factory for cross-server broadcast.
|
|
230
230
|
- `cache.silenceSingleServerWarning`, `storage.silenceSingleServerWarning`, `socket.silenceSingleServerWarning` — silence the production warnings for in-memory cache, local storage, and adapter-less sockets.
|
|
231
231
|
|
|
232
|
+
`http.modelValidationErrorStatus` sets the status for a Cascade model-write validation failure (a model failing its own schema on save). It defaults to `500` with an opaque body, because request input is already validated with a `422` before the model is touched. Set a `4xx` (e.g. `422`) to treat it as a client error instead; the response then carries `{ errors }`.
|
|
233
|
+
|
|
232
234
|
## Per-env knobs
|
|
233
235
|
|
|
234
236
|
For one-off switches gated on environment, the canonical pattern is `Application.isProduction` (also `isDevelopment`, `isTest`):
|
|
@@ -102,6 +102,10 @@ Two pieces:
|
|
|
102
102
|
|
|
103
103
|
If validation fails, the framework returns a 400 with an `errors` payload and your handler never runs.
|
|
104
104
|
|
|
105
|
+
### Validating route params
|
|
106
|
+
|
|
107
|
+
`handler.validation = { params: v.object({ id: v.int().min(1) }) }` validates path segments (e.g. `/posts/:id`). Number and boolean validators convert the string segments; the app's schema object is never changed. Parsed values replace `request.params` (`request.input("id")` is a number) and merge into `request.validated()`, with params winning over body fields. Failures answer like a `schema` failure.
|
|
108
|
+
|
|
105
109
|
### Typing a guarded handler
|
|
106
110
|
|
|
107
111
|
Routes behind `authMiddleware` need `request.locals.user` typed. Project conventions add a `GuardedRequest<TSchema>` (overriding `locals` to `RequestLocals & { user: User }`) and a paired `GuardedRequestHandler<TSchema>` alias in `app/auth/requests/guarded.request`:
|
|
@@ -221,6 +221,10 @@ export class Product extends Model {
|
|
|
221
221
|
|
|
222
222
|
This is convention, not a framework requirement. Controllers/services that import the resource explicitly work the same way.
|
|
223
223
|
|
|
224
|
+
### `id` and Mongo `_id`
|
|
225
|
+
|
|
226
|
+
The output `id` falls back to `_id` when the record has no `id`; an explicit `id` always wins. If a collection uses native ObjectIds, set `public primaryKey = "_id"` on the model so `Model.find(hex)` resolves the `id` your resource emits.
|
|
227
|
+
|
|
224
228
|
## Output-only rule
|
|
225
229
|
|
|
226
230
|
Resources map fields. They do not:
|
|
@@ -55,6 +55,7 @@ type TracingContext = {
|
|
|
55
55
|
type TracingPhaseInfo = {
|
|
56
56
|
name: string;
|
|
57
57
|
durationMs: number;
|
|
58
|
+
startedAt?: number; // epoch milliseconds; supplied when the phase started
|
|
58
59
|
attrs?: Record<string, unknown>;
|
|
59
60
|
};
|
|
60
61
|
|
|
@@ -73,29 +74,52 @@ type TracingHooks = {
|
|
|
73
74
|
|
|
74
75
|
Every verb is optional — a hook that only wants phase spans need not implement
|
|
75
76
|
`onRequestStart`/`onRequestEnd`. Register as many hooks as you like via
|
|
76
|
-
`hooks: TracingHooks[]`; each fires independently.
|
|
77
|
+
`hooks: TracingHooks[]`; each fires independently. Use `registerTracingHooks()`
|
|
78
|
+
when the observer is installed by application code rather than static config:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { registerTracingHooks, type TracingHooks } from "@warlock.js/core";
|
|
82
|
+
|
|
83
|
+
const hooks: TracingHooks = {
|
|
84
|
+
onPhase(ctx, phase) {
|
|
85
|
+
console.log(ctx.traceId, phase.name, phase.startedAt);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const unregister = registerTracingHooks(hooks);
|
|
90
|
+
// Later, for example during plugin teardown:
|
|
91
|
+
unregister();
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`registerTracingHooks(hooks): () => void` enables tracing immediately. Runtime
|
|
95
|
+
hooks run after configured hooks; calling the returned unregister function more
|
|
96
|
+
than once is safe. When a phase did not provide `startedAt`, the dispatcher
|
|
97
|
+
derives it from its duration before delivering the callback.
|
|
77
98
|
|
|
78
99
|
## Phase names
|
|
79
100
|
|
|
80
101
|
`core` wires five phases into the request lifecycle, in this order, for every
|
|
81
102
|
HTTP request:
|
|
82
103
|
|
|
83
|
-
| Phase
|
|
84
|
-
|
|
|
85
|
-
| `route.match`
|
|
86
|
-
| `middleware`
|
|
87
|
-
| `validation`
|
|
88
|
-
| `handler`
|
|
104
|
+
| Phase | Fires around |
|
|
105
|
+
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
106
|
+
| `route.match` | Resolving the incoming path/method to a registered route |
|
|
107
|
+
| `middleware` | Each middleware in the route's chain — one `onPhase` call per middleware, with `attrs: { name, index }` |
|
|
108
|
+
| `validation` | The route's input validation (`v.object(...)` / RESTful resource validation) |
|
|
109
|
+
| `handler` | The route handler itself |
|
|
89
110
|
| `response.write` | The overall request span, closed once the response has settled (success or thrown error) — this is also where `onRequestEnd` fires |
|
|
90
111
|
|
|
91
112
|
`@warlock.js/web` page requests report through this same `onPhase` surface
|
|
92
113
|
instead of adding a separate hook API. They add three phases:
|
|
93
114
|
|
|
94
|
-
| Phase
|
|
95
|
-
|
|
|
96
|
-
| `loader`
|
|
97
|
-
| `render.shell`
|
|
98
|
-
| `stream.end`
|
|
115
|
+
| Phase | Fires around |
|
|
116
|
+
| ----------------- | --------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| `loader` | Each loader level, once per app/layout/page, with `attrs: { level, layoutPath? }` |
|
|
118
|
+
| `render.shell` | Time from render start until React's shell is ready to stream |
|
|
119
|
+
| `stream.end` | The whole streamed response, including every `defer()` value settling |
|
|
120
|
+
| `page.middleware` | Page middleware chain; `attrs: { count, outcome }`, where outcome is `next`, `response`, or `error` |
|
|
121
|
+
| `page.cache` | Page-cache lookup; `attrs: { status }`, where status is `hit`, `miss`, or `bypass` |
|
|
122
|
+
| `defer.settle` | A deferred key settling; `attrs: { key, status }`, where status is `fulfilled` or `rejected` |
|
|
99
123
|
|
|
100
124
|
## Trace id derivation
|
|
101
125
|
|
|
@@ -189,9 +213,8 @@ export default {
|
|
|
189
213
|
- **No OTel dependency, and none planned for `core`.** An OTel (or other
|
|
190
214
|
vendor) bridge is a separate, optional package that subscribes to these
|
|
191
215
|
hooks — never add `@opentelemetry/api` to `core` itself.
|
|
192
|
-
-
|
|
193
|
-
|
|
194
|
-
phases fire in this release.
|
|
216
|
+
- **Web phases share the same hook.** Do not create a second page-tracing
|
|
217
|
+
integration: subscribe with `TracingHooks.onPhase`.
|
|
195
218
|
- **`route` is `undefined` until routing has matched.** There's no path where
|
|
196
219
|
a hook fires before that, but code branching on `ctx.route` for an
|
|
197
220
|
early-failing request (e.g. a 404 with no match) must handle `undefined`.
|
package/skills/run-app/SKILL.md
CHANGED
|
@@ -28,11 +28,11 @@ Boots the framework in dev mode: file watcher, HMR-style module reload, on-disk
|
|
|
28
28
|
|
|
29
29
|
### Flags
|
|
30
30
|
|
|
31
|
-
| Flag | Default | Purpose
|
|
32
|
-
| --------------------- | ------- |
|
|
31
|
+
| Flag | Default | Purpose |
|
|
32
|
+
| --------------------- | ------- | ----------------------------------------------------------------------------------------------------------- |
|
|
33
33
|
| `--fresh, -f` | off | Delete `.warlock/manifest.json` before start — forces a full re-parse from disk. Use after odd boot states. |
|
|
34
|
-
| `--skip-typings, -st` | off | Skip background type generation **for this run**. Overrides `devServer.generateTypings` config.
|
|
35
|
-
| `--skip-health, -sh` | off | Skip file health checkers **for this run**. Overrides `devServer.healthCheckers` config.
|
|
34
|
+
| `--skip-typings, -st` | off | Skip background type generation **for this run**. Overrides `devServer.generateTypings` config. |
|
|
35
|
+
| `--skip-health, -sh` | off | Skip file health checkers **for this run**. Overrides `devServer.healthCheckers` config. |
|
|
36
36
|
|
|
37
37
|
When a flag is **not** passed, the corresponding `warlock.config.ts > devServer.*` value applies. When passed, the flag wins.
|
|
38
38
|
|
|
@@ -40,14 +40,14 @@ When a flag is **not** passed, the corresponding `warlock.config.ts > devServer.
|
|
|
40
40
|
|
|
41
41
|
Once the server is ready, `warlock dev` listens for single keypresses:
|
|
42
42
|
|
|
43
|
-
| Key | Does
|
|
44
|
-
| -------- |
|
|
45
|
-
| `r` | Restart the server on a fresh process.
|
|
46
|
-
| `c` | Clear the console.
|
|
47
|
-
| `q` | Graceful shutdown, exit `0` — the same path as `Ctrl+C`.
|
|
48
|
-
| `h` | Print the shortcuts that are armed right now.
|
|
43
|
+
| Key | Does |
|
|
44
|
+
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| `r` | Restart the server on a fresh process. |
|
|
46
|
+
| `c` | Clear the console. |
|
|
47
|
+
| `q` | Graceful shutdown, exit `0` — the same path as `Ctrl+C`. |
|
|
48
|
+
| `h` | Print the shortcuts that are armed right now. |
|
|
49
49
|
| `u` | **Only while an update notice is showing** — update every `@warlock.js/*` package, install, and restart. See [`update-packages/SKILL.md`](../update-packages/SKILL.md). |
|
|
50
|
-
| `Ctrl+C` | Graceful shutdown, unchanged.
|
|
50
|
+
| `Ctrl+C` | Graceful shutdown, unchanged. |
|
|
51
51
|
|
|
52
52
|
A "restart" is a fresh process (`r` and `u` both use it) — see [The supervisor](#the-supervisor) below for how that works.
|
|
53
53
|
|
|
@@ -67,7 +67,7 @@ shell
|
|
|
67
67
|
|
|
68
68
|
The supervisor spawns the worker with `stdio: "inherit"` and mirrors its exit code. To restart, the worker shuts down cleanly — freeing the http port — and exits `75`; the supervisor spawns a replacement. **The tree never gets deeper**, however many restarts happen.
|
|
69
69
|
|
|
70
|
-
The supervisor is chosen in the `dev` command's `preAction`, which runs
|
|
70
|
+
The supervisor is chosen in the `dev` command's `preAction`, which runs _before_ the preloaders — deliberately, so the supervisor never loads config or starts connectors. A supervisor holding a second database connection open for the session would be a real bug, not a cosmetic one.
|
|
71
71
|
|
|
72
72
|
Signals: `SIGINT` reaches the worker directly (same process group), so the supervisor ignores it and only notes that the next exit is final — acting on it would exit the parent while the worker was still draining. `SIGTERM` / `SIGHUP` are forwarded explicitly, since they don't propagate to the group on Windows. After any worker exits, the supervisor puts stdin back out of raw mode in case the worker was killed before it could.
|
|
73
73
|
|
|
@@ -81,13 +81,13 @@ A worker that dies **after running healthily for at least 5s** — OOM, a native
|
|
|
81
81
|
14:31:02 Development server was killed by SIGSEGV — restarting.
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
A worker that dies
|
|
84
|
+
A worker that dies _sooner_ than that failed to **boot** — a broken config, a port already taken — and it has already printed why. Restarting there would just reprint the same error and bury it, so the supervisor mirrors the exit code and stops.
|
|
85
85
|
|
|
86
|
-
A busy port is now caught
|
|
86
|
+
A busy port is now caught _before_ the bind attempt: the HTTP connector calls `assertPortIsAvailable(port, host)` immediately before `listen()`, so the failure is `Port <port> is already in use on <host>. Stop the dev server (or whatever else is listening on port <port>) and run again, or start on a free port…` — not a raw `EADDRINUSE` surfacing from inside Fastify. Same connector, same preflight, for `warlock start`.
|
|
87
87
|
|
|
88
88
|
Flapping is capped: more than 3 crashes inside 60s and the supervisor gives up rather than restarting behind your back.
|
|
89
89
|
|
|
90
|
-
An explicit restart (`r`, `u`, a config change) is a
|
|
90
|
+
An explicit restart (`r`, `u`, a config change) is a _request_, not a crash, so the uptime rule never swallows it.
|
|
91
91
|
|
|
92
92
|
### Restart on config change
|
|
93
93
|
|
|
@@ -103,7 +103,7 @@ Set `devServer.restartOnConfigChange: false` for the previous behaviour (a warni
|
|
|
103
103
|
|
|
104
104
|
On first boot `warlock dev` bundles its ESM loader hook and writes it to **your project's** `.warlock/loader-hook.mjs` — the hook runs in a fresh Node worker thread with no TypeScript loader of its own, so it has to be plain, pre-bundled ESM.
|
|
105
105
|
|
|
106
|
-
Because that file lives in your directory rather than core's, every npm import inside it is rewritten at generation time to an **absolute path resolved from core's own install**. A bare `import "esbuild"` there would resolve from
|
|
106
|
+
Because that file lives in your directory rather than core's, every npm import inside it is rewritten at generation time to an **absolute path resolved from core's own install**. A bare `import "esbuild"` there would resolve from _your_ `node_modules`, and `esbuild` / `get-tsconfig` are core's dependencies, not yours.
|
|
107
107
|
|
|
108
108
|
:::note[Fixed in 4.9.2 — pnpm users]
|
|
109
109
|
Before 4.9.2 those imports were left bare. npm and yarn hoist every transitive dependency into one flat tree, so they resolved by accident; pnpm's strict layout does not, and the dev server failed with `ERR_MODULE_NOT_FOUND: Cannot find package 'esbuild'`. The workaround was declaring `esbuild` and `get-tsconfig` in your own `package.json` — no longer needed, and you can drop them.
|
|
@@ -165,12 +165,13 @@ esbuild bundle of the app down to a single JS file in `dist/`. No flags — ever
|
|
|
165
165
|
```ts title="warlock.config.ts"
|
|
166
166
|
export default defineConfig({
|
|
167
167
|
build: {
|
|
168
|
-
outdir: "dist",
|
|
169
|
-
outFile: "app.js",
|
|
170
|
-
minify: true,
|
|
171
|
-
sourcemap: true,
|
|
172
|
-
singleBundle: false,
|
|
173
|
-
esmShim: true,
|
|
168
|
+
outdir: "dist", // default — relative or absolute
|
|
169
|
+
outFile: "app.js", // default — bundle filename
|
|
170
|
+
minify: true, // default — esbuild minify
|
|
171
|
+
sourcemap: true, // default — true | false | "inline" | "linked"
|
|
172
|
+
singleBundle: false, // default — one runnable file, deps inlined
|
|
173
|
+
esmShim: true, // default — require/__filename/__dirname for bundled CJS
|
|
174
|
+
routeRegistrationTimeoutMs: 30_000, // default — route-registration child deadline
|
|
174
175
|
},
|
|
175
176
|
});
|
|
176
177
|
```
|
|
@@ -182,8 +183,9 @@ Defaults are sensible for the typical "Node service" deployment. Knobs to actual
|
|
|
182
183
|
- **`minify: false`** — flip to debug a production-only bug. Larger bundle, readable stack traces.
|
|
183
184
|
- **`sourcemap: "inline"`** — embed the source map in the bundle. Useful when your error reporter only captures the bundle and can't fetch a `.map` sidecar.
|
|
184
185
|
- **`sourcemap: false`** — skip source maps entirely. Smaller artifact, but stack traces in production logs lose their file:line precision (and `warlock start` will not enable `--enable-source-maps` since there's nothing to map).
|
|
185
|
-
- **`singleBundle: true`** — inline dependencies so `node dist/app.js` runs with no `node_modules` and no launcher. Sets `packages: "bundle"` + `splitting: false` as **defaults you can override**. ⚠ Native `.node` addons are still emitted alongside — "single bundle" is one JS file
|
|
186
|
+
- **`singleBundle: true`** — inline dependencies so `node dist/app.js` runs with no `node_modules` and no launcher. Sets `packages: "bundle"` + `splitting: false` as **defaults you can override**. ⚠ Native `.node` addons are still emitted alongside — "single bundle" is one JS file _plus_ any native addons. Do NOT reach for it as the default: keeping deps external is right when you deploy the folder.
|
|
186
187
|
- **`esmShim: false`** — only if you are certain nothing in the graph is CommonJS. Leave it on.
|
|
188
|
+
- **`routeRegistrationTimeoutMs`** — increase on busy CI runners when static route registration needs more than 30 seconds. `WARLOCK_ROUTE_REGISTRATION_TIMEOUT_MS` overrides this value for a build; invalid values warn and use 30,000ms.
|
|
187
189
|
|
|
188
190
|
⚠ **The trap this replaced.** Setting `packages: "bundle"` by hand used to produce a clean build whose process died on start with `Error: Dynamic require of "node:assert" is not supported`. The output is ESM; bundled CJS deps call `require(...)` and read `__dirname`, and neither exists in an ES module, so the bundler substitutes a throwing stub. **A green `warlock build` was not evidence the bundle ran.** `esmShim` now injects `createRequire(import.meta.url)` and friends automatically for any ESM build, so both `singleBundle` and a hand-written `packages: "bundle"` work. An existing hand-written `banner` is preserved — the shim is prepended, not substituted.
|
|
189
191
|
|
|
@@ -241,6 +243,41 @@ npx warlock start --inspect # → spawns node --enable-source-maps
|
|
|
241
243
|
npx warlock start --max-old-space-size=4096 # → spawns node --enable-source-maps --max-old-space-size=4096 dist/app.js
|
|
242
244
|
```
|
|
243
245
|
|
|
246
|
+
### Deploy one build in parts
|
|
247
|
+
|
|
248
|
+
One production build can run as API, page, and background-worker processes.
|
|
249
|
+
Omit `--role` to run every role (the existing default); combine roles with a
|
|
250
|
+
comma-separated list:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
npx warlock start --role=api
|
|
254
|
+
npx warlock start --role=web --sites=admin,storefront
|
|
255
|
+
npx warlock start --role=worker
|
|
256
|
+
npx warlock start --role=web,api
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`--sites=a,b` is valid only with the `web` role and installs the listed site
|
|
260
|
+
keys' pages (shared non-site pages remain installed). Unknown roles, an empty
|
|
261
|
+
list, `--sites` without `web`, and unknown site keys fail boot rather than
|
|
262
|
+
silently serving the wrong traffic.
|
|
263
|
+
|
|
264
|
+
An API or web role binds HTTP. A worker-only process binds no HTTP port; it
|
|
265
|
+
loads worker work, starts queue workers and the scheduler there, and its
|
|
266
|
+
readiness banner reports `worker ready`. `warlock dev` always runs every role.
|
|
267
|
+
|
|
268
|
+
Inside application code, inspect the roles resolved at bootstrap:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { Application } from "@warlock.js/core";
|
|
272
|
+
|
|
273
|
+
if (Application.hasRole("worker")) {
|
|
274
|
+
// worker-only integration
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
Application.roles; // ReadonlySet<"api" | "web" | "worker">
|
|
278
|
+
Application.sites; // ReadonlySet<string> | undefined (undefined = all sites)
|
|
279
|
+
```
|
|
280
|
+
|
|
244
281
|
Everything you pass after `start` is forwarded to the spawned Node process. Use this to attach a debugger (`--inspect`), tune memory (`--max-old-space-size`), or pass any other Node flag without editing the command.
|
|
245
282
|
|
|
246
283
|
### Source maps
|
|
@@ -283,9 +320,9 @@ Nothing else is needed for pnpm. Warlock never requires an app to declare a pack
|
|
|
283
320
|
|
|
284
321
|
**A success line on stdout means the app is serving requests.** That is a contract, not a convention, and you can build a CI gate or a health probe on it.
|
|
285
322
|
|
|
286
|
-
| Stream | Carries
|
|
287
|
-
| ---------- |
|
|
288
|
-
| **stdout** | the started banner, and start failures. Nothing else.
|
|
323
|
+
| Stream | Carries |
|
|
324
|
+
| ---------- | -------------------------------------------------------------------------------------- |
|
|
325
|
+
| **stdout** | the started banner, and start failures. Nothing else. |
|
|
289
326
|
| **stderr** | progress (`🚀 Starting production server...`), diagnostics, the application's own logs |
|
|
290
327
|
|
|
291
328
|
The started banner prints **only** when the running application reports a completed boot — not when the command starts, not when the child is spawned. A child that dies before reporting is a failed start: the failure is written to **both** streams (stderr for humans and log collectors, stdout so a supervisor greping for the banner finds a failure rather than silence), and `warlock start` exits non-zero **even when the child itself exited `0`**.
|
|
@@ -321,7 +358,7 @@ else is listening on port 3000) and run again, or start on a free port — e.g.
|
|
|
321
358
|
startHttpTestServer({ port: 3001 }).
|
|
322
359
|
```
|
|
323
360
|
|
|
324
|
-
**The process exits `78` (`EX_CONFIG`), and `warlock dev`'s supervisor treats that as terminal — it prints once and stops rather than restarting.** That code is the counterpart of `75`, which a worker uses to ask for a fresh process. The distinction is the point: a code error is worth restarting, and a
|
|
361
|
+
**The process exits `78` (`EX_CONFIG`), and `warlock dev`'s supervisor treats that as terminal — it prints once and stops rather than restarting.** That code is the counterpart of `75`, which a worker uses to ask for a fresh process. The distinction is the point: a code error is worth restarting, and a _startup precondition_ — a port held by something else, a port value that is not a port, a boot validator that rejected — cannot change because we tried again six seconds later. Retrying one only reprints the diagnostic and then scrolls it away.
|
|
325
362
|
|
|
326
363
|
`EACCES` on the port is treated the same way as `EADDRINUSE` (a privileged port you may not bind is also "not available"); anything else the probe throws is rethrown untouched.
|
|
327
364
|
|
|
@@ -336,7 +373,7 @@ Configured http.port "03999" normalised to 3999.
|
|
|
336
373
|
`warlock start` spawns the bundle with an IPC channel and sets `WARLOCK_BOOT_SIGNAL=1` on it. `Application.markBooted()` — which the production entry calls after the late-phase connectors (http, socket) are up — sends one versioned message and closes the channel:
|
|
337
374
|
|
|
338
375
|
```ts
|
|
339
|
-
{ type: "warlock:ready", version: 1, pid, at, environment, runtimeStrategy, bootDurationMs?, port
|
|
376
|
+
{ type: "warlock:ready", version: 1, pid, at, environment, runtimeStrategy, bootDurationMs?, port?, roles }
|
|
340
377
|
```
|
|
341
378
|
|
|
342
379
|
Three consequences worth knowing:
|
|
@@ -351,11 +388,11 @@ If you need the same fact inside the app, use `Application.onceBooted()` / `Appl
|
|
|
351
388
|
|
|
352
389
|
`Application.environment` and `Application.runtimeStrategy` are separate axes:
|
|
353
390
|
|
|
354
|
-
| Mode
|
|
355
|
-
|
|
|
356
|
-
| `warlock dev`
|
|
357
|
-
| `warlock build`
|
|
358
|
-
| `warlock start`
|
|
391
|
+
| Mode | `environment` | `runtimeStrategy` | How |
|
|
392
|
+
| --------------- | ------------------ | ----------------- | ------------------------------------- |
|
|
393
|
+
| `warlock dev` | `development`* | `development` | preload force-sets `runtimeStrategy` |
|
|
394
|
+
| `warlock build` | n/a (no app boots) | n/a | only loads warlock.config.ts |
|
|
395
|
+
| `warlock start` | `production`* | `production`* | usually set via `NODE_ENV` in the env |
|
|
359
396
|
|
|
360
397
|
`*` — `environment` follows `NODE_ENV`. The dev command doesn't force it, but the default in most projects is `development`. `start` doesn't force it either; deployments set `NODE_ENV=production` themselves.
|
|
361
398
|
|
|
@@ -414,7 +451,7 @@ export default defineConfig({
|
|
|
414
451
|
|
|
415
452
|
CI sets `BUILD_OUT=build/<sha>` per pipeline. `warlock start` reads the same config and finds the bundle without any hardcoded paths.
|
|
416
453
|
|
|
417
|
-
**This recipe only started working in 4.11.0.** Before that, `warlock.config.ts` was evaluated
|
|
454
|
+
**This recipe only started working in 4.11.0.** Before that, `warlock.config.ts` was evaluated _before_ any `.env` file was read, so every `env()` call in it returned its default — silently, under every command. If you copied this recipe earlier and concluded that `BUILD_OUT` was ignored, it was.
|
|
418
455
|
|
|
419
456
|
**Which file the value comes from is decided by `NODE_ENV`, and no Warlock command sets it.** `env()` reads `.env.<NODE_ENV>` when that file exists and falls back to plain `.env`. So:
|
|
420
457
|
|
|
@@ -12,10 +12,7 @@ Two APIs over the same engine. The fluent `Mail` builder reads top-to-bottom in
|
|
|
12
12
|
```ts
|
|
13
13
|
import { Mail } from "@warlock.js/core";
|
|
14
14
|
|
|
15
|
-
await Mail.to("user@example.com")
|
|
16
|
-
.subject("Welcome!")
|
|
17
|
-
.text("Thanks for joining.")
|
|
18
|
-
.send();
|
|
15
|
+
await Mail.to("user@example.com").subject("Welcome!").text("Thanks for joining.").send();
|
|
19
16
|
```
|
|
20
17
|
|
|
21
18
|
Or:
|
|
@@ -59,10 +56,25 @@ For named mailers (multi-provider apps), export a `MailersConfig` instead:
|
|
|
59
56
|
|
|
60
57
|
```ts
|
|
61
58
|
const config: MailersConfig = {
|
|
62
|
-
default: {
|
|
59
|
+
default: {
|
|
60
|
+
host: "smtp.sendgrid.net",
|
|
61
|
+
port: 587,
|
|
62
|
+
username: "apikey",
|
|
63
|
+
password: env("SENDGRID_KEY"),
|
|
64
|
+
},
|
|
63
65
|
mailers: {
|
|
64
|
-
marketing: {
|
|
65
|
-
|
|
66
|
+
marketing: {
|
|
67
|
+
host: "smtp.mailchimp.com",
|
|
68
|
+
port: 587,
|
|
69
|
+
username: env("MC_USER"),
|
|
70
|
+
password: env("MC_PASS"),
|
|
71
|
+
},
|
|
72
|
+
transactional: {
|
|
73
|
+
host: "smtp.postmark.com",
|
|
74
|
+
port: 587,
|
|
75
|
+
username: env("PM_USER"),
|
|
76
|
+
password: env("PM_PASS"),
|
|
77
|
+
},
|
|
66
78
|
},
|
|
67
79
|
};
|
|
68
80
|
```
|
|
@@ -94,9 +106,9 @@ The mail pipeline switches behavior on a global mode:
|
|
|
94
106
|
```ts
|
|
95
107
|
import { setMailMode } from "@warlock.js/core";
|
|
96
108
|
|
|
97
|
-
setMailMode("production");
|
|
98
|
-
setMailMode("development");
|
|
99
|
-
setMailMode("test");
|
|
109
|
+
setMailMode("production"); // actually send (default)
|
|
110
|
+
setMailMode("development"); // log subject + recipient, no send
|
|
111
|
+
setMailMode("test"); // capture to test mailbox, no send
|
|
100
112
|
```
|
|
101
113
|
|
|
102
114
|
Production sends. Development logs and returns a fake-success `MailResult`. Test captures every call into an in-memory mailbox (cleared between tests) and returns success — handy for asserting "the welcome email got sent" without hitting SMTP.
|
|
@@ -109,7 +121,7 @@ Production sends. Development logs and returns a fake-success `MailResult`. Test
|
|
|
109
121
|
|
|
110
122
|
```ts
|
|
111
123
|
await Mail.to("a@example.com")
|
|
112
|
-
.to(["a@example.com", "b@example.com"])
|
|
124
|
+
.to(["a@example.com", "b@example.com"]) // override
|
|
113
125
|
.cc("manager@example.com")
|
|
114
126
|
.bcc("audit@example.com")
|
|
115
127
|
.replyTo("support@example.com")
|
|
@@ -150,15 +162,15 @@ await Mail.to("u@e.com")
|
|
|
150
162
|
|
|
151
163
|
### Other knobs
|
|
152
164
|
|
|
153
|
-
| Method
|
|
154
|
-
|
|
|
155
|
-
| `.priority("high" \| "normal" \| "low")`
|
|
156
|
-
| `.headers({ "X-Foo": "bar" })`
|
|
157
|
-
| `.header("X-Foo", "bar")`
|
|
158
|
-
| `.tags(["welcome"])` / `.tag("transactional")` | categorization tags
|
|
159
|
-
| `.correlationId("req-123")`
|
|
160
|
-
| `.config(MailConfigurations)`
|
|
161
|
-
| `.mailer("marketing")`
|
|
165
|
+
| Method | Purpose |
|
|
166
|
+
| ---------------------------------------------- | ------------------------------------- |
|
|
167
|
+
| `.priority("high" \| "normal" \| "low")` | priority header |
|
|
168
|
+
| `.headers({ "X-Foo": "bar" })` | replace custom headers |
|
|
169
|
+
| `.header("X-Foo", "bar")` | add one header |
|
|
170
|
+
| `.tags(["welcome"])` / `.tag("transactional")` | categorization tags |
|
|
171
|
+
| `.correlationId("req-123")` | tracking id (logged with sends) |
|
|
172
|
+
| `.config(MailConfigurations)` | one-off override of the global config |
|
|
173
|
+
| `.mailer("marketing")` | route via a named mailer from config |
|
|
162
174
|
|
|
163
175
|
### Per-mail event handlers
|
|
164
176
|
|
|
@@ -171,9 +183,9 @@ await Mail.to("u@e.com")
|
|
|
171
183
|
.beforeSending((mail) => {
|
|
172
184
|
// mutate `mail` or return false to cancel
|
|
173
185
|
})
|
|
174
|
-
.onSent((mail, result, error) => {})
|
|
175
|
-
.onSuccess((mail, result) => {})
|
|
176
|
-
.onError((mail, error) => {})
|
|
186
|
+
.onSent((mail, result, error) => {}) // always fires after attempt
|
|
187
|
+
.onSuccess((mail, result) => {}) // only on success
|
|
188
|
+
.onError((mail, error) => {}) // only on failure
|
|
177
189
|
.send();
|
|
178
190
|
```
|
|
179
191
|
|
|
@@ -243,6 +255,23 @@ mailEvents.onBeforeSending((mail) => {
|
|
|
243
255
|
|
|
244
256
|
Global hooks fire for **every** mail. Per-mail handlers from the builder / `sendMail` fire only for that one send.
|
|
245
257
|
|
|
258
|
+
### Observe captured development and test mail
|
|
259
|
+
|
|
260
|
+
Development and test sends create a `CapturedMail` and synchronously emit the
|
|
261
|
+
global `mail.captured` event. Subscribe with `mailEvents.onCaptured`:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { mailEvents, type CapturedMail } from "@warlock.js/core";
|
|
265
|
+
|
|
266
|
+
mailEvents.onCaptured((mail: CapturedMail) => {
|
|
267
|
+
console.log(mail.id, mail.options.subject);
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`CapturedMail.id` is the mail's generated or supplied id. This event is for
|
|
272
|
+
development/test tooling; production sends do not emit it. Listener failures
|
|
273
|
+
are logged and never interrupt mail delivery.
|
|
274
|
+
|
|
246
275
|
For correlated tracking of a specific mail, generate an id and subscribe by id:
|
|
247
276
|
|
|
248
277
|
```ts
|
|
@@ -296,10 +325,11 @@ Captured mail shape:
|
|
|
296
325
|
|
|
297
326
|
```ts
|
|
298
327
|
type CapturedMail = {
|
|
299
|
-
|
|
328
|
+
id: string;
|
|
329
|
+
options: MailOptions; // original payload
|
|
300
330
|
normalized: NormalizedMail; // post-normalization (arrays, resolved from)
|
|
301
331
|
timestamp: Date;
|
|
302
|
-
result?: MailResult;
|
|
332
|
+
result?: MailResult; // in test mode, always success
|
|
303
333
|
error?: MailError;
|
|
304
334
|
};
|
|
305
335
|
```
|