@warlock.js/core 5.2.4 → 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.
Files changed (38) hide show
  1. package/esm/cli/commands/generate/templates/stubs.mjs +13 -14
  2. package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
  3. package/esm/generations/stubs.mjs +13 -25
  4. package/esm/generations/stubs.mjs.map +1 -1
  5. package/esm/http/index.d.mts +1 -1
  6. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  7. package/esm/http/middleware/inject-request-context.mjs +2 -21
  8. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  9. package/esm/http/request.d.mts +25 -6
  10. package/esm/http/request.d.mts.map +1 -1
  11. package/esm/http/request.mjs +48 -0
  12. package/esm/http/request.mjs.map +1 -1
  13. package/esm/http/response.d.mts +3 -3
  14. package/esm/http/response.d.mts.map +1 -1
  15. package/esm/http/types.d.mts +36 -5
  16. package/esm/http/types.d.mts.map +1 -1
  17. package/esm/index.d.mts +2 -2
  18. package/esm/validation/plugins/file-plugin.mjs.map +1 -1
  19. package/esm/validation/plugins/localized-plugin.mjs +2 -2
  20. package/esm/validation/plugins/localized-plugin.mjs.map +1 -1
  21. package/esm/validation/types.d.mts +17 -4
  22. package/esm/validation/types.d.mts.map +1 -1
  23. package/llms-full.txt +109 -86
  24. package/llms.txt +1 -1
  25. package/package.json +12 -12
  26. package/skills/README.md +1 -1
  27. package/skills/create-controller/SKILL.md +9 -9
  28. package/skills/send-response/SKILL.md +51 -37
  29. package/skills/store-file/SKILL.md +8 -3
  30. package/skills/upload-file/SKILL.md +9 -7
  31. package/skills/use-app-context/SKILL.md +2 -2
  32. package/skills/use-localization/SKILL.md +6 -2
  33. package/skills/use-repository/SKILL.md +2 -2
  34. package/skills/use-request-locals/SKILL.md +3 -3
  35. package/skills/validate-input/SKILL.md +2 -2
  36. package/skills/warlock-conventions/SKILL.md +2 -2
  37. package/skills/wire-socket/SKILL.md +2 -2
  38. package/skills/write-middleware/SKILL.md +13 -15
