@classytic/arc 2.6.1 → 2.6.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.
- package/README.md +48 -2
- package/dist/{BaseController-AbbRx3e0.mjs → BaseController-DzRtluEF.mjs} +88 -8
- package/dist/{ResourceRegistry-DeCIFlix.mjs → ResourceRegistry-C6ngvOnn.mjs} +1 -0
- package/dist/adapters/index.d.mts +2 -2
- package/dist/adapters/index.mjs +1 -1
- package/dist/{adapters-CTn28N4y.mjs → adapters-gM-WYjNe.mjs} +6 -4
- package/dist/audit/index.d.mts +31 -5
- package/dist/audit/index.mjs +21 -3
- package/dist/auth/index.d.mts +1 -1
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +2 -2
- package/dist/{createApp-Bol7DLUf.mjs → createApp-D2w0LdYJ.mjs} +27 -11
- package/dist/{defineResource-bVKHjQzE.mjs → defineResource-wWMBB4GP.mjs} +48 -30
- package/dist/docs/index.d.mts +1 -1
- package/dist/dynamic/index.d.mts +1 -1
- package/dist/dynamic/index.mjs +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +31 -15
- package/dist/hooks/index.d.mts +1 -1
- package/dist/{index-BIsZ_su5.d.mts → index-CHeJa4Zd.d.mts} +3 -3
- package/dist/{index-Cb3gtbg7.d.mts → index-gz6iuzCp.d.mts} +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +4 -4
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/{interface-DDW43OmS.d.mts → interface-DYH8AXGe.d.mts} +89 -4
- package/dist/org/index.d.mts +1 -1
- package/dist/plugins/index.d.mts +1 -1
- package/dist/plugins/index.mjs +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +1 -1
- package/dist/{resourceToTools-DH3c3e-T.mjs → resourceToTools-nCJWnG1r.mjs} +250 -13
- package/dist/testing/index.d.mts +26 -3
- package/dist/testing/index.mjs +46 -2
- package/dist/types/index.d.mts +1 -1
- package/dist/{types-D5rjsS_i.d.mts → types-B4_TDdPe.d.mts} +1 -1
- package/dist/{types-D5hJ-k_3.d.mts → types-By-5mIfn.d.mts} +7 -1
- package/dist/utils/index.d.mts +1 -1
- package/package.json +18 -18
- package/skills/arc/SKILL.md +80 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as BaseController } from "./BaseController-
|
|
1
|
+
import { t as BaseController } from "./BaseController-DzRtluEF.mjs";
|
|
2
2
|
import { t as pluralize } from "./pluralize-CcT6qF0a.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/integrations/mcp/createMcpServer.ts
|
|
@@ -314,6 +314,146 @@ function buildScope(auth) {
|
|
|
314
314
|
};
|
|
315
315
|
}
|
|
316
316
|
//#endregion
|
|
317
|
+
//#region src/integrations/mcp/jsonSchemaToZod.ts
|
|
318
|
+
/**
|
|
319
|
+
* @classytic/arc — JSON Schema → Zod shape converter
|
|
320
|
+
*
|
|
321
|
+
* Converts an adapter-emitted JSON Schema body shape (`createBody` / `updateBody`)
|
|
322
|
+
* to a flat Zod shape compatible with the MCP SDK's `registerTool({ inputSchema })`
|
|
323
|
+
* contract.
|
|
324
|
+
*
|
|
325
|
+
* Why this exists:
|
|
326
|
+
* - The MCP SDK expects a flat `Record<string, ZodType>` shape (it wraps it in
|
|
327
|
+
* z.object() internally).
|
|
328
|
+
* - When users don't supply explicit `schemaOptions.fieldRules`, MCP would
|
|
329
|
+
* otherwise see an empty schema and silently strip every body field — that's
|
|
330
|
+
* a real DX footgun.
|
|
331
|
+
* - Adapters (Mongoose, MongoKit's buildCrudSchemasFromModel, custom) already
|
|
332
|
+
* emit JSON Schema describing the body. We translate it to Zod so MCP tools
|
|
333
|
+
* can validate input the same way REST routes do.
|
|
334
|
+
*
|
|
335
|
+
* Supported JSON Schema features:
|
|
336
|
+
* - Primitives: string, number, integer, boolean, null (skipped)
|
|
337
|
+
* - Constraints: minLength, maxLength, minimum, maximum, pattern, enum, format
|
|
338
|
+
* - Arrays: typed items + nested object items
|
|
339
|
+
* - Nested objects with `properties` (recursive)
|
|
340
|
+
* - Type unions: ["string", "null"] → string (null skipped)
|
|
341
|
+
* - Composition: oneOf / anyOf / allOf → first viable branch
|
|
342
|
+
* - $ref → permissive (z.unknown()) — refs are not resolved
|
|
343
|
+
* - Unknown types → z.unknown() (lenient — let the controller validate)
|
|
344
|
+
*
|
|
345
|
+
* NOT supported (intentionally — keeps the surface small + deterministic):
|
|
346
|
+
* - Conditional schemas (if/then/else)
|
|
347
|
+
* - dependencies / dependentRequired
|
|
348
|
+
* - Custom keywords beyond standard JSON Schema
|
|
349
|
+
*/
|
|
350
|
+
/**
|
|
351
|
+
* Convert a JSON Schema **object** body to a flat Zod shape.
|
|
352
|
+
* Returns `undefined` if the input has no usable properties.
|
|
353
|
+
*
|
|
354
|
+
* @param schema Top-level JSON Schema (must be `type: 'object'` with `properties`)
|
|
355
|
+
* @param mode 'create' enforces required fields, 'update' makes everything optional
|
|
356
|
+
*/
|
|
357
|
+
function jsonSchemaToZodShape(schema, mode = "create") {
|
|
358
|
+
if (!schema || typeof schema !== "object") return void 0;
|
|
359
|
+
if (!schema.properties || typeof schema.properties !== "object") return void 0;
|
|
360
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
361
|
+
const shape = {};
|
|
362
|
+
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
363
|
+
if (!propSchema || typeof propSchema !== "object") continue;
|
|
364
|
+
const fieldZod = jsonSchemaPropertyToZod(propSchema);
|
|
365
|
+
if (!fieldZod) continue;
|
|
366
|
+
shape[name] = mode === "create" && requiredSet.has(name) ? fieldZod : fieldZod.optional();
|
|
367
|
+
}
|
|
368
|
+
return Object.keys(shape).length > 0 ? shape : void 0;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Convert one JSON Schema node to a Zod type.
|
|
372
|
+
* Handles primitives, arrays, nested objects, type unions, and composition.
|
|
373
|
+
*/
|
|
374
|
+
function jsonSchemaPropertyToZod(prop) {
|
|
375
|
+
if (prop.$ref) return applyDescription(z.unknown(), prop);
|
|
376
|
+
if (Array.isArray(prop.oneOf) && prop.oneOf.length > 0) for (const branch of prop.oneOf) {
|
|
377
|
+
const z1 = jsonSchemaPropertyToZod(branch);
|
|
378
|
+
if (z1) return applyDescription(z1, prop);
|
|
379
|
+
}
|
|
380
|
+
if (Array.isArray(prop.anyOf) && prop.anyOf.length > 0) for (const branch of prop.anyOf) {
|
|
381
|
+
const z1 = jsonSchemaPropertyToZod(branch);
|
|
382
|
+
if (z1) return applyDescription(z1, prop);
|
|
383
|
+
}
|
|
384
|
+
if (Array.isArray(prop.allOf) && prop.allOf.length > 0) for (let i = prop.allOf.length - 1; i >= 0; i--) {
|
|
385
|
+
const branch = prop.allOf[i];
|
|
386
|
+
const z1 = jsonSchemaPropertyToZod(branch);
|
|
387
|
+
if (z1) return applyDescription(z1, prop);
|
|
388
|
+
}
|
|
389
|
+
if (Array.isArray(prop.enum) && prop.enum.length > 0) {
|
|
390
|
+
const stringValues = prop.enum.filter((v) => typeof v === "string");
|
|
391
|
+
if (stringValues.length > 0) return applyDescription(z.enum(stringValues), prop);
|
|
392
|
+
return applyDescription(z.number(), prop);
|
|
393
|
+
}
|
|
394
|
+
const typeCandidates = pickEffectiveType(prop.type);
|
|
395
|
+
for (const t of typeCandidates) switch (t) {
|
|
396
|
+
case "string": return applyStringConstraints(z.string(), prop);
|
|
397
|
+
case "number":
|
|
398
|
+
case "integer": return applyNumberConstraints(z.number(), prop);
|
|
399
|
+
case "boolean": return applyDescription(z.boolean(), prop);
|
|
400
|
+
case "array": return applyDescription(arrayToZod(prop), prop);
|
|
401
|
+
case "object": return applyDescription(objectToZod(prop), prop);
|
|
402
|
+
case "null": continue;
|
|
403
|
+
default: break;
|
|
404
|
+
}
|
|
405
|
+
return applyDescription(z.unknown(), prop);
|
|
406
|
+
}
|
|
407
|
+
function pickEffectiveType(rawType) {
|
|
408
|
+
if (!rawType) return [];
|
|
409
|
+
if (Array.isArray(rawType)) return rawType.filter((t) => t !== "null");
|
|
410
|
+
return rawType === "null" ? [] : [rawType];
|
|
411
|
+
}
|
|
412
|
+
function arrayToZod(prop) {
|
|
413
|
+
const items = prop.items;
|
|
414
|
+
if (items && typeof items === "object") {
|
|
415
|
+
const itemZod = jsonSchemaPropertyToZod(items);
|
|
416
|
+
if (itemZod) return z.array(itemZod);
|
|
417
|
+
}
|
|
418
|
+
return z.array(z.unknown());
|
|
419
|
+
}
|
|
420
|
+
function objectToZod(prop) {
|
|
421
|
+
if (prop.properties && typeof prop.properties === "object") {
|
|
422
|
+
const requiredSet = new Set(prop.required ?? []);
|
|
423
|
+
const innerShape = {};
|
|
424
|
+
for (const [k, v] of Object.entries(prop.properties)) {
|
|
425
|
+
if (!v || typeof v !== "object") continue;
|
|
426
|
+
const inner = jsonSchemaPropertyToZod(v);
|
|
427
|
+
if (!inner) continue;
|
|
428
|
+
innerShape[k] = requiredSet.has(k) ? inner : inner.optional();
|
|
429
|
+
}
|
|
430
|
+
if (Object.keys(innerShape).length > 0) return z.object(innerShape);
|
|
431
|
+
}
|
|
432
|
+
return z.record(z.string(), z.unknown());
|
|
433
|
+
}
|
|
434
|
+
function applyStringConstraints(base, prop) {
|
|
435
|
+
let s = base;
|
|
436
|
+
if (typeof prop.minLength === "number") s = s.min(prop.minLength);
|
|
437
|
+
if (typeof prop.maxLength === "number") s = s.max(prop.maxLength);
|
|
438
|
+
if (typeof prop.pattern === "string") try {
|
|
439
|
+
s = s.regex(new RegExp(prop.pattern));
|
|
440
|
+
} catch {}
|
|
441
|
+
if (prop.format === "email") s = s.email();
|
|
442
|
+
if (prop.format === "uuid") s = s.uuid();
|
|
443
|
+
if (prop.format === "uri" || prop.format === "url") s = s.url();
|
|
444
|
+
return applyDescription(s, prop);
|
|
445
|
+
}
|
|
446
|
+
function applyNumberConstraints(base, prop) {
|
|
447
|
+
let n = base;
|
|
448
|
+
if (typeof prop.minimum === "number") n = n.min(prop.minimum);
|
|
449
|
+
if (typeof prop.maximum === "number") n = n.max(prop.maximum);
|
|
450
|
+
return applyDescription(n, prop);
|
|
451
|
+
}
|
|
452
|
+
function applyDescription(zodType, prop) {
|
|
453
|
+
if (typeof prop.description === "string" && prop.description.length > 0) return zodType.describe(prop.description);
|
|
454
|
+
return zodType;
|
|
455
|
+
}
|
|
456
|
+
//#endregion
|
|
317
457
|
//#region src/integrations/mcp/resourceToTools.ts
|
|
318
458
|
/**
|
|
319
459
|
* @classytic/arc — Resource → MCP Tools Generator
|
|
@@ -359,9 +499,11 @@ const ANNOTATIONS = {
|
|
|
359
499
|
function resourceToTools(resource, config = {}) {
|
|
360
500
|
const controller = resource.controller ?? (resource.adapter ? createMcpController(resource) : void 0);
|
|
361
501
|
if (!controller) return [];
|
|
362
|
-
const
|
|
502
|
+
const explicitFieldRules = resource.schemaOptions?.fieldRules;
|
|
363
503
|
const hiddenFields = resource.schemaOptions?.hiddenFields;
|
|
364
504
|
const readonlyFields = resource.schemaOptions?.readonlyFields;
|
|
505
|
+
const adapterBodies = explicitFieldRules ? void 0 : getAdapterBodies(resource);
|
|
506
|
+
const fieldRules = explicitFieldRules ?? deriveFieldRulesFromAdapter(resource);
|
|
365
507
|
const filterableFields = resource.schemaOptions?.filterableFields ?? resource.queryParser?.allowedFilterFields;
|
|
366
508
|
const sortableFields = resource.queryParser?.allowedSortFields;
|
|
367
509
|
const allowedOperators = resource.queryParser?.allowedOperators;
|
|
@@ -388,7 +530,8 @@ function resourceToTools(resource, config = {}) {
|
|
|
388
530
|
readonlyFields,
|
|
389
531
|
extraHideFields: config.hideFields,
|
|
390
532
|
filterableFields,
|
|
391
|
-
allowedOperators
|
|
533
|
+
allowedOperators,
|
|
534
|
+
adapterBodies
|
|
392
535
|
}),
|
|
393
536
|
handler: createHandler(op, controller, resource.name, resource.permissions)
|
|
394
537
|
});
|
|
@@ -442,20 +585,57 @@ function buildInputSchema(op, fieldRules, opts) {
|
|
|
442
585
|
...opts
|
|
443
586
|
});
|
|
444
587
|
case "get": return { id: z.string().describe("Resource ID") };
|
|
445
|
-
case "create":
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
mode: "update",
|
|
588
|
+
case "create":
|
|
589
|
+
if (!fieldRules && opts.adapterBodies?.createBody) {
|
|
590
|
+
const shape = jsonSchemaToZodShape(opts.adapterBodies.createBody, "create");
|
|
591
|
+
if (shape) return shape;
|
|
592
|
+
}
|
|
593
|
+
return fieldRulesToZod(fieldRules, {
|
|
594
|
+
mode: "create",
|
|
453
595
|
...opts
|
|
454
|
-
})
|
|
455
|
-
|
|
596
|
+
});
|
|
597
|
+
case "update": {
|
|
598
|
+
const idShape = { id: z.string().describe("Resource ID") };
|
|
599
|
+
if (!fieldRules && opts.adapterBodies?.updateBody) {
|
|
600
|
+
const shape = jsonSchemaToZodShape(opts.adapterBodies.updateBody, "update");
|
|
601
|
+
if (shape) return {
|
|
602
|
+
...idShape,
|
|
603
|
+
...shape
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
...idShape,
|
|
608
|
+
...fieldRulesToZod(fieldRules, {
|
|
609
|
+
mode: "update",
|
|
610
|
+
...opts
|
|
611
|
+
})
|
|
612
|
+
};
|
|
613
|
+
}
|
|
456
614
|
case "delete": return { id: z.string().describe("Resource ID") };
|
|
457
615
|
}
|
|
458
616
|
}
|
|
617
|
+
/**
|
|
618
|
+
* Pull the adapter's `createBody` / `updateBody` schemas, if any.
|
|
619
|
+
* Returns `undefined` when the adapter doesn't generate schemas or throws.
|
|
620
|
+
*/
|
|
621
|
+
function getAdapterBodies(resource) {
|
|
622
|
+
const adapter = resource.adapter;
|
|
623
|
+
if (!adapter || typeof adapter.generateSchemas !== "function") return void 0;
|
|
624
|
+
try {
|
|
625
|
+
const generated = adapter.generateSchemas(resource.schemaOptions, {
|
|
626
|
+
idField: resource.idField,
|
|
627
|
+
resourceName: resource.name
|
|
628
|
+
});
|
|
629
|
+
if (!generated || typeof generated !== "object") return void 0;
|
|
630
|
+
const schemas = generated;
|
|
631
|
+
return {
|
|
632
|
+
createBody: schemas.createBody,
|
|
633
|
+
updateBody: schemas.updateBody
|
|
634
|
+
};
|
|
635
|
+
} catch {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
459
639
|
function createHandler(op, controller, resourceName, permissions) {
|
|
460
640
|
const ctrl = controller;
|
|
461
641
|
return async (input, ctx) => {
|
|
@@ -550,6 +730,63 @@ async function evaluatePermission(check, session, resource, action, input) {
|
|
|
550
730
|
if (!permResult.granted) return false;
|
|
551
731
|
return permResult.filters ?? null;
|
|
552
732
|
}
|
|
733
|
+
/**
|
|
734
|
+
* Derive a fieldRules-shaped object from the adapter's auto-generated body
|
|
735
|
+
* schemas. Used as a fallback when the resource doesn't supply explicit
|
|
736
|
+
* fieldRules — this lets MCP create/update tools accept the same body fields
|
|
737
|
+
* that the REST routes already accept.
|
|
738
|
+
*
|
|
739
|
+
* Returns `undefined` if no usable schema can be extracted, in which case
|
|
740
|
+
* `fieldRulesToZod` falls back to its own behavior (empty shape).
|
|
741
|
+
*/
|
|
742
|
+
function deriveFieldRulesFromAdapter(resource) {
|
|
743
|
+
const adapter = resource.adapter;
|
|
744
|
+
if (!adapter || typeof adapter.generateSchemas !== "function") return void 0;
|
|
745
|
+
let generated;
|
|
746
|
+
try {
|
|
747
|
+
generated = adapter.generateSchemas(resource.schemaOptions, {
|
|
748
|
+
idField: resource.idField,
|
|
749
|
+
resourceName: resource.name
|
|
750
|
+
});
|
|
751
|
+
} catch {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (!generated || typeof generated !== "object") return void 0;
|
|
755
|
+
const schemas = generated;
|
|
756
|
+
const createBody = schemas.createBody;
|
|
757
|
+
const updateBody = schemas.updateBody;
|
|
758
|
+
const properties = createBody?.properties ?? updateBody?.properties;
|
|
759
|
+
if (!properties || typeof properties !== "object") return void 0;
|
|
760
|
+
const requiredSet = new Set(createBody?.required ?? []);
|
|
761
|
+
const rules = {};
|
|
762
|
+
for (const [name, propSchema] of Object.entries(properties)) {
|
|
763
|
+
if (!propSchema || typeof propSchema !== "object") continue;
|
|
764
|
+
const prop = propSchema;
|
|
765
|
+
const rawType = prop.type;
|
|
766
|
+
const rule = { type: mapJsonSchemaTypeToArcType((Array.isArray(rawType) ? rawType.filter((t) => typeof t === "string") : typeof rawType === "string" ? [rawType] : [])[0]) };
|
|
767
|
+
if (requiredSet.has(name)) rule.required = true;
|
|
768
|
+
if (typeof prop.description === "string") rule.description = prop.description;
|
|
769
|
+
if (Array.isArray(prop.enum)) rule.enum = prop.enum.filter((v) => typeof v === "string");
|
|
770
|
+
if (typeof prop.minLength === "number") rule.minLength = prop.minLength;
|
|
771
|
+
if (typeof prop.maxLength === "number") rule.maxLength = prop.maxLength;
|
|
772
|
+
if (typeof prop.minimum === "number") rule.min = prop.minimum;
|
|
773
|
+
if (typeof prop.maximum === "number") rule.max = prop.maximum;
|
|
774
|
+
if (typeof prop.pattern === "string") rule.pattern = prop.pattern;
|
|
775
|
+
rules[name] = rule;
|
|
776
|
+
}
|
|
777
|
+
return Object.keys(rules).length > 0 ? rules : void 0;
|
|
778
|
+
}
|
|
779
|
+
function mapJsonSchemaTypeToArcType(jsonType) {
|
|
780
|
+
switch (jsonType) {
|
|
781
|
+
case "string": return "string";
|
|
782
|
+
case "number":
|
|
783
|
+
case "integer": return "number";
|
|
784
|
+
case "boolean": return "boolean";
|
|
785
|
+
case "array": return "array";
|
|
786
|
+
case "object": return "object";
|
|
787
|
+
default: return "string";
|
|
788
|
+
}
|
|
789
|
+
}
|
|
553
790
|
function toCallToolResult(result) {
|
|
554
791
|
if (!result.success) return {
|
|
555
792
|
content: [{
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as CreateAppOptions } from "../types-
|
|
1
|
+
import { Ut as CrudRepository, Vt as ResourceDefinition, u as AnyRecord } from "../interface-DYH8AXGe.mjs";
|
|
2
|
+
import { d as ResourceLike, r as CreateAppOptions } from "../types-By-5mIfn.mjs";
|
|
3
3
|
import Fastify, { FastifyInstance, FastifyServerOptions } from "fastify";
|
|
4
4
|
import { Connection } from "mongoose";
|
|
5
5
|
import { Mock } from "vitest";
|
|
@@ -572,6 +572,29 @@ declare function createTestTimer(): {
|
|
|
572
572
|
reset: () => void;
|
|
573
573
|
};
|
|
574
574
|
//#endregion
|
|
575
|
+
//#region src/testing/preloadResources.d.ts
|
|
576
|
+
/** Eager glob result: `{ '/path/to/file.ts': resourceModule }` */
|
|
577
|
+
type EagerGlobResult = Record<string, unknown>;
|
|
578
|
+
/** Lazy glob result: `{ '/path/to/file.ts': () => Promise<unknown> }` */
|
|
579
|
+
type LazyGlobResult = Record<string, () => Promise<unknown>>;
|
|
580
|
+
/**
|
|
581
|
+
* Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
|
|
582
|
+
*
|
|
583
|
+
* Accepts either:
|
|
584
|
+
* - `{ import: 'default' }` form: values are the resource directly
|
|
585
|
+
* - default form: values are the full module — picks first export with `toPlugin()`
|
|
586
|
+
*
|
|
587
|
+
* Throws if any module doesn't yield a valid `ResourceLike`.
|
|
588
|
+
*/
|
|
589
|
+
declare function preloadResources(globResult: EagerGlobResult): ResourceLike[];
|
|
590
|
+
/**
|
|
591
|
+
* Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
|
|
592
|
+
*
|
|
593
|
+
* Use this when resources depend on prior bootstrap (e.g., engine init) and
|
|
594
|
+
* cannot be evaluated at import time of the preload file.
|
|
595
|
+
*/
|
|
596
|
+
declare function preloadResourcesAsync(globResult: LazyGlobResult): Promise<ResourceLike[]>;
|
|
597
|
+
//#endregion
|
|
575
598
|
//#region src/testing/TestHarness.d.ts
|
|
576
599
|
/**
|
|
577
600
|
* Test fixtures for a resource
|
|
@@ -898,4 +921,4 @@ declare class TestDataLoader {
|
|
|
898
921
|
cleanup(): Promise<void>;
|
|
899
922
|
}
|
|
900
923
|
//#endregion
|
|
901
|
-
export { type AuthProvider, type AuthResponse, type BetterAuthTestHelpers, type BetterAuthTestHelpersOptions, type CreateTestAppOptions, DatabaseSnapshot, TestFixtures as DbTestFixtures, type GenerateTestFileOptions, HttpTestHarness, type HttpTestHarnessOptions, InMemoryDatabase, type OrgResponse, type SetupBetterAuthOrgOptions, type SetupUserConfig, type TestAppResult, TestDataLoader, TestDatabase, type TestFixtures$1 as TestFixtures, TestHarness, type TestHarnessOptions, type TestOrgContext, TestRequestBuilder, TestSeeder, TestTransaction, type TestUserContext, createBetterAuthProvider, createBetterAuthTestHelpers, createConfigTestSuite, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSnapshotMatcher, createSpy, createTestApp, createTestAuth, createTestHarness, createTestTimer, generateTestFile, request, safeParseBody, setupBetterAuthOrg, waitFor, withTestDb };
|
|
924
|
+
export { type AuthProvider, type AuthResponse, type BetterAuthTestHelpers, type BetterAuthTestHelpersOptions, type CreateTestAppOptions, DatabaseSnapshot, TestFixtures as DbTestFixtures, type GenerateTestFileOptions, HttpTestHarness, type HttpTestHarnessOptions, InMemoryDatabase, type OrgResponse, type SetupBetterAuthOrgOptions, type SetupUserConfig, type TestAppResult, TestDataLoader, TestDatabase, type TestFixtures$1 as TestFixtures, TestHarness, type TestHarnessOptions, type TestOrgContext, TestRequestBuilder, TestSeeder, TestTransaction, type TestUserContext, createBetterAuthProvider, createBetterAuthTestHelpers, createConfigTestSuite, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSnapshotMatcher, createSpy, createTestApp, createTestAuth, createTestHarness, createTestTimer, generateTestFile, preloadResources, preloadResourcesAsync, request, safeParseBody, setupBetterAuthOrg, waitFor, withTestDb };
|
package/dist/testing/index.mjs
CHANGED
|
@@ -1066,6 +1066,50 @@ function createTestTimer() {
|
|
|
1066
1066
|
};
|
|
1067
1067
|
}
|
|
1068
1068
|
//#endregion
|
|
1069
|
+
//#region src/testing/preloadResources.ts
|
|
1070
|
+
/**
|
|
1071
|
+
* Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
|
|
1072
|
+
*
|
|
1073
|
+
* Accepts either:
|
|
1074
|
+
* - `{ import: 'default' }` form: values are the resource directly
|
|
1075
|
+
* - default form: values are the full module — picks first export with `toPlugin()`
|
|
1076
|
+
*
|
|
1077
|
+
* Throws if any module doesn't yield a valid `ResourceLike`.
|
|
1078
|
+
*/
|
|
1079
|
+
function preloadResources(globResult) {
|
|
1080
|
+
const resources = [];
|
|
1081
|
+
for (const [path, value] of Object.entries(globResult)) {
|
|
1082
|
+
const resource = pickResource(value);
|
|
1083
|
+
if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
|
|
1084
|
+
resources.push(resource);
|
|
1085
|
+
}
|
|
1086
|
+
return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
|
|
1090
|
+
*
|
|
1091
|
+
* Use this when resources depend on prior bootstrap (e.g., engine init) and
|
|
1092
|
+
* cannot be evaluated at import time of the preload file.
|
|
1093
|
+
*/
|
|
1094
|
+
async function preloadResourcesAsync(globResult) {
|
|
1095
|
+
return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
|
|
1096
|
+
const resource = pickResource(await loader());
|
|
1097
|
+
if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
|
|
1098
|
+
return resource;
|
|
1099
|
+
}))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
1100
|
+
}
|
|
1101
|
+
function pickResource(value) {
|
|
1102
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1103
|
+
if (typeof value.toPlugin === "function") return value;
|
|
1104
|
+
const mod = value;
|
|
1105
|
+
const candidates = [
|
|
1106
|
+
mod.default,
|
|
1107
|
+
mod.resource,
|
|
1108
|
+
...Object.values(mod)
|
|
1109
|
+
];
|
|
1110
|
+
for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
|
|
1111
|
+
}
|
|
1112
|
+
//#endregion
|
|
1069
1113
|
//#region src/testing/TestHarness.ts
|
|
1070
1114
|
/**
|
|
1071
1115
|
* Resource Test Harness
|
|
@@ -1752,7 +1796,7 @@ function runEventTests(resourceName, displayName, events) {
|
|
|
1752
1796
|
* ```
|
|
1753
1797
|
*/
|
|
1754
1798
|
async function createTestApp(options = {}) {
|
|
1755
|
-
const { createApp } = await import("../createApp-
|
|
1799
|
+
const { createApp } = await import("../createApp-D2w0LdYJ.mjs").then((n) => n.r);
|
|
1756
1800
|
const { useInMemoryDb = true, mongoUri: providedMongoUri, ...appOptions } = options;
|
|
1757
1801
|
const defaultAuth = {
|
|
1758
1802
|
type: "jwt",
|
|
@@ -1950,4 +1994,4 @@ var TestDataLoader = class {
|
|
|
1950
1994
|
}
|
|
1951
1995
|
};
|
|
1952
1996
|
//#endregion
|
|
1953
|
-
export { DatabaseSnapshot, TestFixtures as DbTestFixtures, HttpTestHarness, InMemoryDatabase, TestDataLoader, TestDatabase, TestHarness, TestRequestBuilder, TestSeeder, TestTransaction, createBetterAuthProvider, createBetterAuthTestHelpers, createConfigTestSuite, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSnapshotMatcher, createSpy, createTestApp, createTestAuth, createTestHarness, createTestTimer, generateTestFile, request, safeParseBody, setupBetterAuthOrg, waitFor, withTestDb };
|
|
1997
|
+
export { DatabaseSnapshot, TestFixtures as DbTestFixtures, HttpTestHarness, InMemoryDatabase, TestDataLoader, TestDatabase, TestHarness, TestRequestBuilder, TestSeeder, TestTransaction, createBetterAuthProvider, createBetterAuthTestHelpers, createConfigTestSuite, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSnapshotMatcher, createSpy, createTestApp, createTestAuth, createTestHarness, createTestTimer, generateTestFile, preloadResources, preloadResourcesAsync, request, safeParseBody, setupBetterAuthOrg, waitFor, withTestDb };
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { _ as isMember, a as AUTHENTICATED_SCOPE, d as getTeamId, g as isElevated, h as isAuthenticated, l as getOrgId, m as hasOrgAccess, n as ElevationOptions, o as PUBLIC_SCOPE, s as RequestScope, t as ElevationEvent, u as getOrgRoles } from "../elevation-C_taLQrM.mjs";
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as RateLimitConfig, A as FieldRule, B as JwtContext, C as CrudRouterOptions, Ct as getUserId, D as FastifyRequestExtras, E as EventsDecorator, F as InferDocType, Ft as IController, G as OpenApiSchemas, Gt as PaginatedResult, H as MiddlewareConfig, I as InferResourceDoc, It as IControllerResponse, J as PopulateOption, K as OwnershipCheck, Kt as PaginationParams, L as IntrospectionData, Lt as IRequestContext, M as HealthCheck, Mt as ControllerHandler, N as HealthOptions, Nt as ControllerLike, O as FastifyWithAuth, P as InferAdapterDoc, Pt as FastifyHandler, Q as QueryParserInterface, R as IntrospectionPluginOptions, Rt as RouteHandler, S as CrudRouteKey, St as envelope, T as EventDefinition, Tt as BaseControllerOptions, U as MiddlewareHandler, Ut as CrudRepository, V as LookupOption, W as ObjectId, Wt as InferDoc, X as PresetHook, Y as PresetFunction, Z as PresetResult, _ as Authenticator, _t as TypedResourceConfig, at as ResourceCacheConfig, b as ControllerQueryOptions, bt as ValidateOptions, ct as ResourceHooks, d as ApiResponse, dt as RouteHandlerMethod, et as RegistryEntry, f as ArcDecorator, ft as RouteSchemaOptions, g as AuthPluginOptions, gt as TypedRepository, h as AuthHelpers, ht as TypedController, it as RequestWithExtras, j as GracefulShutdownOptions, k as FastifyWithDecorators, l as AdditionalRoute, lt as ResourceMetadata, m as ArcRequest, mt as TokenPair, nt as RequestContext, ot as ResourceConfig, p as ArcInternalMetadata, pt as ServiceContext, q as ParsedQuery, qt as QueryOptions, rt as RequestIdOptions, st as ResourceHookContext, tt as RegistryStats, u as AnyRecord, ut as ResourcePermissions, v as AuthenticatorContext, vt as UserLike, w as CrudSchemas, x as CrudController, xt as ValidationResult, y as ConfigError, yt as UserOrganization, z as JWTPayload } from "../interface-DYH8AXGe.mjs";
|
|
3
3
|
import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "../types-BNUccdcf.mjs";
|
|
4
4
|
export { AUTHENTICATED_SCOPE, AdditionalRoute, AnyRecord, ApiResponse, ArcDecorator, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRepository, CrudRouteKey, CrudRouterOptions, CrudSchemas, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OpenApiSchemas, OwnershipCheck, PUBLIC_SCOPE, PaginatedResult, PaginationParams, ParsedQuery, PermissionCheck, PermissionContext, PermissionResult, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryOptions, QueryParserInterface, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResourceCacheConfig, ResourceConfig, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteHandler, RouteHandlerMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult, envelope, getOrgId, getOrgRoles, getTeamId, getUserId, hasOrgAccess, isAuthenticated, isElevated, isMember };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as ElevationOptions } from "./elevation-C_taLQrM.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { _ as Authenticator } from "./interface-DYH8AXGe.mjs";
|
|
3
3
|
import { t as ExternalOpenApiPaths } from "./externalPaths-DpO-s7r8.mjs";
|
|
4
4
|
import { i as CacheStore } from "./interface-D_BWALyZ.mjs";
|
|
5
5
|
import { r as QueryCachePluginOptions } from "./queryCachePlugin-DcmETvcB.mjs";
|
|
@@ -52,6 +52,12 @@ import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, Fast
|
|
|
52
52
|
* // Minimal resource (plain object)
|
|
53
53
|
* const simple: ResourceLike = { name: 'ping', toPlugin: () => () => {} };
|
|
54
54
|
* ```
|
|
55
|
+
*
|
|
56
|
+
* **DO NOT add an index signature** (`[key: string]: unknown`) to this interface.
|
|
57
|
+
* Class instances (like `ResourceDefinition`) don't implicitly carry index signatures,
|
|
58
|
+
* so adding one here makes `ResourceDefinition` *unassignable* to `ResourceLike` —
|
|
59
|
+
* the exact opposite of the intent. TypeScript's structural typing already allows
|
|
60
|
+
* classes with extra properties to satisfy this interface without an index signature.
|
|
55
61
|
*/
|
|
56
62
|
interface ResourceLike {
|
|
57
63
|
/** Plugin factory — called by createApp to register routes */
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as OpenApiSchemas, Q as QueryParserInterface, q as ParsedQuery, u as AnyRecord } from "../interface-DYH8AXGe.mjs";
|
|
2
2
|
import { a as NotFoundError, c as RateLimitError, d as ValidationError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorDetails, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-CcVbl1-T.mjs";
|
|
3
3
|
import { a as CircuitBreakerStats, c as createCircuitBreakerRegistry, i as CircuitBreakerRegistry, n as CircuitBreakerError, o as CircuitState, r as CircuitBreakerOptions, s as createCircuitBreaker, t as CircuitBreaker } from "../circuitBreaker-JP2GdJ4b.mjs";
|
|
4
4
|
import { FastifyInstance } from "fastify";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.3",
|
|
4
4
|
"description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -222,31 +222,31 @@
|
|
|
222
222
|
"peerDependencies": {
|
|
223
223
|
"@classytic/mongokit": ">=3.5.0",
|
|
224
224
|
"@classytic/streamline": ">=2.0.0",
|
|
225
|
-
"@fastify/cors": "
|
|
226
|
-
"@fastify/helmet": "
|
|
227
|
-
"@fastify/jwt": "
|
|
228
|
-
"@fastify/multipart": "
|
|
229
|
-
"@fastify/rate-limit": "
|
|
230
|
-
"@fastify/sensible": "
|
|
231
|
-
"@fastify/type-provider-typebox": "
|
|
232
|
-
"@fastify/under-pressure": "
|
|
233
|
-
"@fastify/websocket": "
|
|
225
|
+
"@fastify/cors": ">=11.0.0",
|
|
226
|
+
"@fastify/helmet": ">=13.0.0",
|
|
227
|
+
"@fastify/jwt": ">=10.0.0",
|
|
228
|
+
"@fastify/multipart": ">=9.0.0",
|
|
229
|
+
"@fastify/rate-limit": ">=10.0.0",
|
|
230
|
+
"@fastify/sensible": ">=6.0.0",
|
|
231
|
+
"@fastify/type-provider-typebox": ">=6.0.0",
|
|
232
|
+
"@fastify/under-pressure": ">=9.0.0",
|
|
233
|
+
"@fastify/websocket": ">=11.0.0",
|
|
234
234
|
"@modelcontextprotocol/sdk": ">=1.28.0",
|
|
235
235
|
"@opentelemetry/auto-instrumentations-node": ">=0.40.0",
|
|
236
236
|
"@opentelemetry/exporter-trace-otlp-http": ">=0.50.0",
|
|
237
237
|
"@opentelemetry/instrumentation-http": ">=0.50.0",
|
|
238
238
|
"@opentelemetry/instrumentation-mongodb": ">=0.40.0",
|
|
239
239
|
"@opentelemetry/sdk-node": ">=0.50.0",
|
|
240
|
-
"@sinclair/typebox": "
|
|
240
|
+
"@sinclair/typebox": ">=0.34.0",
|
|
241
241
|
"better-auth": ">=1.5.5",
|
|
242
|
-
"bullmq": "
|
|
243
|
-
"fastify": "
|
|
244
|
-
"fastify-raw-body": "
|
|
245
|
-
"ioredis": "
|
|
246
|
-
"mongodb": "
|
|
242
|
+
"bullmq": ">=5.0.0",
|
|
243
|
+
"fastify": ">=5.0.0",
|
|
244
|
+
"fastify-raw-body": ">=5.0.0",
|
|
245
|
+
"ioredis": ">=5.0.0",
|
|
246
|
+
"mongodb": ">=6.0.0",
|
|
247
247
|
"mongoose": ">=9.0.0",
|
|
248
|
-
"pino-pretty": "
|
|
249
|
-
"zod": "
|
|
248
|
+
"pino-pretty": ">=13.0.0",
|
|
249
|
+
"zod": ">=4.0.0"
|
|
250
250
|
},
|
|
251
251
|
"peerDependenciesMeta": {
|
|
252
252
|
"@classytic/mongokit": {
|