@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,474 @@
1
+ import fp from 'fastify-plugin';
2
+ import { writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+
15
+ // src/registry/ResourceRegistry.ts
16
+ var ResourceRegistry, registryKey, globalScope, resourceRegistry;
17
+ var init_ResourceRegistry = __esm({
18
+ "src/registry/ResourceRegistry.ts"() {
19
+ ResourceRegistry = class {
20
+ _resources;
21
+ _frozen;
22
+ constructor() {
23
+ this._resources = /* @__PURE__ */ new Map();
24
+ this._frozen = false;
25
+ }
26
+ /**
27
+ * Register a resource
28
+ */
29
+ register(resource, options = {}) {
30
+ if (this._frozen) {
31
+ throw new Error(
32
+ `Registry frozen. Cannot register '${resource.name}' after startup.`
33
+ );
34
+ }
35
+ if (this._resources.has(resource.name)) {
36
+ throw new Error(`Resource '${resource.name}' already registered.`);
37
+ }
38
+ const entry = {
39
+ name: resource.name,
40
+ displayName: resource.displayName,
41
+ tag: resource.tag,
42
+ prefix: resource.prefix,
43
+ module: options.module ?? void 0,
44
+ adapter: resource.adapter ? {
45
+ type: resource.adapter.type,
46
+ name: resource.adapter.name
47
+ } : null,
48
+ permissions: resource.permissions,
49
+ presets: resource._appliedPresets ?? [],
50
+ routes: [],
51
+ // Populated later by getIntrospection()
52
+ additionalRoutes: resource.additionalRoutes.map((r) => ({
53
+ method: r.method,
54
+ path: r.path,
55
+ handler: typeof r.handler === "string" ? r.handler : r.handler.name || "anonymous",
56
+ summary: r.summary,
57
+ description: r.description,
58
+ permissions: r.permissions,
59
+ wrapHandler: r.wrapHandler,
60
+ schema: r.schema
61
+ // Include schema for OpenAPI docs
62
+ })),
63
+ events: Object.keys(resource.events ?? {}),
64
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
65
+ disableDefaultRoutes: resource.disableDefaultRoutes,
66
+ openApiSchemas: options.openApiSchemas,
67
+ plugin: resource.toPlugin()
68
+ // Store plugin factory
69
+ };
70
+ this._resources.set(resource.name, entry);
71
+ return this;
72
+ }
73
+ /**
74
+ * Get resource by name
75
+ */
76
+ get(name) {
77
+ return this._resources.get(name);
78
+ }
79
+ /**
80
+ * Get all resources
81
+ */
82
+ getAll() {
83
+ return Array.from(this._resources.values());
84
+ }
85
+ /**
86
+ * Get resources by module
87
+ */
88
+ getByModule(moduleName) {
89
+ return this.getAll().filter((r) => r.module === moduleName);
90
+ }
91
+ /**
92
+ * Get resources by preset
93
+ */
94
+ getByPreset(presetName) {
95
+ return this.getAll().filter((r) => r.presets.includes(presetName));
96
+ }
97
+ /**
98
+ * Check if resource exists
99
+ */
100
+ has(name) {
101
+ return this._resources.has(name);
102
+ }
103
+ /**
104
+ * Get registry statistics
105
+ */
106
+ getStats() {
107
+ const resources = this.getAll();
108
+ const presetCounts = {};
109
+ for (const r of resources) {
110
+ for (const preset of r.presets) {
111
+ presetCounts[preset] = (presetCounts[preset] ?? 0) + 1;
112
+ }
113
+ }
114
+ return {
115
+ totalResources: resources.length,
116
+ byModule: this._groupBy(resources, "module"),
117
+ presetUsage: presetCounts,
118
+ totalRoutes: resources.reduce((sum, r) => {
119
+ const defaultRouteCount = r.disableDefaultRoutes ? 0 : 5;
120
+ return sum + (r.additionalRoutes?.length ?? 0) + defaultRouteCount;
121
+ }, 0),
122
+ totalEvents: resources.reduce((sum, r) => sum + (r.events?.length ?? 0), 0)
123
+ };
124
+ }
125
+ /**
126
+ * Get full introspection data
127
+ */
128
+ getIntrospection() {
129
+ return {
130
+ resources: this.getAll().map((r) => {
131
+ const defaultRoutes = r.disableDefaultRoutes ? [] : [
132
+ { method: "GET", path: r.prefix, operation: "list" },
133
+ { method: "GET", path: `${r.prefix}/:id`, operation: "get" },
134
+ { method: "POST", path: r.prefix, operation: "create" },
135
+ { method: "PATCH", path: `${r.prefix}/:id`, operation: "update" },
136
+ { method: "DELETE", path: `${r.prefix}/:id`, operation: "delete" }
137
+ ];
138
+ return {
139
+ name: r.name,
140
+ displayName: r.displayName,
141
+ prefix: r.prefix,
142
+ module: r.module,
143
+ presets: r.presets,
144
+ permissions: r.permissions,
145
+ routes: [
146
+ ...defaultRoutes,
147
+ ...r.additionalRoutes?.map((ar) => ({
148
+ method: ar.method,
149
+ path: `${r.prefix}${ar.path}`,
150
+ operation: typeof ar.handler === "string" ? ar.handler : "custom",
151
+ handler: typeof ar.handler === "string" ? ar.handler : void 0,
152
+ summary: ar.summary
153
+ })) ?? []
154
+ ],
155
+ events: r.events
156
+ };
157
+ }),
158
+ stats: this.getStats(),
159
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
160
+ };
161
+ }
162
+ /**
163
+ * Freeze registry (prevent further registrations)
164
+ */
165
+ freeze() {
166
+ this._frozen = true;
167
+ }
168
+ /**
169
+ * Check if frozen
170
+ */
171
+ isFrozen() {
172
+ return this._frozen;
173
+ }
174
+ /**
175
+ * Unfreeze registry (for testing)
176
+ */
177
+ _unfreeze() {
178
+ this._frozen = false;
179
+ }
180
+ /**
181
+ * Clear all resources (for testing)
182
+ */
183
+ _clear() {
184
+ this._resources.clear();
185
+ this._frozen = false;
186
+ }
187
+ /**
188
+ * Group by key
189
+ */
190
+ _groupBy(arr, key) {
191
+ const result = {};
192
+ for (const item of arr) {
193
+ const k = String(item[key] ?? "uncategorized");
194
+ result[k] = (result[k] ?? 0) + 1;
195
+ }
196
+ return result;
197
+ }
198
+ };
199
+ registryKey = /* @__PURE__ */ Symbol.for("arc.resourceRegistry");
200
+ globalScope = globalThis;
201
+ resourceRegistry = globalScope[registryKey] ?? new ResourceRegistry();
202
+ if (!globalScope[registryKey]) {
203
+ globalScope[registryKey] = resourceRegistry;
204
+ }
205
+ }
206
+ });
207
+ var introspectionPlugin, introspectionPlugin_default;
208
+ var init_introspectionPlugin = __esm({
209
+ "src/registry/introspectionPlugin.ts"() {
210
+ init_ResourceRegistry();
211
+ introspectionPlugin = async (fastify, opts = {}) => {
212
+ const {
213
+ prefix = "/_resources",
214
+ authRoles = ["superadmin"],
215
+ enabled = process.env.NODE_ENV !== "production" || process.env.ENABLE_INTROSPECTION === "true"
216
+ } = opts;
217
+ if (!enabled) {
218
+ fastify.log?.info?.("Introspection plugin disabled");
219
+ return;
220
+ }
221
+ const typedFastify = fastify;
222
+ const authMiddleware = authRoles.length > 0 && typedFastify.authenticate ? [
223
+ typedFastify.authenticate,
224
+ typedFastify.authorize?.(...authRoles)
225
+ ].filter(Boolean) : [];
226
+ await fastify.register(async (instance) => {
227
+ instance.get(
228
+ "/",
229
+ {
230
+ preHandler: authMiddleware
231
+ },
232
+ async (_req, _reply) => {
233
+ return resourceRegistry.getIntrospection();
234
+ }
235
+ );
236
+ instance.get(
237
+ "/stats",
238
+ {
239
+ preHandler: authMiddleware
240
+ },
241
+ async (_req, _reply) => {
242
+ return resourceRegistry.getStats();
243
+ }
244
+ );
245
+ instance.get(
246
+ "/:name",
247
+ {
248
+ schema: {
249
+ params: {
250
+ type: "object",
251
+ properties: {
252
+ name: { type: "string" }
253
+ },
254
+ required: ["name"]
255
+ }
256
+ },
257
+ preHandler: authMiddleware
258
+ },
259
+ async (req, reply) => {
260
+ const resource = resourceRegistry.get(req.params.name);
261
+ if (!resource) {
262
+ return reply.code(404).send({
263
+ error: `Resource '${req.params.name}' not found`
264
+ });
265
+ }
266
+ return resource;
267
+ }
268
+ );
269
+ }, { prefix });
270
+ fastify.log?.info?.(`Introspection API at ${prefix}`);
271
+ };
272
+ introspectionPlugin_default = fp(introspectionPlugin, { name: "arc-introspection" });
273
+ }
274
+ });
275
+
276
+ // src/registry/index.ts
277
+ var registry_exports = {};
278
+ __export(registry_exports, {
279
+ ResourceRegistry: () => ResourceRegistry,
280
+ introspectionPlugin: () => introspectionPlugin_default,
281
+ introspectionPluginFn: () => introspectionPlugin,
282
+ resourceRegistry: () => resourceRegistry
283
+ });
284
+ var init_registry = __esm({
285
+ "src/registry/index.ts"() {
286
+ init_ResourceRegistry();
287
+ init_introspectionPlugin();
288
+ }
289
+ });
290
+ async function exportDocs(args) {
291
+ const [outputPath = "./openapi.json"] = args;
292
+ console.log("Exporting OpenAPI specification...\n");
293
+ try {
294
+ const { resourceRegistry: resourceRegistry2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
295
+ const resources = resourceRegistry2.getAll();
296
+ if (resources.length === 0) {
297
+ console.warn("⚠️ No resources registered.");
298
+ console.log("\nTo export docs, you need to load your resources first:");
299
+ console.log(" arc docs ./openapi.json --entry ./index.js");
300
+ console.log("\nWhere index.js imports all your resource definitions.");
301
+ process.exit(1);
302
+ }
303
+ const spec = {
304
+ openapi: "3.0.0",
305
+ info: {
306
+ title: "Arc API",
307
+ version: "1.0.0",
308
+ description: "Auto-generated from Arc resources"
309
+ },
310
+ servers: [
311
+ {
312
+ url: "http://localhost:8040/api/v1",
313
+ description: "Development server"
314
+ }
315
+ ],
316
+ paths: {},
317
+ components: {
318
+ schemas: {},
319
+ securitySchemes: {
320
+ bearerAuth: {
321
+ type: "http",
322
+ scheme: "bearer",
323
+ bearerFormat: "JWT"
324
+ }
325
+ }
326
+ }
327
+ };
328
+ resources.forEach((resource) => {
329
+ const basePath = resource.prefix || `/${resource.name}s`;
330
+ spec.paths[basePath] = {
331
+ get: {
332
+ tags: [resource.name],
333
+ summary: `List ${resource.name}s`,
334
+ security: resource.permissions?.list ? [{ bearerAuth: [] }] : [],
335
+ parameters: [
336
+ {
337
+ name: "page",
338
+ in: "query",
339
+ schema: { type: "integer", default: 1 }
340
+ },
341
+ {
342
+ name: "limit",
343
+ in: "query",
344
+ schema: { type: "integer", default: 20 }
345
+ }
346
+ ],
347
+ responses: {
348
+ 200: {
349
+ description: "Successful response",
350
+ content: {
351
+ "application/json": {
352
+ schema: {
353
+ type: "object",
354
+ properties: {
355
+ success: { type: "boolean" },
356
+ data: {
357
+ type: "array",
358
+ items: { $ref: `#/components/schemas/${resource.name}` }
359
+ },
360
+ total: { type: "integer" },
361
+ page: { type: "integer" }
362
+ }
363
+ }
364
+ }
365
+ }
366
+ }
367
+ }
368
+ },
369
+ post: {
370
+ tags: [resource.name],
371
+ summary: `Create ${resource.name}`,
372
+ security: resource.permissions?.create ? [{ bearerAuth: [] }] : [],
373
+ requestBody: {
374
+ required: true,
375
+ content: {
376
+ "application/json": {
377
+ schema: { $ref: `#/components/schemas/${resource.name}` }
378
+ }
379
+ }
380
+ },
381
+ responses: {
382
+ 201: {
383
+ description: "Created successfully"
384
+ }
385
+ }
386
+ }
387
+ };
388
+ spec.paths[`${basePath}/{id}`] = {
389
+ get: {
390
+ tags: [resource.name],
391
+ summary: `Get ${resource.name} by ID`,
392
+ security: resource.permissions?.get ? [{ bearerAuth: [] }] : [],
393
+ parameters: [
394
+ {
395
+ name: "id",
396
+ in: "path",
397
+ required: true,
398
+ schema: { type: "string" }
399
+ }
400
+ ],
401
+ responses: {
402
+ 200: {
403
+ description: "Successful response"
404
+ }
405
+ }
406
+ },
407
+ patch: {
408
+ tags: [resource.name],
409
+ summary: `Update ${resource.name}`,
410
+ security: resource.permissions?.update ? [{ bearerAuth: [] }] : [],
411
+ parameters: [
412
+ {
413
+ name: "id",
414
+ in: "path",
415
+ required: true,
416
+ schema: { type: "string" }
417
+ }
418
+ ],
419
+ requestBody: {
420
+ required: true,
421
+ content: {
422
+ "application/json": {
423
+ schema: { $ref: `#/components/schemas/${resource.name}` }
424
+ }
425
+ }
426
+ },
427
+ responses: {
428
+ 200: {
429
+ description: "Updated successfully"
430
+ }
431
+ }
432
+ },
433
+ delete: {
434
+ tags: [resource.name],
435
+ summary: `Delete ${resource.name}`,
436
+ security: resource.permissions?.delete ? [{ bearerAuth: [] }] : [],
437
+ parameters: [
438
+ {
439
+ name: "id",
440
+ in: "path",
441
+ required: true,
442
+ schema: { type: "string" }
443
+ }
444
+ ],
445
+ responses: {
446
+ 200: {
447
+ description: "Deleted successfully"
448
+ }
449
+ }
450
+ }
451
+ };
452
+ spec.components.schemas[resource.name] = {
453
+ type: "object",
454
+ properties: {
455
+ _id: { type: "string" },
456
+ createdAt: { type: "string", format: "date-time" },
457
+ updatedAt: { type: "string", format: "date-time" }
458
+ }
459
+ };
460
+ });
461
+ const fullPath = join(process.cwd(), outputPath);
462
+ writeFileSync(fullPath, JSON.stringify(spec, null, 2));
463
+ console.log(`✅ OpenAPI spec exported to: ${fullPath}`);
464
+ console.log(`
465
+ Resources included: ${resources.length}`);
466
+ console.log(`Total endpoints: ${Object.keys(spec.paths).length}`);
467
+ } catch (error) {
468
+ console.error("Error:", error.message);
469
+ process.exit(1);
470
+ }
471
+ }
472
+ var docs_default = { exportDocs };
473
+
474
+ export { docs_default as default, exportDocs };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Arc CLI - Introspect Command
3
+ *
4
+ * Shows all registered resources and their configuration
5
+ */
6
+ declare function introspect(args: string[]): Promise<void>;
7
+
8
+ export { introspect as default, introspect };