@classytic/arc 1.0.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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +900 -0
  3. package/bin/arc.js +344 -0
  4. package/dist/adapters/index.d.ts +237 -0
  5. package/dist/adapters/index.js +668 -0
  6. package/dist/arcCorePlugin-DTPWXcZN.d.ts +273 -0
  7. package/dist/audit/index.d.ts +195 -0
  8. package/dist/audit/index.js +319 -0
  9. package/dist/auth/index.d.ts +47 -0
  10. package/dist/auth/index.js +174 -0
  11. package/dist/cli/commands/docs.d.ts +11 -0
  12. package/dist/cli/commands/docs.js +474 -0
  13. package/dist/cli/commands/introspect.d.ts +8 -0
  14. package/dist/cli/commands/introspect.js +338 -0
  15. package/dist/cli/index.d.ts +43 -0
  16. package/dist/cli/index.js +520 -0
  17. package/dist/createApp-pzUAkzbz.d.ts +77 -0
  18. package/dist/docs/index.d.ts +166 -0
  19. package/dist/docs/index.js +650 -0
  20. package/dist/errors-8WIxGS_6.d.ts +122 -0
  21. package/dist/events/index.d.ts +117 -0
  22. package/dist/events/index.js +89 -0
  23. package/dist/factory/index.d.ts +38 -0
  24. package/dist/factory/index.js +1664 -0
  25. package/dist/hooks/index.d.ts +4 -0
  26. package/dist/hooks/index.js +199 -0
  27. package/dist/idempotency/index.d.ts +323 -0
  28. package/dist/idempotency/index.js +500 -0
  29. package/dist/index-DkAW8BXh.d.ts +1302 -0
  30. package/dist/index.d.ts +331 -0
  31. package/dist/index.js +4734 -0
  32. package/dist/migrations/index.d.ts +185 -0
  33. package/dist/migrations/index.js +274 -0
  34. package/dist/org/index.d.ts +129 -0
  35. package/dist/org/index.js +220 -0
  36. package/dist/permissions/index.d.ts +144 -0
  37. package/dist/permissions/index.js +100 -0
  38. package/dist/plugins/index.d.ts +46 -0
  39. package/dist/plugins/index.js +1069 -0
  40. package/dist/policies/index.d.ts +398 -0
  41. package/dist/policies/index.js +196 -0
  42. package/dist/presets/index.d.ts +336 -0
  43. package/dist/presets/index.js +382 -0
  44. package/dist/presets/multiTenant.d.ts +39 -0
  45. package/dist/presets/multiTenant.js +112 -0
  46. package/dist/registry/index.d.ts +16 -0
  47. package/dist/registry/index.js +253 -0
  48. package/dist/testing/index.d.ts +618 -0
  49. package/dist/testing/index.js +48032 -0
  50. package/dist/types/index.d.ts +4 -0
  51. package/dist/types/index.js +8 -0
  52. package/dist/types-0IPhH_NR.d.ts +143 -0
  53. package/dist/types-B99TBmFV.d.ts +76 -0
  54. package/dist/utils/index.d.ts +655 -0
  55. package/dist/utils/index.js +905 -0
  56. package/package.json +227 -0
