@classytic/arc 2.15.3 → 2.16.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 (159) hide show
  1. package/README.md +1 -0
  2. package/bin/arc.js +12 -0
  3. package/dist/{BaseController-dx3m2J8V.mjs → BaseController-DlCCTIxJ.mjs} +61 -19
  4. package/dist/{HookSystem-Iiebom92.mjs → HookSystem-Cmf7-Etp.mjs} +8 -4
  5. package/dist/{QueryCache-D41bfdBB.d.mts → QueryCache-SvmT_9ti.d.mts} +1 -1
  6. package/dist/{ResourceRegistry-CTERg_2x.mjs → ResourceRegistry-f48hFk3m.mjs} +52 -9
  7. package/dist/audit/index.d.mts +1 -1
  8. package/dist/audit/index.mjs +4 -2
  9. package/dist/auth/index.d.mts +4 -4
  10. package/dist/auth/index.mjs +4 -4
  11. package/dist/auth/redis-session.d.mts +1 -1
  12. package/dist/{betterAuthOpenApi--M_i87dQ.mjs → betterAuthOpenApi-ClWxaceA.mjs} +10 -6
  13. package/dist/buildHandler-BZX6zzDM.mjs +300 -0
  14. package/dist/cache/index.d.mts +3 -3
  15. package/dist/cache/index.mjs +3 -3
  16. package/dist/{caching-SM8gghN6.mjs → caching-TeHE8G-v.mjs} +1 -1
  17. package/dist/cli/commands/describe.d.mts +35 -1
  18. package/dist/cli/commands/describe.mjs +52 -12
  19. package/dist/cli/commands/docs.d.mts +1 -4
  20. package/dist/cli/commands/docs.mjs +4 -16
  21. package/dist/cli/commands/generate.d.mts +2 -20
  22. package/dist/cli/commands/generate.mjs +1 -546
  23. package/dist/cli/commands/init.d.mts +2 -40
  24. package/dist/cli/commands/init.mjs +1 -3036
  25. package/dist/cli/commands/introspect.mjs +53 -64
  26. package/dist/cli/index.d.mts +2 -2
  27. package/dist/cli/index.mjs +2 -2
  28. package/dist/{constants-Cxde4rpC.mjs → constants-TrJVIJl0.mjs} +7 -0
  29. package/dist/core/index.d.mts +3 -3
  30. package/dist/core/index.mjs +5 -5
  31. package/dist/{core-CvmOqEms.mjs → core-DBJ_j6rX.mjs} +222 -44
  32. package/dist/createActionRouter-DUpN3Dd1.mjs +288 -0
  33. package/dist/{createAggregationRouter-B0bPDf5b.mjs → createAggregationRouter-Dq-TUCuY.mjs} +3 -2
  34. package/dist/{createApp-PFegs47-.mjs → createApp-DNccuhyI.mjs} +16 -14
  35. package/dist/{defineEvent-D5h7EvAx.mjs → defineEvent-DRwY0fYm.mjs} +1 -1
  36. package/dist/docs/index.d.mts +2 -2
  37. package/dist/docs/index.mjs +1 -1
  38. package/dist/{errorHandler-Bk-AGhkU.mjs → errorHandler-DpoXQHZ9.mjs} +17 -14
  39. package/dist/errors-C1lX_jlm.d.mts +91 -0
  40. package/dist/{eventPlugin-CaKTYkYM.mjs → eventPlugin-C2cGqtRO.mjs} +1 -1
  41. package/dist/{eventPlugin-qXpqTebY.d.mts → eventPlugin-CtHC_av1.d.mts} +1 -1
  42. package/dist/events/index.d.mts +3 -3
  43. package/dist/events/index.mjs +5 -5
  44. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  45. package/dist/events/transports/redis.d.mts +1 -1
  46. package/dist/factory/index.d.mts +1 -1
  47. package/dist/factory/index.mjs +2 -2
  48. package/dist/{fields-COhcH3fk.d.mts → fields-Anj0xdih.d.mts} +1 -1
  49. package/dist/generate-BWFwgcCM.d.mts +38 -0
  50. package/dist/generate-CYac-OLv.mjs +654 -0
  51. package/dist/hooks/index.d.mts +1 -1
  52. package/dist/hooks/index.mjs +1 -1
  53. package/dist/idempotency/index.d.mts +2 -2
  54. package/dist/idempotency/index.mjs +1 -1
  55. package/dist/idempotency/redis.d.mts +1 -1
  56. package/dist/{index-BTqLEvhu.d.mts → index-3oIimXQn.d.mts} +12 -12
  57. package/dist/{index-BstGxcc3.d.mts → index-B-ulKx5P.d.mts} +55 -4
  58. package/dist/{index-BswOSJCE.d.mts → index-CkW0flkU.d.mts} +355 -16
  59. package/dist/index.d.mts +6 -6
  60. package/dist/index.mjs +7 -8
  61. package/dist/init-Dv71MsJr.d.mts +71 -0
  62. package/dist/init-HDvoO9L5.mjs +3098 -0
  63. package/dist/integrations/event-gateway.d.mts +2 -2
  64. package/dist/integrations/event-gateway.mjs +1 -1
  65. package/dist/integrations/index.d.mts +2 -2
  66. package/dist/integrations/jobs.mjs +3 -3
  67. package/dist/integrations/mcp/index.d.mts +239 -7
  68. package/dist/integrations/mcp/index.mjs +2 -528
  69. package/dist/integrations/mcp/testing.d.mts +2 -2
  70. package/dist/integrations/mcp/testing.mjs +6 -10
  71. package/dist/integrations/streamline.d.mts +71 -2
  72. package/dist/integrations/streamline.mjs +81 -8
  73. package/dist/integrations/websocket-redis.d.mts +1 -1
  74. package/dist/integrations/websocket.d.mts +1 -1
  75. package/dist/integrations/websocket.mjs +1 -0
  76. package/dist/loadResourcesFromEntry-BLMEI2Xa.mjs +51 -0
  77. package/dist/{resourceToTools-tFYUNmM0.mjs → mcpPlugin-7vGV51ED.mjs} +1021 -318
  78. package/dist/{memory-UBydS5ku.mjs → memory-QOLe11D5.mjs} +2 -0
  79. package/dist/middleware/index.d.mts +1 -1
  80. package/dist/middleware/index.mjs +1 -1
  81. package/dist/{openapi-BHXhoX8O.mjs → openapi-34T9yNwd.mjs} +47 -36
  82. package/dist/permissions/index.d.mts +2 -2
  83. package/dist/permissions/index.mjs +1 -1
  84. package/dist/{permissions-ohQyv50e.mjs → permissions-CTxMrreC.mjs} +2 -2
  85. package/dist/{pipe-Zr0KXjQe.mjs → pipe-DiCyvyPN.mjs} +1 -0
  86. package/dist/pipeline/index.d.mts +1 -1
  87. package/dist/pipeline/index.mjs +1 -1
  88. package/dist/plugins/index.d.mts +5 -5
  89. package/dist/plugins/index.mjs +10 -10
  90. package/dist/plugins/response-cache.mjs +5 -5
  91. package/dist/plugins/tracing-entry.d.mts +1 -1
  92. package/dist/plugins/tracing-entry.mjs +1 -1
  93. package/dist/{pluralize-DQgqgifU.mjs → pluralize-B9M8xvy-.mjs} +2 -1
  94. package/dist/presets/filesUpload.d.mts +4 -4
  95. package/dist/presets/filesUpload.mjs +2 -2
  96. package/dist/presets/index.d.mts +1 -1
  97. package/dist/presets/index.mjs +1 -1
  98. package/dist/presets/multiTenant.d.mts +1 -1
  99. package/dist/presets/multiTenant.mjs +4 -3
  100. package/dist/presets/search.d.mts +2 -2
  101. package/dist/presets/search.mjs +1 -1
  102. package/dist/{presets-BbkjdPeH.mjs → presets-C9BE6WaZ.mjs} +2 -2
  103. package/dist/{queryCachePlugin-m1XsgAIJ.mjs → queryCachePlugin-B4XMSSe7.mjs} +2 -2
  104. package/dist/{queryCachePlugin-CqMdLI2-.d.mts → queryCachePlugin-Biqzfbi5.d.mts} +2 -2
  105. package/dist/{redis-DiMkdHEl.d.mts → redis-Cyzrz6SX.d.mts} +1 -1
  106. package/dist/{redis-stream-D6HzR1Z_.d.mts → redis-stream-DT-YjzrB.d.mts} +1 -1
  107. package/dist/registry/index.d.mts +319 -2
  108. package/dist/registry/index.mjs +3 -3
  109. package/dist/registry-BBE23CDj.mjs +576 -0
  110. package/dist/{routerShared-DrOa-26E.mjs → routerShared-CZV5aabX.mjs} +3 -3
  111. package/dist/scope/index.d.mts +3 -3
  112. package/dist/scope/index.mjs +3 -3
  113. package/dist/{sse-Bz-5ZeTt.mjs → sse-BY6sTy4P.mjs} +1 -1
  114. package/dist/testing/index.d.mts +2 -2
  115. package/dist/testing/index.mjs +16 -7
  116. package/dist/testing/storageContract.d.mts +1 -1
  117. package/dist/types/index.d.mts +5 -5
  118. package/dist/types/storage.d.mts +1 -1
  119. package/dist/{types-C_s5moIu.mjs → types-Bi0r0vjG.mjs} +53 -1
  120. package/dist/{types-BQsjgQzS.d.mts → types-BsJMEQ4D.d.mts} +106 -12
  121. package/dist/{types-DrBaUwyV.d.mts → types-D-fYtKjb.d.mts} +33 -10
  122. package/dist/{types-CTYvcwHe.d.mts → types-DVfpSfx2.d.mts} +42 -1
  123. package/dist/utils/index.d.mts +1286 -2
  124. package/dist/utils/index.mjs +1 -1
  125. package/dist/{utils-_h9B3c57.mjs → utils-DC5ycPfr.mjs} +89 -40
  126. package/dist/{buildHandler-CcFOpJLh.mjs → validate-By96rH0r.mjs} +8 -299
  127. package/dist/{versioning-hmkPcDlX.d.mts → versioning-ZwX9tmbS.d.mts} +1 -1
  128. package/package.json +22 -29
  129. package/skills/arc/SKILL.md +299 -689
  130. package/skills/arc/references/auth.md +19 -7
  131. package/skills/arc-code-review/SKILL.md +1 -1
  132. package/skills/arc-code-review/references/arc-cheatsheet.md +100 -322
  133. package/dist/createActionRouter-S3MLVYot.mjs +0 -220
  134. package/dist/index-bRjYu21O.d.mts +0 -1320
  135. package/dist/org/index.d.mts +0 -66
  136. package/dist/org/index.mjs +0 -486
  137. package/dist/org/types.d.mts +0 -82
  138. package/dist/org/types.mjs +0 -1
  139. package/dist/registry-I-ogLgL9.mjs +0 -46
  140. /package/dist/{EventTransport-CT_52aWU.d.mts → EventTransport-C-2oAHtw.d.mts} +0 -0
  141. /package/dist/{EventTransport-DLWoUMHy.mjs → EventTransport-Hxvv5QQz.mjs} +0 -0
  142. /package/dist/{actionPermissions-CyUkQu6O.mjs → actionPermissions-Bjmvn7Eb.mjs} +0 -0
  143. /package/dist/{elevation-BXOWoGCF.d.mts → elevation-0YBpa663.d.mts} +0 -0
  144. /package/dist/{elevation-DgoeTyfX.mjs → elevation-Dci0AYLT.mjs} +0 -0
  145. /package/dist/{errorHandler-DFr45ZG4.d.mts → errorHandler-mHuyWzZE.d.mts} +0 -0
  146. /package/dist/{externalPaths-BD5nw6St.d.mts → externalPaths-DFg-2KTp.d.mts} +0 -0
  147. /package/dist/{interface-beEtJyWM.d.mts → interface-CH0OQudo.d.mts} +0 -0
  148. /package/dist/{interface-DfLGcus7.d.mts → interface-NwJ_qPlY.d.mts} +0 -0
  149. /package/dist/{keys-CGcCbNyu.mjs → keys-DopsCuyQ.mjs} +0 -0
  150. /package/dist/{loadResources-DBMQg_Aj.mjs → loadResources-ChQEj8ih.mjs} +0 -0
  151. /package/dist/{metrics-Qnvwc-LQ.mjs → metrics-TuOmguhi.mjs} +0 -0
  152. /package/dist/{replyHelpers-CK-FNO8E.mjs → replyHelpers-C-gD32oF.mjs} +0 -0
  153. /package/dist/{schemaIR-lYhC2gE5.mjs → schemaIR-Ctc89DSn.mjs} +0 -0
  154. /package/dist/{sessionManager-C4Le_UB3.d.mts → sessionManager-BqFegc0W.d.mts} +0 -0
  155. /package/dist/{storage-Dfzt4VTl.d.mts → storage-D2KZJAmn.d.mts} +0 -0
  156. /package/dist/{store-helpers-BkIN9-vu.mjs → store-helpers-B0sunfZZ.mjs} +0 -0
  157. /package/dist/{tracing-QJVprktp.d.mts → tracing-Dm8n7Cnn.d.mts} +0 -0
  158. /package/dist/{versioning-BUrT5aP4.mjs → versioning-B6mimogM.mjs} +0 -0
  159. /package/dist/{websocket-ChC2rqe1.d.mts → websocket-BkjeGZRn.d.mts} +0 -0
