@classytic/arc 1.1.0 → 2.1.3

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 (200) hide show
  1. package/README.md +247 -794
  2. package/bin/arc.js +91 -52
  3. package/dist/EventTransport-BkUDYZEb.d.mts +99 -0
  4. package/dist/HookSystem-BsGV-j2l.mjs +404 -0
  5. package/dist/ResourceRegistry-7Ic20ZMw.mjs +249 -0
  6. package/dist/adapters/index.d.mts +5 -0
  7. package/dist/adapters/index.mjs +3 -0
  8. package/dist/audit/index.d.mts +81 -0
  9. package/dist/audit/index.mjs +275 -0
  10. package/dist/audit/mongodb.d.mts +5 -0
  11. package/dist/audit/mongodb.mjs +3 -0
  12. package/dist/audited-CGdLiSlE.mjs +140 -0
  13. package/dist/auth/index.d.mts +188 -0
  14. package/dist/auth/index.mjs +1096 -0
  15. package/dist/auth/redis-session.d.mts +43 -0
  16. package/dist/auth/redis-session.mjs +75 -0
  17. package/dist/betterAuthOpenApi-DjWDddNc.mjs +249 -0
  18. package/dist/cache/index.d.mts +145 -0
  19. package/dist/cache/index.mjs +91 -0
  20. package/dist/caching-GSDJcA6-.mjs +93 -0
  21. package/dist/chunk-C7Uep-_p.mjs +20 -0
  22. package/dist/circuitBreaker-DYhWBW_D.mjs +1096 -0
  23. package/dist/cli/commands/describe.d.mts +18 -0
  24. package/dist/cli/commands/describe.mjs +238 -0
  25. package/dist/cli/commands/docs.d.mts +13 -0
  26. package/dist/cli/commands/docs.mjs +52 -0
  27. package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -2
  28. package/dist/cli/commands/generate.mjs +357 -0
  29. package/dist/cli/commands/{init.d.ts → init.d.mts} +11 -8
  30. package/dist/cli/commands/{init.js → init.mjs} +807 -617
  31. package/dist/cli/commands/introspect.d.mts +10 -0
  32. package/dist/cli/commands/introspect.mjs +75 -0
  33. package/dist/cli/index.d.mts +16 -0
  34. package/dist/cli/index.mjs +156 -0
  35. package/dist/constants-DdXFXQtN.mjs +84 -0
  36. package/dist/core/index.d.mts +5 -0
  37. package/dist/core/index.mjs +4 -0
  38. package/dist/createApp-D2D5XXaV.mjs +559 -0
  39. package/dist/defineResource-PXzSJ15_.mjs +2197 -0
  40. package/dist/discovery/index.d.mts +46 -0
  41. package/dist/discovery/index.mjs +109 -0
  42. package/dist/docs/index.d.mts +162 -0
  43. package/dist/docs/index.mjs +74 -0
  44. package/dist/elevation-DGo5shaX.d.mts +87 -0
  45. package/dist/elevation-DSTbVvYj.mjs +113 -0
  46. package/dist/errorHandler-C3GY3_ow.mjs +108 -0
  47. package/dist/errorHandler-CW3OOeYq.d.mts +72 -0
  48. package/dist/errors-DAWRdiYP.d.mts +124 -0
  49. package/dist/errors-DBANPbGr.mjs +211 -0
  50. package/dist/eventPlugin-BEOvaDqo.mjs +229 -0
  51. package/dist/eventPlugin-H6wDDjGO.d.mts +124 -0
  52. package/dist/events/index.d.mts +53 -0
  53. package/dist/events/index.mjs +51 -0
  54. package/dist/events/transports/redis-stream-entry.d.mts +2 -0
  55. package/dist/events/transports/redis-stream-entry.mjs +177 -0
  56. package/dist/events/transports/redis.d.mts +76 -0
  57. package/dist/events/transports/redis.mjs +124 -0
  58. package/dist/externalPaths-SyPF2tgK.d.mts +50 -0
  59. package/dist/factory/index.d.mts +63 -0
  60. package/dist/factory/index.mjs +3 -0
  61. package/dist/fastifyAdapter-C8DlE0YH.d.mts +216 -0
  62. package/dist/fields-Bi_AVKSo.d.mts +109 -0
  63. package/dist/fields-CTd_CrKr.mjs +114 -0
  64. package/dist/hooks/index.d.mts +4 -0
  65. package/dist/hooks/index.mjs +3 -0
  66. package/dist/idempotency/index.d.mts +96 -0
  67. package/dist/idempotency/index.mjs +319 -0
  68. package/dist/idempotency/mongodb.d.mts +2 -0
  69. package/dist/idempotency/mongodb.mjs +114 -0
  70. package/dist/idempotency/redis.d.mts +2 -0
  71. package/dist/idempotency/redis.mjs +103 -0
  72. package/dist/index.d.mts +260 -0
  73. package/dist/index.mjs +104 -0
  74. package/dist/integrations/event-gateway.d.mts +46 -0
  75. package/dist/integrations/event-gateway.mjs +43 -0
  76. package/dist/integrations/index.d.mts +5 -0
  77. package/dist/integrations/index.mjs +1 -0
  78. package/dist/integrations/jobs.d.mts +103 -0
  79. package/dist/integrations/jobs.mjs +123 -0
  80. package/dist/integrations/streamline.d.mts +60 -0
  81. package/dist/integrations/streamline.mjs +125 -0
  82. package/dist/integrations/websocket.d.mts +82 -0
  83. package/dist/integrations/websocket.mjs +288 -0
  84. package/dist/interface-CSNjltAc.d.mts +77 -0
  85. package/dist/interface-DTbsvIWe.d.mts +54 -0
  86. package/dist/interface-e9XfSsUV.d.mts +1097 -0
  87. package/dist/introspectionPlugin-B3JkrjwU.mjs +53 -0
  88. package/dist/keys-DhqDRxv3.mjs +42 -0
  89. package/dist/logger-ByrvQWZO.mjs +78 -0
  90. package/dist/memory-B2v7KrCB.mjs +143 -0
  91. package/dist/migrations/index.d.mts +156 -0
  92. package/dist/migrations/index.mjs +260 -0
  93. package/dist/mongodb-ClykrfGo.d.mts +118 -0
  94. package/dist/mongodb-DNKEExbf.mjs +93 -0
  95. package/dist/mongodb-Dg8O_gvd.d.mts +71 -0
  96. package/dist/openapi-9nB_kiuR.mjs +525 -0
  97. package/dist/org/index.d.mts +68 -0
  98. package/dist/org/index.mjs +513 -0
  99. package/dist/org/types.d.mts +82 -0
  100. package/dist/org/types.mjs +1 -0
  101. package/dist/permissions/index.d.mts +278 -0
  102. package/dist/permissions/index.mjs +579 -0
  103. package/dist/plugins/index.d.mts +172 -0
  104. package/dist/plugins/index.mjs +522 -0
  105. package/dist/plugins/response-cache.d.mts +87 -0
  106. package/dist/plugins/response-cache.mjs +283 -0
  107. package/dist/plugins/tracing-entry.d.mts +2 -0
  108. package/dist/plugins/tracing-entry.mjs +185 -0
  109. package/dist/pluralize-CM-jZg7p.mjs +86 -0
  110. package/dist/policies/{index.d.ts → index.d.mts} +204 -170
  111. package/dist/policies/index.mjs +321 -0
  112. package/dist/presets/{index.d.ts → index.d.mts} +62 -131
  113. package/dist/presets/index.mjs +143 -0
  114. package/dist/presets/multiTenant.d.mts +24 -0
  115. package/dist/presets/multiTenant.mjs +113 -0
  116. package/dist/presets-BTeYbw7h.d.mts +57 -0
  117. package/dist/presets-CeFtfDR8.mjs +119 -0
  118. package/dist/prisma-C3iornoK.d.mts +274 -0
  119. package/dist/prisma-DJbMt3yf.mjs +627 -0
  120. package/dist/queryCachePlugin-B6R0d4av.mjs +138 -0
  121. package/dist/queryCachePlugin-Q6SYuHZ6.d.mts +71 -0
  122. package/dist/redis-UwjEp8Ea.d.mts +49 -0
  123. package/dist/redis-stream-CBg0upHI.d.mts +103 -0
  124. package/dist/registry/index.d.mts +11 -0
  125. package/dist/registry/index.mjs +4 -0
  126. package/dist/requestContext-xi6OKBL-.mjs +55 -0
  127. package/dist/schemaConverter-Dtg0Kt9T.mjs +98 -0
  128. package/dist/schemas/index.d.mts +63 -0
  129. package/dist/schemas/index.mjs +82 -0
  130. package/dist/scope/index.d.mts +21 -0
  131. package/dist/scope/index.mjs +65 -0
  132. package/dist/sessionManager-D_iEHjQl.d.mts +186 -0
  133. package/dist/sse-DkqQ1uxb.mjs +123 -0
  134. package/dist/testing/index.d.mts +907 -0
  135. package/dist/testing/index.mjs +1976 -0
  136. package/dist/tracing-8CEbhF0w.d.mts +70 -0
  137. package/dist/typeGuards-DwxA1t_L.mjs +9 -0
  138. package/dist/types/index.d.mts +946 -0
  139. package/dist/types/index.mjs +14 -0
  140. package/dist/types-B0dhNrnd.d.mts +445 -0
  141. package/dist/types-Beqn1Un7.mjs +38 -0
  142. package/dist/types-DelU6kln.mjs +25 -0
  143. package/dist/types-RLkFVgaw.d.mts +101 -0
  144. package/dist/utils/index.d.mts +747 -0
  145. package/dist/utils/index.mjs +6 -0
  146. package/package.json +194 -68
  147. package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
  148. package/dist/adapters/index.d.ts +0 -237
  149. package/dist/adapters/index.js +0 -668
  150. package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
  151. package/dist/audit/index.d.ts +0 -195
  152. package/dist/audit/index.js +0 -319
  153. package/dist/auth/index.d.ts +0 -47
  154. package/dist/auth/index.js +0 -174
  155. package/dist/cli/commands/docs.d.ts +0 -11
  156. package/dist/cli/commands/docs.js +0 -474
  157. package/dist/cli/commands/generate.js +0 -334
  158. package/dist/cli/commands/introspect.d.ts +0 -8
  159. package/dist/cli/commands/introspect.js +0 -338
  160. package/dist/cli/index.d.ts +0 -4
  161. package/dist/cli/index.js +0 -3269
  162. package/dist/core/index.d.ts +0 -220
  163. package/dist/core/index.js +0 -2786
  164. package/dist/createApp-Ce9wl8W9.d.ts +0 -77
  165. package/dist/docs/index.d.ts +0 -166
  166. package/dist/docs/index.js +0 -658
  167. package/dist/errors-8WIxGS_6.d.ts +0 -122
  168. package/dist/events/index.d.ts +0 -117
  169. package/dist/events/index.js +0 -89
  170. package/dist/factory/index.d.ts +0 -38
  171. package/dist/factory/index.js +0 -1652
  172. package/dist/hooks/index.d.ts +0 -4
  173. package/dist/hooks/index.js +0 -199
  174. package/dist/idempotency/index.d.ts +0 -323
  175. package/dist/idempotency/index.js +0 -500
  176. package/dist/index-B4t03KQ0.d.ts +0 -1366
  177. package/dist/index.d.ts +0 -135
  178. package/dist/index.js +0 -4756
  179. package/dist/migrations/index.d.ts +0 -185
  180. package/dist/migrations/index.js +0 -274
  181. package/dist/org/index.d.ts +0 -129
  182. package/dist/org/index.js +0 -220
  183. package/dist/permissions/index.d.ts +0 -144
  184. package/dist/permissions/index.js +0 -103
  185. package/dist/plugins/index.d.ts +0 -46
  186. package/dist/plugins/index.js +0 -1069
  187. package/dist/policies/index.js +0 -196
  188. package/dist/presets/index.js +0 -384
  189. package/dist/presets/multiTenant.d.ts +0 -39
  190. package/dist/presets/multiTenant.js +0 -112
  191. package/dist/registry/index.d.ts +0 -16
  192. package/dist/registry/index.js +0 -253
  193. package/dist/testing/index.d.ts +0 -618
  194. package/dist/testing/index.js +0 -48020
  195. package/dist/types/index.d.ts +0 -4
  196. package/dist/types/index.js +0 -8
  197. package/dist/types-B99TBmFV.d.ts +0 -76
  198. package/dist/types-BvckRbs2.d.ts +0 -143
  199. package/dist/utils/index.d.ts +0 -679
  200. package/dist/utils/index.js +0 -931