@@ -11,7 +11,7 @@ function controllerStub(name, options = {}) {
11
11
  const { withValidation } = options;
12
12
  if (!withValidation) return `import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
13
13
 
14
- export const ${name.camel}Controller: GuardedRequestHandler = async (request, response) => {
14
+ export const ${name.camel}Controller: GuardedRequestHandler = async ({ response }) => {
15
15
  // TODO: Implement controller logic
16
16
  return response.success({});
17
17
  };
@@ -19,10 +19,9 @@ export const ${name.camel}Controller: GuardedRequestHandler = async (request, re
19
19
  return `import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
20
20
  import { type ${name.pascal}Schema, ${name.camel}Schema } from "../schema/${name.kebab}.schema";
21
21
 
22
- export const ${name.camel}Controller: GuardedRequestHandler<${name.pascal}Schema> = async (
23
- request,
22
+ export const ${name.camel}Controller: GuardedRequestHandler<${name.pascal}Schema> = async ({
24
23
  response,
25
- ) => {
24
+ }) => {
26
25
  // TODO: Implement controller logic
27
26
  return response.success({});
28
27
  };
@@ -41,10 +40,10 @@ function crudCreateControllerStub(moduleName) {
41
40
  import { type Create${moduleName.pascal}Schema, create${moduleName.pascal}Schema } from "../schema/create-${moduleName.kebab}.schema";
42
41
  import { create${moduleName.pascal}Service } from "../services/create-${moduleName.kebab}.service";
43
42
 
44
- export const create${moduleName.pascal}Controller: GuardedRequestHandler<Create${moduleName.pascal}Schema> = async (
43
+ export const create${moduleName.pascal}Controller: GuardedRequestHandler<Create${moduleName.pascal}Schema> = async ({
45
44
  request,
46
45
  response,
47
- ) => {
46
+ }) => {
48
47
  const ${moduleName.camel} = await create${moduleName.pascal}Service(request.validated());
49
48
 
50
49
  return response.successCreate({
@@ -65,10 +64,10 @@ function crudUpdateControllerStub(moduleName) {
65
64
  import { type Update${moduleName.pascal}Schema, update${moduleName.pascal}Schema } from "../schema/update-${moduleName.kebab}.schema";
66
65
  import { update${moduleName.pascal}Service } from "../services/update-${moduleName.kebab}.service";
67
66
 
68
- export const update${moduleName.pascal}Controller: GuardedRequestHandler<Update${moduleName.pascal}Schema> = async (
67
+ export const update${moduleName.pascal}Controller: GuardedRequestHandler<Update${moduleName.pascal}Schema> = async ({
69
68
  request,
70
69
  response,
71
- ) => {
70
+ }) => {
72
71
  const ${moduleName.camel} = await update${moduleName.pascal}Service(request.input("id"), request.validated());
73
72
 
74
73
  return response.success({
@@ -89,10 +88,10 @@ function crudListControllerStub(moduleName) {
89
88
  return `import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
90
89
  import { list${plural.pascal}Service } from "../services/list-${plural.kebab}.service";
91
90
 
92
- export const list${plural.pascal}Controller: GuardedRequestHandler = async (
91
+ export const list${plural.pascal}Controller: GuardedRequestHandler = async ({
93
92
  request,
94
93
  response,
95
- ) => {
94
+ }) => {
96
95
  const { data, pagination } = await list${plural.pascal}Service(request.all());
97
96
 
98
97
  return response.success({
@@ -109,10 +108,10 @@ function crudShowControllerStub(moduleName) {
109
108
  return `import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
110
109
  import { get${moduleName.pascal}Service } from "../services/get-${moduleName.kebab}.service";
111
110
 
112
- export const get${moduleName.pascal}Controller: GuardedRequestHandler = async (
111
+ export const get${moduleName.pascal}Controller: GuardedRequestHandler = async ({
113
112
  request,
114
113
  response,
115
- ) => {
114
+ }) => {
116
115
  const ${moduleName.camel} = await get${moduleName.pascal}Service(request.input("id"));
117
116
 
118
117
  if (!${moduleName.camel}) {
@@ -132,10 +131,10 @@ function crudDeleteControllerStub(moduleName) {
132
131
  return `import { type GuardedRequestHandler } from "app/auth/requests/guarded.request";
133
132
  import { delete${moduleName.pascal}Service } from "../services/delete-${moduleName.kebab}.service";
134
133
 
135
- export const delete${moduleName.pascal}Controller: GuardedRequestHandler = async (
134
+ export const delete${moduleName.pascal}Controller: GuardedRequestHandler = async ({
136
135
  request,
137
136
  response,
138
- ) => {
137
+ }) => {
139
138
  await delete${moduleName.pascal}Service(request.input("id"));
140
139
 
141
140
  return response.noContent();
@@ -1 +1 @@
1
- {"version":3,"file":"stubs.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/templates/stubs.ts"],"sourcesContent":["import type { ParsedName } from \"../types\";\nimport { Name } from \"../utils/name-parser\";\n\n/**\n * Controller template stub\n *\n * Controllers default to the guarded handler type since application\n * routes run behind `guarded()`. When `withValidation` is set, the\n * schema's exported type + value are imported directly from the\n * `schema/` folder and bound to the handler — no `requests/` alias.\n */\nexport function controllerStub(\n name: ParsedName,\n options: { withValidation?: boolean } = {},\n): string {\n const { withValidation } = options;\n\n if (!withValidation) {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\n\nexport const ${name.camel}Controller: GuardedRequestHandler = async (request, response) => {\n // TODO: Implement controller logic\n return response.success({});\n};\n`;\n }\n\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type ${name.pascal}Schema, ${name.camel}Schema } from \"../schema/${name.kebab}.schema\";\n\nexport const ${name.camel}Controller: GuardedRequestHandler<${name.pascal}Schema> = async (\n request,\n response,\n) => {\n // TODO: Implement controller logic\n return response.success({});\n};\n\n${name.camel}Controller.validation = {\n schema: ${name.camel}Schema,\n};\n`;\n}\n\n/**\n * CRUD Create Controller template\n * Note: moduleName is the module (plural), we need singular entity name\n */\nexport function crudCreateControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type Create${moduleName.pascal}Schema, create${moduleName.pascal}Schema } from \"../schema/create-${moduleName.kebab}.schema\";\nimport { create${moduleName.pascal}Service } from \"../services/create-${moduleName.kebab}.service\";\n\nexport const create${moduleName.pascal}Controller: GuardedRequestHandler<Create${moduleName.pascal}Schema> = async (\n request,\n response,\n) => {\n const ${moduleName.camel} = await create${moduleName.pascal}Service(request.validated());\n\n return response.successCreate({\n ${moduleName.camel},\n });\n};\n\ncreate${moduleName.pascal}Controller.validation = {\n schema: create${moduleName.pascal}Schema,\n};\n`;\n}\n\n/**\n * CRUD Update Controller template\n */\nexport function crudUpdateControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type Update${moduleName.pascal}Schema, update${moduleName.pascal}Schema } from \"../schema/update-${moduleName.kebab}.schema\";\nimport { update${moduleName.pascal}Service } from \"../services/update-${moduleName.kebab}.service\";\n\nexport const update${moduleName.pascal}Controller: GuardedRequestHandler<Update${moduleName.pascal}Schema> = async (\n request,\n response,\n) => {\n const ${moduleName.camel} = await update${moduleName.pascal}Service(request.input(\"id\"), request.validated());\n\n return response.success({\n ${moduleName.camel},\n });\n};\n\nupdate${moduleName.pascal}Controller.validation = {\n schema: update${moduleName.pascal}Schema,\n};\n`;\n}\n\n/**\n * CRUD List Controller template\n */\nexport function crudListControllerStub(moduleName: Name): string {\n const plural = moduleName.plural;\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { list${plural.pascal}Service } from \"../services/list-${plural.kebab}.service\";\n\nexport const list${plural.pascal}Controller: GuardedRequestHandler = async (\n request,\n response,\n) => {\n const { data, pagination } = await list${plural.pascal}Service(request.all());\n\n return response.success({\n data,\n pagination,\n });\n};\n`;\n}\n\n/**\n * CRUD Show/Get Controller template\n */\nexport function crudShowControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { get${moduleName.pascal}Service } from \"../services/get-${moduleName.kebab}.service\";\n\nexport const get${moduleName.pascal}Controller: GuardedRequestHandler = async (\n request,\n response,\n) => {\n const ${moduleName.camel} = await get${moduleName.pascal}Service(request.input(\"id\"));\n\n if (!${moduleName.camel}) {\n return response.notFound();\n }\n\n return response.success({\n ${moduleName.camel},\n });\n};\n`;\n}\n\n/**\n * CRUD Delete Controller template\n */\nexport function crudDeleteControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { delete${moduleName.pascal}Service } from \"../services/delete-${moduleName.kebab}.service\";\n\nexport const delete${moduleName.pascal}Controller: GuardedRequestHandler = async (\n request,\n response,\n) => {\n await delete${moduleName.pascal}Service(request.input(\"id\"));\n\n return response.noContent();\n};\n`;\n}\n\n/**\n * CRUD Routes template\n */\nexport function crudRoutesStub(moduleName: Name): string {\n const singular = moduleName.singular;\n const plural = moduleName.plural;\n\n return `import { router } from \"@warlock.js/core\";\nimport { guarded } from \"app/shared/utils/router\";\nimport { create${singular.pascal}Controller } from \"./controllers/create-${singular.kebab}.controller\";\nimport { delete${singular.pascal}Controller } from \"./controllers/delete-${singular.kebab}.controller\";\nimport { get${singular.pascal}Controller } from \"./controllers/get-${singular.kebab}.controller\";\nimport { list${plural.pascal}Controller } from \"./controllers/list-${plural.kebab}.controller\";\nimport { update${singular.pascal}Controller } from \"./controllers/update-${singular.kebab}.controller\";\n\nguarded(() => {\n router\n .route(\"/${plural.kebab}\")\n .list(list${plural.pascal}Controller)\n .show(get${singular.pascal}Controller)\n .create(create${singular.pascal}Controller)\n .update(update${singular.pascal}Controller)\n .destroy(delete${singular.pascal}Controller);\n});\n`;\n}\n\n/**\n * CRUD Model template\n */\nexport function crudModelStub(moduleName: Name): string {\n const singular = moduleName.singular;\n const plural = moduleName.plural;\n\n return `import { Model, RegisterModel } from \"@warlock.js/cascade\";\nimport { type Infer, v } from \"@warlock.js/seal\";\nimport { ${singular.pascal}Resource } from \"app/${plural.kebab}/resources/${singular.kebab}.resource\";\n\nexport const ${singular.camel}Schema = v.object({\n // TODO: Add more fields\n});\n\nexport type ${singular.pascal}Schema = Infer.Output<typeof ${singular.camel}Schema>;\n\n@RegisterModel()\nexport class ${singular.pascal} extends Model<${singular.pascal}Schema> {\n public static table = \"${plural.snake}\";\n\n public static schema = ${singular.camel}Schema;\n\n public static relations = {};\n\n public static resource = ${singular.pascal}Resource;\n}\n`;\n}\n\n/**\n * CRUD Resource template\n */\nexport function crudResourceStub(moduleName: Name): string {\n // Get singular entity name\n const entity = moduleName.singular;\n\n return `import { defineResource } from \"@warlock.js/core\";\n\nexport const ${entity.pascal}Resource = defineResource({\n schema: {\n id: \"number\",\n // TODO: Add more resource fields\n },\n});\n`;\n}\n\n/**\n * CRUD Repository template\n */\nexport function crudRepositoryStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n const modulePluralName = entity.plural;\n return `import type { FilterRules, TypedRepositoryOptions } from \"@warlock.js/core\";\nimport { RepositoryManager } from \"@warlock.js/core\";\nimport { ${moduleSingularName.pascal} } from \"../models/${moduleSingularName.kebab}\";\n\ntype ${moduleSingularName.pascal}ListFilter = {\n // Repository list filters\n};\n\nexport type ${moduleSingularName.pascal}ListOptions = TypedRepositoryOptions<${moduleSingularName.pascal}ListFilter>;\n\nclass ${modulePluralName.pascal}Repository extends RepositoryManager<${moduleSingularName.pascal}, ${moduleSingularName.pascal}ListOptions> {\n public source = ${moduleSingularName.pascal};\n\n public simpleSelectColumns: string[] = [\"id\"];\n\n public filterBy: FilterRules = {\n id: \"=\",\n };\n\n public defaultOptions: RepositoryOptions = {\n orderBy: {\n id: \"desc\",\n },\n };\n}\n\nexport const ${modulePluralName.camel}Repository = new ${modulePluralName.pascal}Repository();\n`;\n}\n\n/**\n * CRUD Create Service template\n */\nexport function crudCreateServiceStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n return `import { ${moduleSingularName.pascal} } from \"../models/${moduleSingularName.kebab}\";\nimport type { Create${moduleSingularName.pascal}Schema } from \"../schema/create-${moduleSingularName.kebab}.schema\";\n\nexport async function create${moduleSingularName.pascal}Service(data: Create${moduleSingularName.pascal}Schema) {\n const ${moduleSingularName.camel} = await ${moduleSingularName.pascal}.create(data);\n return ${moduleSingularName.camel};\n}\n`;\n}\n\n/**\n * CRUD Update Service template\n */\nexport function crudUpdateServiceStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n return `import { ResourceNotFoundError } from \"@warlock.js/core\";\nimport { get${moduleSingularName.pascal}Service } from \"./get-${moduleSingularName.kebab}.service\";\nimport type { Update${moduleSingularName.pascal}Schema } from \"../schema/update-${moduleSingularName.kebab}.schema\";\n\nexport async function update${moduleSingularName.pascal}Service(id: number | string, data: Update${moduleSingularName.pascal}Schema) {\n const ${moduleSingularName.camel} = await get${moduleSingularName.pascal}Service(id);\n\n await ${moduleSingularName.camel}.save({ merge: data });\n return ${moduleSingularName.camel};\n}\n`;\n}\n\n/**\n * CRUD List Service template\n */\nexport function crudListServiceStub(entity: Name): string {\n const modulePluralName = entity.plural;\n return `import { ${modulePluralName.camel}Repository } from \"../repositories/${modulePluralName.kebab}.repository\";\n\nexport async function list${modulePluralName.pascal}Service(filters: any) {\n return ${modulePluralName.camel}Repository.listCached(filters);\n}\n`;\n}\n\n/**\n * CRUD Get Service template\n */\nexport function crudGetServiceStub(entity: Name): string {\n return `import { ${entity.plural.camel}Repository } from \"../repositories/${entity.plural.kebab}.repository\";\nimport { ResourceNotFoundError } from \"@warlock.js/core\";\n\nexport async function get${entity.singular.pascal}Service(id: number | string) {\n const ${entity.singular.camel} = await ${entity.plural.camel}Repository.getCached(id);\n\n if (!${entity.singular.camel}) {\n throw new ResourceNotFoundError(\"${entity.singular.pascal} resource not found!\");\n }\n\n return ${entity.singular.camel};\n}\n`;\n}\n\n/**\n * CRUD Delete Service template\n */\nexport function crudDeleteServiceStub(entity: Name): string {\n const singular = entity.singular;\n return `import { ResourceNotFoundError } from \"@warlock.js/core\";\nimport { get${singular.pascal}Service } from \"./get-${singular.kebab}.service\";\n\nexport async function delete${singular.pascal}Service(id: number | string) {\n const ${singular.camel} = await get${singular.pascal}Service(id);\n if (!${singular.camel}) {\n throw new ResourceNotFoundError(\"${singular.pascal} not found\");\n }\n await ${singular.camel}.destroy();\n}\n`;\n}\n\n/**\n * CRUD Seed template\n */\nexport function crudSeedStub(entity: Name): string {\n return `import { seeder } from \"@warlock.js/core\";\nimport { ${entity.singular.pascal} } from \"../models/${entity.singular.kebab}\";\n\nexport default seeder({\n name: \"Seed ${entity.plural.pascal}\",\n once: true,\n enabled: true,\n run: async ({ track }) => {\n const total = 10;\n for (let i = 0; i < total; i++) {\n track(\n await ${entity.singular.pascal}.create({\n // TODO: Add more fields\n }),\n );\n }\n },\n});\n`;\n}\n\n/**\n * Migration template\n */\n/**\n * Migration Create template\n */\nexport function migrationStub(\n entityName: ParsedName,\n options: {\n columns?: string;\n imports?: string[];\n timestamps?: boolean;\n tableName?: string;\n } = {},\n): string {\n const { columns = \"\", imports = [], timestamps = true } = options;\n\n const allImports = [\"Migration\", ...imports].join(\", \");\n\n let optionsString = \"\";\n if (timestamps === false) {\n optionsString = `, { timestamps: false }`;\n }\n\n return `import { ${allImports} } from \"@warlock.js/cascade\";\nimport { ${entityName.pascal} } from \"../${entityName.kebab}.model\";\n\nexport default Migration.create(${entityName.pascal}, {\n${columns ? columns : \" // add your columns here, id is auto added to the list\"}\n}${optionsString});\n`;\n}\n\n/**\n * Migration Alter template\n */\nexport function migrationAlterStub(\n entityName: ParsedName,\n options: {\n add?: string;\n drop?: string; // stringified array like `\"[\\\"col1\\\", \\\"col2\\\"]\"`\n rename?: string; // stringified object like `{ old: \"new\" }`\n imports?: string[];\n } = {},\n): string {\n const { add = \"\", drop, rename, imports = [] } = options;\n const allImports = [\"Migration\", ...imports].join(\", \");\n\n // Build the schema object dynamically\n const schemaParts: string[] = [];\n\n if (add) {\n schemaParts.push(` add: {\\n${add}\\n },`);\n }\n\n if (drop) {\n schemaParts.push(` drop: ${drop},`);\n }\n\n if (rename) {\n schemaParts.push(` rename: ${rename},`);\n }\n\n return `import { ${allImports} } from \"@warlock.js/cascade\";\nimport { ${entityName.pascal} } from \"../${entityName.kebab}.model\";\n\nexport default Migration.alter(${entityName.pascal}, {\n${schemaParts.join(\"\\n\")}\n});\n`;\n}\n\n/**\n * CRUD Create Schema template\n * Outputs to: schema/create-{entity}.schema.ts\n */\nexport function crudCreateSchemaStub(moduleName: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const create${moduleName.pascal}Schema = v.object({\n // TODO: Add validation rules\n});\n\nexport type Create${moduleName.pascal}Schema = Infer<typeof create${moduleName.pascal}Schema>;\n`;\n}\n\n/**\n * CRUD Update Schema template\n * Outputs to: schema/update-{entity}.schema.ts\n */\nexport function crudUpdateSchemaStub(moduleName: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const update${moduleName.pascal}Schema = v.object({\n // TODO: Add validation rules\n});\n\nexport type Update${moduleName.pascal}Schema = Infer<typeof update${moduleName.pascal}Schema>;\n`;\n}\n\n/**\n * Service template stub\n */\nexport function serviceStub(name: Name): string {\n return `export async function ${name.camel}Service(data: any): Promise<any> {\n // TODO: Implement service logic\n throw new Error(\"${name.camel}Service not implemented\");\n}\n`;\n}\n\n/**\n * Schema template stub\n * Outputs to: schema/{name}.schema.ts\n */\nexport function schemaStub(name: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const ${name.camel}Schema = v.object({\n // TODO: Define validation schema\n});\n\nexport type ${name.pascal}Schema = Infer<typeof ${name.camel}Schema>;\n`;\n}\n\n/**\n * Model template stub\n */\nexport function modelStub(\n name: Name,\n options: { tableName?: string; withResource?: boolean } = {},\n): string {\n const { tableName = `${name.plural.snake}`, withResource } = options;\n\n return `import { Model, type StrictMode } from \"@warlock.js/cascade\";\nimport { v, type Infer } from \"@warlock.js/seal\";\n${withResource ? `import { ${name.singular.pascal}Resource } from \"../../resources/${name.singular.kebab}.resource\";` : \"\"}\n\nconst ${name.singular.camel}Schema = v.object({\n // TODO: Define model schema\n});\n\nexport type ${name.singular.pascal}Type = Infer.Output<typeof ${name.singular.camel}Schema>;\n\nexport class ${name.singular.pascal} extends Model<${name.singular.pascal}Type> {\n public static table = \"${tableName}\";\n public static strictMode: StrictMode = \"fail\";\n${withResource ? ` public static resource = ${name.singular.pascal}Resource;` : \"\"}\n\n public static schema = ${name.singular.camel}Schema;\n\n public static relations = {\n // TODO: Define relations\n };\n}\n`;\n}\n\n/**\n * Repository template stub\n */\nexport function repositoryStub(name: Name): string {\n return `import type { FilterByOptions, RepositoryOptions } from \"@warlock.js/core\";\nimport { RepositoryManager } from \"@warlock.js/core\";\nimport { ${name.singular.pascal} } from \"../models/${name.singular.kebab}\";\n\ntype ${name.singular.pascal}ListFilter = {\n // Repository list filters\n};\n\nexport type ${name.singular.pascal}ListOptions = RepositoryOptions & ${name.singular.pascal}ListFilter;\n\nexport class ${name.plural.pascal}Repository extends RepositoryManager<${name.singular.pascal}, ${name.singular.pascal}ListFilter> {\n public source = ${name.singular.pascal};\n\n protected defaultOptions: RepositoryOptions = this.withDefaultOptions({});\n\n protected filterBy: FilterByOptions = this.withDefaultFilters({\n name: \"like\",\n });\n}\n\nexport const ${name.plural.camel}Repository = new ${name.plural.pascal}Repository();\n`;\n}\n\n/**\n * Resource template stub\n */\nexport function resourceStub(name: Name): string {\n return `import { Resource } from \"@warlock.js/core\";\n\nexport class ${name.singular.pascal}Resource extends Resource {\n public schema = {\n id: \"int\",\n name: \"string\",\n // TODO: Define resource schema\n };\n}\n`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"stubs.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/templates/stubs.ts"],"sourcesContent":["import type { ParsedName } from \"../types\";\nimport { Name } from \"../utils/name-parser\";\n\n/**\n * Controller template stub\n *\n * Controllers default to the guarded handler type since application\n * routes run behind `guarded()`. When `withValidation` is set, the\n * schema's exported type + value are imported directly from the\n * `schema/` folder and bound to the handler — no `requests/` alias.\n */\nexport function controllerStub(\n name: ParsedName,\n options: { withValidation?: boolean } = {},\n): string {\n const { withValidation } = options;\n\n if (!withValidation) {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\n\nexport const ${name.camel}Controller: GuardedRequestHandler = async ({ response }) => {\n // TODO: Implement controller logic\n return response.success({});\n};\n`;\n }\n\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type ${name.pascal}Schema, ${name.camel}Schema } from \"../schema/${name.kebab}.schema\";\n\nexport const ${name.camel}Controller: GuardedRequestHandler<${name.pascal}Schema> = async ({\n response,\n}) => {\n // TODO: Implement controller logic\n return response.success({});\n};\n\n${name.camel}Controller.validation = {\n schema: ${name.camel}Schema,\n};\n`;\n}\n\n/**\n * CRUD Create Controller template\n * Note: moduleName is the module (plural), we need singular entity name\n */\nexport function crudCreateControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type Create${moduleName.pascal}Schema, create${moduleName.pascal}Schema } from \"../schema/create-${moduleName.kebab}.schema\";\nimport { create${moduleName.pascal}Service } from \"../services/create-${moduleName.kebab}.service\";\n\nexport const create${moduleName.pascal}Controller: GuardedRequestHandler<Create${moduleName.pascal}Schema> = async ({\n request,\n response,\n}) => {\n const ${moduleName.camel} = await create${moduleName.pascal}Service(request.validated());\n\n return response.successCreate({\n ${moduleName.camel},\n });\n};\n\ncreate${moduleName.pascal}Controller.validation = {\n schema: create${moduleName.pascal}Schema,\n};\n`;\n}\n\n/**\n * CRUD Update Controller template\n */\nexport function crudUpdateControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type Update${moduleName.pascal}Schema, update${moduleName.pascal}Schema } from \"../schema/update-${moduleName.kebab}.schema\";\nimport { update${moduleName.pascal}Service } from \"../services/update-${moduleName.kebab}.service\";\n\nexport const update${moduleName.pascal}Controller: GuardedRequestHandler<Update${moduleName.pascal}Schema> = async ({\n request,\n response,\n}) => {\n const ${moduleName.camel} = await update${moduleName.pascal}Service(request.input(\"id\"), request.validated());\n\n return response.success({\n ${moduleName.camel},\n });\n};\n\nupdate${moduleName.pascal}Controller.validation = {\n schema: update${moduleName.pascal}Schema,\n};\n`;\n}\n\n/**\n * CRUD List Controller template\n */\nexport function crudListControllerStub(moduleName: Name): string {\n const plural = moduleName.plural;\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { list${plural.pascal}Service } from \"../services/list-${plural.kebab}.service\";\n\nexport const list${plural.pascal}Controller: GuardedRequestHandler = async ({\n request,\n response,\n}) => {\n const { data, pagination } = await list${plural.pascal}Service(request.all());\n\n return response.success({\n data,\n pagination,\n });\n};\n`;\n}\n\n/**\n * CRUD Show/Get Controller template\n */\nexport function crudShowControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { get${moduleName.pascal}Service } from \"../services/get-${moduleName.kebab}.service\";\n\nexport const get${moduleName.pascal}Controller: GuardedRequestHandler = async ({\n request,\n response,\n}) => {\n const ${moduleName.camel} = await get${moduleName.pascal}Service(request.input(\"id\"));\n\n if (!${moduleName.camel}) {\n return response.notFound();\n }\n\n return response.success({\n ${moduleName.camel},\n });\n};\n`;\n}\n\n/**\n * CRUD Delete Controller template\n */\nexport function crudDeleteControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { delete${moduleName.pascal}Service } from \"../services/delete-${moduleName.kebab}.service\";\n\nexport const delete${moduleName.pascal}Controller: GuardedRequestHandler = async ({\n request,\n response,\n}) => {\n await delete${moduleName.pascal}Service(request.input(\"id\"));\n\n return response.noContent();\n};\n`;\n}\n\n/**\n * CRUD Routes template\n */\nexport function crudRoutesStub(moduleName: Name): string {\n const singular = moduleName.singular;\n const plural = moduleName.plural;\n\n return `import { router } from \"@warlock.js/core\";\nimport { guarded } from \"app/shared/utils/router\";\nimport { create${singular.pascal}Controller } from \"./controllers/create-${singular.kebab}.controller\";\nimport { delete${singular.pascal}Controller } from \"./controllers/delete-${singular.kebab}.controller\";\nimport { get${singular.pascal}Controller } from \"./controllers/get-${singular.kebab}.controller\";\nimport { list${plural.pascal}Controller } from \"./controllers/list-${plural.kebab}.controller\";\nimport { update${singular.pascal}Controller } from \"./controllers/update-${singular.kebab}.controller\";\n\nguarded(() => {\n router\n .route(\"/${plural.kebab}\")\n .list(list${plural.pascal}Controller)\n .show(get${singular.pascal}Controller)\n .create(create${singular.pascal}Controller)\n .update(update${singular.pascal}Controller)\n .destroy(delete${singular.pascal}Controller);\n});\n`;\n}\n\n/**\n * CRUD Model template\n */\nexport function crudModelStub(moduleName: Name): string {\n const singular = moduleName.singular;\n const plural = moduleName.plural;\n\n return `import { Model, RegisterModel } from \"@warlock.js/cascade\";\nimport { type Infer, v } from \"@warlock.js/seal\";\nimport { ${singular.pascal}Resource } from \"app/${plural.kebab}/resources/${singular.kebab}.resource\";\n\nexport const ${singular.camel}Schema = v.object({\n // TODO: Add more fields\n});\n\nexport type ${singular.pascal}Schema = Infer.Output<typeof ${singular.camel}Schema>;\n\n@RegisterModel()\nexport class ${singular.pascal} extends Model<${singular.pascal}Schema> {\n public static table = \"${plural.snake}\";\n\n public static schema = ${singular.camel}Schema;\n\n public static relations = {};\n\n public static resource = ${singular.pascal}Resource;\n}\n`;\n}\n\n/**\n * CRUD Resource template\n */\nexport function crudResourceStub(moduleName: Name): string {\n // Get singular entity name\n const entity = moduleName.singular;\n\n return `import { defineResource } from \"@warlock.js/core\";\n\nexport const ${entity.pascal}Resource = defineResource({\n schema: {\n id: \"number\",\n // TODO: Add more resource fields\n },\n});\n`;\n}\n\n/**\n * CRUD Repository template\n */\nexport function crudRepositoryStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n const modulePluralName = entity.plural;\n return `import type { FilterRules, TypedRepositoryOptions } from \"@warlock.js/core\";\nimport { RepositoryManager } from \"@warlock.js/core\";\nimport { ${moduleSingularName.pascal} } from \"../models/${moduleSingularName.kebab}\";\n\ntype ${moduleSingularName.pascal}ListFilter = {\n // Repository list filters\n};\n\nexport type ${moduleSingularName.pascal}ListOptions = TypedRepositoryOptions<${moduleSingularName.pascal}ListFilter>;\n\nclass ${modulePluralName.pascal}Repository extends RepositoryManager<${moduleSingularName.pascal}, ${moduleSingularName.pascal}ListOptions> {\n public source = ${moduleSingularName.pascal};\n\n public simpleSelectColumns: string[] = [\"id\"];\n\n public filterBy: FilterRules = {\n id: \"=\",\n };\n\n public defaultOptions: RepositoryOptions = {\n orderBy: {\n id: \"desc\",\n },\n };\n}\n\nexport const ${modulePluralName.camel}Repository = new ${modulePluralName.pascal}Repository();\n`;\n}\n\n/**\n * CRUD Create Service template\n */\nexport function crudCreateServiceStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n return `import { ${moduleSingularName.pascal} } from \"../models/${moduleSingularName.kebab}\";\nimport type { Create${moduleSingularName.pascal}Schema } from \"../schema/create-${moduleSingularName.kebab}.schema\";\n\nexport async function create${moduleSingularName.pascal}Service(data: Create${moduleSingularName.pascal}Schema) {\n const ${moduleSingularName.camel} = await ${moduleSingularName.pascal}.create(data);\n return ${moduleSingularName.camel};\n}\n`;\n}\n\n/**\n * CRUD Update Service template\n */\nexport function crudUpdateServiceStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n return `import { ResourceNotFoundError } from \"@warlock.js/core\";\nimport { get${moduleSingularName.pascal}Service } from \"./get-${moduleSingularName.kebab}.service\";\nimport type { Update${moduleSingularName.pascal}Schema } from \"../schema/update-${moduleSingularName.kebab}.schema\";\n\nexport async function update${moduleSingularName.pascal}Service(id: number | string, data: Update${moduleSingularName.pascal}Schema) {\n const ${moduleSingularName.camel} = await get${moduleSingularName.pascal}Service(id);\n\n await ${moduleSingularName.camel}.save({ merge: data });\n return ${moduleSingularName.camel};\n}\n`;\n}\n\n/**\n * CRUD List Service template\n */\nexport function crudListServiceStub(entity: Name): string {\n const modulePluralName = entity.plural;\n return `import { ${modulePluralName.camel}Repository } from \"../repositories/${modulePluralName.kebab}.repository\";\n\nexport async function list${modulePluralName.pascal}Service(filters: any) {\n return ${modulePluralName.camel}Repository.listCached(filters);\n}\n`;\n}\n\n/**\n * CRUD Get Service template\n */\nexport function crudGetServiceStub(entity: Name): string {\n return `import { ${entity.plural.camel}Repository } from \"../repositories/${entity.plural.kebab}.repository\";\nimport { ResourceNotFoundError } from \"@warlock.js/core\";\n\nexport async function get${entity.singular.pascal}Service(id: number | string) {\n const ${entity.singular.camel} = await ${entity.plural.camel}Repository.getCached(id);\n\n if (!${entity.singular.camel}) {\n throw new ResourceNotFoundError(\"${entity.singular.pascal} resource not found!\");\n }\n\n return ${entity.singular.camel};\n}\n`;\n}\n\n/**\n * CRUD Delete Service template\n */\nexport function crudDeleteServiceStub(entity: Name): string {\n const singular = entity.singular;\n return `import { ResourceNotFoundError } from \"@warlock.js/core\";\nimport { get${singular.pascal}Service } from \"./get-${singular.kebab}.service\";\n\nexport async function delete${singular.pascal}Service(id: number | string) {\n const ${singular.camel} = await get${singular.pascal}Service(id);\n if (!${singular.camel}) {\n throw new ResourceNotFoundError(\"${singular.pascal} not found\");\n }\n await ${singular.camel}.destroy();\n}\n`;\n}\n\n/**\n * CRUD Seed template\n */\nexport function crudSeedStub(entity: Name): string {\n return `import { seeder } from \"@warlock.js/core\";\nimport { ${entity.singular.pascal} } from \"../models/${entity.singular.kebab}\";\n\nexport default seeder({\n name: \"Seed ${entity.plural.pascal}\",\n once: true,\n enabled: true,\n run: async ({ track }) => {\n const total = 10;\n for (let i = 0; i < total; i++) {\n track(\n await ${entity.singular.pascal}.create({\n // TODO: Add more fields\n }),\n );\n }\n },\n});\n`;\n}\n\n/**\n * Migration template\n */\n/**\n * Migration Create template\n */\nexport function migrationStub(\n entityName: ParsedName,\n options: {\n columns?: string;\n imports?: string[];\n timestamps?: boolean;\n tableName?: string;\n } = {},\n): string {\n const { columns = \"\", imports = [], timestamps = true } = options;\n\n const allImports = [\"Migration\", ...imports].join(\", \");\n\n let optionsString = \"\";\n if (timestamps === false) {\n optionsString = `, { timestamps: false }`;\n }\n\n return `import { ${allImports} } from \"@warlock.js/cascade\";\nimport { ${entityName.pascal} } from \"../${entityName.kebab}.model\";\n\nexport default Migration.create(${entityName.pascal}, {\n${columns ? columns : \" // add your columns here, id is auto added to the list\"}\n}${optionsString});\n`;\n}\n\n/**\n * Migration Alter template\n */\nexport function migrationAlterStub(\n entityName: ParsedName,\n options: {\n add?: string;\n drop?: string; // stringified array like `\"[\\\"col1\\\", \\\"col2\\\"]\"`\n rename?: string; // stringified object like `{ old: \"new\" }`\n imports?: string[];\n } = {},\n): string {\n const { add = \"\", drop, rename, imports = [] } = options;\n const allImports = [\"Migration\", ...imports].join(\", \");\n\n // Build the schema object dynamically\n const schemaParts: string[] = [];\n\n if (add) {\n schemaParts.push(` add: {\\n${add}\\n },`);\n }\n\n if (drop) {\n schemaParts.push(` drop: ${drop},`);\n }\n\n if (rename) {\n schemaParts.push(` rename: ${rename},`);\n }\n\n return `import { ${allImports} } from \"@warlock.js/cascade\";\nimport { ${entityName.pascal} } from \"../${entityName.kebab}.model\";\n\nexport default Migration.alter(${entityName.pascal}, {\n${schemaParts.join(\"\\n\")}\n});\n`;\n}\n\n/**\n * CRUD Create Schema template\n * Outputs to: schema/create-{entity}.schema.ts\n */\nexport function crudCreateSchemaStub(moduleName: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const create${moduleName.pascal}Schema = v.object({\n // TODO: Add validation rules\n});\n\nexport type Create${moduleName.pascal}Schema = Infer<typeof create${moduleName.pascal}Schema>;\n`;\n}\n\n/**\n * CRUD Update Schema template\n * Outputs to: schema/update-{entity}.schema.ts\n */\nexport function crudUpdateSchemaStub(moduleName: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const update${moduleName.pascal}Schema = v.object({\n // TODO: Add validation rules\n});\n\nexport type Update${moduleName.pascal}Schema = Infer<typeof update${moduleName.pascal}Schema>;\n`;\n}\n\n/**\n * Service template stub\n */\nexport function serviceStub(name: Name): string {\n return `export async function ${name.camel}Service(data: any): Promise<any> {\n // TODO: Implement service logic\n throw new Error(\"${name.camel}Service not implemented\");\n}\n`;\n}\n\n/**\n * Schema template stub\n * Outputs to: schema/{name}.schema.ts\n */\nexport function schemaStub(name: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const ${name.camel}Schema = v.object({\n // TODO: Define validation schema\n});\n\nexport type ${name.pascal}Schema = Infer<typeof ${name.camel}Schema>;\n`;\n}\n\n/**\n * Model template stub\n */\nexport function modelStub(\n name: Name,\n options: { tableName?: string; withResource?: boolean } = {},\n): string {\n const { tableName = `${name.plural.snake}`, withResource } = options;\n\n return `import { Model, type StrictMode } from \"@warlock.js/cascade\";\nimport { v, type Infer } from \"@warlock.js/seal\";\n${withResource ? `import { ${name.singular.pascal}Resource } from \"../../resources/${name.singular.kebab}.resource\";` : \"\"}\n\nconst ${name.singular.camel}Schema = v.object({\n // TODO: Define model schema\n});\n\nexport type ${name.singular.pascal}Type = Infer.Output<typeof ${name.singular.camel}Schema>;\n\nexport class ${name.singular.pascal} extends Model<${name.singular.pascal}Type> {\n public static table = \"${tableName}\";\n public static strictMode: StrictMode = \"fail\";\n${withResource ? ` public static resource = ${name.singular.pascal}Resource;` : \"\"}\n\n public static schema = ${name.singular.camel}Schema;\n\n public static relations = {\n // TODO: Define relations\n };\n}\n`;\n}\n\n/**\n * Repository template stub\n */\nexport function repositoryStub(name: Name): string {\n return `import type { FilterByOptions, RepositoryOptions } from \"@warlock.js/core\";\nimport { RepositoryManager } from \"@warlock.js/core\";\nimport { ${name.singular.pascal} } from \"../models/${name.singular.kebab}\";\n\ntype ${name.singular.pascal}ListFilter = {\n // Repository list filters\n};\n\nexport type ${name.singular.pascal}ListOptions = RepositoryOptions & ${name.singular.pascal}ListFilter;\n\nexport class ${name.plural.pascal}Repository extends RepositoryManager<${name.singular.pascal}, ${name.singular.pascal}ListFilter> {\n public source = ${name.singular.pascal};\n\n protected defaultOptions: RepositoryOptions = this.withDefaultOptions({});\n\n protected filterBy: FilterByOptions = this.withDefaultFilters({\n name: \"like\",\n });\n}\n\nexport const ${name.plural.camel}Repository = new ${name.plural.pascal}Repository();\n`;\n}\n\n/**\n * Resource template stub\n */\nexport function resourceStub(name: Name): string {\n return `import { Resource } from \"@warlock.js/core\";\n\nexport class ${name.singular.pascal}Resource extends Resource {\n public schema = {\n id: \"int\",\n name: \"string\",\n // TODO: Define resource schema\n };\n}\n`;\n}\n"],"mappings":"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"}
@@ -460,7 +460,7 @@ import { Notification } from "../notification.model";
460
460
  */
461
461
  export default Migration.create(Notification, notificationColumns(Notification));
462
462
  `;
463
- const notificationControllersStub = `import { type Request, type RequestHandler, type Response } from "@warlock.js/core";
463
+ const notificationControllersStub = `import { type RequestHandler } from "@warlock.js/core";
464
464
  import { inApp } from "@warlock.js/notifications";
465
465
 
466
466
  /**
@@ -471,10 +471,7 @@ import { inApp } from "@warlock.js/notifications";
471
471
  */
472
472
 
473
473
  /** GET /notifications — list, most recent first (page / limit / type / unread via query). */
474
- export const listNotificationsController: RequestHandler = async (
475
- request: Request,
476
- response: Response,
477
- ) => {
474
+ export const listNotificationsController: RequestHandler = async ({ request, response }) => {
478
475
  const { data, pagination } = await inApp.list(request.user!, request.all());
479
476
 
480
477
  return response.success({ notifications: data, pagination });
@@ -483,10 +480,10 @@ export const listNotificationsController: RequestHandler = async (
483
480
  listNotificationsController.description = "List notifications";
484
481
 
485
482
  /** GET /notifications/unread-count — drives the bell badge. */
486
- export const unreadNotificationsCountController: RequestHandler = async (
487
- request: Request,
488
- response: Response,
489
- ) => {
483
+ export const unreadNotificationsCountController: RequestHandler = async ({
484
+ request,
485
+ response,
486
+ }) => {
490
487
  const count = await inApp.countUnread(request.user!);
491
488
 
492
489
  return response.success({ count });
@@ -495,10 +492,7 @@ export const unreadNotificationsCountController: RequestHandler = async (
495
492
  unreadNotificationsCountController.description = "Unread notifications count";
496
493
 
497
494
  /** PATCH /notifications/:id/read — mark one read, return the updated row. */
498
- export const markNotificationReadController: RequestHandler = async (
499
- request: Request,
500
- response: Response,
501
- ) => {
495
+ export const markNotificationReadController: RequestHandler = async ({ request, response }) => {
502
496
  const id = request.input("id");
503
497
 
504
498
  await inApp.markAsRead(request.user!, id);
@@ -510,10 +504,10 @@ export const markNotificationReadController: RequestHandler = async (
510
504
  markNotificationReadController.description = "Mark notification read";
511
505
 
512
506
  /** PATCH /notifications/read-all — mark every unread one read. */
513
- export const markAllNotificationsReadController: RequestHandler = async (
514
- request: Request,
515
- response: Response,
516
- ) => {
507
+ export const markAllNotificationsReadController: RequestHandler = async ({
508
+ request,
509
+ response,
510
+ }) => {
517
511
  const count = await inApp.markAsRead(request.user!);
518
512
 
519
513
  return response.success({ count });
@@ -522,10 +516,7 @@ export const markAllNotificationsReadController: RequestHandler = async (
522
516
  markAllNotificationsReadController.description = "Mark all notifications read";
523
517
 
524
518
  /** DELETE /notifications — dismiss all for the user. */
525
- export const clearNotificationsController: RequestHandler = async (
526
- request: Request,
527
- response: Response,
528
- ) => {
519
+ export const clearNotificationsController: RequestHandler = async ({ request, response }) => {
529
520
  await inApp.dismiss(request.user!);
530
521
 
531
522
  return response.noContent();
@@ -534,10 +525,7 @@ export const clearNotificationsController: RequestHandler = async (
534
525
  clearNotificationsController.description = "Clear notifications";
535
526
 
536
527
  /** DELETE /notifications/:id — dismiss one. */
537
- export const deleteNotificationController: RequestHandler = async (
538
- request: Request,
539
- response: Response,
540
- ) => {
528
+ export const deleteNotificationController: RequestHandler = async ({ request, response }) => {
541
529
  await inApp.dismiss(request.user!, request.input("id"));
542
530
 
543
531
  return response.noContent();
@@ -1 +1 @@
1
- {"version":3,"file":"stubs.mjs","names":[],"sources":["../../../../../../../core/src/generations/stubs.ts"],"sourcesContent":["export const accessConfigStub = `import { type AccessConfigurations } from \"@warlock.js/access\";\r\nimport { DatabaseAccessResolver } from \"app/access/services/access-resolver\";\r\n\r\n/**\r\n * Authorization configuration — read by @warlock.js/access on boot.\r\n *\r\n * The resolver is the one required piece: it tells the engine how to read a\r\n * user's roles + permissions. The ejected DatabaseAccessResolver reads roles\r\n * from the user_roles table and maps them through the roles catalog table (so\r\n * roles + their permissions are managed at runtime, in the DB).\r\n *\r\n * For a fixed, code-defined catalog with no tables, swap in DefaultAccessResolver:\r\n * import { DefaultAccessResolver } from \"@warlock.js/access\";\r\n * resolver: new DefaultAccessResolver({ admin: [\"*\"], editor: [\"orders.*\"] }),\r\n *\r\n * Multi-tenant? Add a \\`resolveTenant()\\` to the resolver to read the active\r\n * tenant from the request; checks then scope to it automatically.\r\n */\r\nconst access: AccessConfigurations = {\r\n resolver: new DatabaseAccessResolver(),\r\n\r\n // Cache resolved permission sets (default \"10m\").\r\n // cache: { ttl: \"10m\" },\r\n};\r\n\r\nexport default access;\r\n`;\r\n\r\nexport const aiConfigStub = `import type { AIConfig } from \"@warlock.js/ai\";\r\n\r\n// >>> warlock:ai-packages (auto-managed) >>>\r\n// Satellite packages augment the \"ai\" object on import — e.g. ai.workspace,\r\n// ai.tools / ai.mcp, and panoptic's ai.config({ panoptic }) wiring. The command\r\n// \"warlock add ai-workspace | ai-tools | ai-panoptic\" adds the matching\r\n// side-effect import below; keep them so the augmentation + runtime registration\r\n// load before the ai connector applies this config.\r\n// <<< warlock:ai-packages <<<\r\n\r\n/**\r\n * AI configuration — applied on boot by the ai connector, which calls\r\n * ai.config(...) with the object below. Cross-cutting defaults live here\r\n * (shared cache / snapshot stores, observability); per-call options always win.\r\n *\r\n * Wire a default model from a provider you installed, e.g.:\r\n * import { OpenAISDK } from \"@warlock.js/ai-openai\";\r\n * const openai = OpenAISDK({ apiKey: env(\"OPENAI_API_KEY\") });\r\n * // then pass openai.model({ name: \"gpt-4o-mini\" }) into your agents.\r\n */\r\nconst ai: Partial<AIConfig> = {\r\n // Default cache driver for cache-backed AI features (semantic cache, rag / memory vector stores).\r\n // defaultStore: cache.driver(\"redis\", { client }),\r\n\r\n // Observability — requires \"warlock add ai-panoptic\". Exporters + the local dashboard.\r\n // panoptic: { exporters: [], dashboard: false, observeAll: false },\r\n};\r\n\r\nexport default ai;\r\n`;\r\n\r\nexport const accessRoleModelStub = `import { Model, RegisterModel } from \"@warlock.js/cascade\";\r\nimport { type Infer, v } from \"@warlock.js/seal\";\r\n\r\n/**\r\n * Validation schema for the roles catalog — mirrors the migration columns\r\n * (snake_case). Each row is a role name plus the permission strings it grants;\r\n * wildcards work (\"orders.*\", \"*\"). The DatabaseAccessResolver maps a user's\r\n * assigned role names through this table to their effective permissions.\r\n */\r\nexport const roleSchema = v.object({\r\n name: v.string(),\r\n permissions: v.array(v.string()).default([]),\r\n});\r\n\r\nexport type RoleSchema = Infer<typeof roleSchema>;\r\n\r\n/**\r\n * The roles catalog — role name → the permissions it grants. Managed at runtime\r\n * (admins add roles + edit their permissions), unlike a fixed code map. Read by\r\n * DatabaseAccessResolver.resolvePermissions to expand a user's roles to permissions.\r\n */\r\n@RegisterModel()\r\nexport class Role extends Model<RoleSchema> {\r\n public static table = \"roles\";\r\n\r\n public static schema = roleSchema;\r\n\r\n /** The permission strings this role grants. */\r\n public get permissions(): string[] {\r\n return this.get<string[]>(\"permissions\", []);\r\n }\r\n}\r\n`;\r\n\r\nexport const accessRoleModelIndexStub = `export * from \"./role.model\";\r\n`;\r\n\r\nexport const accessRoleMigrationStub = `import { arrayText, Migration, text } from \"@warlock.js/cascade\";\r\nimport { Role } from \"../role.model\";\r\n\r\n/**\r\n * Roles catalog table. \\`name\\` is unique (one row per role); \\`permissions\\` is a\r\n * text array of the permission strings the role grants.\r\n */\r\nexport default Migration.create(Role, {\r\n name: text().notNullable().unique(),\r\n permissions: arrayText().nullable(),\r\n});\r\n`;\r\n\r\nexport const accessUserRoleModelStub = `import { access } from \"@warlock.js/access\";\r\nimport type { Auth } from \"@warlock.js/auth\";\r\nimport { Model, RegisterModel } from \"@warlock.js/cascade\";\r\nimport { type Infer, v } from \"@warlock.js/seal\";\r\n\r\n/**\r\n * Validation schema for a role assignment — mirrors the migration columns\r\n * (snake_case). \\`tenant\\` is nullable: a null tenant is a GLOBAL assignment.\r\n */\r\nexport const userRoleSchema = v.object({\r\n user_id: v.string(),\r\n user_type: v.string(),\r\n role: v.string(),\r\n tenant: v.string().optional(),\r\n});\r\n\r\nexport type UserRoleSchema = Infer<typeof userRoleSchema>;\r\n\r\n/**\r\n * The role-assignment table — which roles a user holds, optionally per tenant.\r\n * Read by DatabaseAccessResolver.resolveRoles; mutated via the statics below.\r\n * \\`assign\\` / \\`revoke\\` flush the cached permission set automatically, so callers\r\n * never need to call \\`access.flush(user, tenant)\\` themselves.\r\n */\r\n@RegisterModel()\r\nexport class UserRole extends Model<UserRoleSchema> {\r\n public static table = \"user_roles\";\r\n\r\n public static schema = userRoleSchema;\r\n\r\n /**\r\n * Role names assigned to the user in the given tenant.\r\n *\r\n * An unresolved tenant (\\`undefined\\`) scopes to GLOBAL roles only — the rows\r\n * stored with no tenant (\\`null\\`) — never the union across every tenant. The\r\n * union would be a privilege-escalation: a user who is \\`owner\\` in one tenant\r\n * must not be treated as \\`owner\\` everywhere just because a check didn't carry\r\n * a tenant. This mirrors how \\`assign(user, role)\\` stores a global row.\r\n */\r\n public static async rolesFor(user: Auth, tenant?: string): Promise<string[]> {\r\n const rows = await this.query()\r\n .where({\r\n user_id: user.id,\r\n user_type: user.userType,\r\n tenant: tenant ?? null,\r\n })\r\n .get();\r\n\r\n // De-dupe so a duplicate row (a concurrent assign that slipped past the\r\n // existence check) can't distort the resolved set.\r\n return [...new Set(rows.map((row) => row.get(\"role\") as string))];\r\n }\r\n\r\n /**\r\n * Assign a role to the user. No-op if the assignment already exists.\r\n * Flushes the user's cached permission set automatically.\r\n */\r\n public static async assign(user: Auth, role: string, tenant?: string): Promise<void> {\r\n const existing = await this.first({\r\n user_id: user.id,\r\n user_type: user.userType,\r\n role,\r\n tenant: tenant ?? null,\r\n });\r\n\r\n if (existing) return;\r\n\r\n await this.create({\r\n user_id: user.id,\r\n user_type: user.userType,\r\n role,\r\n tenant,\r\n });\r\n\r\n await access.flush(user, tenant);\r\n }\r\n\r\n /**\r\n * Remove a role assignment from the user.\r\n * Flushes the user's cached permission set automatically.\r\n */\r\n public static async revoke(user: Auth, role: string, tenant?: string): Promise<void> {\r\n await this.delete({\r\n user_id: user.id,\r\n user_type: user.userType,\r\n role,\r\n tenant: tenant ?? null,\r\n });\r\n\r\n await access.flush(user, tenant);\r\n }\r\n}\r\n`;\r\n\r\nexport const accessUserRoleModelIndexStub = `export * from \"./user-role.model\";\r\n`;\r\n\r\nexport const accessUserRoleMigrationStub = `import { Migration, text, uuid } from \"@warlock.js/cascade\";\r\nimport { UserRole } from \"../user-role.model\";\r\n\r\n/**\r\n * Role-assignment table. \\`user_id\\` is a UUID — override this migration if your\r\n * user ids are integers. The composite index powers the per-user (per-tenant)\r\n * lookup the resolver runs on every check.\r\n */\r\nexport default Migration.create(\r\n UserRole,\r\n {\r\n user_id: uuid().notNullable().index(),\r\n user_type: text().notNullable(),\r\n role: text().notNullable().index(),\r\n tenant: text().nullable().index(),\r\n },\r\n {\r\n index: [{ columns: [\"user_id\", \"user_type\", \"tenant\"] }],\r\n },\r\n);\r\n`;\r\n\r\nexport const accessResolverStub = `import type { AccessResolver } from \"@warlock.js/access\";\r\nimport type { Auth } from \"@warlock.js/auth\";\r\nimport { Role } from \"app/access/models/role\";\r\nimport { UserRole } from \"app/access/models/user-role\";\r\n\r\n/**\r\n * The app's access adapter — connects @warlock.js/access to the ejected role\r\n * tables. Roles come from the user_roles assignment table; permissions are\r\n * expanded by mapping those role names through the roles catalog table. Both\r\n * are managed at runtime (in the DB), so admins can add roles + edit their\r\n * permissions without a deploy.\r\n *\r\n * The engine owns the hard parts (wildcard matching, caching, fail-closed); this\r\n * resolver only fetches — keep it dumb, never cache inside it.\r\n */\r\nexport class DatabaseAccessResolver implements AccessResolver {\r\n /** The role names this user holds (powers \\`hasRole\\` / \\`hasAnyRole\\`). */\r\n public async resolveRoles(user: Auth, tenant?: string): Promise<string[]> {\r\n return UserRole.rolesFor(user, tenant);\r\n }\r\n\r\n /** The effective permission strings this user has (powers \\`can\\` / \\`authorize\\`). */\r\n public async resolvePermissions(user: Auth, tenant?: string): Promise<string[]> {\r\n const names = await this.resolveRoles(user, tenant);\r\n\r\n if (names.length === 0) return [];\r\n\r\n const roles = await Role.query().whereIn(\"name\", names).get();\r\n\r\n // Flatten + de-dupe so two roles granting the same permission yield one entry.\r\n return [...new Set(roles.flatMap((role) => role.permissions))];\r\n }\r\n\r\n /**\r\n * Optional. Resolve the ambient tenant when a check doesn't pass one\r\n * explicitly — derive it from the authenticated user (safer than reading\r\n * client request input, which a caller could spoof). Uncomment + adapt for a\r\n * multi-tenant app (single-tenant apps leave this off and return undefined).\r\n */\r\n // public resolveTenant(user: Auth): string | undefined {\r\n // return user.get(\"organization_id\");\r\n // }\r\n}\r\n`;\r\n\r\nexport const socketConfigStub = `import type { SocketOptions } from \"@warlock.js/core\";\r\n\r\n/**\r\n * Socket.IO configuration — read by the framework's socket connector\r\n * on boot. When the HTTP server is running the socket server attaches\r\n * to it; otherwise it listens on its own configured port.\r\n *\r\n * Remove this file to disable the socket server entirely.\r\n */\r\nexport default {\r\n options: {\r\n cors: {\r\n origin: \"*\",\r\n },\r\n },\r\n} as SocketOptions;\r\n`;\r\n\r\nexport const communicatorsConfigStub = `import { env } from \"@warlock.js/core\";\r\nimport type { BrokerConfigurations, RabbitMQClientOptions } from \"@warlock.js/herald\";\r\n\r\nconst heraldConfigurations: BrokerConfigurations<RabbitMQClientOptions> = {\r\n driver: \"rabbitmq\",\r\n name: \"default\",\r\n isDefault: true,\r\n\r\n // ============================================================================\r\n // Connection Settings\r\n // ============================================================================\r\n\r\n host: env(\"RABBITMQ_HOST\", \"localhost\"),\r\n port: env(\"RABBITMQ_PORT\", 5672),\r\n username: env(\"RABBITMQ_USERNAME\", \"guest\"),\r\n password: env(\"RABBITMQ_PASSWORD\", \"guest\"),\r\n vhost: env(\"RABBITMQ_VHOST\", \"/\"),\r\n\r\n // Or use connection URI (takes precedence over host/port)\r\n // uri: env(\"RABBITMQ_URL\"),\r\n\r\n // ============================================================================\r\n // Connection Options\r\n // ============================================================================\r\n\r\n /** Heartbeat interval in seconds */\r\n heartbeat: 60,\r\n\r\n /** Connection timeout in milliseconds */\r\n connectionTimeout: 10000,\r\n\r\n /** Enable automatic reconnection on disconnect */\r\n reconnect: true,\r\n\r\n /** Delay between reconnection attempts in milliseconds */\r\n reconnectDelay: 5_000,\r\n\r\n // ============================================================================\r\n // Consumer Options\r\n // ============================================================================\r\n\r\n /** Default prefetch count (number of unacknowledged messages per consumer) */\r\n prefetch: 10,\r\n\r\n // ============================================================================\r\n // Client Options (Native amqplib options)\r\n // ============================================================================\r\n // These options are passed directly to amqplib.connect()\r\n // for low-level configuration like frame size, TLS, socket options, etc.\r\n // ============================================================================\r\n clientOptions: {\r\n // Frame max size in bytes (0 = no limit)\r\n // frameMax: 0,\r\n\r\n // Channel max (0 = unlimited)\r\n // channelMax: 0,\r\n\r\n // Socket options\r\n socket: {\r\n // Enable TCP keep-alive\r\n keepAlive: true,\r\n\r\n // Disable Nagle's algorithm for lower latency\r\n noDelay: true,\r\n\r\n // Socket timeout (in addition to heartbeat)\r\n // timeout: 30000,\r\n },\r\n\r\n // TLS/SSL options (uncomment for secure connections)\r\n // socket: {\r\n // ca: fs.readFileSync('/path/to/ca.pem'),\r\n // cert: fs.readFileSync('/path/to/cert.pem'),\r\n // key: fs.readFileSync('/path/to/key.pem'),\r\n // rejectUnauthorized: true,\r\n // },\r\n },\r\n};\r\n\r\nexport default heraldConfigurations;\r\n`;\r\n\r\nexport const notificationsConfigStub = `import { type NotificationConfig, inApp, mailChannel } from \"@warlock.js/notifications\";\r\nimport { Notification } from \"app/notifications/notification.model\";\r\n\r\n/**\r\n * Notifications configuration. Auto-loaded from src/config on boot — the\r\n * framework's notifications connector reads this default export and hands it to\r\n * setNotificationConfig, so this file stays declarative (no side-effect call).\r\n *\r\n * Each channel is payload-typed, so notify.mail(...) / notify.database(...)\r\n * and defineNotification are type-checked against the registry.\r\n *\r\n * Channels enabled here:\r\n * - mail wraps @warlock.js/core sendMail; route is notifiable.email.\r\n * The \"from\" address defaults to config/mail.ts; override per\r\n * channel with mailChannel({ from: \"no-reply@yourapp.com\" }).\r\n * - database in-app store backed by the Notification model. The \"inApp\"\r\n * facade exposes the recipient-scoped read API: listUnread,\r\n * countUnread, markAsRead, dismiss, ...\r\n *\r\n * Async delivery (.queue()) is OPTIONAL: run \"npx warlock add herald\",\r\n * import { heraldQueue } from \"@warlock.js/notifications\", and uncomment the\r\n * queue line below.\r\n */\r\nconst config: NotificationConfig = {\r\n channels: {\r\n mail: mailChannel(),\r\n database: inApp.configure({ model: Notification }),\r\n },\r\n\r\n // Async queue — requires @warlock.js/herald (npx warlock add herald):\r\n // queue: heraldQueue(),\r\n};\r\n\r\nexport default config;\r\n`;\r\n\r\nexport const notificationModelStub = `import { RegisterModel } from \"@warlock.js/cascade\";\r\nimport { DatabaseNotification, type NotificationColumnMap } from \"@warlock.js/notifications\";\r\nimport { v } from \"@warlock.js/seal\";\r\n\r\n/**\r\n * Validation schema for the notifications table — mirrors the migration\r\n * columns (snake_case). Cascade validates + casts every write against it:\r\n * nullable columns use .nullish() (may be absent or null), and payload is\r\n * free-form JSON. Keep this in sync with the migration + columnMap when you\r\n * add or rename columns.\r\n */\r\nconst notificationSchema = v.object({\r\n user_id: v.string(),\r\n type: v.string(),\r\n title: v.string(),\r\n body: v.string().nullish(),\r\n payload: v.record(v.any()).nullish(),\r\n read_at: v.date().nullish(),\r\n idempotency_key: v.string().nullish(),\r\n});\r\n\r\n/**\r\n * In-app notification model.\r\n *\r\n * Extends the package's DatabaseNotification base, which provides the stable\r\n * accessors (recipientId, tenantId, isRead, readAt, markRead) — all derived\r\n * from the columnMap below. The read/write API lives on the inApp facade\r\n * (configured in config/notifications.ts); you rarely touch this class directly.\r\n */\r\n@RegisterModel()\r\nexport class Notification extends DatabaseNotification {\r\n public static table = \"notifications\";\r\n public static schema = notificationSchema;\r\n\r\n /**\r\n * Maps the in-app store's roles to your columns. This default is\r\n * single-tenant + read_at-only. Add tenant: \"organization_id\" for\r\n * multi-tenant; use isRead: \"is_read\" (instead of, or alongside, readAt) to\r\n * track a boolean read flag. The migration + accessors all follow this map.\r\n */\r\n public static columnMap: NotificationColumnMap = { readAt: \"read_at\" };\r\n}\r\n`;\r\n\r\nexport const notificationMigrationStub = `import { Migration } from \"@warlock.js/cascade\";\r\nimport { notificationColumns } from \"@warlock.js/notifications\";\r\nimport { Notification } from \"../notification.model\";\r\n\r\n/**\r\n * Notifications table.\r\n *\r\n * Columns come from notificationColumns(Notification) — the recipient / tenant\r\n * / read-state names follow the model's columnMap; type / title / body /\r\n * payload / idempotency_key are fixed. Spread it to add your own columns\r\n * (remember to mirror them in the model schema):\r\n *\r\n * import { uuid } from \"@warlock.js/cascade\";\r\n *\r\n * export default Migration.create(Notification, {\r\n * ...notificationColumns(Notification),\r\n * // category_id: uuid().index().nullable(),\r\n * });\r\n */\r\nexport default Migration.create(Notification, notificationColumns(Notification));\r\n`;\r\n\r\nexport const notificationControllersStub = `import { type Request, type RequestHandler, type Response } from \"@warlock.js/core\";\r\nimport { inApp } from \"@warlock.js/notifications\";\r\n\r\n/**\r\n * The authenticated user's notification HTTP surface — thin wrappers over the\r\n * recipient-scoped \\`inApp\\` facade (a foreign id can never touch another user's\r\n * rows). Notifications are produced by domain events, never over HTTP, so there\r\n * is no create. Trim or split these as your app grows.\r\n */\r\n\r\n/** GET /notifications — list, most recent first (page / limit / type / unread via query). */\r\nexport const listNotificationsController: RequestHandler = async (\r\n request: Request,\r\n response: Response,\r\n) => {\r\n const { data, pagination } = await inApp.list(request.user!, request.all());\r\n\r\n return response.success({ notifications: data, pagination });\r\n};\r\n\r\nlistNotificationsController.description = \"List notifications\";\r\n\r\n/** GET /notifications/unread-count — drives the bell badge. */\r\nexport const unreadNotificationsCountController: RequestHandler = async (\r\n request: Request,\r\n response: Response,\r\n) => {\r\n const count = await inApp.countUnread(request.user!);\r\n\r\n return response.success({ count });\r\n};\r\n\r\nunreadNotificationsCountController.description = \"Unread notifications count\";\r\n\r\n/** PATCH /notifications/:id/read — mark one read, return the updated row. */\r\nexport const markNotificationReadController: RequestHandler = async (\r\n request: Request,\r\n response: Response,\r\n) => {\r\n const id = request.input(\"id\");\r\n\r\n await inApp.markAsRead(request.user!, id);\r\n const notification = await inApp.find(request.user!, id);\r\n\r\n return response.success({ notification });\r\n};\r\n\r\nmarkNotificationReadController.description = \"Mark notification read\";\r\n\r\n/** PATCH /notifications/read-all — mark every unread one read. */\r\nexport const markAllNotificationsReadController: RequestHandler = async (\r\n request: Request,\r\n response: Response,\r\n) => {\r\n const count = await inApp.markAsRead(request.user!);\r\n\r\n return response.success({ count });\r\n};\r\n\r\nmarkAllNotificationsReadController.description = \"Mark all notifications read\";\r\n\r\n/** DELETE /notifications — dismiss all for the user. */\r\nexport const clearNotificationsController: RequestHandler = async (\r\n request: Request,\r\n response: Response,\r\n) => {\r\n await inApp.dismiss(request.user!);\r\n\r\n return response.noContent();\r\n};\r\n\r\nclearNotificationsController.description = \"Clear notifications\";\r\n\r\n/** DELETE /notifications/:id — dismiss one. */\r\nexport const deleteNotificationController: RequestHandler = async (\r\n request: Request,\r\n response: Response,\r\n) => {\r\n await inApp.dismiss(request.user!, request.input(\"id\"));\r\n\r\n return response.noContent();\r\n};\r\n\r\ndeleteNotificationController.description = \"Delete notification\";\r\n`;\r\n\r\nexport const notificationRoutesStub = `import { authMiddleware } from \"@warlock.js/auth\";\r\nimport { router } from \"@warlock.js/core\";\r\nimport {\r\n clearNotificationsController,\r\n deleteNotificationController,\r\n listNotificationsController,\r\n markAllNotificationsReadController,\r\n markNotificationReadController,\r\n unreadNotificationsCountController,\r\n} from \"./controllers/notifications.controller\";\r\n\r\n/**\r\n * Notification routes — the authenticated user's read + dismiss surface.\r\n *\r\n * Notifications are produced by domain events (never created over HTTP), so\r\n * there is no POST. Every route is gated by \\`authMiddleware\\` and recipient-\r\n * scoped by \\`inApp\\` (a foreign id touches zero rows). Delete any endpoint you\r\n * don't need; if your app reads notifications over sockets/GraphQL instead,\r\n * delete this file + the controllers entirely.\r\n */\r\nrouter.group({ prefix: \"/notifications\", middleware: [authMiddleware([])] }, () => {\r\n router.get(\"/\", listNotificationsController);\r\n router.get(\"/unread-count\", unreadNotificationsCountController);\r\n router.patch(\"/read-all\", markAllNotificationsReadController);\r\n router.patch(\"/:id/read\", markNotificationReadController);\r\n router.delete(\"/\", clearNotificationsController);\r\n router.delete(\"/:id\", deleteNotificationController);\r\n});\r\n`;\r\n\r\n/**\r\n * `src/web/root.tsx` — the application root for the SSR page layer.\r\n *\r\n * Deliberately minimal. The framework ships a default root, so this exists to\r\n * give you a place to start rather than because anything requires it. The\r\n * reference app (`v5/app/src/web/root.tsx`) is where to look for the fuller\r\n * shape: middleware, an app-level loader, locales, an ErrorBoundary.\r\n */\r\nexport const webRootStub = `import { Head, Scripts } from \"@warlock.js/web\";\r\nimport type { AppProps } from \"@warlock.js/web\";\r\n\r\n/**\r\n * The application root.\r\n *\r\n * NOT async, and it receives no request/response: it renders on the server and\r\n * again in the browser during hydration, where neither exists.\r\n */\r\nexport default function App({ children }: AppProps) {\r\n return (\r\n <html lang=\"en\">\r\n <head>\r\n {/*\r\n Placement only. The framework injects the page's \\`metadata\\`, the\r\n stylesheet and preload tags for this route, and the canonical links\r\n into <head> by default — <Head /> just says WHERE they land.\r\n\r\n Do not add a <title> here: the page's \\`metadata\\` owns it, and a root\r\n that emits one too produces two.\r\n */}\n <Head />\n <link rel=\"icon\" href=\"data:,\" />\n </head>\r\n <body>\r\n {/*\r\n REQUIRED — this is the hydration mount point, not a styling wrapper.\r\n\r\n The browser runtime looks up \\`#root\\` and hydrates that element only.\r\n Remove this div, or rename the id, and the page still renders from the\r\n server but never becomes interactive: the runtime throws in the console\r\n and nothing on screen changes.\r\n\r\n Wrap it in your own markup freely, and put anything that must live\r\n outside the hydrated tree (a static footer, a portal target) outside\r\n it — just keep an element with \\`id=\"root\"\\` around {children}.\r\n */}\r\n <div id=\"root\">{children}</div>\r\n {/*\r\n The hydration payload and module tags. Written explicitly because\r\n placement occasionally matters — a CSP nonce, or ordering against\r\n your own scripts.\r\n */}\r\n <Scripts />\r\n </body>\r\n </html>\r\n );\r\n}\r\n`;\r\n\r\n/**\r\n * `src/app/contact/controllers/contact.controller.ts` — a real API endpoint\r\n * for the Web starter's contact form. It intentionally has no persistence\r\n * dependency: replace the acknowledgement with a mail/job/database action.\r\n */\r\nexport const webContactControllerStub = `import { type Request, type RequestHandler } from \"@warlock.js/core\";\r\nimport { type Infer, v } from \"@warlock.js/seal\";\r\n\r\nexport const contactSchema = v.object({\r\n name: v.string().min(2).required(),\r\n email: v.email().required(),\r\n message: v.string().min(10).required(),\r\n});\r\n\r\nexport type ContactSchema = Infer.Output<typeof contactSchema>;\r\n\r\n/** POST /api/contact — validates the starter contact form. */\r\nexport const contactController: RequestHandler<Request<ContactSchema>> = async ({ request, response }) => {\r\n const contact = request.validated();\r\n\r\n // Replace this with delivery/persistence for your app. Keeping the accepted\r\n // payload visible makes the endpoint useful while remaining side-effect free.\r\n return response.success({\r\n message: \"Thanks, \" + contact.name + \". Your message has been received.\",\r\n });\r\n};\r\n\r\ncontactController.validation = { schema: contactSchema };\r\n`;\r\n\r\n/** `src/app/contact/routes.ts` — discovered by the standard app route loader. */\r\nexport const webContactRoutesStub = `import { router } from \"@warlock.js/core\";\r\nimport { contactController } from \"./controllers/contact.controller\";\r\n\r\nrouter.post(\"/api/contact\", contactController);\r\n`;\r\n\n/**\n * `src/web/index.register.ts` — universal static setup for the starter page.\n *\n * The page re-exports this stable binding so Warlock's `register()` lifecycle\n * still sees it in both realms without making React Fast Refresh treat every\n * JSX edit as an incompatible function-export replacement.\n */\nexport const webHomeRegisterStub = `import { extend } from \"@mongez/localization\";\n\nexport function register() {\n extend(\"en\", {\n starter: {\n title: \"Your Warlock app is running.\",\n introduction: \"This page is rendered on the server and hydrated in the browser.\",\n language: \"العربية\",\n contact: \"Send a message\",\n name: \"Name\",\n email: \"Email\",\n message: \"Message\",\n submit: \"Send message\",\n sent: \"Thanks — your message has been received.\",\n },\n });\n extend(\"ar\", {\n starter: {\n title: \"تطبيق Warlock يعمل الآن.\",\n introduction: \"تُعرض هذه الصفحة على الخادم ثم تُفعَّل في المتصفح.\",\n language: \"English\",\n contact: \"أرسل رسالة\",\n name: \"الاسم\",\n email: \"البريد الإلكتروني\",\n message: \"الرسالة\",\n submit: \"إرسال الرسالة\",\n sent: \"شكرًا — تم استلام رسالتك.\",\n },\n });\n}\n`;\n\n/**\n * `src/web/index.page.tsx` — one page, so \\`warlock dev\\` has something to serve\n * the moment this finishes.\n */\nexport const webHomePageStub = `import { http } from \"@mongez/http\";\nimport { Form, useFormControl, type FormControlProps } from \"@mongez/react-form\";\nimport { setCurrentLocaleCode } from \"@mongez/localization\";\nimport { transX } from \"@mongez/react-localization\";\nimport { v } from \"@warlock.js/seal\";\nimport { useState } from \"react\";\nimport { Link, type PageProps } from \"@warlock.js/web\";\n\nexport { register } from \"./index.register\";\n\n/**\n * A page route is an ordinary Warlock route whose handler renders React\n * instead of returning JSON.\n *\n * The URL and stable hydration name are the ones this file DECLARES below.\n * This page answers \\`GET \"/\"\\` because \\`route.path = \"/\"\\`, not because of\n * where the file lives. A page file with\n * no \\`route\\` export is REFUSED by both the dev server and the build.\n */\nexport const route = { path: \"/\", name: \"index\" } as const;\n\nexport const metadata = { title: \"Home\" };\n\nconst contactSchema = v.object({\n name: v.string().min(2).required(),\n email: v.email().required(),\n message: v.string().min(10).required(),\n});\n\nfunction TextInput({ label, ...controlProps }: FormControlProps & { label: string }) {\n const { error, getErrorProps, getInputProps } = useFormControl(controlProps);\r\n\r\n return (\r\n <div className=\"wk-field\">\r\n <label htmlFor={controlProps.name}>{label}</label>\r\n <input {...getInputProps()} />\r\n {error && <p {...getErrorProps()}>{error}</p>}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Add a \\`loader\\` export to fetch data on the server, and it arrives here as\r\n * \\`data\\`, typed:\r\n *\r\n * export const loader = (async () => ({ items: await itemsRepository.all() }));\r\n * export default function HomePage({ data }: PageProps<typeof loader>) { ... }\r\n */\r\nexport default function HomePage(_props: PageProps) {\r\n // Live state. If the button below does nothing, the page rendered on the\r\n // server but never hydrated — the runtime never mounted at \\`#root\\`. This is\r\n // deliberately here so that failure is impossible to miss.\r\n const [count, setCount] = useState(0);\r\n const [locale, setLocale] = useState<\"en\" | \"ar\">(\"en\");\r\n const [submitted, setSubmitted] = useState(false);\r\n const [submitError, setSubmitError] = useState<string | null>(null);\r\n\r\n const toggleLocale = () => {\r\n const nextLocale = locale === \"en\" ? \"ar\" : \"en\";\r\n setCurrentLocaleCode(nextLocale);\r\n setLocale(nextLocale);\r\n };\r\n\r\n return (\r\n <>\r\n {/*\r\n Self-contained, dependency-free styling: plain CSS, system fonts, and\r\n CSS custom properties, scoped to this page. No CSS framework, no utility\r\n classes, no external stylesheet — this page looks the same whether or\r\n not \\`warlock add tailwind\\` has ever been run.\r\n */}\r\n <style>{\\`\r\n .wk-home {\r\n --wk-fg: #0f172a;\r\n --wk-muted: #64748b;\r\n --wk-accent: #4f46e5;\r\n --wk-border: #e2e8f0;\r\n font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\r\n color: var(--wk-fg);\r\n max-width: 42rem;\r\n margin: 4rem auto;\r\n padding: 0 1.5rem;\r\n line-height: 1.6;\r\n }\r\n .wk-home h1 { font-size: 2.25rem; margin: 0 0 0.5rem; }\r\n .wk-home p { color: var(--wk-muted); margin: 0 0 1.5rem; }\r\n .wk-home code {\r\n font-family: ui-monospace, \"SFMono-Regular\", Menlo, monospace;\r\n background: #f1f5f9;\r\n padding: 0.1rem 0.35rem;\r\n border-radius: 0.25rem;\r\n }\r\n .wk-check {\r\n border: 1px solid var(--wk-border);\r\n border-radius: 0.75rem;\r\n padding: 1.25rem 1.5rem;\r\n margin: 2rem 0;\r\n }\r\n .wk-check strong { display: block; font-size: 1.5rem; }\r\n .wk-check button {\r\n font: inherit;\r\n cursor: pointer;\r\n background: var(--wk-accent);\r\n color: #fff;\r\n border: 0;\r\n border-radius: 0.5rem;\r\n padding: 0.5rem 1rem;\r\n margin-top: 0.75rem;\r\n }\r\n .wk-links { display: flex; gap: 1.25rem; font-size: 0.95rem; }\r\n .wk-links a { color: var(--wk-accent); text-decoration: none; }\r\n .wk-links a:hover { text-decoration: underline; }\r\n .wk-language { margin-left: auto; }\r\n .wk-contact { margin-top: 2rem; }\r\n .wk-field { display: grid; gap: 0.35rem; margin: 0.8rem 0; }\r\n .wk-field input, .wk-field textarea { font: inherit; padding: 0.55rem; }\r\n .wk-field p, .wk-submit-error { color: #b91c1c; margin: 0; }\r\n .wk-success { color: #047857; }\r\n \\`}</style>\r\n\r\n <main className=\"wk-home\" dir={locale === \"ar\" ? \"rtl\" : \"ltr\"}>\r\n <nav className=\"wk-links\" aria-label=\"Starter links\">\r\n <a href=\"https://warlock.js.org\" target=\"_blank\" rel=\"noreferrer\">Docs</a>\r\n <Link href=\"/\" aria-current=\"page\">Home</Link>\r\n <button\r\n className=\"wk-language\"\r\n type=\"button\"\r\n aria-pressed={locale === \"ar\"}\r\n onClick={toggleLocale}\r\n >\r\n {transX(\"starter.language\")}\r\n </button>\r\n </nav>\r\n\r\n <h1>{transX(\"starter.title\")}</h1>\r\n <p>{transX(\"starter.introduction\")}</p>\r\n\r\n <section className=\"wk-check\">\r\n <label>If this number goes up when you click, React is hydrated:</label>\r\n <strong>{count}</strong>\r\n <button type=\"button\" onClick={() => setCount(c => c + 1)}>\r\n Count up\r\n </button>\r\n </section>\r\n\r\n <section className=\"wk-contact\" aria-labelledby=\"contact-heading\">\r\n <h2 id=\"contact-heading\">{transX(\"starter.contact\")}</h2>\r\n <Form<typeof contactSchema>\n id=\"contact-form\"\n schema={contactSchema}\n onSubmit={async ({ form, values }) => {\r\n setSubmitted(false);\r\n setSubmitError(null);\r\n const result = await http.post<{ message: string }>(\"/api/contact\", values);\r\n\r\n if (result.error) {\r\n if (result.error.isValidationError) {\r\n const body = result.error.body as {\r\n errors?: Array<{ input: string; error: string }>;\r\n message?: string;\r\n };\r\n form.setErrors(\r\n Object.fromEntries(\r\n (body.errors ?? []).map(({ input, error }) => [input, error]),\r\n ),\r\n );\r\n setSubmitError(body.message ?? \"Please correct the highlighted fields.\");\r\n } else {\r\n setSubmitError(\"Your message could not be sent. Please try again.\");\r\n }\r\n return;\r\n }\r\n\r\n setSubmitted(true);\r\n form.reset();\r\n }}\r\n >\r\n <TextInput name=\"name\" label={transX(\"starter.name\")} autoComplete=\"name\" />\r\n <TextInput name=\"email\" label={transX(\"starter.email\")} type=\"email\" autoComplete=\"email\" />\r\n <ContactMessage />\r\n <button type=\"submit\">{transX(\"starter.submit\")}</button>\r\n {submitError && <p className=\"wk-submit-error\" role=\"alert\">{submitError}</p>}\r\n {submitted && <p className=\"wk-success\" role=\"status\">{transX(\"starter.sent\")}</p>}\r\n </Form>\r\n </section>\r\n </main>\r\n </>\r\n );\r\n}\r\n\r\nfunction ContactMessage() {\r\n const { error, getErrorProps, getInputProps } = useFormControl({ name: \"message\" });\r\n\r\n return (\r\n <div className=\"wk-field\">\r\n <label htmlFor=\"message\">{transX(\"starter.message\")}</label>\r\n <textarea {...getInputProps()} rows={5} />\r\n {error && <p {...getErrorProps()}>{error}</p>}\r\n </div>\r\n );\r\n}\r\n`;\r\n"],"mappings":";AAAA,MAAa,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BhC,MAAa,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+B5B,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCnC,MAAa,2BAA2B;;AAGxC,MAAa,0BAA0B;;;;;;;;;;;;AAavC,MAAa,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8FvC,MAAa,+BAA+B;;AAG5C,MAAa,8BAA8B;;;;;;;;;;;;;;;;;;;;;AAsB3C,MAAa,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6ClC,MAAa,mBAAmB;;;;;;;;;;;;;;;;;AAkBhC,MAAa,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkFvC,MAAa,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCvC,MAAa,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CrC,MAAa,4BAA4B;;;;;;;;;;;;;;;;;;;;;AAsBzC,MAAa,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsF3C,MAAa,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCtC,MAAa,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuD3B,MAAa,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;AA0BxC,MAAa,uBAAuB;;;;;;;;;;;;AAapC,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCnC,MAAa,kBAAkB"}
1
+ {"version":3,"file":"stubs.mjs","names":[],"sources":["../../../../../../../core/src/generations/stubs.ts"],"sourcesContent":["export const accessConfigStub = `import { type AccessConfigurations } from \"@warlock.js/access\";\r\nimport { DatabaseAccessResolver } from \"app/access/services/access-resolver\";\r\n\r\n/**\r\n * Authorization configuration — read by @warlock.js/access on boot.\r\n *\r\n * The resolver is the one required piece: it tells the engine how to read a\r\n * user's roles + permissions. The ejected DatabaseAccessResolver reads roles\r\n * from the user_roles table and maps them through the roles catalog table (so\r\n * roles + their permissions are managed at runtime, in the DB).\r\n *\r\n * For a fixed, code-defined catalog with no tables, swap in DefaultAccessResolver:\r\n * import { DefaultAccessResolver } from \"@warlock.js/access\";\r\n * resolver: new DefaultAccessResolver({ admin: [\"*\"], editor: [\"orders.*\"] }),\r\n *\r\n * Multi-tenant? Add a \\`resolveTenant()\\` to the resolver to read the active\r\n * tenant from the request; checks then scope to it automatically.\r\n */\r\nconst access: AccessConfigurations = {\r\n resolver: new DatabaseAccessResolver(),\r\n\r\n // Cache resolved permission sets (default \"10m\").\r\n // cache: { ttl: \"10m\" },\r\n};\r\n\r\nexport default access;\r\n`;\r\n\r\nexport const aiConfigStub = `import type { AIConfig } from \"@warlock.js/ai\";\r\n\r\n// >>> warlock:ai-packages (auto-managed) >>>\r\n// Satellite packages augment the \"ai\" object on import — e.g. ai.workspace,\r\n// ai.tools / ai.mcp, and panoptic's ai.config({ panoptic }) wiring. The command\r\n// \"warlock add ai-workspace | ai-tools | ai-panoptic\" adds the matching\r\n// side-effect import below; keep them so the augmentation + runtime registration\r\n// load before the ai connector applies this config.\r\n// <<< warlock:ai-packages <<<\r\n\r\n/**\r\n * AI configuration — applied on boot by the ai connector, which calls\r\n * ai.config(...) with the object below. Cross-cutting defaults live here\r\n * (shared cache / snapshot stores, observability); per-call options always win.\r\n *\r\n * Wire a default model from a provider you installed, e.g.:\r\n * import { OpenAISDK } from \"@warlock.js/ai-openai\";\r\n * const openai = OpenAISDK({ apiKey: env(\"OPENAI_API_KEY\") });\r\n * // then pass openai.model({ name: \"gpt-4o-mini\" }) into your agents.\r\n */\r\nconst ai: Partial<AIConfig> = {\r\n // Default cache driver for cache-backed AI features (semantic cache, rag / memory vector stores).\r\n // defaultStore: cache.driver(\"redis\", { client }),\r\n\r\n // Observability — requires \"warlock add ai-panoptic\". Exporters + the local dashboard.\r\n // panoptic: { exporters: [], dashboard: false, observeAll: false },\r\n};\r\n\r\nexport default ai;\r\n`;\r\n\r\nexport const accessRoleModelStub = `import { Model, RegisterModel } from \"@warlock.js/cascade\";\r\nimport { type Infer, v } from \"@warlock.js/seal\";\r\n\r\n/**\r\n * Validation schema for the roles catalog — mirrors the migration columns\r\n * (snake_case). Each row is a role name plus the permission strings it grants;\r\n * wildcards work (\"orders.*\", \"*\"). The DatabaseAccessResolver maps a user's\r\n * assigned role names through this table to their effective permissions.\r\n */\r\nexport const roleSchema = v.object({\r\n name: v.string(),\r\n permissions: v.array(v.string()).default([]),\r\n});\r\n\r\nexport type RoleSchema = Infer<typeof roleSchema>;\r\n\r\n/**\r\n * The roles catalog — role name → the permissions it grants. Managed at runtime\r\n * (admins add roles + edit their permissions), unlike a fixed code map. Read by\r\n * DatabaseAccessResolver.resolvePermissions to expand a user's roles to permissions.\r\n */\r\n@RegisterModel()\r\nexport class Role extends Model<RoleSchema> {\r\n public static table = \"roles\";\r\n\r\n public static schema = roleSchema;\r\n\r\n /** The permission strings this role grants. */\r\n public get permissions(): string[] {\r\n return this.get<string[]>(\"permissions\", []);\r\n }\r\n}\r\n`;\r\n\r\nexport const accessRoleModelIndexStub = `export * from \"./role.model\";\r\n`;\r\n\r\nexport const accessRoleMigrationStub = `import { arrayText, Migration, text } from \"@warlock.js/cascade\";\r\nimport { Role } from \"../role.model\";\r\n\r\n/**\r\n * Roles catalog table. \\`name\\` is unique (one row per role); \\`permissions\\` is a\r\n * text array of the permission strings the role grants.\r\n */\r\nexport default Migration.create(Role, {\r\n name: text().notNullable().unique(),\r\n permissions: arrayText().nullable(),\r\n});\r\n`;\r\n\r\nexport const accessUserRoleModelStub = `import { access } from \"@warlock.js/access\";\r\nimport type { Auth } from \"@warlock.js/auth\";\r\nimport { Model, RegisterModel } from \"@warlock.js/cascade\";\r\nimport { type Infer, v } from \"@warlock.js/seal\";\r\n\r\n/**\r\n * Validation schema for a role assignment — mirrors the migration columns\r\n * (snake_case). \\`tenant\\` is nullable: a null tenant is a GLOBAL assignment.\r\n */\r\nexport const userRoleSchema = v.object({\r\n user_id: v.string(),\r\n user_type: v.string(),\r\n role: v.string(),\r\n tenant: v.string().optional(),\r\n});\r\n\r\nexport type UserRoleSchema = Infer<typeof userRoleSchema>;\r\n\r\n/**\r\n * The role-assignment table — which roles a user holds, optionally per tenant.\r\n * Read by DatabaseAccessResolver.resolveRoles; mutated via the statics below.\r\n * \\`assign\\` / \\`revoke\\` flush the cached permission set automatically, so callers\r\n * never need to call \\`access.flush(user, tenant)\\` themselves.\r\n */\r\n@RegisterModel()\r\nexport class UserRole extends Model<UserRoleSchema> {\r\n public static table = \"user_roles\";\r\n\r\n public static schema = userRoleSchema;\r\n\r\n /**\r\n * Role names assigned to the user in the given tenant.\r\n *\r\n * An unresolved tenant (\\`undefined\\`) scopes to GLOBAL roles only — the rows\r\n * stored with no tenant (\\`null\\`) — never the union across every tenant. The\r\n * union would be a privilege-escalation: a user who is \\`owner\\` in one tenant\r\n * must not be treated as \\`owner\\` everywhere just because a check didn't carry\r\n * a tenant. This mirrors how \\`assign(user, role)\\` stores a global row.\r\n */\r\n public static async rolesFor(user: Auth, tenant?: string): Promise<string[]> {\r\n const rows = await this.query()\r\n .where({\r\n user_id: user.id,\r\n user_type: user.userType,\r\n tenant: tenant ?? null,\r\n })\r\n .get();\r\n\r\n // De-dupe so a duplicate row (a concurrent assign that slipped past the\r\n // existence check) can't distort the resolved set.\r\n return [...new Set(rows.map((row) => row.get(\"role\") as string))];\r\n }\r\n\r\n /**\r\n * Assign a role to the user. No-op if the assignment already exists.\r\n * Flushes the user's cached permission set automatically.\r\n */\r\n public static async assign(user: Auth, role: string, tenant?: string): Promise<void> {\r\n const existing = await this.first({\r\n user_id: user.id,\r\n user_type: user.userType,\r\n role,\r\n tenant: tenant ?? null,\r\n });\r\n\r\n if (existing) return;\r\n\r\n await this.create({\r\n user_id: user.id,\r\n user_type: user.userType,\r\n role,\r\n tenant,\r\n });\r\n\r\n await access.flush(user, tenant);\r\n }\r\n\r\n /**\r\n * Remove a role assignment from the user.\r\n * Flushes the user's cached permission set automatically.\r\n */\r\n public static async revoke(user: Auth, role: string, tenant?: string): Promise<void> {\r\n await this.delete({\r\n user_id: user.id,\r\n user_type: user.userType,\r\n role,\r\n tenant: tenant ?? null,\r\n });\r\n\r\n await access.flush(user, tenant);\r\n }\r\n}\r\n`;\r\n\r\nexport const accessUserRoleModelIndexStub = `export * from \"./user-role.model\";\r\n`;\r\n\r\nexport const accessUserRoleMigrationStub = `import { Migration, text, uuid } from \"@warlock.js/cascade\";\r\nimport { UserRole } from \"../user-role.model\";\r\n\r\n/**\r\n * Role-assignment table. \\`user_id\\` is a UUID — override this migration if your\r\n * user ids are integers. The composite index powers the per-user (per-tenant)\r\n * lookup the resolver runs on every check.\r\n */\r\nexport default Migration.create(\r\n UserRole,\r\n {\r\n user_id: uuid().notNullable().index(),\r\n user_type: text().notNullable(),\r\n role: text().notNullable().index(),\r\n tenant: text().nullable().index(),\r\n },\r\n {\r\n index: [{ columns: [\"user_id\", \"user_type\", \"tenant\"] }],\r\n },\r\n);\r\n`;\r\n\r\nexport const accessResolverStub = `import type { AccessResolver } from \"@warlock.js/access\";\r\nimport type { Auth } from \"@warlock.js/auth\";\r\nimport { Role } from \"app/access/models/role\";\r\nimport { UserRole } from \"app/access/models/user-role\";\r\n\r\n/**\r\n * The app's access adapter — connects @warlock.js/access to the ejected role\r\n * tables. Roles come from the user_roles assignment table; permissions are\r\n * expanded by mapping those role names through the roles catalog table. Both\r\n * are managed at runtime (in the DB), so admins can add roles + edit their\r\n * permissions without a deploy.\r\n *\r\n * The engine owns the hard parts (wildcard matching, caching, fail-closed); this\r\n * resolver only fetches — keep it dumb, never cache inside it.\r\n */\r\nexport class DatabaseAccessResolver implements AccessResolver {\r\n /** The role names this user holds (powers \\`hasRole\\` / \\`hasAnyRole\\`). */\r\n public async resolveRoles(user: Auth, tenant?: string): Promise<string[]> {\r\n return UserRole.rolesFor(user, tenant);\r\n }\r\n\r\n /** The effective permission strings this user has (powers \\`can\\` / \\`authorize\\`). */\r\n public async resolvePermissions(user: Auth, tenant?: string): Promise<string[]> {\r\n const names = await this.resolveRoles(user, tenant);\r\n\r\n if (names.length === 0) return [];\r\n\r\n const roles = await Role.query().whereIn(\"name\", names).get();\r\n\r\n // Flatten + de-dupe so two roles granting the same permission yield one entry.\r\n return [...new Set(roles.flatMap((role) => role.permissions))];\r\n }\r\n\r\n /**\r\n * Optional. Resolve the ambient tenant when a check doesn't pass one\r\n * explicitly — derive it from the authenticated user (safer than reading\r\n * client request input, which a caller could spoof). Uncomment + adapt for a\r\n * multi-tenant app (single-tenant apps leave this off and return undefined).\r\n */\r\n // public resolveTenant(user: Auth): string | undefined {\r\n // return user.get(\"organization_id\");\r\n // }\r\n}\r\n`;\r\n\r\nexport const socketConfigStub = `import type { SocketOptions } from \"@warlock.js/core\";\r\n\r\n/**\r\n * Socket.IO configuration — read by the framework's socket connector\r\n * on boot. When the HTTP server is running the socket server attaches\r\n * to it; otherwise it listens on its own configured port.\r\n *\r\n * Remove this file to disable the socket server entirely.\r\n */\r\nexport default {\r\n options: {\r\n cors: {\r\n origin: \"*\",\r\n },\r\n },\r\n} as SocketOptions;\r\n`;\r\n\r\nexport const communicatorsConfigStub = `import { env } from \"@warlock.js/core\";\r\nimport type { BrokerConfigurations, RabbitMQClientOptions } from \"@warlock.js/herald\";\r\n\r\nconst heraldConfigurations: BrokerConfigurations<RabbitMQClientOptions> = {\r\n driver: \"rabbitmq\",\r\n name: \"default\",\r\n isDefault: true,\r\n\r\n // ============================================================================\r\n // Connection Settings\r\n // ============================================================================\r\n\r\n host: env(\"RABBITMQ_HOST\", \"localhost\"),\r\n port: env(\"RABBITMQ_PORT\", 5672),\r\n username: env(\"RABBITMQ_USERNAME\", \"guest\"),\r\n password: env(\"RABBITMQ_PASSWORD\", \"guest\"),\r\n vhost: env(\"RABBITMQ_VHOST\", \"/\"),\r\n\r\n // Or use connection URI (takes precedence over host/port)\r\n // uri: env(\"RABBITMQ_URL\"),\r\n\r\n // ============================================================================\r\n // Connection Options\r\n // ============================================================================\r\n\r\n /** Heartbeat interval in seconds */\r\n heartbeat: 60,\r\n\r\n /** Connection timeout in milliseconds */\r\n connectionTimeout: 10000,\r\n\r\n /** Enable automatic reconnection on disconnect */\r\n reconnect: true,\r\n\r\n /** Delay between reconnection attempts in milliseconds */\r\n reconnectDelay: 5_000,\r\n\r\n // ============================================================================\r\n // Consumer Options\r\n // ============================================================================\r\n\r\n /** Default prefetch count (number of unacknowledged messages per consumer) */\r\n prefetch: 10,\r\n\r\n // ============================================================================\r\n // Client Options (Native amqplib options)\r\n // ============================================================================\r\n // These options are passed directly to amqplib.connect()\r\n // for low-level configuration like frame size, TLS, socket options, etc.\r\n // ============================================================================\r\n clientOptions: {\r\n // Frame max size in bytes (0 = no limit)\r\n // frameMax: 0,\r\n\r\n // Channel max (0 = unlimited)\r\n // channelMax: 0,\r\n\r\n // Socket options\r\n socket: {\r\n // Enable TCP keep-alive\r\n keepAlive: true,\r\n\r\n // Disable Nagle's algorithm for lower latency\r\n noDelay: true,\r\n\r\n // Socket timeout (in addition to heartbeat)\r\n // timeout: 30000,\r\n },\r\n\r\n // TLS/SSL options (uncomment for secure connections)\r\n // socket: {\r\n // ca: fs.readFileSync('/path/to/ca.pem'),\r\n // cert: fs.readFileSync('/path/to/cert.pem'),\r\n // key: fs.readFileSync('/path/to/key.pem'),\r\n // rejectUnauthorized: true,\r\n // },\r\n },\r\n};\r\n\r\nexport default heraldConfigurations;\r\n`;\r\n\r\nexport const notificationsConfigStub = `import { type NotificationConfig, inApp, mailChannel } from \"@warlock.js/notifications\";\r\nimport { Notification } from \"app/notifications/notification.model\";\r\n\r\n/**\r\n * Notifications configuration. Auto-loaded from src/config on boot — the\r\n * framework's notifications connector reads this default export and hands it to\r\n * setNotificationConfig, so this file stays declarative (no side-effect call).\r\n *\r\n * Each channel is payload-typed, so notify.mail(...) / notify.database(...)\r\n * and defineNotification are type-checked against the registry.\r\n *\r\n * Channels enabled here:\r\n * - mail wraps @warlock.js/core sendMail; route is notifiable.email.\r\n * The \"from\" address defaults to config/mail.ts; override per\r\n * channel with mailChannel({ from: \"no-reply@yourapp.com\" }).\r\n * - database in-app store backed by the Notification model. The \"inApp\"\r\n * facade exposes the recipient-scoped read API: listUnread,\r\n * countUnread, markAsRead, dismiss, ...\r\n *\r\n * Async delivery (.queue()) is OPTIONAL: run \"npx warlock add herald\",\r\n * import { heraldQueue } from \"@warlock.js/notifications\", and uncomment the\r\n * queue line below.\r\n */\r\nconst config: NotificationConfig = {\r\n channels: {\r\n mail: mailChannel(),\r\n database: inApp.configure({ model: Notification }),\r\n },\r\n\r\n // Async queue — requires @warlock.js/herald (npx warlock add herald):\r\n // queue: heraldQueue(),\r\n};\r\n\r\nexport default config;\r\n`;\r\n\r\nexport const notificationModelStub = `import { RegisterModel } from \"@warlock.js/cascade\";\r\nimport { DatabaseNotification, type NotificationColumnMap } from \"@warlock.js/notifications\";\r\nimport { v } from \"@warlock.js/seal\";\r\n\r\n/**\r\n * Validation schema for the notifications table — mirrors the migration\r\n * columns (snake_case). Cascade validates + casts every write against it:\r\n * nullable columns use .nullish() (may be absent or null), and payload is\r\n * free-form JSON. Keep this in sync with the migration + columnMap when you\r\n * add or rename columns.\r\n */\r\nconst notificationSchema = v.object({\r\n user_id: v.string(),\r\n type: v.string(),\r\n title: v.string(),\r\n body: v.string().nullish(),\r\n payload: v.record(v.any()).nullish(),\r\n read_at: v.date().nullish(),\r\n idempotency_key: v.string().nullish(),\r\n});\r\n\r\n/**\r\n * In-app notification model.\r\n *\r\n * Extends the package's DatabaseNotification base, which provides the stable\r\n * accessors (recipientId, tenantId, isRead, readAt, markRead) — all derived\r\n * from the columnMap below. The read/write API lives on the inApp facade\r\n * (configured in config/notifications.ts); you rarely touch this class directly.\r\n */\r\n@RegisterModel()\r\nexport class Notification extends DatabaseNotification {\r\n public static table = \"notifications\";\r\n public static schema = notificationSchema;\r\n\r\n /**\r\n * Maps the in-app store's roles to your columns. This default is\r\n * single-tenant + read_at-only. Add tenant: \"organization_id\" for\r\n * multi-tenant; use isRead: \"is_read\" (instead of, or alongside, readAt) to\r\n * track a boolean read flag. The migration + accessors all follow this map.\r\n */\r\n public static columnMap: NotificationColumnMap = { readAt: \"read_at\" };\r\n}\r\n`;\r\n\r\nexport const notificationMigrationStub = `import { Migration } from \"@warlock.js/cascade\";\r\nimport { notificationColumns } from \"@warlock.js/notifications\";\r\nimport { Notification } from \"../notification.model\";\r\n\r\n/**\r\n * Notifications table.\r\n *\r\n * Columns come from notificationColumns(Notification) — the recipient / tenant\r\n * / read-state names follow the model's columnMap; type / title / body /\r\n * payload / idempotency_key are fixed. Spread it to add your own columns\r\n * (remember to mirror them in the model schema):\r\n *\r\n * import { uuid } from \"@warlock.js/cascade\";\r\n *\r\n * export default Migration.create(Notification, {\r\n * ...notificationColumns(Notification),\r\n * // category_id: uuid().index().nullable(),\r\n * });\r\n */\r\nexport default Migration.create(Notification, notificationColumns(Notification));\r\n`;\r\n\r\nexport const notificationControllersStub = `import { type RequestHandler } from \"@warlock.js/core\";\r\nimport { inApp } from \"@warlock.js/notifications\";\r\n\r\n/**\r\n * The authenticated user's notification HTTP surface — thin wrappers over the\r\n * recipient-scoped \\`inApp\\` facade (a foreign id can never touch another user's\r\n * rows). Notifications are produced by domain events, never over HTTP, so there\r\n * is no create. Trim or split these as your app grows.\r\n */\r\n\r\n/** GET /notifications — list, most recent first (page / limit / type / unread via query). */\r\nexport const listNotificationsController: RequestHandler = async ({ request, response }) => {\r\n const { data, pagination } = await inApp.list(request.user!, request.all());\r\n\r\n return response.success({ notifications: data, pagination });\r\n};\r\n\r\nlistNotificationsController.description = \"List notifications\";\r\n\r\n/** GET /notifications/unread-count — drives the bell badge. */\r\nexport const unreadNotificationsCountController: RequestHandler = async ({\r\n request,\r\n response,\r\n}) => {\r\n const count = await inApp.countUnread(request.user!);\r\n\r\n return response.success({ count });\r\n};\r\n\r\nunreadNotificationsCountController.description = \"Unread notifications count\";\r\n\r\n/** PATCH /notifications/:id/read — mark one read, return the updated row. */\r\nexport const markNotificationReadController: RequestHandler = async ({ request, response }) => {\r\n const id = request.input(\"id\");\r\n\r\n await inApp.markAsRead(request.user!, id);\r\n const notification = await inApp.find(request.user!, id);\r\n\r\n return response.success({ notification });\r\n};\r\n\r\nmarkNotificationReadController.description = \"Mark notification read\";\r\n\r\n/** PATCH /notifications/read-all — mark every unread one read. */\r\nexport const markAllNotificationsReadController: RequestHandler = async ({\r\n request,\r\n response,\r\n}) => {\r\n const count = await inApp.markAsRead(request.user!);\r\n\r\n return response.success({ count });\r\n};\r\n\r\nmarkAllNotificationsReadController.description = \"Mark all notifications read\";\r\n\r\n/** DELETE /notifications — dismiss all for the user. */\r\nexport const clearNotificationsController: RequestHandler = async ({ request, response }) => {\r\n await inApp.dismiss(request.user!);\r\n\r\n return response.noContent();\r\n};\r\n\r\nclearNotificationsController.description = \"Clear notifications\";\r\n\r\n/** DELETE /notifications/:id — dismiss one. */\r\nexport const deleteNotificationController: RequestHandler = async ({ request, response }) => {\r\n await inApp.dismiss(request.user!, request.input(\"id\"));\r\n\r\n return response.noContent();\r\n};\r\n\r\ndeleteNotificationController.description = \"Delete notification\";\r\n`;\r\n\r\nexport const notificationRoutesStub = `import { authMiddleware } from \"@warlock.js/auth\";\r\nimport { router } from \"@warlock.js/core\";\r\nimport {\r\n clearNotificationsController,\r\n deleteNotificationController,\r\n listNotificationsController,\r\n markAllNotificationsReadController,\r\n markNotificationReadController,\r\n unreadNotificationsCountController,\r\n} from \"./controllers/notifications.controller\";\r\n\r\n/**\r\n * Notification routes — the authenticated user's read + dismiss surface.\r\n *\r\n * Notifications are produced by domain events (never created over HTTP), so\r\n * there is no POST. Every route is gated by \\`authMiddleware\\` and recipient-\r\n * scoped by \\`inApp\\` (a foreign id touches zero rows). Delete any endpoint you\r\n * don't need; if your app reads notifications over sockets/GraphQL instead,\r\n * delete this file + the controllers entirely.\r\n */\r\nrouter.group({ prefix: \"/notifications\", middleware: [authMiddleware([])] }, () => {\r\n router.get(\"/\", listNotificationsController);\r\n router.get(\"/unread-count\", unreadNotificationsCountController);\r\n router.patch(\"/read-all\", markAllNotificationsReadController);\r\n router.patch(\"/:id/read\", markNotificationReadController);\r\n router.delete(\"/\", clearNotificationsController);\r\n router.delete(\"/:id\", deleteNotificationController);\r\n});\r\n`;\r\n\r\n/**\r\n * `src/web/root.tsx` — the application root for the SSR page layer.\r\n *\r\n * Deliberately minimal. The framework ships a default root, so this exists to\r\n * give you a place to start rather than because anything requires it. The\r\n * reference app (`v5/app/src/web/root.tsx`) is where to look for the fuller\r\n * shape: middleware, an app-level loader, locales, an ErrorBoundary.\r\n */\r\nexport const webRootStub = `import { Head, Scripts } from \"@warlock.js/web\";\r\nimport type { AppProps } from \"@warlock.js/web\";\r\n\r\n/**\r\n * The application root.\r\n *\r\n * NOT async, and it receives no request/response: it renders on the server and\r\n * again in the browser during hydration, where neither exists.\r\n */\r\nexport default function App({ children }: AppProps) {\r\n return (\r\n <html lang=\"en\">\r\n <head>\r\n {/*\r\n Placement only. The framework injects the page's \\`metadata\\`, the\r\n stylesheet and preload tags for this route, and the canonical links\r\n into <head> by default — <Head /> just says WHERE they land.\r\n\r\n Do not add a <title> here: the page's \\`metadata\\` owns it, and a root\r\n that emits one too produces two.\r\n */}\r\n <Head />\r\n <link rel=\"icon\" href=\"data:,\" />\r\n </head>\r\n <body>\r\n {/*\r\n REQUIRED — this is the hydration mount point, not a styling wrapper.\r\n\r\n The browser runtime looks up \\`#root\\` and hydrates that element only.\r\n Remove this div, or rename the id, and the page still renders from the\r\n server but never becomes interactive: the runtime throws in the console\r\n and nothing on screen changes.\r\n\r\n Wrap it in your own markup freely, and put anything that must live\r\n outside the hydrated tree (a static footer, a portal target) outside\r\n it — just keep an element with \\`id=\"root\"\\` around {children}.\r\n */}\r\n <div id=\"root\">{children}</div>\r\n {/*\r\n The hydration payload and module tags. Written explicitly because\r\n placement occasionally matters — a CSP nonce, or ordering against\r\n your own scripts.\r\n */}\r\n <Scripts />\r\n </body>\r\n </html>\r\n );\r\n}\r\n`;\r\n\r\n/**\r\n * `src/app/contact/controllers/contact.controller.ts` — a real API endpoint\r\n * for the Web starter's contact form. It intentionally has no persistence\r\n * dependency: replace the acknowledgement with a mail/job/database action.\r\n */\r\nexport const webContactControllerStub = `import { type Request, type RequestHandler } from \"@warlock.js/core\";\r\nimport { type Infer, v } from \"@warlock.js/seal\";\r\n\r\nexport const contactSchema = v.object({\r\n name: v.string().min(2).required(),\r\n email: v.email().required(),\r\n message: v.string().min(10).required(),\r\n});\r\n\r\nexport type ContactSchema = Infer.Output<typeof contactSchema>;\r\n\r\n/** POST /api/contact — validates the starter contact form. */\r\nexport const contactController: RequestHandler<Request<ContactSchema>> = async ({ request, response }) => {\r\n const contact = request.validated();\r\n\r\n // Replace this with delivery/persistence for your app. Keeping the accepted\r\n // payload visible makes the endpoint useful while remaining side-effect free.\r\n return response.success({\r\n message: \"Thanks, \" + contact.name + \". Your message has been received.\",\r\n });\r\n};\r\n\r\ncontactController.validation = { schema: contactSchema };\r\n`;\r\n\r\n/** `src/app/contact/routes.ts` — discovered by the standard app route loader. */\r\nexport const webContactRoutesStub = `import { router } from \"@warlock.js/core\";\r\nimport { contactController } from \"./controllers/contact.controller\";\r\n\r\nrouter.post(\"/api/contact\", contactController);\r\n`;\r\n\r\n/**\r\n * `src/web/index.register.ts` — universal static setup for the starter page.\r\n *\r\n * The page re-exports this stable binding so Warlock's `register()` lifecycle\r\n * still sees it in both realms without making React Fast Refresh treat every\r\n * JSX edit as an incompatible function-export replacement.\r\n */\r\nexport const webHomeRegisterStub = `import { extend } from \"@mongez/localization\";\r\n\r\nexport function register() {\r\n extend(\"en\", {\r\n starter: {\r\n title: \"Your Warlock app is running.\",\r\n introduction: \"This page is rendered on the server and hydrated in the browser.\",\r\n language: \"العربية\",\r\n contact: \"Send a message\",\r\n name: \"Name\",\r\n email: \"Email\",\r\n message: \"Message\",\r\n submit: \"Send message\",\r\n sent: \"Thanks — your message has been received.\",\r\n },\r\n });\r\n extend(\"ar\", {\r\n starter: {\r\n title: \"تطبيق Warlock يعمل الآن.\",\r\n introduction: \"تُعرض هذه الصفحة على الخادم ثم تُفعَّل في المتصفح.\",\r\n language: \"English\",\r\n contact: \"أرسل رسالة\",\r\n name: \"الاسم\",\r\n email: \"البريد الإلكتروني\",\r\n message: \"الرسالة\",\r\n submit: \"إرسال الرسالة\",\r\n sent: \"شكرًا — تم استلام رسالتك.\",\r\n },\r\n });\r\n}\r\n`;\r\n\r\n/**\r\n * `src/web/index.page.tsx` — one page, so \\`warlock dev\\` has something to serve\r\n * the moment this finishes.\r\n */\r\nexport const webHomePageStub = `import { http } from \"@mongez/http\";\r\nimport { Form, useFormControl, type FormControlProps } from \"@mongez/react-form\";\r\nimport { setCurrentLocaleCode } from \"@mongez/localization\";\r\nimport { transX } from \"@mongez/react-localization\";\r\nimport { v } from \"@warlock.js/seal\";\r\nimport { useState } from \"react\";\r\nimport { Link, type PageProps } from \"@warlock.js/web\";\r\n\r\nexport { register } from \"./index.register\";\r\n\r\n/**\r\n * A page route is an ordinary Warlock route whose handler renders React\r\n * instead of returning JSON.\r\n *\r\n * The URL and stable hydration name are the ones this file DECLARES below.\r\n * This page answers \\`GET \"/\"\\` because \\`route.path = \"/\"\\`, not because of\r\n * where the file lives. A page file with\r\n * no \\`route\\` export is REFUSED by both the dev server and the build.\r\n */\r\nexport const route = { path: \"/\", name: \"index\" } as const;\r\n\r\nexport const metadata = { title: \"Home\" };\r\n\r\nconst contactSchema = v.object({\r\n name: v.string().min(2).required(),\r\n email: v.email().required(),\r\n message: v.string().min(10).required(),\r\n});\r\n\r\nfunction TextInput({ label, ...controlProps }: FormControlProps & { label: string }) {\r\n const { error, getErrorProps, getInputProps } = useFormControl(controlProps);\r\n\r\n return (\r\n <div className=\"wk-field\">\r\n <label htmlFor={controlProps.name}>{label}</label>\r\n <input {...getInputProps()} />\r\n {error && <p {...getErrorProps()}>{error}</p>}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Add a \\`loader\\` export to fetch data on the server, and it arrives here as\r\n * \\`data\\`, typed:\r\n *\r\n * export const loader = (async () => ({ items: await itemsRepository.all() }));\r\n * export default function HomePage({ data }: PageProps<typeof loader>) { ... }\r\n */\r\nexport default function HomePage(_props: PageProps) {\r\n // Live state. If the button below does nothing, the page rendered on the\r\n // server but never hydrated — the runtime never mounted at \\`#root\\`. This is\r\n // deliberately here so that failure is impossible to miss.\r\n const [count, setCount] = useState(0);\r\n const [locale, setLocale] = useState<\"en\" | \"ar\">(\"en\");\r\n const [submitted, setSubmitted] = useState(false);\r\n const [submitError, setSubmitError] = useState<string | null>(null);\r\n\r\n const toggleLocale = () => {\r\n const nextLocale = locale === \"en\" ? \"ar\" : \"en\";\r\n setCurrentLocaleCode(nextLocale);\r\n setLocale(nextLocale);\r\n };\r\n\r\n return (\r\n <>\r\n {/*\r\n Self-contained, dependency-free styling: plain CSS, system fonts, and\r\n CSS custom properties, scoped to this page. No CSS framework, no utility\r\n classes, no external stylesheet — this page looks the same whether or\r\n not \\`warlock add tailwind\\` has ever been run.\r\n */}\r\n <style>{\\`\r\n .wk-home {\r\n --wk-fg: #0f172a;\r\n --wk-muted: #64748b;\r\n --wk-accent: #4f46e5;\r\n --wk-border: #e2e8f0;\r\n font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\r\n color: var(--wk-fg);\r\n max-width: 42rem;\r\n margin: 4rem auto;\r\n padding: 0 1.5rem;\r\n line-height: 1.6;\r\n }\r\n .wk-home h1 { font-size: 2.25rem; margin: 0 0 0.5rem; }\r\n .wk-home p { color: var(--wk-muted); margin: 0 0 1.5rem; }\r\n .wk-home code {\r\n font-family: ui-monospace, \"SFMono-Regular\", Menlo, monospace;\r\n background: #f1f5f9;\r\n padding: 0.1rem 0.35rem;\r\n border-radius: 0.25rem;\r\n }\r\n .wk-check {\r\n border: 1px solid var(--wk-border);\r\n border-radius: 0.75rem;\r\n padding: 1.25rem 1.5rem;\r\n margin: 2rem 0;\r\n }\r\n .wk-check strong { display: block; font-size: 1.5rem; }\r\n .wk-check button {\r\n font: inherit;\r\n cursor: pointer;\r\n background: var(--wk-accent);\r\n color: #fff;\r\n border: 0;\r\n border-radius: 0.5rem;\r\n padding: 0.5rem 1rem;\r\n margin-top: 0.75rem;\r\n }\r\n .wk-links { display: flex; gap: 1.25rem; font-size: 0.95rem; }\r\n .wk-links a { color: var(--wk-accent); text-decoration: none; }\r\n .wk-links a:hover { text-decoration: underline; }\r\n .wk-language { margin-left: auto; }\r\n .wk-contact { margin-top: 2rem; }\r\n .wk-field { display: grid; gap: 0.35rem; margin: 0.8rem 0; }\r\n .wk-field input, .wk-field textarea { font: inherit; padding: 0.55rem; }\r\n .wk-field p, .wk-submit-error { color: #b91c1c; margin: 0; }\r\n .wk-success { color: #047857; }\r\n \\`}</style>\r\n\r\n <main className=\"wk-home\" dir={locale === \"ar\" ? \"rtl\" : \"ltr\"}>\r\n <nav className=\"wk-links\" aria-label=\"Starter links\">\r\n <a href=\"https://warlock.js.org\" target=\"_blank\" rel=\"noreferrer\">Docs</a>\r\n <Link href=\"/\" aria-current=\"page\">Home</Link>\r\n <button\r\n className=\"wk-language\"\r\n type=\"button\"\r\n aria-pressed={locale === \"ar\"}\r\n onClick={toggleLocale}\r\n >\r\n {transX(\"starter.language\")}\r\n </button>\r\n </nav>\r\n\r\n <h1>{transX(\"starter.title\")}</h1>\r\n <p>{transX(\"starter.introduction\")}</p>\r\n\r\n <section className=\"wk-check\">\r\n <label>If this number goes up when you click, React is hydrated:</label>\r\n <strong>{count}</strong>\r\n <button type=\"button\" onClick={() => setCount(c => c + 1)}>\r\n Count up\r\n </button>\r\n </section>\r\n\r\n <section className=\"wk-contact\" aria-labelledby=\"contact-heading\">\r\n <h2 id=\"contact-heading\">{transX(\"starter.contact\")}</h2>\r\n <Form<typeof contactSchema>\r\n id=\"contact-form\"\r\n schema={contactSchema}\r\n onSubmit={async ({ form, values }) => {\r\n setSubmitted(false);\r\n setSubmitError(null);\r\n const result = await http.post<{ message: string }>(\"/api/contact\", values);\r\n\r\n if (result.error) {\r\n if (result.error.isValidationError) {\r\n const body = result.error.body as {\r\n errors?: Array<{ input: string; error: string }>;\r\n message?: string;\r\n };\r\n form.setErrors(\r\n Object.fromEntries(\r\n (body.errors ?? []).map(({ input, error }) => [input, error]),\r\n ),\r\n );\r\n setSubmitError(body.message ?? \"Please correct the highlighted fields.\");\r\n } else {\r\n setSubmitError(\"Your message could not be sent. Please try again.\");\r\n }\r\n return;\r\n }\r\n\r\n setSubmitted(true);\r\n form.reset();\r\n }}\r\n >\r\n <TextInput name=\"name\" label={transX(\"starter.name\")} autoComplete=\"name\" />\r\n <TextInput name=\"email\" label={transX(\"starter.email\")} type=\"email\" autoComplete=\"email\" />\r\n <ContactMessage />\r\n <button type=\"submit\">{transX(\"starter.submit\")}</button>\r\n {submitError && <p className=\"wk-submit-error\" role=\"alert\">{submitError}</p>}\r\n {submitted && <p className=\"wk-success\" role=\"status\">{transX(\"starter.sent\")}</p>}\r\n </Form>\r\n </section>\r\n </main>\r\n </>\r\n );\r\n}\r\n\r\nfunction ContactMessage() {\r\n const { error, getErrorProps, getInputProps } = useFormControl({ name: \"message\" });\r\n\r\n return (\r\n <div className=\"wk-field\">\r\n <label htmlFor=\"message\">{transX(\"starter.message\")}</label>\r\n <textarea {...getInputProps()} rows={5} />\r\n {error && <p {...getErrorProps()}>{error}</p>}\r\n </div>\r\n );\r\n}\r\n`;\r\n"],"mappings":";AAAA,MAAa,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BhC,MAAa,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+B5B,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCnC,MAAa,2BAA2B;;AAGxC,MAAa,0BAA0B;;;;;;;;;;;;AAavC,MAAa,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8FvC,MAAa,+BAA+B;;AAG5C,MAAa,8BAA8B;;;;;;;;;;;;;;;;;;;;;AAsB3C,MAAa,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6ClC,MAAa,mBAAmB;;;;;;;;;;;;;;;;;AAkBhC,MAAa,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkFvC,MAAa,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCvC,MAAa,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CrC,MAAa,4BAA4B;;;;;;;;;;;;;;;;;;;;;AAsBzC,MAAa,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0E3C,MAAa,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCtC,MAAa,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuD3B,MAAa,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;AA0BxC,MAAa,uBAAuB;;;;;;;;;;;;AAapC,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCnC,MAAa,kBAAkB"}
@@ -2,7 +2,7 @@ import { FileNamingStrategy, ImageTransformCallback, ImageTransformConfig, Prefi
2
2
  import { FileValidationOptions, UploadedFile, UploadedFileJson } from "./uploaded-file.mjs";
3
3
  import { Request } from "./request.mjs";
4
4
  import { CookieOptions, Response, ResponseStatus, SendBufferOptions, SendFileOptions } from "./response.mjs";
5
- import { HttpConfigurations, PartialMiddleware, RequestEvent, RequestLocals, RequestUser, ResponseEvent, ResponseSSEController, ResponseStreamController, ReturnedResponse } from "./types.mjs";
5
+ import { DecodedAccessToken, HttpConfigurations, PartialMiddleware, RequestEvent, RequestLocals, RequestUser, ResponseEvent, ResponseSSEController, ResponseStreamController, ReturnedResponse } from "./types.mjs";
6
6
  import { defaultHttpConfigurations, httpConfig } from "./config.mjs";
7
7
  import { createHttpApplication, stopHttpApplication } from "./createHttpApplication.mjs";
8
8
  import { RequestLog } from "./database/RequestLog.mjs";
@@ -1 +1 @@
1
- {"version":3,"file":"inject-request-context.d.mts","names":[],"sources":["../../../../../../../../core/src/http/middleware/inject-request-context.ts"],"mappings":";;;;;;;AAiDA;;;iBAAgB,kBAAA,CACd,OAAA,EAAS,OAAA,OACT,QAAA,EAAU,QAAA,GACT,OAAA,CAAQ,gBAAA;;;;iBAkHK,CAAA,CAAE,OAAA,UAAiB,YAAkB"}
1
+ {"version":3,"file":"inject-request-context.d.mts","names":[],"sources":["../../../../../../../../core/src/http/middleware/inject-request-context.ts"],"mappings":";;;;;;;AA0CA;;;iBAAgB,kBAAA,CACd,OAAA,EAAS,OAAA,OACT,QAAA,EAAU,QAAA,GACT,OAAA,CAAQ,gBAAA;;;;iBAiGK,CAAA,CAAE,OAAA,UAAiB,YAAkB"}
@@ -1,7 +1,7 @@
1
1
  import { environment } from "../../utils/environment.mjs";
2
2
  import { requestContext } from "../context/request-context.mjs";
3
3
  import "../../utils/index.mjs";
4
- import { BadRequestError, ForbiddenError, HttpError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "../errors/errors.mjs";
4
+ import { HttpError } from "../errors/errors.mjs";
5
5
  import config from "@mongez/config";
6
6
  import { trans } from "@mongez/localization";
7
7
  import { DatabaseWriterValidationError } from "@warlock.js/cascade";
@@ -63,33 +63,14 @@ function createRequestStore(request, response) {
63
63
  * @internal
64
64
  */
65
65
  function handleRequestError(error, response) {
66
+ response.header("Cache-Control", "private, no-store");
66
67
  if (error instanceof HttpError) {
67
68
  const payload = { error: error.message };
68
69
  if (error.payload) payload.payload = error.payload;
69
70
  if (environment() === "development") payload.stack = error.stack;
70
71
  return response.setStatusCode(error.status).send(payload);
71
72
  }
72
- if (error instanceof ResourceNotFoundError) return response.notFound({
73
- error: error.message,
74
- ...error.payload
75
- });
76
- if (error instanceof UnAuthorizedError) return response.unauthorized({
77
- error: error.message,
78
- ...error.payload
79
- });
80
- if (error instanceof ForbiddenError) return response.forbidden({
81
- error: error.message,
82
- ...error.payload
83
- });
84
- if (error instanceof BadRequestError) return response.badRequest({
85
- error: error.message,
86
- ...error.payload
87
- });
88
73
  if (error instanceof DatabaseWriterValidationError) return response.badRequest({ errors: error.errors });
89
- if (error instanceof ServerError) return response.serverError({
90
- error: error.message,
91
- ...error.payload
92
- });
93
74
  console.error("[warlock] unhandled request error:", error);
94
75
  return response.serverError({ error: "Internal server error." });
95
76
  }
@@ -1 +1 @@
1
- {"version":3,"file":"inject-request-context.mjs","names":["requestContextInstance"],"sources":["../../../../../../../../core/src/http/middleware/inject-request-context.ts"],"sourcesContent":["/**\r\n * Request Context Middleware\r\n *\r\n * Creates a unified context for each request using the ContextManager.\r\n * All framework contexts (request, storage, database) are available throughout the request lifecycle.\r\n */\r\nimport { trans } from \"@mongez/localization\";\r\nimport { type GenericObject } from \"@mongez/reinforcements\";\r\nimport { DatabaseWriterValidationError } from \"@warlock.js/cascade\";\r\nimport { contextManager } from \"@warlock.js/context\";\r\nimport config from \"@mongez/config\";\r\nimport { environment } from \"../../utils\";\r\nimport { requestContext as requestContextInstance } from \"../context/request-context\";\r\nimport {\r\n BadRequestError,\r\n ForbiddenError,\r\n HttpError,\r\n ResourceNotFoundError,\r\n ServerError,\r\n UnAuthorizedError,\r\n} from \"../errors\";\r\nimport { type Request } from \"../request\";\r\nimport { type Response } from \"../response\";\r\nimport { type ReturnedResponse } from \"./../types\";\r\n\r\n// Contexts are now registered in core/context/init-contexts.ts via initializeContexts()\r\n\r\n/**\r\n * Echo `request.id` back as a response header so the FE / proxies / log\r\n * aggregators can correlate by the same value the server logs against.\r\n *\r\n * Reads the header name from `http.requestId.header` (default `X-Request-Id`).\r\n * Skip when `http.requestId.enabled` is explicitly false.\r\n */\r\nfunction stampRequestIdHeader(request: Request, response: Response) {\r\n const requestIdConfig = config.get(\"http.requestId\", {} as Record<string, any>);\r\n\r\n if (requestIdConfig.enabled === false) return;\r\n\r\n const headerName = requestIdConfig.header || \"X-Request-Id\";\r\n\r\n response.header(headerName, request.id);\r\n}\r\n\r\n/**\r\n * Create request store and execute middleware + handler\r\n *\r\n * Runs all registered contexts together using ContextManager.\r\n */\r\nexport function createRequestStore(\r\n request: Request<any>,\r\n response: Response,\r\n): Promise<ReturnedResponse> {\r\n stampRequestIdHeader(request, response);\r\n\r\n // Build all context stores using the immutable API\r\n // Each context defines its own store initialization via buildStore()\r\n const httpContextStore = contextManager.buildStores({ request, response });\r\n\r\n // Run all contexts together!\r\n return contextManager.runAll(httpContextStore, async () => {\r\n try {\r\n // Run middleware chain\r\n const result = await request.runMiddleware();\r\n\r\n if (result) {\r\n return result as ReturnedResponse;\r\n }\r\n\r\n // Execute route handler\r\n request.trigger(\"executingAction\", request.route);\r\n\r\n const handler = request.getHandler();\r\n\r\n request.log(\"Executing Handler\", \"info\");\r\n\r\n const output = await handler({ request, response });\r\n\r\n request.log(\"Handler Executed Successfully\", \"success\");\r\n\r\n request.trigger(\"executedAction\", request.route);\r\n\r\n return output as ReturnedResponse;\r\n } catch (error) {\r\n request.log(error, \"error\");\r\n return handleRequestError(error, response);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Handle request errors\r\n * @internal\r\n */\r\nfunction handleRequestError(error: unknown, response: Response): ReturnedResponse {\r\n if (error instanceof HttpError) {\r\n const payload: GenericObject = {\r\n error: error.message,\r\n };\r\n if (error.payload) {\r\n payload.payload = error.payload;\r\n }\r\n\r\n if (environment() === \"development\") {\r\n payload.stack = error.stack;\r\n }\r\n\r\n return response.setStatusCode(error.status).send(payload);\r\n }\r\n\r\n if (error instanceof ResourceNotFoundError) {\r\n return response.notFound({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof UnAuthorizedError) {\r\n return response.unauthorized({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof ForbiddenError) {\r\n return response.forbidden({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof BadRequestError) {\r\n return response.badRequest({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof DatabaseWriterValidationError) {\r\n return response.badRequest({\r\n errors: error.errors,\r\n });\r\n }\r\n\r\n if (error instanceof ServerError) {\r\n return response.serverError({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n // Last resort: the error matched none of the known shapes above, so the\r\n // client gets a deliberately opaque message. Without this line the error\r\n // itself is discarded here — no stack, no message, nothing in any log — and\r\n // an unrecognised failure becomes indistinguishable from a working server\r\n // returning 500. Never swallow the only copy of an error (`65e476ee`).\r\n console.error(\"[warlock] unhandled request error:\", error);\r\n\r\n return response.serverError({\r\n error: \"Internal server error.\",\r\n });\r\n}\r\n\r\n/**\r\n * Translate a keyword (uses request context for locale)\r\n */\r\nexport function t(keyword: string, placeholders?: any) {\r\n return (\r\n requestContextInstance.getRequest()?.trans(keyword, placeholders) ||\r\n trans(keyword, placeholders)\r\n );\r\n}\r\n\r\n// `fromRequest` was removed in v5. It cached computed values as dynamic\r\n// properties on the Request instance, which only compiled because of the\r\n// `[key: string]: any` index signature that v5 deletes (eed20184). Use\r\n// `requestMemo(key, fn)` from `../context/request-memo` instead — same\r\n// per-request lifetime, single-flight, and it never touches the Request object.\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAS,qBAAqB,SAAkB,UAAoB;CAClE,MAAM,kBAAkB,OAAO,IAAI,kBAAkB,CAAC,CAAwB;CAE9E,IAAI,gBAAgB,YAAY,OAAO;CAEvC,MAAM,aAAa,gBAAgB,UAAU;CAE7C,SAAS,OAAO,YAAY,QAAQ,EAAE;AACxC;;;;;;AAOA,SAAgB,mBACd,SACA,UAC2B;CAC3B,qBAAqB,SAAS,QAAQ;CAItC,MAAM,mBAAmB,eAAe,YAAY;EAAE;EAAS;CAAS,CAAC;CAGzE,OAAO,eAAe,OAAO,kBAAkB,YAAY;EACzD,IAAI;GAEF,MAAM,SAAS,MAAM,QAAQ,cAAc;GAE3C,IAAI,QACF,OAAO;GAIT,QAAQ,QAAQ,mBAAmB,QAAQ,KAAK;GAEhD,MAAM,UAAU,QAAQ,WAAW;GAEnC,QAAQ,IAAI,qBAAqB,MAAM;GAEvC,MAAM,SAAS,MAAM,QAAQ;IAAE;IAAS;GAAS,CAAC;GAElD,QAAQ,IAAI,iCAAiC,SAAS;GAEtD,QAAQ,QAAQ,kBAAkB,QAAQ,KAAK;GAE/C,OAAO;EACT,SAAS,OAAO;GACd,QAAQ,IAAI,OAAO,OAAO;GAC1B,OAAO,mBAAmB,OAAO,QAAQ;EAC3C;CACF,CAAC;AACH;;;;;AAMA,SAAS,mBAAmB,OAAgB,UAAsC;CAChF,IAAI,iBAAiB,WAAW;EAC9B,MAAM,UAAyB,EAC7B,OAAO,MAAM,QACf;EACA,IAAI,MAAM,SACR,QAAQ,UAAU,MAAM;EAG1B,IAAI,YAAY,MAAM,eACpB,QAAQ,QAAQ,MAAM;EAGxB,OAAO,SAAS,cAAc,MAAM,MAAM,CAAC,CAAC,KAAK,OAAO;CAC1D;CAEA,IAAI,iBAAiB,uBACnB,OAAO,SAAS,SAAS;EACvB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,mBACnB,OAAO,SAAS,aAAa;EAC3B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,gBACnB,OAAO,SAAS,UAAU;EACxB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,iBACnB,OAAO,SAAS,WAAW;EACzB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,+BACnB,OAAO,SAAS,WAAW,EACzB,QAAQ,MAAM,OAChB,CAAC;CAGH,IAAI,iBAAiB,aACnB,OAAO,SAAS,YAAY;EAC1B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAQH,QAAQ,MAAM,sCAAsC,KAAK;CAEzD,OAAO,SAAS,YAAY,EAC1B,OAAO,yBACT,CAAC;AACH;;;;AAKA,SAAgB,EAAE,SAAiB,cAAoB;CACrD,OACEA,eAAuB,WAAW,CAAC,EAAE,MAAM,SAAS,YAAY,KAChE,MAAM,SAAS,YAAY;AAE/B"}
1
+ {"version":3,"file":"inject-request-context.mjs","names":["requestContextInstance"],"sources":["../../../../../../../../core/src/http/middleware/inject-request-context.ts"],"sourcesContent":["/**\n * Request Context Middleware\n *\n * Creates a unified context for each request using the ContextManager.\n * All framework contexts (request, storage, database) are available throughout the request lifecycle.\n */\nimport { trans } from \"@mongez/localization\";\nimport { type GenericObject } from \"@mongez/reinforcements\";\nimport { DatabaseWriterValidationError } from \"@warlock.js/cascade\";\nimport { contextManager } from \"@warlock.js/context\";\nimport config from \"@mongez/config\";\nimport { environment } from \"../../utils\";\nimport { requestContext as requestContextInstance } from \"../context/request-context\";\nimport { HttpError } from \"../errors\";\nimport { type Request } from \"../request\";\nimport { type Response } from \"../response\";\nimport { type ReturnedResponse } from \"./../types\";\n\n// Contexts are now registered in core/context/init-contexts.ts via initializeContexts()\n\n/**\n * Echo `request.id` back as a response header so the FE / proxies / log\n * aggregators can correlate by the same value the server logs against.\n *\n * Reads the header name from `http.requestId.header` (default `X-Request-Id`).\n * Skip when `http.requestId.enabled` is explicitly false.\n */\nfunction stampRequestIdHeader(request: Request, response: Response) {\n const requestIdConfig = config.get(\"http.requestId\", {} as Record<string, any>);\n\n if (requestIdConfig.enabled === false) return;\n\n const headerName = requestIdConfig.header || \"X-Request-Id\";\n\n response.header(headerName, request.id);\n}\n\n/**\n * Create request store and execute middleware + handler\n *\n * Runs all registered contexts together using ContextManager.\n */\nexport function createRequestStore(\n request: Request<any>,\n response: Response,\n): Promise<ReturnedResponse> {\n stampRequestIdHeader(request, response);\n\n // Build all context stores using the immutable API\n // Each context defines its own store initialization via buildStore()\n const httpContextStore = contextManager.buildStores({ request, response });\n\n // Run all contexts together!\n return contextManager.runAll(httpContextStore, async () => {\n try {\n // Run middleware chain\n const result = await request.runMiddleware();\n\n if (result) {\n return result as ReturnedResponse;\n }\n\n // Execute route handler\n request.trigger(\"executingAction\", request.route);\n\n const handler = request.getHandler();\n\n request.log(\"Executing Handler\", \"info\");\n\n const output = await handler({ request, response });\n\n request.log(\"Handler Executed Successfully\", \"success\");\n\n request.trigger(\"executedAction\", request.route);\n\n return output as ReturnedResponse;\n } catch (error) {\n request.log(error, \"error\");\n return handleRequestError(error, response);\n }\n });\n}\n\n/**\n * Handle request errors\n * @internal\n */\nfunction handleRequestError(error: unknown, response: Response): ReturnedResponse {\n // Availability floor, not a cache-policy nit: `handleRequestError` is the\n // single funnel every unhandled error in every Warlock app passes through\n // (`createRequestStore`'s catch above), and none of the branches below set\n // a `Cache-Control` header. Without this, an error response — a 500 as\n // much as a 401/403/404 carrying per-request/-user state — can be stored\n // by a shared cache or CDN and replayed to other requests/users long after\n // the condition that caused it is gone: a cached 500 becomes an outage\n // that outlives its cause; a cached 401/403 becomes a leak across users.\n // Set once, here, before any branch runs — not per branch — because the\n // branches below do not partition by status. `ResourceNotFoundError`,\n // `UnAuthorizedError`, `ForbiddenError`, `BadRequestError` and `ServerError`\n // all extend `HttpError`, so the `HttpError` branch answers for every one of\n // them, and a raw `HttpError` can carry any caller-chosen status, 4xx or 5xx.\n // Gating the floor on the eventual status would mean re-deriving that status\n // per branch — one rule meeting one form while others reach the same output.\n // Applying it once, unconditionally, is both simpler and safer.\n response.header(\"Cache-Control\", \"private, no-store\");\n\n if (error instanceof HttpError) {\n const payload: GenericObject = {\n error: error.message,\n };\n if (error.payload) {\n payload.payload = error.payload;\n }\n\n if (environment() === \"development\") {\n payload.stack = error.stack;\n }\n\n return response.setStatusCode(error.status).send(payload);\n }\n\n if (error instanceof DatabaseWriterValidationError) {\n return response.badRequest({\n errors: error.errors,\n });\n }\n\n // Last resort: the error matched none of the known shapes above, so the\n // client gets a deliberately opaque message. Without this line the error\n // itself is discarded here — no stack, no message, nothing in any log — and\n // an unrecognised failure becomes indistinguishable from a working server\n // returning 500. Never swallow the only copy of an error (`65e476ee`).\n console.error(\"[warlock] unhandled request error:\", error);\n\n return response.serverError({\n error: \"Internal server error.\",\n });\n}\n\n/**\n * Translate a keyword (uses request context for locale)\n */\nexport function t(keyword: string, placeholders?: any) {\n return (\n requestContextInstance.getRequest()?.trans(keyword, placeholders) ||\n trans(keyword, placeholders)\n );\n}\n\n// `fromRequest` was removed in v5. It cached computed values as dynamic\n// properties on the Request instance, which only compiled because of the\n// `[key: string]: any` index signature that v5 deletes (eed20184). Use\n// `requestMemo(key, fn)` from `../context/request-memo` instead — same\n// per-request lifetime, single-flight, and it never touches the Request object.\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAS,qBAAqB,SAAkB,UAAoB;CAClE,MAAM,kBAAkB,OAAO,IAAI,kBAAkB,CAAC,CAAwB;CAE9E,IAAI,gBAAgB,YAAY,OAAO;CAEvC,MAAM,aAAa,gBAAgB,UAAU;CAE7C,SAAS,OAAO,YAAY,QAAQ,EAAE;AACxC;;;;;;AAOA,SAAgB,mBACd,SACA,UAC2B;CAC3B,qBAAqB,SAAS,QAAQ;CAItC,MAAM,mBAAmB,eAAe,YAAY;EAAE;EAAS;CAAS,CAAC;CAGzE,OAAO,eAAe,OAAO,kBAAkB,YAAY;EACzD,IAAI;GAEF,MAAM,SAAS,MAAM,QAAQ,cAAc;GAE3C,IAAI,QACF,OAAO;GAIT,QAAQ,QAAQ,mBAAmB,QAAQ,KAAK;GAEhD,MAAM,UAAU,QAAQ,WAAW;GAEnC,QAAQ,IAAI,qBAAqB,MAAM;GAEvC,MAAM,SAAS,MAAM,QAAQ;IAAE;IAAS;GAAS,CAAC;GAElD,QAAQ,IAAI,iCAAiC,SAAS;GAEtD,QAAQ,QAAQ,kBAAkB,QAAQ,KAAK;GAE/C,OAAO;EACT,SAAS,OAAO;GACd,QAAQ,IAAI,OAAO,OAAO;GAC1B,OAAO,mBAAmB,OAAO,QAAQ;EAC3C;CACF,CAAC;AACH;;;;;AAMA,SAAS,mBAAmB,OAAgB,UAAsC;CAiBhF,SAAS,OAAO,iBAAiB,mBAAmB;CAEpD,IAAI,iBAAiB,WAAW;EAC9B,MAAM,UAAyB,EAC7B,OAAO,MAAM,QACf;EACA,IAAI,MAAM,SACR,QAAQ,UAAU,MAAM;EAG1B,IAAI,YAAY,MAAM,eACpB,QAAQ,QAAQ,MAAM;EAGxB,OAAO,SAAS,cAAc,MAAM,MAAM,CAAC,CAAC,KAAK,OAAO;CAC1D;CAEA,IAAI,iBAAiB,+BACnB,OAAO,SAAS,WAAW,EACzB,QAAQ,MAAM,OAChB,CAAC;CAQH,QAAQ,MAAM,sCAAsC,KAAK;CAEzD,OAAO,SAAS,YAAY,EAC1B,OAAO,yBACT,CAAC;AACH;;;;AAKA,SAAgB,EAAE,SAAiB,cAAoB;CACrD,OACEA,eAAuB,WAAW,CAAC,EAAE,MAAM,SAAS,YAAY,KAChE,MAAM,SAAS,YAAY;AAE/B"}