@@ -1,547 +1,2 @@
1
- import { t as pluralize } from "../../pluralize-DQgqgifU.mjs";
2
- import { join } from "node:path";
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
- //#region src/cli/commands/generate.ts
5
- /**
6
- * Arc CLI - Generate Command
7
- *
8
- * Scaffolds resources with consistent naming:
9
- * - src/resources/product/product.model.ts
10
- * - src/resources/product/product.repository.ts
11
- * - src/resources/product/product.resource.ts
12
- *
13
- * Handles kebab-case names: `arc g r org-profile` generates:
14
- * - Class names: OrgProfile, OrgProfileRepository
15
- * - Variable names: orgProfileSchema, orgProfileRepository
16
- * - File names: org-profile.model.ts, org-profile.repository.ts
17
- */
18
- function readProjectConfig() {
19
- try {
20
- const rcPath = join(process.cwd(), ".arcrc");
21
- return JSON.parse(readFileSync(rcPath, "utf-8"));
22
- } catch {
23
- return {};
24
- }
25
- }
26
- function isTypeScriptProject() {
27
- return existsSync(join(process.cwd(), "tsconfig.json"));
28
- }
29
- /** Convert kebab-case to PascalCase: org-profile → OrgProfile */
30
- function toPascalCase(name) {
31
- return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
32
- }
33
- /** Convert PascalCase to camelCase: OrgProfile → orgProfile */
34
- function toCamelCase(pascalName) {
35
- return pascalName.charAt(0).toLowerCase() + pascalName.slice(1);
36
- }
37
- /**
38
- * Template functions accept:
39
- * - name: PascalCase class name (e.g., OrgProfile)
40
- * - fileName: kebab-case for file paths (e.g., org-profile)
41
- */
42
- function getTemplates(ts, config = {}) {
43
- const isMultiTenant = config.tenant === "multi";
44
- return {
45
- model: (name, _fileName) => {
46
- const camel = toCamelCase(name);
47
- return `/**
48
- * ${name} Model
49
- * Generated by Arc CLI
50
- */
51
-
52
- ${ts ? "import mongoose, { type HydratedDocument, type Model, type Types } from 'mongoose';" : "import mongoose from 'mongoose';"}
53
-
54
- const { Schema } = mongoose;
55
- ${ts ? [
56
- "",
57
- "/**",
58
- " * Persisted shape — what `.lean()` and `.toObject()` return. Carrying this",
59
- ` * through \`Model<I${name}>\` lets \`.select(...)\` / \`.find(...)\` / \`.lean()\``,
60
- " * infer correctly so domain methods don't need `as` casts.",
61
- " *",
62
- " * Replace the placeholder fields with your real domain shape.",
63
- " */",
64
- `export interface I${name} {`,
65
- " _id: Types.ObjectId;",
66
- ...isMultiTenant ? [" organizationId: Types.ObjectId;"] : [],
67
- " // TODO: define your fields here",
68
- " createdAt: Date;",
69
- " updatedAt: Date;",
70
- "}",
71
- "",
72
- `export type ${name}Document = HydratedDocument<I${name}>;`,
73
- ""
74
- ].join("\n") : ""}
75
- const ${camel}Schema = new Schema${ts ? `<I${name}>` : ""}(
76
- {
77
- ${isMultiTenant ? " organizationId: { type: Schema.Types.ObjectId, required: true, index: true },\n" : ""} // TODO: declare your fields here, e.g. name: { type: String, required: true, trim: true },
78
- },
79
- { timestamps: true }
80
- );
81
-
82
- ${isMultiTenant ? `${camel}Schema.index({ organizationId: 1, createdAt: -1 });\n` : ""}${ts ? [
83
- `const ${name}: Model<I${name}> =`,
84
- ` (mongoose.models.${name} as Model<I${name}> | undefined) ??`,
85
- ` mongoose.model<I${name}>('${name}', ${camel}Schema);`
86
- ].join("\n") : `const ${name} = mongoose.models.${name} || mongoose.model('${name}', ${camel}Schema);`}
87
-
88
- export default ${name};
89
- `;
90
- },
91
- repository: (name, fileName) => {
92
- const camel = toCamelCase(name);
93
- if (!(config.adapter === "mongokit" || !config.adapter)) {
94
- const generic = ts ? `<I${name}>` : "";
95
- return `/**
96
- * ${name} Repository
97
- * Generated by Arc CLI
98
- *
99
- * This project uses a custom adapter — wire your repository to whichever
100
- * kit you're using (sqlitekit, prismakit, custom). Replace the body below
101
- * with your kit's Repository constructor. Arc only requires the
102
- * \`MinimalRepo\` floor (getAll/getById/create/update/delete) declared in
103
- * \`@classytic/repo-core/repository\`.
104
- */
105
- ${ts ? `\nimport type { I${name} } from './${fileName}.model.js';` : ""}
106
-
107
- // Replace with your kit's repository instance:
108
- // import { Repository } from '@classytic/sqlitekit';
109
- // const ${camel}Repository = new Repository${generic}(${name}Table);
110
- // export default ${camel}Repository;
111
-
112
- const ${camel}Repository = {
113
- // TODO: implement MinimalRepo<${ts ? `I${name}` : "any"}>
114
- } as never;
115
-
116
- export default ${camel}Repository;
117
- `;
118
- }
119
- const generic = ts ? `<I${name}>` : "";
120
- return `/**
121
- * ${name} Repository
122
- * Generated by Arc CLI
123
- */
124
-
125
- import {
126
- Repository,
127
- methodRegistryPlugin,
128
- softDeletePlugin,
129
- } from '@classytic/mongokit';
130
- import ${name} from './${fileName}.model.js';${ts ? `\nimport type { I${name} } from './${fileName}.model.js';` : ""}
131
-
132
- class ${name}Repository extends Repository${generic} {
133
- constructor() {
134
- super(${name}, [methodRegistryPlugin(), softDeletePlugin()]);
135
- }
136
-
137
- // Add domain methods here. arc + mongokit ship the standard CRUD
138
- // (getAll/getById/create/update/delete) on the base class, so only
139
- // write a method when there's real domain logic to capture.
140
- }
141
-
142
- const ${camel}Repository = new ${name}Repository();
143
- export default ${camel}Repository;
144
- export { ${name}Repository };
145
- `;
146
- },
147
- controller: (name, fileName) => {
148
- const camel = toCamelCase(name);
149
- return `/**
150
- * ${name} Controller
151
- * Generated by Arc CLI
152
- *
153
- * Note: defineResource() auto-creates a controller from the adapter.
154
- * Only create a custom controller when you need custom methods.
155
- */
156
-
157
- import { BaseController } from '@classytic/arc';
158
- import ${camel}Repository from './${fileName}.repository.js';
159
-
160
- class ${name}Controller extends BaseController {
161
- constructor() {
162
- super(${camel}Repository, {
163
- resourceName: '${fileName}',
164
- });
165
- }
166
-
167
- // Add custom controller methods here
168
- }
169
-
170
- const ${camel}Controller = new ${name}Controller();
171
- export default ${camel}Controller;
172
- `;
173
- },
174
- schemas: (name, fileName) => `/**
175
- * ${name} Schemas
176
- * Generated by Arc CLI
177
- */
178
-
179
- import ${name} from './${fileName}.model.js';
180
- import { buildCrudSchemasFromModel } from '@classytic/mongokit';
181
-
182
- /**
183
- * CRUD Schemas with Field Rules
184
- */
185
- const crudSchemas = buildCrudSchemasFromModel(${name}, {
186
- strictAdditionalProperties: true,
187
- fieldRules: {
188
- // systemManaged: framework stamps it — strip from body + required[]
189
- // deletedAt: { systemManaged: true },
190
- // immutable: cannot be updated after creation
191
- // slug: { immutable: true },
192
- // immutableAfterCreate: alias for immutable
193
- // organizationId: { immutableAfterCreate: true },
194
- // optional: removed from required[] (properties kept)
195
- // description: { optional: true },
196
- // nullable: widen JSON-Schema type to accept null (Zod .nullable() rescue)
197
- // priceMode: { nullable: true },
198
- // preserveForElevated: elevated admins keep the field on ingest (cross-tenant writes)
199
- // organizationId: { systemManaged: true, preserveForElevated: true },
200
- },
201
- query: {
202
- // Add your filterable fields here. createdAt is the default so the
203
- // generated routes accept ?createdAt[gte]=2026-01-01 with no extra
204
- // wiring. Add domain fields below as your model grows.
205
- filterableFields: {
206
- createdAt: 'date',
207
- },
208
- },
209
- });
210
-
211
- export default crudSchemas;
212
- `,
213
- resource: (name, fileName) => {
214
- const camel = toCamelCase(name);
215
- const useMongoKit = config.adapter === "mongokit" || !config.adapter;
216
- const schemaGeneratorImport = useMongoKit ? `import { buildCrudSchemasFromModel } from '@classytic/mongokit';\n` : "";
217
- const queryParserImport = useMongoKit ? `\nimport { QueryParser } from '@classytic/mongokit';\n\nconst queryParser = new QueryParser({\n // Whitelist the fields this resource accepts in URL filters.\n // Empty by default — only \`createdAt\` is implicit; add yours.\n allowedFilterFields: [],\n});\n` : "";
218
- const adapterCall = useMongoKit ? `createMongooseAdapter({ model: ${name}, repository: ${camel}Repository, schemaGenerator: buildCrudSchemasFromModel })` : `createMongooseAdapter({ model: ${name}, repository: ${camel}Repository })`;
219
- const queryParserConfig = useMongoKit ? `\n queryParser,` : "";
220
- return isMultiTenant ? `/**
221
- * ${name} Resource
222
- * Generated by Arc CLI
223
- */
224
-
225
- import { defineResource } from '@classytic/arc';
226
- import { createMongooseAdapter } from '@classytic/mongokit/adapter';
227
- import { allOf, requireOrgMembership, requireRoles } from '@classytic/arc/permissions';
228
- import { multiTenantPreset } from '@classytic/arc/presets';
229
- ${schemaGeneratorImport}import ${name}${ts ? `, { type I${name} }` : ""} from './${fileName}.model.js';
230
- import ${camel}Repository from './${fileName}.repository.js';${queryParserImport}
231
-
232
- const ${camel}Resource = defineResource${ts ? `<I${name}>` : ""}({
233
- name: '${fileName}',
234
- adapter: ${adapterCall},${queryParserConfig}
235
-
236
- // Multi-tenant default: scope reads/writes by \`organizationId\`. For
237
- // company-wide tables (lookup data, platform settings) set
238
- // \`tenantField: false\` instead — otherwise queries silently return
239
- // nothing because the column doesn't exist.
240
- //
241
- // Multi-level tenancy (org + branch + project, etc.) — replace with:
242
- // multiTenantPreset({ tenantFields: [
243
- // { field: 'organizationId', type: 'org' },
244
- // { field: 'branchId', contextKey: 'branchId' },
245
- // ] })
246
- presets: ['softDelete', multiTenantPreset({ tenantField: 'organizationId' })],
247
-
248
- permissions: {
249
- list: requireOrgMembership(),
250
- get: requireOrgMembership(),
251
- create: allOf(requireOrgMembership(), requireRoles(['admin'])),
252
- update: allOf(requireOrgMembership(), requireRoles(['admin'])),
253
- delete: allOf(requireOrgMembership(), requireRoles(['admin'])),
254
- },
255
- });
256
-
257
- export default ${camel}Resource;
258
- ` : `/**
259
- * ${name} Resource
260
- * Generated by Arc CLI
261
- */
262
-
263
- import { defineResource } from '@classytic/arc';
264
- import { createMongooseAdapter } from '@classytic/mongokit/adapter';
265
- import { requireAuth, requireRoles } from '@classytic/arc/permissions';
266
- ${schemaGeneratorImport}import ${name}${ts ? `, { type I${name} }` : ""} from './${fileName}.model.js';
267
- import ${camel}Repository from './${fileName}.repository.js';${queryParserImport}
268
-
269
- const ${camel}Resource = defineResource${ts ? `<I${name}>` : ""}({
270
- name: '${fileName}',
271
- adapter: ${adapterCall},${queryParserConfig}
272
-
273
- // Single-tenant default: arc auto-infers \`tenantField: false\` when
274
- // the model has no \`organizationId\` path (silent-zero-results
275
- // footgun closed in 2.12). Set \`tenantField: '<field>'\` to opt INTO
276
- // tenant scoping, or \`tenantField: false\` to make it explicit.
277
- presets: ['softDelete'],
278
-
279
- permissions: {
280
- list: requireAuth(),
281
- get: requireAuth(),
282
- create: requireRoles(['admin']),
283
- update: requireRoles(['admin']),
284
- delete: requireRoles(['admin']),
285
- },
286
- });
287
-
288
- export default ${camel}Resource;
289
- `;
290
- },
291
- mcp: (name, fileName) => {
292
- const camel = toCamelCase(name);
293
- return `/**
294
- * ${name} MCP Tools
295
- * Generated by Arc CLI
296
- *
297
- * Custom MCP tools for the ${name} domain.
298
- * CRUD tools (list/get/create/update/delete) are auto-generated by mcpPlugin.
299
- * Add domain-specific tools here (actions, analytics, workflows).
300
- */
301
-
302
- import { defineTool } from '@classytic/arc/mcp';
303
- ${ts ? "import { z } from 'zod';\n" : "const { z } = require('zod');\n"}
304
- // Example: domain-specific action tool
305
- // export const activate${name}Tool = defineTool('activate_${fileName}', {
306
- // description: 'Activate a ${name.toLowerCase()} by ID',
307
- // input: { id: z.string().describe('${name} ID') },
308
- // annotations: { destructiveHint: true, idempotentHint: true },
309
- // handler: async ({ id }, ctx) => {
310
- // // Your logic here
311
- // return { content: [{ type: 'text', text: \`Activated ${name.toLowerCase()} \${id}\` }] };
312
- // },
313
- // });
314
-
315
- // Example: read-only analytics tool
316
- // export const ${camel}StatsTool = defineTool('${fileName}_stats', {
317
- // description: 'Get ${name.toLowerCase()} statistics',
318
- // input: { period: z.enum(['7d', '30d', '90d']).optional() },
319
- // annotations: { readOnlyHint: true },
320
- // handler: async ({ period }) => {
321
- // // Your logic here
322
- // return { content: [{ type: 'text', text: JSON.stringify({ total: 0, period }) }] };
323
- // },
324
- // });
325
- `;
326
- },
327
- test: (name, fileName) => {
328
- const camel = toCamelCase(name);
329
- return `/**
330
- * ${name} Tests
331
- * Generated by Arc CLI
332
- *
333
- * Testing surface (arc 2.12+):
334
- * - createTestApp turnkey Fastify + in-memory Mongo + auth + fixtures
335
- * - expectArc fluent matchers — .ok / .failed / .unauthorized /
336
- * .forbidden / .notFound / .conflict / .validationError /
337
- * .paginated / .hidesField / .hasData / .hasStatus
338
- * - ctx.auth unified TestAuthProvider — register a role, reuse .headers
339
- *
340
- * Wire shape (post-2.12): single-doc responses are flat (\`{_id, name, ...}\`),
341
- * paginated responses are \`{ method: 'offset', data: [...], page, ... }\`.
342
- * No \`success\` envelope — HTTP status discriminates success vs error;
343
- * errors carry the canonical ErrorContract \`{ code, message, status }\`.
344
- */
345
-
346
- import { describe, it, beforeAll, afterAll, expect } from 'vitest';
347
- import { createTestApp, expectArc } from '@classytic/arc/testing';
348
- import type { TestAppContext } from '@classytic/arc/testing';
349
- import ${camel}Resource from '../src/resources/${fileName}/${fileName}.resource.js';
350
-
351
- describe('${name} Resource', () => {
352
- let ctx${ts ? ": TestAppContext" : ""};
353
-
354
- beforeAll(async () => {
355
- ctx = await createTestApp({
356
- resources: [${camel}Resource],
357
- authMode: 'jwt',
358
- connectMongoose: true,
359
- });
360
-
361
- ctx.auth${ts ? "!" : ""}.register('admin', {
362
- user: { id: '1', roles: ['admin'] },
363
- orgId: 'org-1',
364
- });
365
- });
366
-
367
- afterAll(() => ctx.close());
368
-
369
- describe('GET /${pluralize(fileName)}', () => {
370
- it('returns a paginated list', async () => {
371
- const res = await ctx.app.inject({
372
- method: 'GET',
373
- url: '/${pluralize(fileName)}',
374
- headers: ctx.auth${ts ? "!" : ""}.as('admin').headers,
375
- });
376
- expectArc(res).ok().paginated();
377
- });
378
-
379
- it('rejects unauthenticated requests', async () => {
380
- const res = await ctx.app.inject({ method: 'GET', url: '/${pluralize(fileName)}' });
381
- expectArc(res).unauthorized();
382
- });
383
- });
384
-
385
- describe('POST /${pluralize(fileName)}', () => {
386
- it('creates a record (flat wire shape — no envelope)', async () => {
387
- const res = await ctx.app.inject({
388
- method: 'POST',
389
- url: '/${pluralize(fileName)}',
390
- headers: ctx.auth${ts ? "!" : ""}.as('admin').headers,
391
- payload: { name: 'Example' },
392
- });
393
- expectArc(res).ok(201);
394
- // Single-doc response is flat: \`body._id\` (not \`body.data._id\`).
395
- const body = res.json()${ts ? " as { _id: string; name: string }" : ""};
396
- expect(body._id).toBeDefined();
397
- expect(body.name).toBe('Example');
398
- });
399
- });
400
-
401
- // More patterns to extend:
402
- // - expectArc(res).forbidden() / .notFound() / .validationError()
403
- // - expectArc(res).hidesField('password') for field-level perms
404
- // - ctx.fixtures.create('${fileName}', {...}) for seeded data
405
- // - error wire shape: body.code === 'arc.not_found' (ErrorContract)
406
- });
407
- `;
408
- }
409
- };
410
- }
411
- /**
412
- * Generate command handler
413
- */
414
- async function generate(type, args) {
415
- if (!type) throw new Error("Missing type argument\nUsage: arc generate <resource|controller|model|repository|schemas> <name>");
416
- const [name, ...restArgs] = args;
417
- if (!name) throw new Error("Missing name argument\nUsage: arc generate <type> <name>\nExample: arc generate resource product");
418
- const capitalizedName = toPascalCase(name);
419
- const lowerName = name.toLowerCase();
420
- if ((type === "resource" || type === "r" || type === "model" || type === "m") && new Set([
421
- "user",
422
- "session",
423
- "account",
424
- "verification",
425
- "organization",
426
- "member",
427
- "invitation",
428
- "team",
429
- "team-member",
430
- "apikey"
431
- ]).has(lowerName)) {
432
- console.warn(`\n[arc generate] "${lowerName}" is a Better Auth-owned collection.\nBetter Auth's organization/admin/bearer plugins write this collection directly,\nso generating a parallel arc model would create a duplicate registration.\n\nRecommended pattern:\n import { createBetterAuthOverlay } from '@classytic/mongokit/better-auth';\n const adapter = await createBetterAuthOverlay({\n auth, mongoose, collection: '${lowerName}',\n });\n // ...then \`defineResource({ name: '${lowerName}', adapter, ... })\` reads\n // BA's collection through arc with full pagination/filters/permissions.\n\nAborting. Re-run with a different name if you need a separate domain model.\n`);
433
- return;
434
- }
435
- const projectConfig = readProjectConfig();
436
- const ts = projectConfig.typescript ?? isTypeScriptProject();
437
- const ext = ts ? "ts" : "js";
438
- const templates = getTemplates(ts, projectConfig);
439
- const resourcePath = join(process.cwd(), "src", "resources", lowerName);
440
- switch (type) {
441
- case "resource":
442
- case "r":
443
- await generateResource(capitalizedName, lowerName, resourcePath, templates, ext, projectConfig.mcp === true || restArgs.includes("--mcp"));
444
- break;
445
- case "controller":
446
- case "c":
447
- await generateFile(capitalizedName, lowerName, resourcePath, "controller", templates.controller, ext);
448
- break;
449
- case "model":
450
- case "m":
451
- await generateFile(capitalizedName, lowerName, resourcePath, "model", templates.model, ext);
452
- break;
453
- case "repository":
454
- case "repo":
455
- await generateFile(capitalizedName, lowerName, resourcePath, "repository", templates.repository, ext);
456
- break;
457
- case "schemas":
458
- case "s":
459
- await generateFile(capitalizedName, lowerName, resourcePath, "schemas", templates.schemas, ext);
460
- break;
461
- case "mcp":
462
- await generateFile(capitalizedName, lowerName, resourcePath, "mcp", templates.mcp, ext);
463
- break;
464
- default: throw new Error(`Unknown type: ${type}\nAvailable types: resource, controller, model, repository, schemas, mcp`);
465
- }
466
- }
467
- /**
468
- * Generate a resource-first scaffold
469
- */
470
- async function generateResource(name, lowerName, resourcePath, templates, ext, includeMcp = false) {
471
- console.log(`\nGenerating resource: ${name}...\n`);
472
- if (!existsSync(resourcePath)) {
473
- mkdirSync(resourcePath, { recursive: true });
474
- console.log(` + Created: src/resources/${lowerName}/`);
475
- }
476
- const files = {
477
- [`${lowerName}.model.${ext}`]: templates.model(name, lowerName),
478
- [`${lowerName}.repository.${ext}`]: templates.repository(name, lowerName),
479
- [`${lowerName}.resource.${ext}`]: templates.resource(name, lowerName)
480
- };
481
- if (includeMcp) files[`${lowerName}.mcp.${ext}`] = templates.mcp(name, lowerName);
482
- for (const [filename, content] of Object.entries(files)) {
483
- const filepath = join(resourcePath, filename);
484
- if (existsSync(filepath)) console.warn(` ! Skipped: ${filename} (already exists)`);
485
- else {
486
- writeFileSync(filepath, content);
487
- console.log(` + Created: ${filename}`);
488
- }
489
- }
490
- const testsDir = join(process.cwd(), "tests");
491
- if (!existsSync(testsDir)) mkdirSync(testsDir, { recursive: true });
492
- const testPath = join(testsDir, `${lowerName}.test.${ext}`);
493
- if (!existsSync(testPath)) {
494
- writeFileSync(testPath, templates.test(name, lowerName));
495
- console.log(` + Created: tests/${lowerName}.test.${ext}`);
496
- }
497
- const camel = toCamelCase(name);
498
- const isMultiTenant = readProjectConfig().tenant === "multi";
499
- console.log(`
500
- ╔═══════════════════════════════════════════════════════════════╗
501
- ║ Resource Generated ║
502
- ╚═══════════════════════════════════════════════════════════════╝
503
-
504
- Next steps:
505
-
506
- 1. Register in src/resources/index.${ext}:
507
- import ${camel}Resource from './${lowerName}/${lowerName}.resource.js';
508
-
509
- export const resources = [
510
- // ... existing resources
511
- ${camel}Resource,
512
- ];
513
-
514
- 2. Customize the model schema in:
515
- src/resources/${lowerName}/${lowerName}.model.${ext}
516
-
517
- 3. Adjust permissions in ${lowerName}.resource.${ext}:
518
- ${isMultiTenant ? ` - requireOrgMembership() → member of the current organization
519
- - multiTenantPreset() → injects and filters organizationId
520
- - requireRoles(['admin']) → admin writes inside the org scope` : ` - requireAuth() → any authenticated user
521
- - requireRoles(['admin']) → specific platform roles`}
522
-
523
- 4. Run tests:
524
- npm test
525
- ${includeMcp ? `
526
- 5. MCP tools file created: ${lowerName}.mcp.${ext}
527
- Uncomment and customize the example tools.
528
- Import and add to extraTools in your mcpPlugin config.
529
- ` : ""}`);
530
- }
531
- /**
532
- * Generate a single file
533
- */
534
- async function generateFile(name, lowerName, resourcePath, fileType, template, ext) {
535
- console.log(`\nGenerating ${fileType}: ${name}...\n`);
536
- if (!existsSync(resourcePath)) {
537
- mkdirSync(resourcePath, { recursive: true });
538
- console.log(` + Created: src/resources/${lowerName}/`);
539
- }
540
- const filename = `${lowerName}.${fileType}.${ext}`;
541
- const filepath = join(resourcePath, filename);
542
- if (existsSync(filepath)) throw new Error(`${filename} already exists. Remove it first or use a different name.`);
543
- writeFileSync(filepath, template(name, lowerName));
544
- console.log(` + Created: ${filename}`);
545
- }
546
- //#endregion
1
+ import { t as generate } from "../../generate-CYac-OLv.mjs";
547
2
  export { generate };