@@ -0,0 +1,18 @@
1
+ //#region src/cli/commands/describe.d.ts
2
+ /**
3
+ * Arc CLI - Describe Command
4
+ *
5
+ * Machine-readable resource metadata for AI agents.
6
+ * Outputs JSON with fields, permissions, pipeline, routes, events —
7
+ * everything an LLM needs to understand and generate code for the API.
8
+ *
9
+ * @example
10
+ * ```bash
11
+ * arc describe ./src/resources.js --json
12
+ * arc describe ./src/resources.js --resource product
13
+ * arc describe ./src/resources.js --pretty
14
+ * ```
15
+ */
16
+ declare function describe(args: string[]): Promise<void>;
17
+ //#endregion
18
+ export { describe as default, describe };
@@ -0,0 +1,238 @@
1
+ import { t as CRUD_OPERATIONS } from "../../constants-DdXFXQtN.mjs";
2
+ import { resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+
5
+ //#region src/cli/commands/describe.ts
6
+ /**
7
+ * Arc CLI - Describe Command
8
+ *
9
+ * Machine-readable resource metadata for AI agents.
10
+ * Outputs JSON with fields, permissions, pipeline, routes, events —
11
+ * everything an LLM needs to understand and generate code for the API.
12
+ *
13
+ * @example
14
+ * ```bash
15
+ * arc describe ./src/resources.js --json
16
+ * arc describe ./src/resources.js --resource product
17
+ * arc describe ./src/resources.js --pretty
18
+ * ```
19
+ */
20
+ function describePermission(check) {
21
+ if (!check || typeof check !== "function") return { type: "custom" };
22
+ const fn = check;
23
+ if (fn._isPublic === true) return { type: "public" };
24
+ if (Array.isArray(fn._roles)) return {
25
+ type: "requireRoles",
26
+ roles: fn._roles
27
+ };
28
+ const src = check.toString();
29
+ if (src.includes("ctx.user") && !src.includes("roles") && src.length < 200) return { type: "requireAuth" };
30
+ return { type: "custom" };
31
+ }
32
+ function describePermissions(perms) {
33
+ if (!perms) return {};
34
+ const result = {};
35
+ for (const op of CRUD_OPERATIONS) {
36
+ const check = perms[op];
37
+ if (check) result[op] = describePermission(check);
38
+ }
39
+ return result;
40
+ }
41
+ function describeFields(fieldPerms) {
42
+ if (!fieldPerms || Object.keys(fieldPerms).length === 0) return void 0;
43
+ const result = {};
44
+ for (const [field, perm] of Object.entries(fieldPerms)) {
45
+ const desc = { type: perm._type };
46
+ if (perm.roles?.length) desc.roles = perm.roles;
47
+ if (perm.redactValue !== void 0) desc.redactValue = perm.redactValue;
48
+ result[field] = desc;
49
+ }
50
+ return result;
51
+ }
52
+ function describePipeline(pipe) {
53
+ if (!pipe) return void 0;
54
+ const steps = [];
55
+ if (Array.isArray(pipe)) steps.push(...pipe);
56
+ else {
57
+ const seen = /* @__PURE__ */ new Set();
58
+ for (const opSteps of Object.values(pipe)) if (Array.isArray(opSteps)) for (const step of opSteps) {
59
+ const key = `${step._type}:${step.name}`;
60
+ if (!seen.has(key)) {
61
+ seen.add(key);
62
+ steps.push(step);
63
+ }
64
+ }
65
+ }
66
+ if (steps.length === 0) return void 0;
67
+ const guards = [];
68
+ const transforms = [];
69
+ const interceptors = [];
70
+ for (const step of steps) {
71
+ const desc = { name: step.name };
72
+ if (step.operations?.length) desc.operations = [...step.operations];
73
+ switch (step._type) {
74
+ case "guard":
75
+ guards.push(desc);
76
+ break;
77
+ case "transform":
78
+ transforms.push(desc);
79
+ break;
80
+ case "interceptor":
81
+ interceptors.push(desc);
82
+ break;
83
+ }
84
+ }
85
+ return {
86
+ guards,
87
+ transforms,
88
+ interceptors
89
+ };
90
+ }
91
+ function describeRoutes(resource) {
92
+ const routes = [];
93
+ if (!resource.disableDefaultRoutes) {
94
+ const disabled = new Set(resource.disabledRoutes ?? []);
95
+ for (const { method, suffix, op } of [
96
+ {
97
+ method: "GET",
98
+ suffix: "",
99
+ op: "list"
100
+ },
101
+ {
102
+ method: "GET",
103
+ suffix: "/:id",
104
+ op: "get"
105
+ },
106
+ {
107
+ method: "POST",
108
+ suffix: "",
109
+ op: "create"
110
+ },
111
+ {
112
+ method: "PATCH",
113
+ suffix: "/:id",
114
+ op: "update"
115
+ },
116
+ {
117
+ method: "DELETE",
118
+ suffix: "/:id",
119
+ op: "delete"
120
+ }
121
+ ]) if (!disabled.has(op)) {
122
+ const route = {
123
+ method,
124
+ path: `${resource.prefix}${suffix}`,
125
+ operation: op
126
+ };
127
+ const perm = resource.permissions[op];
128
+ if (perm) route.permission = describePermission(perm);
129
+ routes.push(route);
130
+ }
131
+ }
132
+ for (const ar of resource.additionalRoutes) routes.push({
133
+ method: ar.method,
134
+ path: `${resource.prefix}${ar.path}`,
135
+ operation: typeof ar.handler === "string" ? ar.handler : "custom",
136
+ summary: ar.summary,
137
+ description: ar.description,
138
+ permission: describePermission(ar.permissions)
139
+ });
140
+ return routes;
141
+ }
142
+ function describeEvents(resourceName, events) {
143
+ if (!events) return [];
144
+ return Object.entries(events).map(([action, def]) => ({
145
+ name: `${resourceName}:${action}`,
146
+ description: def.description,
147
+ hasSchema: !!def.schema
148
+ }));
149
+ }
150
+ function describeMiddlewares(middlewares) {
151
+ if (!middlewares) return [];
152
+ const ops = [];
153
+ for (const [op, handlers] of Object.entries(middlewares)) if (handlers?.length) ops.push(`${op}(${handlers.length})`);
154
+ return ops;
155
+ }
156
+ function describeResource(resource, module) {
157
+ return {
158
+ name: resource.name,
159
+ displayName: resource.displayName,
160
+ prefix: resource.prefix,
161
+ tag: resource.tag,
162
+ module,
163
+ adapter: resource.adapter ? {
164
+ type: resource.adapter.type,
165
+ name: resource.adapter.name
166
+ } : null,
167
+ permissions: describePermissions(resource.permissions),
168
+ presets: resource._appliedPresets ?? [],
169
+ fields: describeFields(resource.fields),
170
+ pipeline: describePipeline(resource.pipe),
171
+ routes: describeRoutes(resource),
172
+ events: describeEvents(resource.name, resource.events),
173
+ schemaOptions: Object.keys(resource.schemaOptions ?? {}).length > 0 ? resource.schemaOptions : void 0,
174
+ rateLimit: resource.rateLimit,
175
+ middlewares: describeMiddlewares(resource.middlewares)
176
+ };
177
+ }
178
+ async function describe(args) {
179
+ try {
180
+ const flags = new Set(args.filter((a) => a.startsWith("--")));
181
+ const positional = args.filter((a) => !a.startsWith("--"));
182
+ const pretty = flags.has("--pretty") || !flags.has("--json");
183
+ const filterResource = positional[1];
184
+ const entryPath = positional[0];
185
+ if (!entryPath) {
186
+ console.log("Usage: arc describe <entry-file> [resource-name] [--json] [--pretty]\n");
187
+ console.log("Outputs machine-readable JSON metadata for AI agents.\n");
188
+ console.log("Options:");
189
+ console.log(" --json Output compact JSON (default if piped)");
190
+ console.log(" --pretty Output formatted JSON (default if terminal)");
191
+ console.log("\nExamples:");
192
+ console.log(" arc describe ./src/resources.js");
193
+ console.log(" arc describe ./src/resources.js product");
194
+ console.log(" arc describe ./src/resources.js --json | jq .");
195
+ return;
196
+ }
197
+ const entryModule = await import(pathToFileURL(resolve(process.cwd(), entryPath)).href);
198
+ const resources = [];
199
+ function tryCollect(value) {
200
+ if (value && typeof value === "object" && "name" in value && "_registryMeta" in value && "toPlugin" in value) resources.push(value);
201
+ }
202
+ for (const exported of Object.values(entryModule)) if (Array.isArray(exported)) exported.forEach(tryCollect);
203
+ else tryCollect(exported);
204
+ if (resources.length === 0) throw new Error("No resource definitions found in entry file.\nMake sure your file exports defineResource() results:\n export const productResource = defineResource({ ... });");
205
+ const filtered = filterResource ? resources.filter((r) => r.name === filterResource) : resources;
206
+ if (filterResource && filtered.length === 0) throw new Error(`Resource '${filterResource}' not found.\nAvailable: ${resources.map((r) => r.name).join(", ")}`);
207
+ const described = filtered.map((r) => describeResource(r, r._registryMeta?.module));
208
+ const presetCounts = {};
209
+ let totalPipelineSteps = 0;
210
+ let totalFields = 0;
211
+ for (const res of described) {
212
+ for (const preset of res.presets) presetCounts[preset] = (presetCounts[preset] ?? 0) + 1;
213
+ if (res.pipeline) totalPipelineSteps += res.pipeline.guards.length + res.pipeline.transforms.length + res.pipeline.interceptors.length;
214
+ if (res.fields) totalFields += Object.keys(res.fields).length;
215
+ }
216
+ const output = {
217
+ $schema: "arc-describe/v1",
218
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
219
+ resources: described,
220
+ stats: {
221
+ totalResources: described.length,
222
+ totalRoutes: described.reduce((sum, r) => sum + r.routes.length, 0),
223
+ totalEvents: described.reduce((sum, r) => sum + r.events.length, 0),
224
+ totalFields,
225
+ presetUsage: presetCounts,
226
+ pipelineSteps: totalPipelineSteps
227
+ }
228
+ };
229
+ const json = pretty ? JSON.stringify(output, null, 2) : JSON.stringify(output);
230
+ console.log(json);
231
+ } catch (error) {
232
+ if (error instanceof Error) throw error;
233
+ throw new Error(String(error));
234
+ }
235
+ }
236
+
237
+ //#endregion
238
+ export { describe as default, describe };
@@ -0,0 +1,13 @@
1
+ //#region src/cli/commands/docs.d.ts
2
+ /**
3
+ * Arc CLI - Docs Command
4
+ *
5
+ * Export OpenAPI specification from registered resources.
6
+ * Requires an entry file that exports defineResource() results.
7
+ */
8
+ declare function exportDocs(args: string[]): Promise<void>;
9
+ declare const _default: {
10
+ exportDocs: typeof exportDocs;
11
+ };
12
+ //#endregion
13
+ export { _default as default, exportDocs };
@@ -0,0 +1,52 @@
1
+ import { t as ResourceRegistry } from "../../ResourceRegistry-7Ic20ZMw.mjs";
2
+ import { t as buildOpenApiSpec } from "../../openapi-9nB_kiuR.mjs";
3
+ import { writeFileSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ //#region src/cli/commands/docs.ts
8
+ /**
9
+ * Arc CLI - Docs Command
10
+ *
11
+ * Export OpenAPI specification from registered resources.
12
+ * Requires an entry file that exports defineResource() results.
13
+ */
14
+ function parseDocsArgs(args) {
15
+ const outputPath = args.find((a) => a.endsWith(".json")) ?? "./openapi.json";
16
+ return {
17
+ entryPath: args.find((a) => !a.endsWith(".json")),
18
+ outputPath
19
+ };
20
+ }
21
+ async function exportDocs(args) {
22
+ const { entryPath, outputPath } = parseDocsArgs(args);
23
+ console.log("Exporting OpenAPI specification...\n");
24
+ if (!entryPath) throw new Error("Missing entry file.\n\nUsage: arc docs <entry-file> [output.json]\nExample: arc docs ./src/resources.js ./openapi.json");
25
+ const entryModule = await import(pathToFileURL(resolve(process.cwd(), entryPath)).href);
26
+ const registry = new ResourceRegistry();
27
+ let registered = 0;
28
+ function tryRegister(value) {
29
+ if (value && typeof value === "object" && "name" in value && "_registryMeta" in value && "toPlugin" in value) {
30
+ registry.register(value, value._registryMeta ?? {});
31
+ registered++;
32
+ }
33
+ }
34
+ for (const exported of Object.values(entryModule)) if (Array.isArray(exported)) exported.forEach(tryRegister);
35
+ else tryRegister(exported);
36
+ if (registered === 0) throw new Error("No resource definitions found in entry file.\nMake sure your file exports defineResource() results:\n export const productResource = defineResource({ ... });");
37
+ const resources = registry.getAll();
38
+ const spec = buildOpenApiSpec(resources, {
39
+ title: "Arc API",
40
+ version: "1.0.0",
41
+ description: "Auto-generated from Arc resources"
42
+ });
43
+ const fullPath = resolve(process.cwd(), outputPath);
44
+ writeFileSync(fullPath, JSON.stringify(spec, null, 2));
45
+ console.log(`OpenAPI spec exported to: ${fullPath}`);
46
+ console.log(`\nResources included: ${resources.length}`);
47
+ console.log(`Total endpoints: ${Object.keys(spec.paths).length}`);
48
+ }
49
+ var docs_default = { exportDocs };
50
+
51
+ //#endregion
52
+ export { docs_default as default, exportDocs };
@@ -1,3 +1,4 @@
1
+ //#region src/cli/commands/generate.d.ts
1
2
  /**
2
3
  * Arc CLI - Generate Command
3
4
  *
@@ -12,5 +13,5 @@
12
13
  * Generate command handler
13
14
  */
14
15
  declare function generate(type: string | undefined, args: string[]): Promise<void>;
15
-
16
- export { generate as default, generate };
16
+ //#endregion
17
+ export { generate as default, generate };
@@ -0,0 +1,357 @@
1
+ import { t as pluralize } from "../../pluralize-CM-jZg7p.mjs";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ //#region src/cli/commands/generate.ts
6
+ /**
7
+ * Arc CLI - Generate Command
8
+ *
9
+ * Scaffolds resources with consistent naming:
10
+ * - src/resources/product/product.model.ts
11
+ * - src/resources/product/product.repository.ts
12
+ * - src/resources/product/product.resource.ts
13
+ * - src/resources/product/product.controller.ts
14
+ * - src/resources/product/product.schemas.ts
15
+ */
16
+ function readProjectConfig() {
17
+ try {
18
+ const rcPath = join(process.cwd(), ".arcrc");
19
+ return JSON.parse(readFileSync(rcPath, "utf-8"));
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+ function isTypeScriptProject() {
25
+ return existsSync(join(process.cwd(), "tsconfig.json"));
26
+ }
27
+ function getTemplates(ts, config = {}) {
28
+ const isMultiTenant = config.tenant === "multi";
29
+ return {
30
+ model: (name) => `/**
31
+ * ${name} Model
32
+ * Generated by Arc CLI
33
+ */
34
+
35
+ import mongoose${ts ? ", { type HydratedDocument }" : ""} from 'mongoose';
36
+
37
+ const { Schema } = mongoose;
38
+ ${ts ? `
39
+ export interface I${name} {
40
+ name: string;
41
+ description?: string;
42
+ isActive: boolean;
43
+ }
44
+
45
+ export type ${name}Document = HydratedDocument<I${name}>;
46
+ ` : ""}
47
+ const ${name.toLowerCase()}Schema = new Schema${ts ? `<I${name}>` : ""}(
48
+ {
49
+ name: { type: String, required: true, trim: true },
50
+ description: { type: String, trim: true },
51
+ isActive: { type: Boolean, default: true },
52
+ },
53
+ { timestamps: true }
54
+ );
55
+
56
+ // Indexes
57
+ ${name.toLowerCase()}Schema.index({ name: 1 });
58
+ ${name.toLowerCase()}Schema.index({ isActive: 1 });
59
+
60
+ const ${name} = mongoose.models.${name} || mongoose.model('${name}', ${name.toLowerCase()}Schema);
61
+ export default ${name};
62
+ `,
63
+ repository: (name) => `/**
64
+ * ${name} Repository
65
+ * Generated by Arc CLI
66
+ */
67
+
68
+ import {
69
+ Repository,
70
+ methodRegistryPlugin,
71
+ softDeletePlugin,
72
+ mongoOperationsPlugin,
73
+ } from '@classytic/mongokit';
74
+ import ${name} from './${name.toLowerCase()}.model.js';
75
+
76
+ class ${name}Repository extends Repository {
77
+ constructor() {
78
+ super(${name}, [
79
+ methodRegistryPlugin(),
80
+ softDeletePlugin(),
81
+ mongoOperationsPlugin(),
82
+ ]);
83
+ }
84
+
85
+ /**
86
+ * Find all active records
87
+ */
88
+ async findActive() {
89
+ return this.Model.find({ isActive: true, deletedAt: null }).lean();
90
+ }
91
+ }
92
+
93
+ const ${name.toLowerCase()}Repository = new ${name}Repository();
94
+ export default ${name.toLowerCase()}Repository;
95
+ export { ${name}Repository };
96
+ `,
97
+ controller: (name) => `/**
98
+ * ${name} Controller
99
+ * Generated by Arc CLI
100
+ *
101
+ * Note: defineResource() auto-creates a controller from the adapter.
102
+ * Only create a custom controller when you need custom methods.
103
+ */
104
+
105
+ import { BaseController } from '@classytic/arc';
106
+ import ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';
107
+
108
+ class ${name}Controller extends BaseController {
109
+ constructor() {
110
+ super(${name.toLowerCase()}Repository, {
111
+ resourceName: '${name.toLowerCase()}',
112
+ });
113
+ }
114
+
115
+ // Add custom controller methods here
116
+ }
117
+
118
+ const ${name.toLowerCase()}Controller = new ${name}Controller();
119
+ export default ${name.toLowerCase()}Controller;
120
+ `,
121
+ schemas: (name) => `/**
122
+ * ${name} Schemas
123
+ * Generated by Arc CLI
124
+ */
125
+
126
+ import ${name} from './${name.toLowerCase()}.model.js';
127
+ import { buildCrudSchemasFromModel } from '@classytic/mongokit/utils';
128
+
129
+ /**
130
+ * CRUD Schemas with Field Rules
131
+ */
132
+ const crudSchemas = buildCrudSchemasFromModel(${name}, {
133
+ strictAdditionalProperties: true,
134
+ fieldRules: {
135
+ // Mark fields as system-managed (excluded from create/update)
136
+ // deletedAt: { systemManaged: true },
137
+ },
138
+ query: {
139
+ filterableFields: {
140
+ isActive: 'boolean',
141
+ createdAt: 'date',
142
+ },
143
+ },
144
+ });
145
+
146
+ export default crudSchemas;
147
+ `,
148
+ resource: (name) => {
149
+ const useMongoKit = config.adapter === "mongokit" || !config.adapter;
150
+ const queryParserImport = useMongoKit ? `\nimport { QueryParser } from '@classytic/mongokit';\n\nconst queryParser = new QueryParser();\n` : "";
151
+ const queryParserConfig = useMongoKit ? `\n queryParser,` : "";
152
+ return isMultiTenant ? `/**
153
+ * ${name} Resource
154
+ * Generated by Arc CLI
155
+ */
156
+
157
+ import { defineResource, createMongooseAdapter } from '@classytic/arc';
158
+ import { requireOrgMembership, requireOrgRole } from '@classytic/arc/permissions';
159
+ import ${name}${ts ? `, { type I${name} }` : ""} from './${name.toLowerCase()}.model.js';
160
+ import ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';${queryParserImport}
161
+
162
+ const ${name.toLowerCase()}Resource = defineResource${ts ? `<I${name}>` : ""}({
163
+ name: '${name.toLowerCase()}',
164
+ adapter: createMongooseAdapter(${name}, ${name.toLowerCase()}Repository),${queryParserConfig}
165
+ presets: ['softDelete'],
166
+ permissions: {
167
+ list: requireOrgMembership(),
168
+ get: requireOrgMembership(),
169
+ create: requireOrgRole('admin'),
170
+ update: requireOrgRole('admin'),
171
+ delete: requireOrgRole('admin'),
172
+ },
173
+ });
174
+
175
+ export default ${name.toLowerCase()}Resource;
176
+ ` : `/**
177
+ * ${name} Resource
178
+ * Generated by Arc CLI
179
+ */
180
+
181
+ import { defineResource, createMongooseAdapter } from '@classytic/arc';
182
+ import { requireAuth, requireRoles } from '@classytic/arc/permissions';
183
+ import ${name}${ts ? `, { type I${name} }` : ""} from './${name.toLowerCase()}.model.js';
184
+ import ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';${queryParserImport}
185
+
186
+ const ${name.toLowerCase()}Resource = defineResource${ts ? `<I${name}>` : ""}({
187
+ name: '${name.toLowerCase()}',
188
+ adapter: createMongooseAdapter(${name}, ${name.toLowerCase()}Repository),${queryParserConfig}
189
+ presets: ['softDelete'],
190
+ permissions: {
191
+ list: requireAuth(),
192
+ get: requireAuth(),
193
+ create: requireRoles(['admin']),
194
+ update: requireRoles(['admin']),
195
+ delete: requireRoles(['admin']),
196
+ },
197
+ });
198
+
199
+ export default ${name.toLowerCase()}Resource;
200
+ `;
201
+ },
202
+ test: (name) => `/**
203
+ * ${name} Tests
204
+ * Generated by Arc CLI
205
+ */
206
+
207
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
208
+ import mongoose from 'mongoose';
209
+ import { createMinimalTestApp } from '@classytic/arc/testing';
210
+ ${ts ? "import type { FastifyInstance } from 'fastify';\n" : ""}import ${name.toLowerCase()}Resource from '../src/resources/${name.toLowerCase()}/${name.toLowerCase()}.resource.js';
211
+
212
+ describe('${name} Resource', () => {
213
+ let app${ts ? ": FastifyInstance" : ""};
214
+
215
+ beforeAll(async () => {
216
+ const testDbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/arc-app-test';
217
+ await mongoose.connect(testDbUri);
218
+
219
+ app = createMinimalTestApp();
220
+ await app.register(${name.toLowerCase()}Resource.toPlugin());
221
+ await app.ready();
222
+ });
223
+
224
+ afterAll(async () => {
225
+ await app.close();
226
+ await mongoose.connection.close();
227
+ });
228
+
229
+ describe('GET /${pluralize(name.toLowerCase())}', () => {
230
+ it('should return a list', async () => {
231
+ const response = await app.inject({
232
+ method: 'GET',
233
+ url: '/${pluralize(name.toLowerCase())}',
234
+ });
235
+
236
+ expect(response.statusCode).toBe(200);
237
+ const body = JSON.parse(response.body);
238
+ expect(body).toHaveProperty('docs');
239
+ });
240
+ });
241
+ });
242
+ `
243
+ };
244
+ }
245
+ /**
246
+ * Generate command handler
247
+ */
248
+ async function generate(type, args) {
249
+ if (!type) throw new Error("Missing type argument\nUsage: arc generate <resource|controller|model|repository|schemas> <name>");
250
+ const [name] = args;
251
+ if (!name) throw new Error("Missing name argument\nUsage: arc generate <type> <name>\nExample: arc generate resource product");
252
+ const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
253
+ const lowerName = name.toLowerCase();
254
+ const projectConfig = readProjectConfig();
255
+ const ts = projectConfig.typescript ?? isTypeScriptProject();
256
+ const ext = ts ? "ts" : "js";
257
+ const templates = getTemplates(ts, projectConfig);
258
+ const resourcePath = join(process.cwd(), "src", "resources", lowerName);
259
+ switch (type) {
260
+ case "resource":
261
+ case "r":
262
+ await generateResource(capitalizedName, lowerName, resourcePath, templates, ext);
263
+ break;
264
+ case "controller":
265
+ case "c":
266
+ await generateFile(capitalizedName, lowerName, resourcePath, "controller", templates.controller, ext);
267
+ break;
268
+ case "model":
269
+ case "m":
270
+ await generateFile(capitalizedName, lowerName, resourcePath, "model", templates.model, ext);
271
+ break;
272
+ case "repository":
273
+ case "repo":
274
+ await generateFile(capitalizedName, lowerName, resourcePath, "repository", templates.repository, ext);
275
+ break;
276
+ case "schemas":
277
+ case "s":
278
+ await generateFile(capitalizedName, lowerName, resourcePath, "schemas", templates.schemas, ext);
279
+ break;
280
+ default: throw new Error(`Unknown type: ${type}\nAvailable types: resource, controller, model, repository, schemas`);
281
+ }
282
+ }
283
+ /**
284
+ * Generate a full resource
285
+ */
286
+ async function generateResource(name, lowerName, resourcePath, templates, ext) {
287
+ console.log(`\nGenerating resource: ${name}...\n`);
288
+ if (!existsSync(resourcePath)) {
289
+ mkdirSync(resourcePath, { recursive: true });
290
+ console.log(` + Created: src/resources/${lowerName}/`);
291
+ }
292
+ const files = {
293
+ [`${lowerName}.model.${ext}`]: templates.model(name),
294
+ [`${lowerName}.repository.${ext}`]: templates.repository(name),
295
+ [`${lowerName}.resource.${ext}`]: templates.resource(name)
296
+ };
297
+ for (const [filename, content] of Object.entries(files)) {
298
+ const filepath = join(resourcePath, filename);
299
+ if (existsSync(filepath)) console.warn(` ! Skipped: ${filename} (already exists)`);
300
+ else {
301
+ writeFileSync(filepath, content);
302
+ console.log(` + Created: ${filename}`);
303
+ }
304
+ }
305
+ const testsDir = join(process.cwd(), "tests");
306
+ if (!existsSync(testsDir)) mkdirSync(testsDir, { recursive: true });
307
+ const testPath = join(testsDir, `${lowerName}.test.${ext}`);
308
+ if (!existsSync(testPath)) {
309
+ writeFileSync(testPath, templates.test(name));
310
+ console.log(` + Created: tests/${lowerName}.test.${ext}`);
311
+ }
312
+ const isMultiTenant = readProjectConfig().tenant === "multi";
313
+ console.log(`
314
+ ╔═══════════════════════════════════════════════════════════════╗
315
+ ║ Resource Generated ║
316
+ ╚═══════════════════════════════════════════════════════════════╝
317
+
318
+ Next steps:
319
+
320
+ 1. Register in src/resources/index.${ext}:
321
+ import ${lowerName}Resource from './${lowerName}/${lowerName}.resource.js';
322
+
323
+ export const resources = [
324
+ // ... existing resources
325
+ ${lowerName}Resource,
326
+ ];
327
+
328
+ 2. Customize the model schema in:
329
+ src/resources/${lowerName}/${lowerName}.model.${ext}
330
+
331
+ 3. Adjust permissions in ${lowerName}.resource.${ext}:
332
+ ${isMultiTenant ? ` - requireOrgMembership() → any org member
333
+ - requireOrgRole('admin') → specific org roles` : ` - requireAuth() → any authenticated user
334
+ - requireRoles(['admin']) → specific platform roles`}
335
+
336
+ 4. Run tests:
337
+ npm test
338
+ `);
339
+ }
340
+ /**
341
+ * Generate a single file
342
+ */
343
+ async function generateFile(name, lowerName, resourcePath, fileType, template, ext) {
344
+ console.log(`\nGenerating ${fileType}: ${name}...\n`);
345
+ if (!existsSync(resourcePath)) {
346
+ mkdirSync(resourcePath, { recursive: true });
347
+ console.log(` + Created: src/resources/${lowerName}/`);
348
+ }
349
+ const filename = `${lowerName}.${fileType}.${ext}`;
350
+ const filepath = join(resourcePath, filename);
351
+ if (existsSync(filepath)) throw new Error(`${filename} already exists. Remove it first or use a different name.`);
352
+ writeFileSync(filepath, template(name));
353
+ console.log(` + Created: ${filename}`);
354
+ }
355
+
356
+ //#endregion
357
+ export { generate as default, generate };