@@ -0,0 +1,338 @@
1
+ import fp from 'fastify-plugin';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // src/registry/ResourceRegistry.ts
14
+ var ResourceRegistry, registryKey, globalScope, resourceRegistry;
15
+ var init_ResourceRegistry = __esm({
16
+ "src/registry/ResourceRegistry.ts"() {
17
+ ResourceRegistry = class {
18
+ _resources;
19
+ _frozen;
20
+ constructor() {
21
+ this._resources = /* @__PURE__ */ new Map();
22
+ this._frozen = false;
23
+ }
24
+ /**
25
+ * Register a resource
26
+ */
27
+ register(resource, options = {}) {
28
+ if (this._frozen) {
29
+ throw new Error(
30
+ `Registry frozen. Cannot register '${resource.name}' after startup.`
31
+ );
32
+ }
33
+ if (this._resources.has(resource.name)) {
34
+ throw new Error(`Resource '${resource.name}' already registered.`);
35
+ }
36
+ const entry = {
37
+ name: resource.name,
38
+ displayName: resource.displayName,
39
+ tag: resource.tag,
40
+ prefix: resource.prefix,
41
+ module: options.module ?? void 0,
42
+ adapter: resource.adapter ? {
43
+ type: resource.adapter.type,
44
+ name: resource.adapter.name
45
+ } : null,
46
+ permissions: resource.permissions,
47
+ presets: resource._appliedPresets ?? [],
48
+ routes: [],
49
+ // Populated later by getIntrospection()
50
+ additionalRoutes: resource.additionalRoutes.map((r) => ({
51
+ method: r.method,
52
+ path: r.path,
53
+ handler: typeof r.handler === "string" ? r.handler : r.handler.name || "anonymous",
54
+ summary: r.summary,
55
+ description: r.description,
56
+ permissions: r.permissions,
57
+ wrapHandler: r.wrapHandler,
58
+ schema: r.schema
59
+ // Include schema for OpenAPI docs
60
+ })),
61
+ events: Object.keys(resource.events ?? {}),
62
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
63
+ disableDefaultRoutes: resource.disableDefaultRoutes,
64
+ openApiSchemas: options.openApiSchemas,
65
+ plugin: resource.toPlugin()
66
+ // Store plugin factory
67
+ };
68
+ this._resources.set(resource.name, entry);
69
+ return this;
70
+ }
71
+ /**
72
+ * Get resource by name
73
+ */
74
+ get(name) {
75
+ return this._resources.get(name);
76
+ }
77
+ /**
78
+ * Get all resources
79
+ */
80
+ getAll() {
81
+ return Array.from(this._resources.values());
82
+ }
83
+ /**
84
+ * Get resources by module
85
+ */
86
+ getByModule(moduleName) {
87
+ return this.getAll().filter((r) => r.module === moduleName);
88
+ }
89
+ /**
90
+ * Get resources by preset
91
+ */
92
+ getByPreset(presetName) {
93
+ return this.getAll().filter((r) => r.presets.includes(presetName));
94
+ }
95
+ /**
96
+ * Check if resource exists
97
+ */
98
+ has(name) {
99
+ return this._resources.has(name);
100
+ }
101
+ /**
102
+ * Get registry statistics
103
+ */
104
+ getStats() {
105
+ const resources = this.getAll();
106
+ const presetCounts = {};
107
+ for (const r of resources) {
108
+ for (const preset of r.presets) {
109
+ presetCounts[preset] = (presetCounts[preset] ?? 0) + 1;
110
+ }
111
+ }
112
+ return {
113
+ totalResources: resources.length,
114
+ byModule: this._groupBy(resources, "module"),
115
+ presetUsage: presetCounts,
116
+ totalRoutes: resources.reduce((sum, r) => {
117
+ const defaultRouteCount = r.disableDefaultRoutes ? 0 : 5;
118
+ return sum + (r.additionalRoutes?.length ?? 0) + defaultRouteCount;
119
+ }, 0),
120
+ totalEvents: resources.reduce((sum, r) => sum + (r.events?.length ?? 0), 0)
121
+ };
122
+ }
123
+ /**
124
+ * Get full introspection data
125
+ */
126
+ getIntrospection() {
127
+ return {
128
+ resources: this.getAll().map((r) => {
129
+ const defaultRoutes = r.disableDefaultRoutes ? [] : [
130
+ { method: "GET", path: r.prefix, operation: "list" },
131
+ { method: "GET", path: `${r.prefix}/:id`, operation: "get" },
132
+ { method: "POST", path: r.prefix, operation: "create" },
133
+ { method: "PATCH", path: `${r.prefix}/:id`, operation: "update" },
134
+ { method: "DELETE", path: `${r.prefix}/:id`, operation: "delete" }
135
+ ];
136
+ return {
137
+ name: r.name,
138
+ displayName: r.displayName,
139
+ prefix: r.prefix,
140
+ module: r.module,
141
+ presets: r.presets,
142
+ permissions: r.permissions,
143
+ routes: [
144
+ ...defaultRoutes,
145
+ ...r.additionalRoutes?.map((ar) => ({
146
+ method: ar.method,
147
+ path: `${r.prefix}${ar.path}`,
148
+ operation: typeof ar.handler === "string" ? ar.handler : "custom",
149
+ handler: typeof ar.handler === "string" ? ar.handler : void 0,
150
+ summary: ar.summary
151
+ })) ?? []
152
+ ],
153
+ events: r.events
154
+ };
155
+ }),
156
+ stats: this.getStats(),
157
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
158
+ };
159
+ }
160
+ /**
161
+ * Freeze registry (prevent further registrations)
162
+ */
163
+ freeze() {
164
+ this._frozen = true;
165
+ }
166
+ /**
167
+ * Check if frozen
168
+ */
169
+ isFrozen() {
170
+ return this._frozen;
171
+ }
172
+ /**
173
+ * Unfreeze registry (for testing)
174
+ */
175
+ _unfreeze() {
176
+ this._frozen = false;
177
+ }
178
+ /**
179
+ * Clear all resources (for testing)
180
+ */
181
+ _clear() {
182
+ this._resources.clear();
183
+ this._frozen = false;
184
+ }
185
+ /**
186
+ * Group by key
187
+ */
188
+ _groupBy(arr, key) {
189
+ const result = {};
190
+ for (const item of arr) {
191
+ const k = String(item[key] ?? "uncategorized");
192
+ result[k] = (result[k] ?? 0) + 1;
193
+ }
194
+ return result;
195
+ }
196
+ };
197
+ registryKey = /* @__PURE__ */ Symbol.for("arc.resourceRegistry");
198
+ globalScope = globalThis;
199
+ resourceRegistry = globalScope[registryKey] ?? new ResourceRegistry();
200
+ if (!globalScope[registryKey]) {
201
+ globalScope[registryKey] = resourceRegistry;
202
+ }
203
+ }
204
+ });
205
+ var introspectionPlugin, introspectionPlugin_default;
206
+ var init_introspectionPlugin = __esm({
207
+ "src/registry/introspectionPlugin.ts"() {
208
+ init_ResourceRegistry();
209
+ introspectionPlugin = async (fastify, opts = {}) => {
210
+ const {
211
+ prefix = "/_resources",
212
+ authRoles = ["superadmin"],
213
+ enabled = process.env.NODE_ENV !== "production" || process.env.ENABLE_INTROSPECTION === "true"
214
+ } = opts;
215
+ if (!enabled) {
216
+ fastify.log?.info?.("Introspection plugin disabled");
217
+ return;
218
+ }
219
+ const typedFastify = fastify;
220
+ const authMiddleware = authRoles.length > 0 && typedFastify.authenticate ? [
221
+ typedFastify.authenticate,
222
+ typedFastify.authorize?.(...authRoles)
223
+ ].filter(Boolean) : [];
224
+ await fastify.register(async (instance) => {
225
+ instance.get(
226
+ "/",
227
+ {
228
+ preHandler: authMiddleware
229
+ },
230
+ async (_req, _reply) => {
231
+ return resourceRegistry.getIntrospection();
232
+ }
233
+ );
234
+ instance.get(
235
+ "/stats",
236
+ {
237
+ preHandler: authMiddleware
238
+ },
239
+ async (_req, _reply) => {
240
+ return resourceRegistry.getStats();
241
+ }
242
+ );
243
+ instance.get(
244
+ "/:name",
245
+ {
246
+ schema: {
247
+ params: {
248
+ type: "object",
249
+ properties: {
250
+ name: { type: "string" }
251
+ },
252
+ required: ["name"]
253
+ }
254
+ },
255
+ preHandler: authMiddleware
256
+ },
257
+ async (req, reply) => {
258
+ const resource = resourceRegistry.get(req.params.name);
259
+ if (!resource) {
260
+ return reply.code(404).send({
261
+ error: `Resource '${req.params.name}' not found`
262
+ });
263
+ }
264
+ return resource;
265
+ }
266
+ );
267
+ }, { prefix });
268
+ fastify.log?.info?.(`Introspection API at ${prefix}`);
269
+ };
270
+ introspectionPlugin_default = fp(introspectionPlugin, { name: "arc-introspection" });
271
+ }
272
+ });
273
+
274
+ // src/registry/index.ts
275
+ var registry_exports = {};
276
+ __export(registry_exports, {
277
+ ResourceRegistry: () => ResourceRegistry,
278
+ introspectionPlugin: () => introspectionPlugin_default,
279
+ introspectionPluginFn: () => introspectionPlugin,
280
+ resourceRegistry: () => resourceRegistry
281
+ });
282
+ var init_registry = __esm({
283
+ "src/registry/index.ts"() {
284
+ init_ResourceRegistry();
285
+ init_introspectionPlugin();
286
+ }
287
+ });
288
+
289
+ // src/cli/commands/introspect.ts
290
+ async function introspect(args) {
291
+ console.log("Introspecting Arc resources...\n");
292
+ try {
293
+ const { resourceRegistry: resourceRegistry2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
294
+ const resources = resourceRegistry2.getAll();
295
+ if (resources.length === 0) {
296
+ console.log("⚠️ No resources registered.");
297
+ console.log("\nTo introspect resources, you need to load them first:");
298
+ console.log(" arc introspect --entry ./index.js");
299
+ console.log("\nWhere index.js imports all your resource definitions.");
300
+ return;
301
+ }
302
+ console.log(`Found ${resources.length} resource(s):
303
+ `);
304
+ resources.forEach((resource, index) => {
305
+ console.log(`${index + 1}. ${resource.name}`);
306
+ console.log(` Display Name: ${resource.displayName}`);
307
+ console.log(` Prefix: ${resource.prefix}`);
308
+ console.log(` Module: ${resource.module || "none"}`);
309
+ if (resource.permissions) {
310
+ console.log(` Permissions:`);
311
+ Object.entries(resource.permissions).forEach(([op, roles]) => {
312
+ console.log(` ${op}: [${roles.join(", ")}]`);
313
+ });
314
+ }
315
+ if (resource.presets && resource.presets.length > 0) {
316
+ console.log(` Presets: ${resource.presets.join(", ")}`);
317
+ }
318
+ if (resource.additionalRoutes && resource.additionalRoutes.length > 0) {
319
+ console.log(` Additional Routes: ${resource.additionalRoutes.length}`);
320
+ }
321
+ console.log("");
322
+ });
323
+ const stats = resourceRegistry2.getStats();
324
+ console.log("Summary:");
325
+ console.log(` Total Resources: ${stats.totalResources}`);
326
+ console.log(` With Presets: ${resources.filter((r) => r.presets?.length > 0).length}`);
327
+ console.log(
328
+ ` With Custom Routes: ${resources.filter((r) => r.additionalRoutes && r.additionalRoutes.length > 0).length}`
329
+ );
330
+ } catch (error) {
331
+ console.error("Error:", error.message);
332
+ console.log("\nTip: Run this command after starting your application.");
333
+ process.exit(1);
334
+ }
335
+ }
336
+ var introspect_default = introspect;
337
+
338
+ export { introspect_default as default, introspect };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * CLI Module - Programmatic API
3
+ *
4
+ * @example
5
+ * import { generate } from '@classytic/arc/cli';
6
+ *
7
+ * await generate('resource', 'product', {
8
+ * module: 'catalog',
9
+ * presets: ['softDelete', 'slugLookup'],
10
+ * });
11
+ */
12
+ interface GenerateOptions {
13
+ module?: string;
14
+ presets?: string[];
15
+ parentField?: string;
16
+ withTests?: boolean;
17
+ dryRun?: boolean;
18
+ force?: boolean;
19
+ outputDir?: string;
20
+ /** Generate TypeScript (default: true) or JavaScript */
21
+ typescript?: boolean;
22
+ }
23
+ interface GeneratedFile {
24
+ name: string;
25
+ content: string;
26
+ }
27
+ interface GenerateResult {
28
+ files: GeneratedFile[];
29
+ dirPath: string;
30
+ }
31
+ /**
32
+ * Generate resource files
33
+ *
34
+ * @param type - 'resource', 'controller', or 'model'
35
+ * @param name - Resource name (e.g., 'product', 'order-item')
36
+ * @param options - Generation options
37
+ */
38
+ declare function generate(type: string, name: string, options?: GenerateOptions): Promise<GenerateResult>;
39
+ declare const _default: {
40
+ generate: typeof generate;
41
+ };
42
+
43
+ export { type GenerateOptions, type GenerateResult, type GeneratedFile, _default as default, generate };