@@ -1,40 +1,2 @@
1
- //#region src/cli/commands/init.d.ts
2
- /**
3
- * Arc CLI - Init Command
4
- *
5
- * Scaffolds a new Arc project with clean architecture:
6
- * - MongoKit or Custom adapter
7
- * - Multi-tenant or Single-tenant
8
- * - TypeScript or JavaScript
9
- *
10
- * Automatically installs dependencies using detected package manager.
11
- */
12
- interface InitOptions {
13
- name?: string;
14
- adapter?: "mongokit" | "custom";
15
- auth?: "jwt" | "better-auth";
16
- tenant?: "multi" | "single";
17
- /**
18
- * Enable Better Auth's `apiKey` plugin (`@better-auth/api-key`) for
19
- * machine-to-machine authentication alongside cookie/session auth.
20
- * Only used when `auth === 'better-auth'`. Default: false.
21
- */
22
- apiKey?: boolean;
23
- /**
24
- * Session strategy when using Better Auth.
25
- * - `cookie` (default): browser cookie + DB-backed session (BA's default)
26
- * - `bearer`: Authorization: Bearer header (mobile apps, SPA, M2M)
27
- * Both can coexist — `bearer: true` adds bearer alongside cookies.
28
- */
29
- session?: "cookie" | "bearer" | "both";
30
- typescript?: boolean;
31
- edge?: boolean;
32
- skipInstall?: boolean;
33
- force?: boolean;
34
- }
35
- /**
36
- * Initialize a new Arc project
37
- */
38
- declare function init(options?: InitOptions): Promise<void>;
39
- //#endregion
40
- export { InitOptions, init };
1
+ import { a as ProjectConfig, i as PackageManager, n as DependencyManifest, r as InitOptions, t as init } from "../../init-Dv71MsJr.mjs";
2
+ export { DependencyManifest, InitOptions, PackageManager, ProjectConfig, init };