@warlock.js/core 5.2.3 → 5.3.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 +6 -0
- package/esm/application/application.d.mts.map +1 -1
- package/esm/application/application.mjs.map +1 -1
- package/esm/benchmark/benchmark-snapshots.d.mts.map +1 -1
- package/esm/benchmark/benchmark.d.mts.map +1 -1
- package/esm/benchmark/channels/console.channel.mjs.map +1 -1
- package/esm/benchmark/channels/noop.channel.d.mts.map +1 -1
- package/esm/benchmark/profiler.d.mts.map +1 -1
- package/esm/benchmark/profiler.mjs.map +1 -1
- package/esm/bootstrap/setup.d.mts.map +1 -1
- package/esm/bootstrap/setup.mjs.map +1 -1
- package/esm/bootstrap.d.mts.map +1 -1
- package/esm/cache/database-cache-driver.d.mts.map +1 -1
- package/esm/cache/database-cache-driver.mjs.map +1 -1
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/cli-commands.utils.mjs.map +1 -1
- package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -1
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/column-dsl-parser.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/templates/stubs.mjs +13 -14
- package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/name-parser.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/prompt.mjs.map +1 -1
- package/esm/cli/commands/routes/diff-page-routes.mjs.map +1 -1
- package/esm/cli/commands/routes/format-routes-table.mjs.map +1 -1
- package/esm/cli/commands/routes/route-row.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-diff.action.mjs.map +1 -1
- package/esm/cli/commands/storage-put.action.mjs.map +1 -1
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/cli/commands-loader.mjs.map +1 -1
- package/esm/cli/start.mjs.map +1 -1
- package/esm/cli/string-similarity.mjs.map +1 -1
- package/esm/commands/cli-command.mjs.map +1 -1
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/config/config-setter.d.mts.map +1 -1
- package/esm/config/load-config-files.mjs.map +1 -1
- package/esm/connectors/access-connector.d.mts.map +1 -1
- package/esm/connectors/ai-connector.d.mts.map +1 -1
- package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -1
- package/esm/connectors/assert-unique-connector-names.mjs.map +1 -1
- package/esm/connectors/base-connector.d.mts.map +1 -1
- package/esm/connectors/base-connector.mjs.map +1 -1
- package/esm/connectors/cache-connector.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/database-connector.d.mts.map +1 -1
- package/esm/connectors/describe-server-address.mjs.map +1 -1
- package/esm/connectors/herald-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.d.mts.map +1 -1
- package/esm/connectors/mail-connector.d.mts.map +1 -1
- package/esm/connectors/notifications-connector.d.mts.map +1 -1
- package/esm/connectors/register-configured-connectors.mjs.map +1 -1
- package/esm/connectors/socket-connector.d.mts.map +1 -1
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/connectors/storage.connector.d.mts.map +1 -1
- package/esm/container/index.d.mts.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.map +1 -1
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/database/seeds/seed-records-table-migration.mjs.map +1 -1
- package/esm/database/seeds/seeders.manager.mjs.map +1 -1
- package/esm/database/seeds/seeds-table-migration.mjs.map +1 -1
- package/esm/dev-server/check-for-updates.mjs.map +1 -1
- package/esm/dev-server/dependency-graph.d.mts.map +1 -1
- package/esm/dev-server/dependency-graph.mjs.map +1 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/file-event-handler.mjs.map +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/file-operations.d.mts.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/typescript-health-checker.mjs.map +1 -1
- package/esm/dev-server/health-checker/file-health-result.d.mts.map +1 -1
- package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
- package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
- package/esm/dev-server/layer-executor.mjs.map +1 -1
- package/esm/dev-server/loader/hook-thread.d.mts.map +1 -1
- package/esm/dev-server/loader/own-resolver.mjs.map +1 -1
- package/esm/dev-server/loader/register-loader.mjs.map +1 -1
- package/esm/dev-server/loader/resolve-hook.mjs.map +1 -1
- package/esm/dev-server/loader/source-slug.mjs.map +1 -1
- package/esm/dev-server/loader/transpile-cache.mjs.map +1 -1
- package/esm/dev-server/loader/version-registry.mjs.map +1 -1
- package/esm/dev-server/manifest-manager.d.mts.map +1 -1
- package/esm/dev-server/manifest-manager.mjs.map +1 -1
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/package-json-manager.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/dev-server/ready-block.mjs.map +1 -1
- package/esm/dev-server/special-files-collector.d.mts.map +1 -1
- package/esm/dev-server/special-files-collector.mjs.map +1 -1
- package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/dev-server/utils.mjs.map +1 -1
- package/esm/encryption/encrypt.mjs.map +1 -1
- package/esm/encryption/hash.mjs.map +1 -1
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/features/shadcn.feature.mjs.map +1 -1
- package/esm/generations/features/shared/migration-timestamp.mjs.map +1 -1
- package/esm/generations/features/tailwind.feature.mjs.map +1 -1
- package/esm/generations/features/web.feature.mjs +2 -1
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/generations/stubs.mjs +53 -54
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/boot-port-preflight.d.mts.map +1 -1
- package/esm/http/context/request-context.d.mts.map +1 -1
- package/esm/http/context/request-context.mjs.map +1 -1
- package/esm/http/context/request-memo.d.mts.map +1 -1
- package/esm/http/context/request-memo.mjs.map +1 -1
- package/esm/http/createHttpApplication.d.mts.map +1 -1
- package/esm/http/createHttpApplication.mjs.map +1 -1
- package/esm/http/health.d.mts.map +1 -1
- package/esm/http/index.d.mts +1 -1
- package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +2 -21
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
- package/esm/http/ready-report.d.mts.map +1 -1
- package/esm/http/request-controller.d.mts.map +1 -1
- package/esm/http/request.d.mts +25 -6
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +48 -0
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts +3 -3
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs.map +1 -1
- package/esm/http/types.d.mts +36 -5
- package/esm/http/types.d.mts.map +1 -1
- package/esm/http/uploaded-file.d.mts.map +1 -1
- package/esm/http/uploaded-file.mjs.map +1 -1
- package/esm/http/uploads-config.d.mts.map +1 -1
- package/esm/image/image.d.mts.map +1 -1
- package/esm/image/image.mjs.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/logger/logger.mjs.map +1 -1
- package/esm/mail/config.d.mts.map +1 -1
- package/esm/mail/events.d.mts.map +1 -1
- package/esm/mail/mail.d.mts.map +1 -1
- package/esm/mail/mail.mjs.map +1 -1
- package/esm/mail/mailer-pool.d.mts.map +1 -1
- package/esm/mail/mailer-pool.mjs.map +1 -1
- package/esm/mail/send-mail.mjs.map +1 -1
- package/esm/mail/test-mailbox.d.mts.map +1 -1
- package/esm/mail/test-mailbox.mjs.map +1 -1
- package/esm/production/assert-generated-imports.mjs.map +1 -1
- package/esm/production/build-app-production.mjs.map +1 -1
- package/esm/production/dist-build-manifest.mjs.map +1 -1
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/production-supervisor.mjs.map +1 -1
- package/esm/production/promote-dist.mjs.map +1 -1
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-adapter.d.mts.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-adapter.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/filter-applicator.mjs.map +1 -1
- package/esm/repositories/repository.manager.d.mts.map +1 -1
- package/esm/repositories/repository.manager.mjs.map +1 -1
- package/esm/resource/register-resource.d.mts.map +1 -1
- package/esm/resource/resource-field-builder.d.mts.map +1 -1
- package/esm/resource/resource-field-builder.mjs.map +1 -1
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/restful/restful.d.mts.map +1 -1
- package/esm/restful/restful.mjs.map +1 -1
- package/esm/router/log-request-lifecycle.mjs.map +1 -1
- package/esm/router/normalize-route-path.d.mts.map +1 -1
- package/esm/router/positional-handler-diagnostics.d.mts.map +1 -1
- package/esm/router/positional-handler-diagnostics.mjs.map +1 -1
- package/esm/router/route-registry.d.mts.map +1 -1
- package/esm/router/route-registry.mjs.map +1 -1
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs.map +1 -1
- package/esm/socket/utils.d.mts.map +1 -1
- package/esm/storage/config.d.mts.map +1 -1
- package/esm/storage/context/storage-driver-context.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
- package/esm/storage/drivers/do-spaces-driver.d.mts.map +1 -1
- package/esm/storage/drivers/local-driver.d.mts.map +1 -1
- package/esm/storage/drivers/local-driver.mjs.map +1 -1
- package/esm/storage/drivers/r2-driver.d.mts.map +1 -1
- package/esm/storage/scoped-storage.d.mts.map +1 -1
- package/esm/storage/scoped-storage.mjs.map +1 -1
- package/esm/storage/storage-file.d.mts.map +1 -1
- package/esm/storage/storage-file.mjs.map +1 -1
- package/esm/storage/storage.d.mts.map +1 -1
- package/esm/storage/utils/safe-fetch.mjs.map +1 -1
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/test-connectors-selection.mjs.map +1 -1
- package/esm/tests/test-helpers.d.mts.map +1 -1
- package/esm/tests/test-setup-timeout.mjs.map +1 -1
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/updater/update-warlock-packages.mjs.map +1 -1
- package/esm/use-cases/use-case-broadcast.d.mts.map +1 -1
- package/esm/use-cases/use-case-events.d.mts.map +1 -1
- package/esm/use-cases/use-case-pipeline.d.mts.map +1 -1
- package/esm/use-cases/use-case.d.mts.map +1 -1
- package/esm/use-cases/use-cases-registry.d.mts.map +1 -1
- package/esm/use-cases/use-cases-registry.mjs.map +1 -1
- package/esm/utils/database-log.d.mts.map +1 -1
- package/esm/utils/database-log.mjs.map +1 -1
- package/esm/utils/environment.d.mts.map +1 -1
- package/esm/utils/framework-vesion.mjs.map +1 -1
- package/esm/utils/get-localized.mjs.map +1 -1
- package/esm/utils/load-environment.mjs.map +1 -1
- package/esm/utils/normalized-path.d.mts.map +1 -1
- package/esm/utils/paths.d.mts.map +1 -1
- package/esm/utils/promise-all-object.d.mts.map +1 -1
- package/esm/utils/promise-all-object.mjs.map +1 -1
- package/esm/utils/sluggable.mjs.map +1 -1
- package/esm/utils/version-compare.mjs.map +1 -1
- package/esm/validation/database/exists-except-current-id.mjs.map +1 -1
- package/esm/validation/database/exists-except-current-user.mjs.map +1 -1
- package/esm/validation/database/unique-except-current-id.mjs.map +1 -1
- package/esm/validation/plugins/file-plugin.mjs.map +1 -1
- package/esm/validation/plugins/localized-plugin.mjs +2 -2
- package/esm/validation/plugins/localized-plugin.mjs.map +1 -1
- package/esm/validation/types.d.mts +17 -4
- package/esm/validation/types.d.mts.map +1 -1
- package/esm/validation/validators/file-validator.mjs.map +1 -1
- package/esm/vite/lower-stage3-decorators.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
- package/llms-full.txt +113 -87
- package/llms.txt +2 -2
- package/package.json +12 -12
- package/skills/README.md +2 -2
- package/skills/create-controller/SKILL.md +9 -9
- package/skills/send-response/SKILL.md +51 -37
- package/skills/store-file/SKILL.md +8 -3
- package/skills/upload-file/SKILL.md +9 -7
- package/skills/use-app-context/SKILL.md +2 -2
- package/skills/use-localization/SKILL.md +6 -2
- package/skills/use-repository/SKILL.md +2 -2
- package/skills/use-request-locals/SKILL.md +3 -3
- package/skills/validate-input/SKILL.md +2 -2
- package/skills/warlock-conventions/SKILL.md +2 -2
- package/skills/wire-socket/SKILL.md +2 -2
- package/skills/write-cli-command/SKILL.md +4 -1
- package/skills/write-middleware/SKILL.md +13 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-validator.mjs","names":[],"sources":["../../../../../../../../core/src/validation/validators/file-validator.ts"],"sourcesContent":["import type { FileSizeOption, JsonSchemaResult, JsonSchemaTarget } from \"@warlock.js/seal\";\nimport {\n BaseValidator,\n maxFileSizeRule,\n maxHeightRule,\n maxWidthRule,\n minFileSizeRule,\n minHeightRule,\n minWidthRule,\n resolveFileSize,\n v,\n} from \"@warlock.js/seal\";\nimport { UploadedFile } from \"../../http\";\nimport { fileExtensionRule, fileRule, fileTypeRule, imageRule } from \"../file\";\n\nexport const uploadedFileMetadataSchema = v.object({\n location: v.string().oneOf([\"local\", \"cloud\"]),\n width: v.int().positive(),\n height: v.int().positive(),\n size: v.int().positive(),\n mimeType: v.string(),\n extension: v.string(),\n name: v.string(),\n});\n\n/**\n * File validator class\n */\nexport class FileValidator extends BaseValidator {\n public constructor(errorMessage?: string) {\n super();\n this.addMutableRule(fileRule, errorMessage);\n }\n\n /**\n * Check if value is a File type\n */\n public matchesType(value: any): boolean {\n return value instanceof UploadedFile;\n }\n\n /** Value must be an image */\n public image(errorMessage?: string): FileValidator {\n return this.addRule(imageRule, errorMessage);\n }\n\n /** Accept specific file extensions */\n public accept(extensions: string | string[], errorMessage?: string): FileValidator {\n return this.addRule(fileExtensionRule, errorMessage, {\n extensions,\n });\n }\n\n /** Allow specific MIME types */\n public mimeType(mimeTypes: string | string[], errorMessage?: string): FileValidator {\n return this.addRule(fileTypeRule, errorMessage, {\n mimeTypes,\n });\n }\n\n /** Allow only pdf files */\n public pdf(errorMessage?: string): FileValidator {\n return this.mimeType(\"application/pdf\", errorMessage);\n }\n\n /** Allow only excel files */\n public excel(errorMessage?: string): FileValidator {\n return this.mimeType(\n [\n \"application/vnd.ms-excel\",\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n ],\n errorMessage,\n );\n }\n\n /** Allow only word files */\n public word(errorMessage?: string): FileValidator {\n return this.mimeType(\n [\n \"application/msword\",\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n ],\n errorMessage,\n );\n }\n\n /** Minimum file size */\n public minSize(size: number | FileSizeOption, errorMessage?: string): FileValidator {\n return this.addRule(minFileSizeRule, errorMessage, {\n minSize: resolveFileSize(size),\n });\n }\n\n /** @alias minSize */\n public min(size: number | FileSizeOption, errorMessage?: string): FileValidator {\n return this.minSize(size, errorMessage);\n }\n\n /** Maximum file size */\n public maxSize(size: number | FileSizeOption, errorMessage?: string): FileValidator {\n return this.addRule(maxFileSizeRule, errorMessage, {\n maxSize: resolveFileSize(size),\n });\n }\n\n /** @alias maxSize */\n public max(size: number, errorMessage?: string): FileValidator {\n return this.maxSize(size, errorMessage);\n }\n\n /** Minimum image width */\n public minWidth(width: number, errorMessage?: string): FileValidator {\n return this.addRule(minWidthRule, errorMessage, {\n minWidth: width,\n });\n }\n\n /** Maximum image width */\n public maxWidth(width: number, errorMessage?: string): FileValidator {\n return this.addRule(maxWidthRule, errorMessage, {\n maxWidth: width,\n });\n }\n\n /** Minimum image height */\n public minHeight(height: number, errorMessage?: string): FileValidator {\n return this.addRule(minHeightRule, errorMessage, {\n minHeight: height,\n });\n }\n\n /** Maximum image height */\n public maxHeight(height: number, errorMessage?: string): FileValidator {\n return this.addRule(maxHeightRule, errorMessage, {\n maxHeight: height,\n });\n }\n\n /**\n * Save the file and return it as a string\n */\n public saveTo(relativeDirectory: string): FileValidator {\n return this.addTransformer(async (file: UploadedFile) => {\n const output = await file.save(relativeDirectory);\n\n return output.path;\n });\n }\n\n /**\n * @inheritdoc\n *\n * File uploads are not natively representable in JSON Schema.\n * The output varies by target:\n * - `openapi-3.0` → `{ type: \"string\", format: \"binary\" }` (standard for multipart/form-data uploads)\n * - `draft-2020-12` → `{ type: \"string\", contentEncoding: \"binary\" }`\n * - `draft-07` → `{}` (no standard binary representation — permissive fallback)\n *\n * @example\n * ```ts\n * v.file().toJsonSchema(\"openapi-3.0\")\n * // → { type: \"string\", format: \"binary\" }\n * ```\n */\n public override toJsonSchema(target: JsonSchemaTarget = \"draft-2020-12\"): JsonSchemaResult {\n if (target === \"openapi-3.0\") {\n return { type: \"string\", format: \"binary\" };\n }\n\n if (target === \"draft-2020-12\") {\n return { type: \"string\", contentEncoding: \"binary\" };\n }\n\n // draft-07: no standard binary representation\n return {};\n }\n}\n"],"mappings":";;;;;;;AAeA,MAAa,6BAA6B,EAAE,OAAO;CACjD,UAAU,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"file-validator.mjs","names":[],"sources":["../../../../../../../../core/src/validation/validators/file-validator.ts"],"sourcesContent":["import type { FileSizeOption, JsonSchemaResult, JsonSchemaTarget } from \"@warlock.js/seal\";\nimport {\n BaseValidator,\n maxFileSizeRule,\n maxHeightRule,\n maxWidthRule,\n minFileSizeRule,\n minHeightRule,\n minWidthRule,\n resolveFileSize,\n v,\n} from \"@warlock.js/seal\";\nimport { UploadedFile } from \"../../http\";\nimport { fileExtensionRule, fileRule, fileTypeRule, imageRule } from \"../file\";\n\nexport const uploadedFileMetadataSchema = v.object({\n location: v.string().oneOf([\"local\", \"cloud\"]),\n width: v.int().positive(),\n height: v.int().positive(),\n size: v.int().positive(),\n mimeType: v.string(),\n extension: v.string(),\n name: v.string(),\n});\n\n/**\n * File validator class\n */\nexport class FileValidator extends BaseValidator {\n public constructor(errorMessage?: string) {\n super();\n this.addMutableRule(fileRule, errorMessage);\n }\n\n /**\n * Check if value is a File type\n */\n public matchesType(value: any): boolean {\n return value instanceof UploadedFile;\n }\n\n /** Value must be an image */\n public image(errorMessage?: string): FileValidator {\n return this.addRule(imageRule, errorMessage);\n }\n\n /** Accept specific file extensions */\n public accept(extensions: string | string[], errorMessage?: string): FileValidator {\n return this.addRule(fileExtensionRule, errorMessage, {\n extensions,\n });\n }\n\n /** Allow specific MIME types */\n public mimeType(mimeTypes: string | string[], errorMessage?: string): FileValidator {\n return this.addRule(fileTypeRule, errorMessage, {\n mimeTypes,\n });\n }\n\n /** Allow only pdf files */\n public pdf(errorMessage?: string): FileValidator {\n return this.mimeType(\"application/pdf\", errorMessage);\n }\n\n /** Allow only excel files */\n public excel(errorMessage?: string): FileValidator {\n return this.mimeType(\n [\n \"application/vnd.ms-excel\",\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n ],\n errorMessage,\n );\n }\n\n /** Allow only word files */\n public word(errorMessage?: string): FileValidator {\n return this.mimeType(\n [\n \"application/msword\",\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n ],\n errorMessage,\n );\n }\n\n /** Minimum file size */\n public minSize(size: number | FileSizeOption, errorMessage?: string): FileValidator {\n return this.addRule(minFileSizeRule, errorMessage, {\n minSize: resolveFileSize(size),\n });\n }\n\n /** @alias minSize */\n public min(size: number | FileSizeOption, errorMessage?: string): FileValidator {\n return this.minSize(size, errorMessage);\n }\n\n /** Maximum file size */\n public maxSize(size: number | FileSizeOption, errorMessage?: string): FileValidator {\n return this.addRule(maxFileSizeRule, errorMessage, {\n maxSize: resolveFileSize(size),\n });\n }\n\n /** @alias maxSize */\n public max(size: number, errorMessage?: string): FileValidator {\n return this.maxSize(size, errorMessage);\n }\n\n /** Minimum image width */\n public minWidth(width: number, errorMessage?: string): FileValidator {\n return this.addRule(minWidthRule, errorMessage, {\n minWidth: width,\n });\n }\n\n /** Maximum image width */\n public maxWidth(width: number, errorMessage?: string): FileValidator {\n return this.addRule(maxWidthRule, errorMessage, {\n maxWidth: width,\n });\n }\n\n /** Minimum image height */\n public minHeight(height: number, errorMessage?: string): FileValidator {\n return this.addRule(minHeightRule, errorMessage, {\n minHeight: height,\n });\n }\n\n /** Maximum image height */\n public maxHeight(height: number, errorMessage?: string): FileValidator {\n return this.addRule(maxHeightRule, errorMessage, {\n maxHeight: height,\n });\n }\n\n /**\n * Save the file and return it as a string\n */\n public saveTo(relativeDirectory: string): FileValidator {\n return this.addTransformer(async (file: UploadedFile) => {\n const output = await file.save(relativeDirectory);\n\n return output.path;\n });\n }\n\n /**\n * @inheritdoc\n *\n * File uploads are not natively representable in JSON Schema.\n * The output varies by target:\n * - `openapi-3.0` → `{ type: \"string\", format: \"binary\" }` (standard for multipart/form-data uploads)\n * - `draft-2020-12` → `{ type: \"string\", contentEncoding: \"binary\" }`\n * - `draft-07` → `{}` (no standard binary representation — permissive fallback)\n *\n * @example\n * ```ts\n * v.file().toJsonSchema(\"openapi-3.0\")\n * // → { type: \"string\", format: \"binary\" }\n * ```\n */\n public override toJsonSchema(target: JsonSchemaTarget = \"draft-2020-12\"): JsonSchemaResult {\n if (target === \"openapi-3.0\") {\n return { type: \"string\", format: \"binary\" };\n }\n\n if (target === \"draft-2020-12\") {\n return { type: \"string\", contentEncoding: \"binary\" };\n }\n\n // draft-07: no standard binary representation\n return {};\n }\n}\n"],"mappings":";;;;;;;AAeA,MAAa,6BAA6B,EAAE,OAAO;CACjD,UAAU,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,SAAS,OAAO,CAAC;CAC7C,OAAO,EAAE,IAAI,CAAC,CAAC,SAAS;CACxB,QAAQ,EAAE,IAAI,CAAC,CAAC,SAAS;CACzB,MAAM,EAAE,IAAI,CAAC,CAAC,SAAS;CACvB,UAAU,EAAE,OAAO;CACnB,WAAW,EAAE,OAAO;CACpB,MAAM,EAAE,OAAO;AACjB,CAAC;;;;AAKD,IAAa,gBAAb,cAAmC,cAAc;CAC/C,AAAO,YAAY,cAAuB;EACxC,MAAM;EACN,KAAK,eAAe,UAAU,YAAY;CAC5C;;;;CAKA,AAAO,YAAY,OAAqB;EACtC,OAAO,iBAAiB;CAC1B;;CAGA,AAAO,MAAM,cAAsC;EACjD,OAAO,KAAK,QAAQ,WAAW,YAAY;CAC7C;;CAGA,AAAO,OAAO,YAA+B,cAAsC;EACjF,OAAO,KAAK,QAAQ,mBAAmB,cAAc,EACnD,WACF,CAAC;CACH;;CAGA,AAAO,SAAS,WAA8B,cAAsC;EAClF,OAAO,KAAK,QAAQ,cAAc,cAAc,EAC9C,UACF,CAAC;CACH;;CAGA,AAAO,IAAI,cAAsC;EAC/C,OAAO,KAAK,SAAS,mBAAmB,YAAY;CACtD;;CAGA,AAAO,MAAM,cAAsC;EACjD,OAAO,KAAK,SACV,CACE,4BACA,mEACF,GACA,YACF;CACF;;CAGA,AAAO,KAAK,cAAsC;EAChD,OAAO,KAAK,SACV,CACE,sBACA,yEACF,GACA,YACF;CACF;;CAGA,AAAO,QAAQ,MAA+B,cAAsC;EAClF,OAAO,KAAK,QAAQ,iBAAiB,cAAc,EACjD,SAAS,gBAAgB,IAAI,EAC/B,CAAC;CACH;;CAGA,AAAO,IAAI,MAA+B,cAAsC;EAC9E,OAAO,KAAK,QAAQ,MAAM,YAAY;CACxC;;CAGA,AAAO,QAAQ,MAA+B,cAAsC;EAClF,OAAO,KAAK,QAAQ,iBAAiB,cAAc,EACjD,SAAS,gBAAgB,IAAI,EAC/B,CAAC;CACH;;CAGA,AAAO,IAAI,MAAc,cAAsC;EAC7D,OAAO,KAAK,QAAQ,MAAM,YAAY;CACxC;;CAGA,AAAO,SAAS,OAAe,cAAsC;EACnE,OAAO,KAAK,QAAQ,cAAc,cAAc,EAC9C,UAAU,MACZ,CAAC;CACH;;CAGA,AAAO,SAAS,OAAe,cAAsC;EACnE,OAAO,KAAK,QAAQ,cAAc,cAAc,EAC9C,UAAU,MACZ,CAAC;CACH;;CAGA,AAAO,UAAU,QAAgB,cAAsC;EACrE,OAAO,KAAK,QAAQ,eAAe,cAAc,EAC/C,WAAW,OACb,CAAC;CACH;;CAGA,AAAO,UAAU,QAAgB,cAAsC;EACrE,OAAO,KAAK,QAAQ,eAAe,cAAc,EAC/C,WAAW,OACb,CAAC;CACH;;;;CAKA,AAAO,OAAO,mBAA0C;EACtD,OAAO,KAAK,eAAe,OAAO,SAAuB;GAGvD,QAAO,MAFc,KAAK,KAAK,iBAAiB,EAEnC,CAAC;EAChB,CAAC;CACH;;;;;;;;;;;;;;;;CAiBA,AAAgB,aAAa,SAA2B,iBAAmC;EACzF,IAAI,WAAW,eACb,OAAO;GAAE,MAAM;GAAU,QAAQ;EAAS;EAG5C,IAAI,WAAW,iBACb,OAAO;GAAE,MAAM;GAAU,iBAAiB;EAAS;EAIrD,OAAO,CAAC;CACV;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lower-stage3-decorators.d.mts","names":[],"sources":["../../../../../../../core/src/vite/lower-stage3-decorators.ts"],"mappings":";;AASA;;;;;;UAAiB,uBAAA;EACf,IAAA;EACA,OAAA;EACA,SAAA,CAAU,IAAA,UAAc,EAAA,WAAa,OAAO;IAAG,IAAA;IAAc,GAAA;EAAA;AAAA;AAoC/D;;;;AAAgE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAhE,iBAAgB,qBAAA,
|
|
1
|
+
{"version":3,"file":"lower-stage3-decorators.d.mts","names":[],"sources":["../../../../../../../core/src/vite/lower-stage3-decorators.ts"],"mappings":";;AASA;;;;;;UAAiB,uBAAA;EACf,IAAA;EACA,OAAA;EACA,SAAA,CAAU,IAAA,UAAc,EAAA,WAAa,OAAO;IAAG,IAAA;IAAc,GAAA;EAAA;AAAA;AAoC/D;;;;AAAgE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAhE,iBAAgB,qBAAA,IAAyB,uBAAuB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"warlock-config.manager.d.mts","names":[],"sources":["../../../../../../../core/src/warlock-config/warlock-config.manager.ts"],"mappings":";;;;;AAeA;;;;AAAwD;AAaxD;iBAbgB,yBAAA,CAA0B,KAAc;;;;;;;cAa3C,oBAAA;EAyHR;;;EAAA,QArHK,MAAA;EAyIS;;;EAAA,QApIT,OAAA;EAqIL;;;;;EA9HU,IAAA,
|
|
1
|
+
{"version":3,"file":"warlock-config.manager.d.mts","names":[],"sources":["../../../../../../../core/src/warlock-config/warlock-config.manager.ts"],"mappings":";;;;;AAeA;;;;AAAwD;AAaxD;iBAbgB,yBAAA,CAA0B,KAAc;;;;;;;cAa3C,oBAAA;EAyHR;;;EAAA,QArHK,MAAA;EAyIS;;;EAAA,QApIT,OAAA;EAqIL;;;;;EA9HU,IAAA,IAAQ,OAAA,CAAQ,aAAA;EAPrB;;;;;;EAAA,QAgCM,MAAA;EA6EH;;;;;;;;EAAA,QAtCG,cAAA;EA2DR;;;;;;;;;;;EArBC,GAAA,mBAAsB,aAAA,EAC3B,GAAA,EAAK,GAAA,EACL,YAAA,GAAe,aAAA,CAAc,GAAA,IAC5B,aAAA,CAAc,GAAA;EAsCV;;;;;AAWuB;AAoBhC;;;;EAnDQ,OAAA,mBAA0B,aAAA,EAC9B,GAAA,EAAK,GAAA,EACL,YAAA,GAAe,aAAA,CAAc,GAAA,IAC5B,OAAA,CAAQ,aAAA,CAAc,GAAA;;;;MAQd,QAAA;;;;;;EASJ,MAAA,IAAU,aAAA;;;;EAWJ,MAAA,IAAU,OAAA;AAAA;;;;;;;;;;;;;;cAoBZ,oBAAA,EAAoB,oBAA6B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"warlock-config.manager.mjs","names":[],"sources":["../../../../../../../core/src/warlock-config/warlock-config.manager.ts"],"sourcesContent":["import { fileExistsAsync } from \"@warlock.js/fs\";\r\nimport { get } from \"@mongez/reinforcements\";\r\nimport { readFile, unlink, writeFile } from \"node:fs/promises\";\r\nimport { pathToFileURL } from \"url\";\r\nimport { rootPath } from \"../utils\";\r\nimport { type WarlockConfig } from \"./types\";\r\n\r\n/**\r\n * True when `error` is Node's \"this runtime can't import a `.ts` file\" error.\r\n *\r\n * Node only executes TypeScript natively from v22.18 / v23.6 onward (earlier\r\n * 22.x needs `--experimental-strip-types`). On older runtimes a bare\r\n * `import(\"warlock.config.ts\")` throws `ERR_UNKNOWN_FILE_EXTENSION` — which is\r\n * the signal to fall back to transpiling the config ourselves.\r\n */\r\nexport function isUnknownTsExtensionError(error: unknown): boolean {\r\n return (\r\n (error as { code?: string })?.code === \"ERR_UNKNOWN_FILE_EXTENSION\" ||\r\n /Unknown file extension \"\\.tsx?\"/.test(String((error as Error)?.message))\r\n );\r\n}\r\n\r\n/**\r\n * Warlock Config Manager\r\n *\r\n * Manages lazy loading of the pre-compiled warlock.config.js file\r\n * from the .warlock/cache directory.\r\n */\r\nexport class WarlockConfigManager {\r\n /**\r\n * Cached config instance\r\n */\r\n private config?: WarlockConfig;\r\n\r\n /**\r\n * Loading promise to prevent duplicate loads\r\n */\r\n private loading?: Promise<WarlockConfig | undefined>;\r\n\r\n /**\r\n * Load warlock.config.js (cached after first load)\r\n *\r\n * @returns The resolved Warlock configuration\r\n */\r\n public async load(): Promise<WarlockConfig | undefined> {\r\n // Already loaded\r\n if (this.config) {\r\n return this.config;\r\n }\r\n\r\n // Currently loading (prevent duplicate loads)\r\n if (this.loading) {\r\n return this.loading;\r\n }\r\n\r\n // Start loading\r\n this.loading = this.doLoad();\r\n this.config = await this.loading;\r\n this.loading = undefined;\r\n\r\n return this.config;\r\n }\r\n\r\n /**\r\n * Internal load implementation\r\n *\r\n * The ESM loader hook transpiles `warlock.config.ts` on import — no\r\n * separate compile-to-disk step is needed.\r\n */\r\n private async doLoad(): Promise<WarlockConfig | undefined> {\r\n const configPath = rootPath(\"warlock.config.ts\");\r\n\r\n if (!(await fileExistsAsync(configPath))) {\r\n // Written straight to the console, NOT through `@warlock.js/logger`.\r\n // This runs during CLI bootstrap, before `LoggerConnector.start()` has\r\n // configured a single channel, so a logger call here reaches nobody in\r\n // any application — the warning would be silently dropped exactly when\r\n // the user most needs it.\r\n console.warn(\"warlock.config.ts is missing — run `warlock init` to create it\");\r\n return;\r\n }\r\n\r\n try {\r\n const configModule = await import(pathToFileURL(configPath).href);\r\n return configModule.default;\r\n } catch (error) {\r\n // `dev` registers a TS loader hook, and Node ≥ 22.18 / 23.6 strips types\r\n // natively — in both cases the direct import above works. But `build` /\r\n // `start` register no hook, so on an older Node the import throws\r\n // ERR_UNKNOWN_FILE_EXTENSION. Transpile the config with esbuild ourselves\r\n // so config loading is Node-version-independent rather than relying on an\r\n // experimental runtime feature.\r\n if (isUnknownTsExtensionError(error)) {\r\n return await this.loadViaEsbuild(configPath);\r\n }\r\n\r\n throw new Error(`Failed to load warlock.config.ts: ${error}`);\r\n }\r\n }\r\n\r\n /**\r\n * Fallback config loader for runtimes without native TypeScript support.\r\n *\r\n * Transpiles `warlock.config.ts` with esbuild (already a core dependency),\r\n * writes the result as a sibling `.mjs` — so the config's own imports (bare\r\n * `@warlock.js/*` and any relative paths) resolve exactly as they would from\r\n * the original location — imports it, then removes the temp file.\r\n */\r\n private async loadViaEsbuild(configPath: string): Promise<WarlockConfig | undefined> {\r\n const { transformSync } = await import(\"esbuild\");\r\n const source = await readFile(configPath, \"utf8\");\r\n\r\n const { code } = transformSync(source, {\r\n loader: \"ts\",\r\n format: \"esm\",\r\n target: `node${process.versions.node.split(\".\")[0]}`,\r\n sourcefile: configPath,\r\n });\r\n\r\n // Unique sibling name so concurrent processes never clobber each other.\r\n const compiledPath = configPath.replace(\r\n /warlock\\.config\\.ts$/,\r\n `.warlock.config.${process.pid}.mjs`,\r\n );\r\n\r\n await writeFile(compiledPath, code, \"utf8\");\r\n\r\n try {\r\n const configModule = await import(pathToFileURL(compiledPath).href);\r\n return configModule.default;\r\n } finally {\r\n await unlink(compiledPath).catch(() => {});\r\n }\r\n }\r\n\r\n /**\r\n * Get config value by key (dot notation supported)\r\n *\r\n * @example\r\n * config.get(\"server.port\") // Returns 3000\r\n * config.get(\"cli.commands\") // Returns array of commands\r\n *\r\n * @param key - Config key (supports dot notation), autocompletes for first level only\r\n * @returns The config value\r\n * @throws Error if config is not loaded\r\n */\r\n public get<Key extends keyof WarlockConfig>(\r\n key: Key,\r\n defaultValue?: WarlockConfig[Key],\r\n ): WarlockConfig[Key] {\r\n if (!this.config) {\r\n throw new Error(\"WarlockConfig not loaded. Call load() first or use lazyGet().\");\r\n }\r\n\r\n return get(this.config, key as string, defaultValue);\r\n }\r\n\r\n /**\r\n * Lazy get - loads config if not already loaded\r\n *\r\n * @example\r\n * const port = await config.lazyGet(\"server\");\r\n *\r\n * @param key - Config key (supports dot notation), autocompletes for first level only\r\n * @param defaultValue - Default value if config key is undefined\r\n * @returns The config value\r\n */\r\n async lazyGet<Key extends keyof WarlockConfig>(\r\n key: Key,\r\n defaultValue?: WarlockConfig[Key],\r\n ): Promise<WarlockConfig[Key]> {\r\n await this.load();\r\n return this.get(key, defaultValue);\r\n }\r\n\r\n /**\r\n * Check if config is loaded\r\n */\r\n public get isLoaded(): boolean {\r\n return this.config !== undefined;\r\n }\r\n\r\n /**\r\n * Get the entire config object\r\n *\r\n * @throws Error if config is not loaded\r\n */\r\n public getAll(): WarlockConfig {\r\n if (!this.config) {\r\n throw new Error(\"WarlockConfig not loaded. Call load() first or use lazyGet().\");\r\n }\r\n\r\n return this.config;\r\n }\r\n\r\n /**\r\n * Reload config (useful for HMR/development)\r\n */\r\n public async reload(): Promise<void> {\r\n this.config = undefined;\r\n this.loading = undefined;\r\n await this.load();\r\n }\r\n}\r\n\r\n/**\r\n * Exported singleton instance\r\n *\r\n * @example\r\n * import { warlockConfig } from \"@warlock.js/core\";\r\n *\r\n * // Lazy load and get value\r\n * const port = await warlockConfig.lazyGet(\"server.port\");\r\n *\r\n * // Or load first, then get\r\n * await warlockConfig.load();\r\n * const commands = warlockConfig.get(\"cli.commands\");\r\n */\r\nexport const warlockConfigManager = new WarlockConfigManager();\r\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,SAAgB,0BAA0B,OAAyB;CACjE,OACG,OAA6B,SAAS,gCACvC,kCAAkC,KAAK,OAAQ,OAAiB,OAAO,CAAC;AAE5E;;;;;;;AAQA,IAAa,uBAAb,MAAkC;;;;;;CAgBhC,MAAa,OAA2C;EAEtD,IAAI,KAAK,QACP,OAAO,KAAK;EAId,IAAI,KAAK,SACP,OAAO,KAAK;EAId,KAAK,UAAU,KAAK,OAAO;EAC3B,KAAK,SAAS,MAAM,KAAK;EACzB,KAAK,UAAU;EAEf,OAAO,KAAK;CACd;;;;;;;CAQA,MAAc,SAA6C;EACzD,MAAM,aAAa,SAAS,mBAAmB;EAE/C,IAAI,CAAE,MAAM,gBAAgB,UAAU,GAAI;GAMxC,QAAQ,KAAK,gEAAgE;GAC7E;EACF;EAEA,IAAI;GAEF,QAAO,MADoB,OAAO,cAAc,UAAU,
|
|
1
|
+
{"version":3,"file":"warlock-config.manager.mjs","names":[],"sources":["../../../../../../../core/src/warlock-config/warlock-config.manager.ts"],"sourcesContent":["import { fileExistsAsync } from \"@warlock.js/fs\";\r\nimport { get } from \"@mongez/reinforcements\";\r\nimport { readFile, unlink, writeFile } from \"node:fs/promises\";\r\nimport { pathToFileURL } from \"url\";\r\nimport { rootPath } from \"../utils\";\r\nimport { type WarlockConfig } from \"./types\";\r\n\r\n/**\r\n * True when `error` is Node's \"this runtime can't import a `.ts` file\" error.\r\n *\r\n * Node only executes TypeScript natively from v22.18 / v23.6 onward (earlier\r\n * 22.x needs `--experimental-strip-types`). On older runtimes a bare\r\n * `import(\"warlock.config.ts\")` throws `ERR_UNKNOWN_FILE_EXTENSION` — which is\r\n * the signal to fall back to transpiling the config ourselves.\r\n */\r\nexport function isUnknownTsExtensionError(error: unknown): boolean {\r\n return (\r\n (error as { code?: string })?.code === \"ERR_UNKNOWN_FILE_EXTENSION\" ||\r\n /Unknown file extension \"\\.tsx?\"/.test(String((error as Error)?.message))\r\n );\r\n}\r\n\r\n/**\r\n * Warlock Config Manager\r\n *\r\n * Manages lazy loading of the pre-compiled warlock.config.js file\r\n * from the .warlock/cache directory.\r\n */\r\nexport class WarlockConfigManager {\r\n /**\r\n * Cached config instance\r\n */\r\n private config?: WarlockConfig;\r\n\r\n /**\r\n * Loading promise to prevent duplicate loads\r\n */\r\n private loading?: Promise<WarlockConfig | undefined>;\r\n\r\n /**\r\n * Load warlock.config.js (cached after first load)\r\n *\r\n * @returns The resolved Warlock configuration\r\n */\r\n public async load(): Promise<WarlockConfig | undefined> {\r\n // Already loaded\r\n if (this.config) {\r\n return this.config;\r\n }\r\n\r\n // Currently loading (prevent duplicate loads)\r\n if (this.loading) {\r\n return this.loading;\r\n }\r\n\r\n // Start loading\r\n this.loading = this.doLoad();\r\n this.config = await this.loading;\r\n this.loading = undefined;\r\n\r\n return this.config;\r\n }\r\n\r\n /**\r\n * Internal load implementation\r\n *\r\n * The ESM loader hook transpiles `warlock.config.ts` on import — no\r\n * separate compile-to-disk step is needed.\r\n */\r\n private async doLoad(): Promise<WarlockConfig | undefined> {\r\n const configPath = rootPath(\"warlock.config.ts\");\r\n\r\n if (!(await fileExistsAsync(configPath))) {\r\n // Written straight to the console, NOT through `@warlock.js/logger`.\r\n // This runs during CLI bootstrap, before `LoggerConnector.start()` has\r\n // configured a single channel, so a logger call here reaches nobody in\r\n // any application — the warning would be silently dropped exactly when\r\n // the user most needs it.\r\n console.warn(\"warlock.config.ts is missing — run `warlock init` to create it\");\r\n return;\r\n }\r\n\r\n try {\r\n const configModule = await import(pathToFileURL(configPath).href);\r\n return configModule.default;\r\n } catch (error) {\r\n // `dev` registers a TS loader hook, and Node ≥ 22.18 / 23.6 strips types\r\n // natively — in both cases the direct import above works. But `build` /\r\n // `start` register no hook, so on an older Node the import throws\r\n // ERR_UNKNOWN_FILE_EXTENSION. Transpile the config with esbuild ourselves\r\n // so config loading is Node-version-independent rather than relying on an\r\n // experimental runtime feature.\r\n if (isUnknownTsExtensionError(error)) {\r\n return await this.loadViaEsbuild(configPath);\r\n }\r\n\r\n throw new Error(`Failed to load warlock.config.ts: ${error}`);\r\n }\r\n }\r\n\r\n /**\r\n * Fallback config loader for runtimes without native TypeScript support.\r\n *\r\n * Transpiles `warlock.config.ts` with esbuild (already a core dependency),\r\n * writes the result as a sibling `.mjs` — so the config's own imports (bare\r\n * `@warlock.js/*` and any relative paths) resolve exactly as they would from\r\n * the original location — imports it, then removes the temp file.\r\n */\r\n private async loadViaEsbuild(configPath: string): Promise<WarlockConfig | undefined> {\r\n const { transformSync } = await import(\"esbuild\");\r\n const source = await readFile(configPath, \"utf8\");\r\n\r\n const { code } = transformSync(source, {\r\n loader: \"ts\",\r\n format: \"esm\",\r\n target: `node${process.versions.node.split(\".\")[0]}`,\r\n sourcefile: configPath,\r\n });\r\n\r\n // Unique sibling name so concurrent processes never clobber each other.\r\n const compiledPath = configPath.replace(\r\n /warlock\\.config\\.ts$/,\r\n `.warlock.config.${process.pid}.mjs`,\r\n );\r\n\r\n await writeFile(compiledPath, code, \"utf8\");\r\n\r\n try {\r\n const configModule = await import(pathToFileURL(compiledPath).href);\r\n return configModule.default;\r\n } finally {\r\n await unlink(compiledPath).catch(() => {});\r\n }\r\n }\r\n\r\n /**\r\n * Get config value by key (dot notation supported)\r\n *\r\n * @example\r\n * config.get(\"server.port\") // Returns 3000\r\n * config.get(\"cli.commands\") // Returns array of commands\r\n *\r\n * @param key - Config key (supports dot notation), autocompletes for first level only\r\n * @returns The config value\r\n * @throws Error if config is not loaded\r\n */\r\n public get<Key extends keyof WarlockConfig>(\r\n key: Key,\r\n defaultValue?: WarlockConfig[Key],\r\n ): WarlockConfig[Key] {\r\n if (!this.config) {\r\n throw new Error(\"WarlockConfig not loaded. Call load() first or use lazyGet().\");\r\n }\r\n\r\n return get(this.config, key as string, defaultValue);\r\n }\r\n\r\n /**\r\n * Lazy get - loads config if not already loaded\r\n *\r\n * @example\r\n * const port = await config.lazyGet(\"server\");\r\n *\r\n * @param key - Config key (supports dot notation), autocompletes for first level only\r\n * @param defaultValue - Default value if config key is undefined\r\n * @returns The config value\r\n */\r\n async lazyGet<Key extends keyof WarlockConfig>(\r\n key: Key,\r\n defaultValue?: WarlockConfig[Key],\r\n ): Promise<WarlockConfig[Key]> {\r\n await this.load();\r\n return this.get(key, defaultValue);\r\n }\r\n\r\n /**\r\n * Check if config is loaded\r\n */\r\n public get isLoaded(): boolean {\r\n return this.config !== undefined;\r\n }\r\n\r\n /**\r\n * Get the entire config object\r\n *\r\n * @throws Error if config is not loaded\r\n */\r\n public getAll(): WarlockConfig {\r\n if (!this.config) {\r\n throw new Error(\"WarlockConfig not loaded. Call load() first or use lazyGet().\");\r\n }\r\n\r\n return this.config;\r\n }\r\n\r\n /**\r\n * Reload config (useful for HMR/development)\r\n */\r\n public async reload(): Promise<void> {\r\n this.config = undefined;\r\n this.loading = undefined;\r\n await this.load();\r\n }\r\n}\r\n\r\n/**\r\n * Exported singleton instance\r\n *\r\n * @example\r\n * import { warlockConfig } from \"@warlock.js/core\";\r\n *\r\n * // Lazy load and get value\r\n * const port = await warlockConfig.lazyGet(\"server.port\");\r\n *\r\n * // Or load first, then get\r\n * await warlockConfig.load();\r\n * const commands = warlockConfig.get(\"cli.commands\");\r\n */\r\nexport const warlockConfigManager = new WarlockConfigManager();\r\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,SAAgB,0BAA0B,OAAyB;CACjE,OACG,OAA6B,SAAS,gCACvC,kCAAkC,KAAK,OAAQ,OAAiB,OAAO,CAAC;AAE5E;;;;;;;AAQA,IAAa,uBAAb,MAAkC;;;;;;CAgBhC,MAAa,OAA2C;EAEtD,IAAI,KAAK,QACP,OAAO,KAAK;EAId,IAAI,KAAK,SACP,OAAO,KAAK;EAId,KAAK,UAAU,KAAK,OAAO;EAC3B,KAAK,SAAS,MAAM,KAAK;EACzB,KAAK,UAAU;EAEf,OAAO,KAAK;CACd;;;;;;;CAQA,MAAc,SAA6C;EACzD,MAAM,aAAa,SAAS,mBAAmB;EAE/C,IAAI,CAAE,MAAM,gBAAgB,UAAU,GAAI;GAMxC,QAAQ,KAAK,gEAAgE;GAC7E;EACF;EAEA,IAAI;GAEF,QAAO,MADoB,OAAO,cAAc,UAAU,CAAC,CAAC,MACzC,CAAC;EACtB,SAAS,OAAO;GAOd,IAAI,0BAA0B,KAAK,GACjC,OAAO,MAAM,KAAK,eAAe,UAAU;GAG7C,MAAM,IAAI,MAAM,qCAAqC,OAAO;EAC9D;CACF;;;;;;;;;CAUA,MAAc,eAAe,YAAwD;EACnF,MAAM,EAAE,kBAAkB,MAAM,OAAO;EAGvC,MAAM,EAAE,SAAS,cAAc,MAFV,SAAS,YAAY,MAAM,GAET;GACrC,QAAQ;GACR,QAAQ;GACR,QAAQ,OAAO,QAAQ,SAAS,KAAK,MAAM,GAAG,CAAC,CAAC;GAChD,YAAY;EACd,CAAC;EAGD,MAAM,eAAe,WAAW,QAC9B,wBACA,mBAAmB,QAAQ,IAAI,KACjC;EAEA,MAAM,UAAU,cAAc,MAAM,MAAM;EAE1C,IAAI;GAEF,QAAO,MADoB,OAAO,cAAc,YAAY,CAAC,CAAC,MAC3C,CAAC;EACtB,UAAU;GACR,MAAM,OAAO,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC;EAC3C;CACF;;;;;;;;;;;;CAaA,AAAO,IACL,KACA,cACoB;EACpB,IAAI,CAAC,KAAK,QACR,MAAM,IAAI,MAAM,+DAA+D;EAGjF,OAAO,IAAI,KAAK,QAAQ,KAAe,YAAY;CACrD;;;;;;;;;;;CAYA,MAAM,QACJ,KACA,cAC6B;EAC7B,MAAM,KAAK,KAAK;EAChB,OAAO,KAAK,IAAI,KAAK,YAAY;CACnC;;;;CAKA,IAAW,WAAoB;EAC7B,OAAO,KAAK,WAAW;CACzB;;;;;;CAOA,AAAO,SAAwB;EAC7B,IAAI,CAAC,KAAK,QACR,MAAM,IAAI,MAAM,+DAA+D;EAGjF,OAAO,KAAK;CACd;;;;CAKA,MAAa,SAAwB;EACnC,KAAK,SAAS;EACd,KAAK,UAAU;EACf,MAAM,KAAK,KAAK;CAClB;AACF;;;;;;;;;;;;;;AAeA,MAAa,uBAAuB,IAAI,qBAAqB"}
|
package/llms-full.txt
CHANGED
|
@@ -1222,7 +1222,7 @@ A controller is a thin function: pull inputs from `request`, call work, return t
|
|
|
1222
1222
|
```ts title="src/app/<module>/controllers/<action>.controller.ts"
|
|
1223
1223
|
import { type RequestHandler } from "@warlock.js/core";
|
|
1224
1224
|
|
|
1225
|
-
export const listProductsController: RequestHandler = async (request, response) => {
|
|
1225
|
+
export const listProductsController: RequestHandler = async ({ request, response }) => {
|
|
1226
1226
|
return response.success({ products: [] });
|
|
1227
1227
|
};
|
|
1228
1228
|
```
|
|
@@ -1277,10 +1277,10 @@ import { type Request, type RequestHandler } from "@warlock.js/core";
|
|
|
1277
1277
|
import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
|
|
1278
1278
|
import { createProductService } from "../services/create-product.service";
|
|
1279
1279
|
|
|
1280
|
-
export const createProductController: RequestHandler<Request<CreateProductSchema>> = async (
|
|
1280
|
+
export const createProductController: RequestHandler<Request<CreateProductSchema>> = async ({
|
|
1281
1281
|
request,
|
|
1282
1282
|
response,
|
|
1283
|
-
) => {
|
|
1283
|
+
}) => {
|
|
1284
1284
|
const product = await createProductService(request.validated());
|
|
1285
1285
|
|
|
1286
1286
|
return response.successCreate({ product });
|
|
@@ -1306,10 +1306,10 @@ Routes behind `authMiddleware` need `request.user` typed. Project conventions ad
|
|
|
1306
1306
|
import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
|
|
1307
1307
|
import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
|
|
1308
1308
|
|
|
1309
|
-
export const createProductController: GuardedRequestHandler<CreateProductSchema> = async (
|
|
1309
|
+
export const createProductController: GuardedRequestHandler<CreateProductSchema> = async ({
|
|
1310
1310
|
request,
|
|
1311
1311
|
response,
|
|
1312
|
-
) => {
|
|
1312
|
+
}) => {
|
|
1313
1313
|
// request.user is typed
|
|
1314
1314
|
const product = await createProductService(request.validated());
|
|
1315
1315
|
return response.successCreate({ product });
|
|
@@ -1356,7 +1356,7 @@ If your controller is over ~30 lines, the work probably belongs in a service.
|
|
|
1356
1356
|
import { type RequestHandler } from "@warlock.js/core";
|
|
1357
1357
|
import { listProductsService } from "../services/list-products.service";
|
|
1358
1358
|
|
|
1359
|
-
export const listProductsController: RequestHandler = async (request, response) => {
|
|
1359
|
+
export const listProductsController: RequestHandler = async ({ request, response }) => {
|
|
1360
1360
|
const { data: products, pagination } = await listProductsService({
|
|
1361
1361
|
...request.all(),
|
|
1362
1362
|
organization_id: request.user.organizationId,
|
|
@@ -1373,10 +1373,10 @@ import { type Request, type RequestHandler } from "@warlock.js/core";
|
|
|
1373
1373
|
import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
|
|
1374
1374
|
import { createProductService } from "../services/create-product.service";
|
|
1375
1375
|
|
|
1376
|
-
export const createProductController: RequestHandler<Request<CreateProductSchema>> = async (
|
|
1376
|
+
export const createProductController: RequestHandler<Request<CreateProductSchema>> = async ({
|
|
1377
1377
|
request,
|
|
1378
1378
|
response,
|
|
1379
|
-
) => {
|
|
1379
|
+
}) => {
|
|
1380
1380
|
const product = await createProductService(request.validated());
|
|
1381
1381
|
|
|
1382
1382
|
return response.successCreate({ product });
|
|
@@ -1410,7 +1410,7 @@ export async function getProductService(id: string) {
|
|
|
1410
1410
|
import type { RequestHandler } from "@warlock.js/core";
|
|
1411
1411
|
import { getProductService } from "../services/get-product.service";
|
|
1412
1412
|
|
|
1413
|
-
export const getProductController: RequestHandler = async (request, response) => {
|
|
1413
|
+
export const getProductController: RequestHandler = async ({ request, response }) => {
|
|
1414
1414
|
const product = await getProductService(request.input("id"));
|
|
1415
1415
|
|
|
1416
1416
|
return response.success({ product });
|
|
@@ -4133,9 +4133,9 @@ description: 'Send HTTP responses via @warlock.js/core''s Response helpers — s
|
|
|
4133
4133
|
## The shape
|
|
4134
4134
|
|
|
4135
4135
|
```ts
|
|
4136
|
-
import type { RequestHandler
|
|
4136
|
+
import type { RequestHandler } from "@warlock.js/core";
|
|
4137
4137
|
|
|
4138
|
-
export const myController: RequestHandler = async (request, response
|
|
4138
|
+
export const myController: RequestHandler = async ({ request, response }) => {
|
|
4139
4139
|
// …choose a helper and return it
|
|
4140
4140
|
return response.success({ data: "…" });
|
|
4141
4141
|
};
|
|
@@ -4145,11 +4145,11 @@ Always `return response.<helper>(...)`. The return value drives Fastify's send.
|
|
|
4145
4145
|
|
|
4146
4146
|
## Success helpers
|
|
4147
4147
|
|
|
4148
|
-
| Method
|
|
4149
|
-
|
|
|
4150
|
-
| `response.success(data?)`
|
|
4151
|
-
| `response.successCreate(data)`
|
|
4152
|
-
| `response.noContent()`
|
|
4148
|
+
| Method | Status | When |
|
|
4149
|
+
| ------------------------------ | ------ | -------------------------------- |
|
|
4150
|
+
| `response.success(data?)` | 200 | normal read / update |
|
|
4151
|
+
| `response.successCreate(data)` | 201 | resource created (POST) |
|
|
4152
|
+
| `response.noContent()` | 204 | delete succeeded, no body needed |
|
|
4153
4153
|
|
|
4154
4154
|
```ts
|
|
4155
4155
|
return response.success({ products: [...] });
|
|
@@ -4163,13 +4163,13 @@ return response.noContent();
|
|
|
4163
4163
|
|
|
4164
4164
|
## Client-error helpers
|
|
4165
4165
|
|
|
4166
|
-
| Method
|
|
4167
|
-
|
|
|
4168
|
-
| `response.badRequest(data)`
|
|
4169
|
-
| `response.unauthorized(data?)`
|
|
4170
|
-
| `response.forbidden(data?)`
|
|
4171
|
-
| `response.notFound(data?)`
|
|
4172
|
-
| `response.conflict(data?)`
|
|
4166
|
+
| Method | Status | When |
|
|
4167
|
+
| ------------------------------ | ------ | ------------------------------------ |
|
|
4168
|
+
| `response.badRequest(data)` | 400 | malformed or invalid input |
|
|
4169
|
+
| `response.unauthorized(data?)` | 401 | missing/invalid auth token |
|
|
4170
|
+
| `response.forbidden(data?)` | 403 | authenticated but not allowed |
|
|
4171
|
+
| `response.notFound(data?)` | 404 | record missing |
|
|
4172
|
+
| `response.conflict(data?)` | 409 | uniqueness violation, state conflict |
|
|
4173
4173
|
|
|
4174
4174
|
```ts
|
|
4175
4175
|
return response.badRequest({ error: t("validation.invalid") });
|
|
@@ -4188,8 +4188,8 @@ Most error helpers accept an optional payload — if you omit it, they send a de
|
|
|
4188
4188
|
## Redirects
|
|
4189
4189
|
|
|
4190
4190
|
```ts
|
|
4191
|
-
return response.redirect("/login");
|
|
4192
|
-
return response.redirect("/new-home", 301);
|
|
4191
|
+
return response.redirect("/login"); // 302
|
|
4192
|
+
return response.redirect("/new-home", 301); // permanent
|
|
4193
4193
|
```
|
|
4194
4194
|
|
|
4195
4195
|
## Files
|
|
@@ -4221,19 +4221,19 @@ stream.end();
|
|
|
4221
4221
|
|
|
4222
4222
|
## Throwing HTTP errors
|
|
4223
4223
|
|
|
4224
|
-
Most of the time, controllers don't need to
|
|
4224
|
+
Most of the time, controllers don't need to _choose_ an error helper — they throw from the service layer instead. The request middleware (`http/middleware/inject-request-context.ts`) catches every `HttpError` subclass and produces the matching response. The error classes mirror the helpers above:
|
|
4225
4225
|
|
|
4226
4226
|
```ts
|
|
4227
4227
|
import {
|
|
4228
|
-
ResourceNotFoundError,
|
|
4229
|
-
UnAuthorizedError,
|
|
4230
|
-
ForbiddenError,
|
|
4231
|
-
BadRequestError,
|
|
4232
|
-
ConflictError,
|
|
4233
|
-
NotAcceptableError,
|
|
4234
|
-
NotAllowedError,
|
|
4235
|
-
ServerError,
|
|
4236
|
-
HttpError,
|
|
4228
|
+
ResourceNotFoundError, // 404
|
|
4229
|
+
UnAuthorizedError, // 401
|
|
4230
|
+
ForbiddenError, // 403
|
|
4231
|
+
BadRequestError, // 400
|
|
4232
|
+
ConflictError, // 409
|
|
4233
|
+
NotAcceptableError, // 406
|
|
4234
|
+
NotAllowedError, // 405
|
|
4235
|
+
ServerError, // 500
|
|
4236
|
+
HttpError, // base class — `new HttpError(status, message, payload?)` for arbitrary codes
|
|
4237
4237
|
} from "@warlock.js/core";
|
|
4238
4238
|
|
|
4239
4239
|
throw new ResourceNotFoundError("product.notFound");
|
|
@@ -4241,13 +4241,27 @@ throw new ForbiddenError("permission.denied", { resource: "product", id });
|
|
|
4241
4241
|
throw new ConflictError("user.duplicateEmail");
|
|
4242
4242
|
```
|
|
4243
4243
|
|
|
4244
|
-
Each class takes `(message, payload?)`. The
|
|
4244
|
+
Each class takes `(message, payload?)`. The framework keeps the optional
|
|
4245
|
+
detail nested under `payload`:
|
|
4246
|
+
|
|
4247
|
+
```json
|
|
4248
|
+
{
|
|
4249
|
+
"error": "Product not found",
|
|
4250
|
+
"payload": { "id": 42 }
|
|
4251
|
+
}
|
|
4252
|
+
```
|
|
4253
|
+
|
|
4254
|
+
Every unhandled error response also carries
|
|
4255
|
+
`Cache-Control: private, no-store`, regardless of status. The floor is
|
|
4256
|
+
applied once at the shared error funnel, so API-route errors cannot be stored
|
|
4257
|
+
and replayed across users. In development mode, the stack trace is included
|
|
4258
|
+
too.
|
|
4245
4259
|
|
|
4246
4260
|
Pick the class, throw from the service or use-case, and forget about response shaping at the call site. The controller stays focused on the success path:
|
|
4247
4261
|
|
|
4248
4262
|
```ts
|
|
4249
|
-
export const getProductController: RequestHandler = async (request, response) => {
|
|
4250
|
-
const product = await getProductService(request.input("id"));
|
|
4263
|
+
export const getProductController: RequestHandler = async ({ request, response }) => {
|
|
4264
|
+
const product = await getProductService(request.input("id")); // throws ResourceNotFoundError on miss
|
|
4251
4265
|
return response.success({ product });
|
|
4252
4266
|
};
|
|
4253
4267
|
```
|
|
@@ -4259,9 +4273,9 @@ See [`create-controller`](../create-controller/SKILL.md) for the "throw from ser
|
|
|
4259
4273
|
```ts
|
|
4260
4274
|
const sse = response.sse();
|
|
4261
4275
|
|
|
4262
|
-
sse.send("tick", { count: 1 });
|
|
4263
|
-
sse.send("tick", { count: 2 }, "msg-2");
|
|
4264
|
-
sse.comment("keep-alive");
|
|
4276
|
+
sse.send("tick", { count: 1 }); // event name, data, optional id
|
|
4277
|
+
sse.send("tick", { count: 2 }, "msg-2"); // third arg is the SSE event id
|
|
4278
|
+
sse.comment("keep-alive"); // invisible to the client, prevents timeout
|
|
4265
4279
|
sse.end();
|
|
4266
4280
|
```
|
|
4267
4281
|
|
|
@@ -4301,11 +4315,11 @@ response.clearCookie("session_id");
|
|
|
4301
4315
|
|
|
4302
4316
|
Every response cookie gets `httpOnly: true`, `sameSite: "lax"`, and `secure: true` outside development, unless you override them:
|
|
4303
4317
|
|
|
4304
|
-
| Flag
|
|
4305
|
-
|
|
4306
|
-
| `httpOnly` | `true`
|
|
4307
|
-
| `sameSite` | `"lax"`
|
|
4308
|
-
| `secure`
|
|
4318
|
+
| Flag | Default | Why it's the default |
|
|
4319
|
+
| ---------- | ----------------------------- | --------------------------------------------------------- |
|
|
4320
|
+
| `httpOnly` | `true` | without it, any injected script can read the cookie |
|
|
4321
|
+
| `sameSite` | `"lax"` | without it, the cookie rides along on cross-site requests |
|
|
4322
|
+
| `secure` | `true`, except in development | without it, the cookie travels in cleartext |
|
|
4309
4323
|
|
|
4310
4324
|
`secure` is relaxed in development only — browsers drop a `Secure` cookie over plain http, which would silently break every local login. It stays on in test and staging.
|
|
4311
4325
|
|
|
@@ -4736,12 +4750,17 @@ Set `STORAGE_DRIVER=r2` in production, leave unset in dev — same code uses loc
|
|
|
4736
4750
|
### Uploading a request file
|
|
4737
4751
|
|
|
4738
4752
|
```ts
|
|
4739
|
-
import
|
|
4753
|
+
import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
|
|
4740
4754
|
import { storage } from "@warlock.js/core";
|
|
4741
4755
|
|
|
4742
|
-
export const uploadAvatarController:
|
|
4756
|
+
export const uploadAvatarController: GuardedRequestHandler = async ({ request, response }) => {
|
|
4743
4757
|
const upload = request.file("avatar");
|
|
4744
|
-
|
|
4758
|
+
|
|
4759
|
+
if (!upload) {
|
|
4760
|
+
return response.badRequest({ error: "missing file" });
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4763
|
+
const file = await storage.put(upload, `avatars/${request.user.id}/${upload.name}`);
|
|
4745
4764
|
|
|
4746
4765
|
return response.successCreate({ url: file.url, hash: file.hash });
|
|
4747
4766
|
};
|
|
@@ -5656,8 +5675,8 @@ Multipart uploads come in as `UploadedFile` instances. The class wraps Fastify's
|
|
|
5656
5675
|
|
|
5657
5676
|
## The shape
|
|
5658
5677
|
|
|
5659
|
-
```ts title="src/app/uploads/schema/
|
|
5660
|
-
import { v } from "@warlock.js/seal";
|
|
5678
|
+
```ts title="src/app/uploads/schema/upload-avatar.schema.ts"
|
|
5679
|
+
import { v, type Infer } from "@warlock.js/seal";
|
|
5661
5680
|
|
|
5662
5681
|
export const uploadAvatarSchema = v.object({
|
|
5663
5682
|
avatar: v
|
|
@@ -5666,16 +5685,18 @@ export const uploadAvatarSchema = v.object({
|
|
|
5666
5685
|
.maxSize({ unit: "MB", size: 5 })
|
|
5667
5686
|
.mimeType(["image/jpeg", "image/png", "image/webp"]),
|
|
5668
5687
|
});
|
|
5688
|
+
|
|
5689
|
+
export type UploadAvatarSchema = Infer<typeof uploadAvatarSchema>;
|
|
5669
5690
|
```
|
|
5670
5691
|
|
|
5671
5692
|
```ts title="src/app/uploads/controllers/upload-avatar.controller.ts"
|
|
5672
|
-
import { type GuardedRequestHandler } from "app/auth/
|
|
5693
|
+
import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
|
|
5673
5694
|
import { type UploadAvatarSchema, uploadAvatarSchema } from "../schema/upload-avatar.schema";
|
|
5674
5695
|
|
|
5675
|
-
export const uploadAvatarController: GuardedRequestHandler<UploadAvatarSchema> = async (
|
|
5696
|
+
export const uploadAvatarController: GuardedRequestHandler<UploadAvatarSchema> = async ({
|
|
5676
5697
|
request,
|
|
5677
5698
|
response,
|
|
5678
|
-
) => {
|
|
5699
|
+
}) => {
|
|
5679
5700
|
const { avatar } = request.validated();
|
|
5680
5701
|
|
|
5681
5702
|
const file = await avatar
|
|
@@ -5701,7 +5722,7 @@ Inside a controller:
|
|
|
5701
5722
|
```ts
|
|
5702
5723
|
import type { RequestHandler, UploadedFile } from "@warlock.js/core";
|
|
5703
5724
|
|
|
5704
|
-
export const uploadController: RequestHandler = async (request, response) => {
|
|
5725
|
+
export const uploadController: RequestHandler = async ({ request, response }) => {
|
|
5705
5726
|
// option A — direct from request, no validation
|
|
5706
5727
|
const file: UploadedFile | undefined = request.file("avatar");
|
|
5707
5728
|
|
|
@@ -5883,7 +5904,7 @@ export const uploadFilesSchema = v.object({
|
|
|
5883
5904
|
```ts title="src/app/uploads/controllers/create-upload.controller.ts"
|
|
5884
5905
|
import type { RequestHandler } from "@warlock.js/core";
|
|
5885
5906
|
|
|
5886
|
-
export const createUploadController: RequestHandler = async (request, response) => {
|
|
5907
|
+
export const createUploadController: RequestHandler = async ({ request, response }) => {
|
|
5887
5908
|
const { files } = request.validated();
|
|
5888
5909
|
|
|
5889
5910
|
const saved = await Promise.all(
|
|
@@ -6180,9 +6201,9 @@ const template = await readFile(appPath("mailers/templates/welcome.html"), "utf-
|
|
|
6180
6201
|
### Health endpoint
|
|
6181
6202
|
|
|
6182
6203
|
```ts title="src/app/system/controllers/health.controller.ts"
|
|
6183
|
-
import { Application, type RequestHandler
|
|
6204
|
+
import { Application, type RequestHandler } from "@warlock.js/core";
|
|
6184
6205
|
|
|
6185
|
-
export const healthController: RequestHandler = async (
|
|
6206
|
+
export const healthController: RequestHandler = async ({ response }) => {
|
|
6186
6207
|
return response.success({
|
|
6187
6208
|
status: "ok",
|
|
6188
6209
|
environment: Application.environment,
|
|
@@ -6481,10 +6502,14 @@ The error's message is locale-aware — the `inject-request-context` middleware
|
|
|
6481
6502
|
### Translated response message in a controller
|
|
6482
6503
|
|
|
6483
6504
|
```ts
|
|
6484
|
-
|
|
6505
|
+
import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
|
|
6506
|
+
import { type CreateProductSchema } from "../schema/create-product.schema";
|
|
6507
|
+
import { createProductService } from "../services/create-product.service";
|
|
6508
|
+
|
|
6509
|
+
export const createProductController: GuardedRequestHandler<CreateProductSchema> = async ({
|
|
6485
6510
|
request,
|
|
6486
6511
|
response,
|
|
6487
|
-
) => {
|
|
6512
|
+
}) => {
|
|
6488
6513
|
const product = await createProductService(request.validated());
|
|
6489
6514
|
return response.success({
|
|
6490
6515
|
message: request.t("products.created"),
|
|
@@ -7301,10 +7326,10 @@ export async function listFaqsService(filters: FaqListOptions) {
|
|
|
7301
7326
|
```
|
|
7302
7327
|
|
|
7303
7328
|
```ts title="src/app/faqs/controllers/list-faqs.controller.ts"
|
|
7304
|
-
import
|
|
7329
|
+
import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
|
|
7305
7330
|
import { listFaqsService } from "../services/list-faqs.service";
|
|
7306
7331
|
|
|
7307
|
-
export const listFaqsController:
|
|
7332
|
+
export const listFaqsController: GuardedRequestHandler = async ({ request, response }) => {
|
|
7308
7333
|
const { data, pagination } = await listFaqsService({
|
|
7309
7334
|
...request.all(),
|
|
7310
7335
|
organization_id: request.user.organizationId,
|
|
@@ -7489,7 +7514,7 @@ public locals: RequestLocals = {};
|
|
|
7489
7514
|
```ts title="src/app/observability/middleware/request-timing.middleware.ts"
|
|
7490
7515
|
import type { Middleware } from "@warlock.js/core";
|
|
7491
7516
|
|
|
7492
|
-
export const requestTimingMiddleware: Middleware = request => {
|
|
7517
|
+
export const requestTimingMiddleware: Middleware = (request) => {
|
|
7493
7518
|
request.startedAt = Date.now();
|
|
7494
7519
|
};
|
|
7495
7520
|
```
|
|
@@ -7497,7 +7522,7 @@ export const requestTimingMiddleware: Middleware = request => {
|
|
|
7497
7522
|
```ts title="src/app/observability/controllers/timing.controller.ts"
|
|
7498
7523
|
import type { RequestHandler } from "@warlock.js/core";
|
|
7499
7524
|
|
|
7500
|
-
export const timingController: RequestHandler = async (request, response) => {
|
|
7525
|
+
export const timingController: RequestHandler = async ({ request, response }) => {
|
|
7501
7526
|
return response.success({ elapsedMs: Date.now() - request.startedAt });
|
|
7502
7527
|
};
|
|
7503
7528
|
```
|
|
@@ -7582,7 +7607,7 @@ Feature-local files such as `src/app/organizations/request-locals.d.ts` are equa
|
|
|
7582
7607
|
|
|
7583
7608
|
Two rules the scaffold's own comments spell out, and both bite silently:
|
|
7584
7609
|
|
|
7585
|
-
- **Keep the trailing `export {}`.** `declare module "x"` inside a file with no top-level import or export declares an
|
|
7610
|
+
- **Keep the trailing `export {}`.** `declare module "x"` inside a file with no top-level import or export declares an _ambient_ module, which REPLACES `@warlock.js/core`'s real typings instead of merging into them — every framework export vanishes. The `export {}` is what makes the file a module and the block an augmentation. It is not an unused statement to clean up.
|
|
7586
7611
|
- **Keep them `interface`, not `type`.** This project otherwise prefers `type`; these are the named exception, because declaration merging is interface-only. `type RequestUser = { ... }` is a duplicate-identifier error, not an augmentation.
|
|
7587
7612
|
|
|
7588
7613
|
On a project scaffolded before 5.1 there is no `src/typings.d.ts`, and `tsconfig.json` carries `"typeRoots": ["./src/typings.d.ts"]` — wrong twice, since `typeRoots` takes directories of `@types` packages rather than files, and that file did not exist. Drop the `typeRoots` entry, create the file, and list it under `include`.
|
|
@@ -7656,10 +7681,10 @@ import type { Request, RequestHandler } from "@warlock.js/core";
|
|
|
7656
7681
|
import { type CreateProductSchema, createProductSchema } from "../schema/create-product.schema";
|
|
7657
7682
|
import { createProductService } from "../services/create-product.service";
|
|
7658
7683
|
|
|
7659
|
-
export const createProductController: RequestHandler<Request<CreateProductSchema>> = async (
|
|
7684
|
+
export const createProductController: RequestHandler<Request<CreateProductSchema>> = async ({
|
|
7660
7685
|
request,
|
|
7661
7686
|
response,
|
|
7662
|
-
) => {
|
|
7687
|
+
}) => {
|
|
7663
7688
|
const product = await createProductService(request.validated());
|
|
7664
7689
|
|
|
7665
7690
|
return response.successCreate({ product });
|
|
@@ -7969,11 +7994,11 @@ This skill is the foundation. Every other warlock skill (`register-route`, `crea
|
|
|
7969
7994
|
```
|
|
7970
7995
|
|
|
7971
7996
|
```ts title="src/app/<module>/controllers/create-<thing>.controller.ts"
|
|
7972
|
-
import { type GuardedRequestHandler } from "app/auth/
|
|
7997
|
+
import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
|
|
7973
7998
|
import { type CreateThingSchema, createThingSchema } from "../schema/create-thing.schema";
|
|
7974
7999
|
import { createThingService } from "../services/create-thing.service";
|
|
7975
8000
|
|
|
7976
|
-
export const createThingController: GuardedRequestHandler<CreateThingSchema> = async (request, response) => {
|
|
8001
|
+
export const createThingController: GuardedRequestHandler<CreateThingSchema> = async ({ request, response }) => {
|
|
7977
8002
|
const thing = await createThingService(request.validated());
|
|
7978
8003
|
return response.success({ thing });
|
|
7979
8004
|
};
|
|
@@ -8459,10 +8484,10 @@ export async function notifyUserService(user: User, payload: unknown) {
|
|
|
8459
8484
|
Then from a controller:
|
|
8460
8485
|
|
|
8461
8486
|
```ts
|
|
8462
|
-
import type { GuardedRequestHandler } from "app/auth/
|
|
8487
|
+
import type { GuardedRequestHandler } from "app/auth/requests/guarded.request";
|
|
8463
8488
|
import { notifyUserService } from "../services/notify-user.service";
|
|
8464
8489
|
|
|
8465
|
-
export const sendNotificationController: GuardedRequestHandler = async (request, response) => {
|
|
8490
|
+
export const sendNotificationController: GuardedRequestHandler = async ({ request, response }) => {
|
|
8466
8491
|
await notifyUserService(request.user, request.input("payload"));
|
|
8467
8492
|
return response.success({ delivered: true });
|
|
8468
8493
|
};
|
|
@@ -8624,7 +8649,7 @@ CLI commands, scheduled jobs, queue workers — anything running outside an HTTP
|
|
|
8624
8649
|
|
|
8625
8650
|
---
|
|
8626
8651
|
name: write-cli-command
|
|
8627
|
-
description: 'Author a custom `warlock <my-cmd>` command via the `command()` factory — name, description, action, options, preload, then register in `warlock.config.ts > cli.commands` or drop in `src/app/<module>/commands/`. Triggers: `command`, `CLICommand`, `CLICommandPreload`, `CLICommandOption`, `preload`, `preAction`, `persistent`, `colors`; "write a custom warlock command", "one-off maintenance task", "ship a CLI from a package", "framework built-in commands"; typical import `import { command } from "@warlock.js/core"`. Skip: framework dev/build/start — `@warlock.js/core/run-app/SKILL.md`; warlock.config.ts wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `commander`, `yargs`, `oclif`.'
|
|
8652
|
+
description: 'Author a custom `warlock <my-cmd>` command via the `command()` factory — name, description, action, options, preload, then register in `warlock.config.ts > cli.commands` or drop in `src/app/<module>/commands/`. Also covers built-in `warlock add` feature scaffolding, including the Web starter and `index.register.ts`. Triggers: `command`, `CLICommand`, `CLICommandPreload`, `CLICommandOption`, `preload`, `preAction`, `persistent`, `colors`, `warlock add`, `index.register.ts`; "write a custom warlock command", "one-off maintenance task", "ship a CLI from a package", "framework built-in commands"; typical import `import { command } from "@warlock.js/core"`. Skip: framework dev/build/start — `@warlock.js/core/run-app/SKILL.md`; warlock.config.ts wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `commander`, `yargs`, `oclif`.'
|
|
8628
8653
|
---
|
|
8629
8654
|
|
|
8630
8655
|
# Warlock — write a CLI command
|
|
@@ -8829,6 +8854,7 @@ warlock generate (alias: g) — interactive picker
|
|
|
8829
8854
|
| Feature | Installs |
|
|
8830
8855
|
| ------------- | --------------------------------------------------------------------------------------------------- |
|
|
8831
8856
|
| `react-email` | `react-email` + `@react-email/components` + `@react-email/render` + `@react-email/tailwind`; drops a `welcome-email.tsx` sample; patches `tsconfig.json` |
|
|
8857
|
+
| `web` | Warlock Web + React stack; scaffolds the application page, localization, contact endpoint, and Web configuration |
|
|
8832
8858
|
| `react` | `react` + `react-dom` + types |
|
|
8833
8859
|
| `image` | `sharp` (for the `Image` class) |
|
|
8834
8860
|
| `mail` | `nodemailer` + types |
|
|
@@ -8846,6 +8872,8 @@ warlock generate (alias: g) — interactive picker
|
|
|
8846
8872
|
|
|
8847
8873
|
Run `warlock add --list` to see what's currently registered. Pass `--packageManager <yarn\|pnpm\|npm>` to override auto-detection (defaults to whichever lockfile is present).
|
|
8848
8874
|
|
|
8875
|
+
The Web starter keeps the page's universal non-component setup in `src/web/index.register.ts`, while `src/web/index.page.tsx` re-exports `register`. Warlock still discovers and runs the hook from the page namespace in both SSR and the browser, but editing the page component remains inside React's Fast Refresh boundary. Do not move the `register()` declaration inline beside the component: React Refresh treats that non-component export as incompatible and reloads the page, losing component state.
|
|
8876
|
+
|
|
8849
8877
|
### Misc
|
|
8850
8878
|
|
|
8851
8879
|
| Command | Purpose |
|
|
@@ -8935,7 +8963,7 @@ export default command({
|
|
|
8935
8963
|
|
|
8936
8964
|
---
|
|
8937
8965
|
name: write-middleware
|
|
8938
|
-
description: 'Author HTTP middleware for @warlock.js/core — the `(request, response)` signature, short-circuit by returning a response, enrich the request with extra fields, register per-route, per-group, or app-wide. Triggers: `Middleware`, `MiddlewareResponse`, `router.group`, `guarded`, `request.detectIp`, `authMiddleware`; "write a custom middleware", "short-circuit a request", "enrich the request with extra fields", "per-route vs per-group middleware"; typical import `import type { Middleware } from "@warlock.js/core"`. Skip: built-in middleware catalog — `@warlock.js/core/use-middleware/SKILL.md`; route attachment — `@warlock.js/core/register-route/SKILL.md`; response helpers — `@warlock.js/core/send-response/SKILL.md`; competing patterns: `express` `(req, res, next)` middleware, Fastify `preHandler` hooks.'
|
|
8966
|
+
description: 'Author HTTP middleware for @warlock.js/core — the `({ request, response })` signature, short-circuit by returning a response, enrich the request with extra fields, register per-route, per-group, or app-wide. Triggers: `Middleware`, `MiddlewareResponse`, `router.group`, `guarded`, `request.detectIp`, `authMiddleware`; "write a custom middleware", "short-circuit a request", "enrich the request with extra fields", "per-route vs per-group middleware"; typical import `import type { Middleware } from "@warlock.js/core"`. Skip: built-in middleware catalog — `@warlock.js/core/use-middleware/SKILL.md`; route attachment — `@warlock.js/core/register-route/SKILL.md`; response helpers — `@warlock.js/core/send-response/SKILL.md`; competing patterns: `express` `(req, res, next)` middleware, Fastify `preHandler` hooks.'
|
|
8939
8967
|
---
|
|
8940
8968
|
|
|
8941
8969
|
# Warlock — write a middleware
|
|
@@ -8947,7 +8975,7 @@ Middleware is a plain function that runs before the controller. Two outcomes: re
|
|
|
8947
8975
|
```ts title="src/app/<module>/utils/<name>.middleware.ts"
|
|
8948
8976
|
import type { Middleware } from "@warlock.js/core";
|
|
8949
8977
|
|
|
8950
|
-
export const requireApiKey: Middleware = (request, response) => {
|
|
8978
|
+
export const requireApiKey: Middleware = ({ request, response }) => {
|
|
8951
8979
|
const key = request.header("X-API-Key");
|
|
8952
8980
|
|
|
8953
8981
|
if (!key || key !== process.env.API_KEY) {
|
|
@@ -8958,16 +8986,14 @@ export const requireApiKey: Middleware = (request, response) => {
|
|
|
8958
8986
|
};
|
|
8959
8987
|
```
|
|
8960
8988
|
|
|
8961
|
-
That's the contract: `(
|
|
8989
|
+
That's the contract: `(context: HttpContext<Request>) => Response | undefined | void`, where `context` is `{ request, response }`. Async is fine — return a `Promise<Response | undefined | void>`.
|
|
8962
8990
|
|
|
8963
8991
|
The real type, from `@warlock.js/core/src/router/types.ts`:
|
|
8964
8992
|
|
|
8965
8993
|
```ts
|
|
8966
8994
|
export type Middleware<MiddlewareRequest extends Request = Request> = {
|
|
8967
|
-
(
|
|
8995
|
+
(context: HttpContext<MiddlewareRequest>): MiddlewareResponse;
|
|
8968
8996
|
};
|
|
8969
|
-
|
|
8970
|
-
export type MiddlewareResponse = ReturnedResponse | undefined | void;
|
|
8971
8997
|
```
|
|
8972
8998
|
|
|
8973
8999
|
## Short-circuit vs continue
|
|
@@ -8977,7 +9003,7 @@ The pattern is "return a response to stop, return nothing to continue":
|
|
|
8977
9003
|
```ts
|
|
8978
9004
|
import type { Middleware } from "@warlock.js/core";
|
|
8979
9005
|
|
|
8980
|
-
export const requireFeatureFlag: Middleware = async (request, response) => {
|
|
9006
|
+
export const requireFeatureFlag: Middleware = async ({ request, response }) => {
|
|
8981
9007
|
const flag = await loadFeatureFlag(request.input("organization_id"));
|
|
8982
9008
|
|
|
8983
9009
|
if (!flag.enabled) {
|
|
@@ -8995,8 +9021,8 @@ If you short-circuit, the controller never runs. The response helper you pick (`
|
|
|
8995
9021
|
You can attach arbitrary fields to `request` from a middleware, and they survive into the controller. The cleanest pattern is to extend `Request` via module augmentation in a `.d.ts` and assign in the middleware:
|
|
8996
9022
|
|
|
8997
9023
|
```ts title="src/app/feature-flags/middleware/load-feature-flag.middleware.ts"
|
|
8998
|
-
import type { Middleware } from "@warlock.js/core";
|
|
8999
|
-
import
|
|
9024
|
+
import type { Middleware, Request, RequestUser } from "@warlock.js/core";
|
|
9025
|
+
import { FeatureFlag } from "../models/feature-flag";
|
|
9000
9026
|
|
|
9001
9027
|
declare module "@warlock.js/core" {
|
|
9002
9028
|
interface Request {
|
|
@@ -9004,7 +9030,7 @@ declare module "@warlock.js/core" {
|
|
|
9004
9030
|
}
|
|
9005
9031
|
}
|
|
9006
9032
|
|
|
9007
|
-
export const loadFeatureFlag: Middleware = async (request) => {
|
|
9033
|
+
export const loadFeatureFlag: Middleware<Request & { user: RequestUser }> = async ({ request }) => {
|
|
9008
9034
|
request.featureFlag = await FeatureFlag.findBy("organization_id", request.user.organizationId);
|
|
9009
9035
|
};
|
|
9010
9036
|
```
|
|
@@ -9074,7 +9100,7 @@ export function guarded(callback: () => void) {
|
|
|
9074
9100
|
}
|
|
9075
9101
|
|
|
9076
9102
|
export function guardedAdmin(callback: () => void) {
|
|
9077
|
-
router.group({ prefix: "/admin", middleware: [authMiddleware()] }, callback);
|
|
9103
|
+
router.group({ prefix: "/admin", middleware: [authMiddleware("admin")] }, callback);
|
|
9078
9104
|
}
|
|
9079
9105
|
|
|
9080
9106
|
export function publicRoutes(callback: () => void) {
|
|
@@ -9092,7 +9118,7 @@ guarded(() => {
|
|
|
9092
9118
|
});
|
|
9093
9119
|
```
|
|
9094
9120
|
|
|
9095
|
-
`authMiddleware(allowedUserType?)`
|
|
9121
|
+
`authMiddleware(allowedUserType, tokenFrom?)` requires a user-type string or array — `[]` accepts any authenticated user without checking `userType`; `"user"` / `"admin"` also checks the decoded `userType` matches.
|
|
9096
9122
|
|
|
9097
9123
|
## Common patterns
|
|
9098
9124
|
|
|
@@ -9119,13 +9145,13 @@ router.group(
|
|
|
9119
9145
|
```ts
|
|
9120
9146
|
import type { Middleware } from "@warlock.js/core";
|
|
9121
9147
|
|
|
9122
|
-
export const optionalAuth: Middleware = async (request, response) => {
|
|
9148
|
+
export const optionalAuth: Middleware = async ({ request, response }) => {
|
|
9123
9149
|
if (!request.authorizationValue) {
|
|
9124
9150
|
return; // anonymous — let it through
|
|
9125
9151
|
}
|
|
9126
9152
|
|
|
9127
9153
|
// token present → enforce it
|
|
9128
|
-
return authMiddleware("user")(request, response);
|
|
9154
|
+
return authMiddleware("user")({ request, response });
|
|
9129
9155
|
};
|
|
9130
9156
|
```
|
|
9131
9157
|
|
|
@@ -9141,7 +9167,7 @@ export const optionalAuth: Middleware = async (request, response) => {
|
|
|
9141
9167
|
- **Group middleware runs before per-route middleware**, in array order. The full chain is `app.all → group → per-route → controller`. Mind the order if you stack auth + rate-limit + audit.
|
|
9142
9168
|
- **Middleware can be async.** Returning `Promise<undefined>` continues the chain. Returning `Promise<Response>` short-circuits. The framework awaits the result.
|
|
9143
9169
|
- **Don't mutate `request.payload` directly.** Use `request.setValidatedData(...)` or attach a new named field (`request.featureFlag = ...`). The internals expect `payload.all` shapes to come from the validator pipeline.
|
|
9144
|
-
- **`Middleware` is generic over the request type.** For middleware that assumes a validated schema, narrow it: `const m: Middleware<CreateProductRequest> = (request) => { ... }`. But most middlewares run before validation, so the default `Middleware` is right.
|
|
9170
|
+
- **`Middleware` is generic over the request type.** For middleware that assumes a validated schema, narrow it: `const m: Middleware<CreateProductRequest> = ({ request }) => { ... }`. But most middlewares run before validation, so the default `Middleware` is right.
|
|
9145
9171
|
- **No `next()` parameter.** Express-style `next()` doesn't apply here. The framework chains based on return value.
|
|
9146
9172
|
- **`request.baseRequest` / `response.baseResponse` are escape hatches, not API.** They expose the underlying Fastify primitives for cases the framework hasn't covered yet (streaming was the historical precedent). Prefer framework helpers first — `response.send()`, `response.header()`, `response.replay()`, `request.input()`, `request.detectIp()`, etc. If you find yourself reaching for `baseResponse` or `baseRequest` for non-streaming work, that's a missing helper — file an issue. The cache and idempotency middlewares both shipped with a quietly-broken FastifyReply-return bug because they bypassed the helper layer; the framework now guards `Response.send()` against double-send, but the right answer is "use the helper."
|
|
9147
9173
|
- **Behind any proxy, use `request.detectIp()` not `request.ip`.** `request.ip` is the immediate peer (likely your load balancer); `request.detectIp()` honors `X-Real-IP` / `X-Forwarded-For`. Either way, only trust the result as far as you trust the upstream chain — those headers are client-settable; verify the request came through your trusted edge before treating the value as authoritative.
|