@classytic/arc 2.8.4 → 2.9.1
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 +116 -5
- package/dist/{BaseController-DAGGc5Xn.mjs → BaseController-Vu2yc56T.mjs} +188 -102
- package/dist/EventTransport-CqZ8FyM_.d.mts +293 -0
- package/dist/adapters/index.d.mts +2 -2
- package/dist/audit/index.d.mts +100 -11
- package/dist/audit/index.mjs +71 -18
- package/dist/auth/index.d.mts +15 -7
- package/dist/auth/index.mjs +13 -6
- package/dist/{betterAuthOpenApi-C5lDyRH2.mjs → betterAuthOpenApi--rdY15Ld.mjs} +1 -1
- package/dist/cache/index.d.mts +71 -1
- package/dist/cache/index.mjs +96 -3
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -5
- package/dist/{core-DKSwNSXf.mjs → core-DNncu0xF.mjs} +1 -1
- package/dist/{createActionRouter-Df1BuawX.mjs → createActionRouter-DH1YFL9m.mjs} +3 -3
- package/dist/{createApp-BOYjBgdI.mjs → createApp-CBJUJKGP.mjs} +6 -5
- package/dist/{defineResource-Bb_Bdhtw.mjs → defineResource-C__jkwvs.mjs} +22 -57
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +1 -1
- package/dist/dynamic/index.d.mts +2 -2
- package/dist/dynamic/index.mjs +3 -3
- package/dist/{elevation-BBGFjzIP.mjs → elevation-DxQ6ACbt.mjs} +20 -6
- package/dist/{errorHandler-mzqk4cGl.mjs → errorHandler-CZDW4EXS.mjs} +59 -7
- package/dist/{errorHandler-CdZDavNH.d.mts → errorHandler-DixGcttC.d.mts} +37 -2
- package/dist/{eventPlugin-CVxlE6De.d.mts → eventPlugin-BxvaCIZF.d.mts} +14 -2
- package/dist/{eventPlugin-D91S2YF4.mjs → eventPlugin-Dl7MoVWH.mjs} +83 -5
- package/dist/events/index.d.mts +147 -36
- package/dist/events/index.mjs +338 -101
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +1 -1
- package/dist/{fields-DC4So2M2.d.mts → fields-BC7zcmI9.d.mts} +15 -3
- package/dist/{fields-ipsbIRPK.mjs → fields-CU6FlaDV.mjs} +18 -5
- package/dist/filesUpload-q8oHt--L.mjs +377 -0
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +28 -4
- package/dist/idempotency/index.mjs +111 -2
- package/dist/idempotency/redis.d.mts +2 -2
- package/dist/idempotency/redis.mjs +134 -13
- package/dist/{index-CSkeivBx.d.mts → index-C-xjcA6F.d.mts} +2 -2
- package/dist/{index-CpTSDqmD.d.mts → index-Cibkchnx.d.mts} +5 -136
- package/dist/{index-BgmMdpm8.d.mts → index-CtGKT0lf.d.mts} +1 -1
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +8 -8
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/jobs.d.mts +25 -3
- package/dist/integrations/jobs.mjs +63 -4
- package/dist/integrations/mcp/index.d.mts +26 -8
- package/dist/integrations/mcp/index.mjs +96 -17
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/webhooks.d.mts +5 -0
- package/dist/integrations/webhooks.mjs +6 -0
- package/dist/{interface-BVuMfeVv.d.mts → interface-YrWsmKqE.d.mts} +324 -194
- package/dist/{openapi-CYCuekCn.mjs → openapi-CXuTG1M9.mjs} +3 -3
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-CH4cNwJi.mjs → permissions-oNZawnkR.mjs} +1 -1
- package/dist/plugins/index.d.mts +6 -6
- package/dist/plugins/index.mjs +4 -4
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/policies/index.d.mts +26 -33
- package/dist/presets/filesUpload.d.mts +71 -0
- package/dist/presets/filesUpload.mjs +2 -0
- package/dist/presets/index.d.mts +4 -2
- package/dist/presets/index.mjs +4 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +1 -1
- package/dist/presets/search.d.mts +91 -0
- package/dist/presets/search.mjs +150 -0
- package/dist/{presets-C2xgzW6x.mjs → presets-hM4WhNWY.mjs} +1 -1
- package/dist/{queryCachePlugin-D0iIVhW_.mjs → queryCachePlugin-DbUVroUG.mjs} +2 -2
- package/dist/redis-MXLp1oOf.d.mts +115 -0
- package/dist/{redis-stream-D54N5oXs.d.mts → redis-stream-Bz-4q96t.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/{resourceToTools-O_HwWXFa.mjs → resourceToTools-C3cWymnW.mjs} +65 -48
- package/dist/rpc/index.mjs +1 -1
- package/dist/{schemaConverter-OxfCshus.mjs → schemaConverter-BxFDdtXu.mjs} +25 -9
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +1 -1
- package/dist/storage-BwGQXUpd.d.mts +146 -0
- package/dist/store-helpers-DFiZl5TL.mjs +57 -0
- package/dist/testing/index.d.mts +7 -15
- package/dist/testing/index.mjs +23 -76
- package/dist/testing/storageContract.d.mts +26 -0
- package/dist/testing/storageContract.mjs +216 -0
- package/dist/types/index.d.mts +5 -5
- package/dist/types/storage.d.mts +2 -0
- package/dist/types/storage.mjs +1 -0
- package/dist/{types-CcG4avic.d.mts → types-CoSzA-s-.d.mts} +1 -1
- package/dist/{types-Bg2X42_m.d.mts → types-CunEX4UX.d.mts} +7 -5
- package/dist/{types-CVC4HOKi.d.mts → types-DZi1aYhm.d.mts} +1 -1
- package/dist/utils/index.d.mts +26 -8
- package/dist/utils/index.mjs +6 -6
- package/dist/{utils-yYT3HDXt.mjs → utils-B7FuRr9w.mjs} +1 -1
- package/package.json +23 -11
- package/skills/arc/SKILL.md +92 -14
- package/skills/arc/references/auth.md +94 -0
- package/skills/arc/references/events.md +229 -12
- package/skills/arc/references/mcp.md +4 -17
- package/skills/arc/references/multi-tenancy.md +43 -0
- package/skills/arc/references/production.md +34 -19
- package/dist/EventTransport-CinyO7zQ.d.mts +0 -135
- package/dist/audit/mongodb.d.mts +0 -2
- package/dist/audit/mongodb.mjs +0 -2
- package/dist/idempotency/mongodb.d.mts +0 -2
- package/dist/idempotency/mongodb.mjs +0 -123
- package/dist/mongodb-B5O6xaW1.mjs +0 -90
- package/dist/mongodb-B8U2xaLj.d.mts +0 -127
- package/dist/mongodb-X7LbEjTN.d.mts +0 -80
- package/dist/redis-z3sFr1UP.d.mts +0 -49
- /package/dist/{applyPermissionResult-D6GPMsvh.mjs → applyPermissionResult-bqGpo9ML.mjs} +0 -0
- /package/dist/{circuitBreaker-cmi5XDv5.mjs → circuitBreaker-l18oRgL5.mjs} +0 -0
- /package/dist/{elevation-s5ykdNHr.d.mts → elevation-B6S5csVA.d.mts} +0 -0
- /package/dist/{errors-Bmn3eZT6.d.mts → errors-BI8kEKsO.d.mts} +0 -0
- /package/dist/{errors-BF2bIOIS.mjs → errors-CqWnSqM-.mjs} +0 -0
- /package/dist/{memory-Cp7_cAko.mjs → memory-BFAYkf8H.mjs} +0 -0
- /package/dist/{pluralize-A0tWEl1K.mjs → pluralize-CWP6MB39.mjs} +0 -0
- /package/dist/{queryParser-CgCtsjti.mjs → queryParser-Cs-6SHQK.mjs} +0 -0
- /package/dist/{requestContext-DYvHl113.mjs → requestContext-DYtmNpm5.mjs} +0 -0
- /package/dist/{tracing-DxjKk7eW.d.mts → tracing-xqXzWeaf.d.mts} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-Cj5Rgvlg.mjs} +0 -0
- /package/dist/{types-C72d3NDn.d.mts → types-BD85MlEK.d.mts} +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-B-pe8fhj.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/idempotency/stores/redis.d.ts
|
|
4
|
+
interface RedisClient {
|
|
5
|
+
get(key: string): Promise<string | null>;
|
|
6
|
+
set(key: string, value: string, options?: {
|
|
7
|
+
EX?: number;
|
|
8
|
+
NX?: boolean;
|
|
9
|
+
}): Promise<string | null>;
|
|
10
|
+
del(key: string | string[]): Promise<number>;
|
|
11
|
+
exists(key: string | string[]): Promise<number>;
|
|
12
|
+
/** SCAN command — compatible with node-redis and ioredis varargs signatures. */
|
|
13
|
+
scan?(cursor: string | number, ...args: (string | number)[]): Promise<[string | number, string[]]>;
|
|
14
|
+
quit?(): Promise<string>;
|
|
15
|
+
disconnect?(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
interface RedisIdempotencyStoreOptions {
|
|
18
|
+
/** Redis client instance */
|
|
19
|
+
client: RedisClient;
|
|
20
|
+
/** Key prefix (default: 'idem:') */
|
|
21
|
+
prefix?: string;
|
|
22
|
+
/** Lock key prefix (default: 'idem:lock:') */
|
|
23
|
+
lockPrefix?: string;
|
|
24
|
+
/** Default TTL in ms (default: 86400000 = 24 hours) */
|
|
25
|
+
ttlMs?: number;
|
|
26
|
+
}
|
|
27
|
+
declare class RedisIdempotencyStore implements IdempotencyStore {
|
|
28
|
+
readonly name = "redis";
|
|
29
|
+
private client;
|
|
30
|
+
private prefix;
|
|
31
|
+
private lockPrefix;
|
|
32
|
+
private ttlMs;
|
|
33
|
+
constructor(options: RedisIdempotencyStoreOptions);
|
|
34
|
+
private resultKey;
|
|
35
|
+
private lockKey;
|
|
36
|
+
get(key: string): Promise<IdempotencyResult | undefined>;
|
|
37
|
+
set(key: string, result: Omit<IdempotencyResult, "key">): Promise<void>;
|
|
38
|
+
tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean>;
|
|
39
|
+
unlock(key: string, requestId: string): Promise<void>;
|
|
40
|
+
isLocked(key: string): Promise<boolean>;
|
|
41
|
+
delete(key: string): Promise<void>;
|
|
42
|
+
deleteByPrefix(prefix: string): Promise<number>;
|
|
43
|
+
findByPrefix(prefix: string): Promise<IdempotencyResult | undefined>;
|
|
44
|
+
/** Scan Redis keys matching a prefix pattern. Falls back to empty if SCAN unavailable. */
|
|
45
|
+
private scanByPrefix;
|
|
46
|
+
close(): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
/** Minimal ioredis shape we depend on — keeps this file peer-dep-free. */
|
|
49
|
+
interface IoredisLike {
|
|
50
|
+
get(key: string): Promise<string | null>;
|
|
51
|
+
set(...args: unknown[]): Promise<string | null>;
|
|
52
|
+
del(...keys: string[]): Promise<number>;
|
|
53
|
+
exists(...keys: string[]): Promise<number>;
|
|
54
|
+
scan(cursor: string | number, ...args: (string | number)[]): Promise<[string, string[]]>;
|
|
55
|
+
eval?(script: string, numKeys: number, ...args: (string | number)[]): Promise<unknown>;
|
|
56
|
+
quit?(): Promise<string>;
|
|
57
|
+
disconnect?(): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Wrap an ioredis instance as the arc idempotency `RedisClient`.
|
|
61
|
+
*
|
|
62
|
+
* Arc's idempotency store expects node-redis-v4 style option objects
|
|
63
|
+
* (`{ EX, NX }`). ioredis uses positional flags. This adapter lets users
|
|
64
|
+
* plug an ioredis instance in without writing the bridge themselves.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* import Redis from 'ioredis';
|
|
69
|
+
* import { RedisIdempotencyStore, ioredisAsIdempotencyClient }
|
|
70
|
+
* from '@classytic/arc/idempotency/redis';
|
|
71
|
+
*
|
|
72
|
+
* const redis = new Redis(process.env.REDIS_URL);
|
|
73
|
+
* const store = new RedisIdempotencyStore({
|
|
74
|
+
* client: ioredisAsIdempotencyClient(redis),
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function ioredisAsIdempotencyClient(client: IoredisLike): RedisClient & {
|
|
79
|
+
eval?: (script: string, numkeys: number, ...args: (string | number)[]) => Promise<unknown>;
|
|
80
|
+
};
|
|
81
|
+
/** Minimal `@upstash/redis` shape we depend on (REST client, edge-safe). */
|
|
82
|
+
interface UpstashRedisLike {
|
|
83
|
+
get(key: string): Promise<string | null | unknown>;
|
|
84
|
+
set(key: string, value: unknown, opts?: Record<string, unknown>): Promise<unknown>;
|
|
85
|
+
del(...keys: string[]): Promise<number>;
|
|
86
|
+
exists(...keys: string[]): Promise<number>;
|
|
87
|
+
scan(cursor: number | string, opts?: {
|
|
88
|
+
match?: string;
|
|
89
|
+
count?: number;
|
|
90
|
+
}): Promise<[number, string[]] | [string, string[]]>;
|
|
91
|
+
eval?(script: string, keys: string[], args: (string | number)[]): Promise<unknown>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Wrap an `@upstash/redis` REST client as an idempotency `RedisClient`.
|
|
95
|
+
*
|
|
96
|
+
* Enables running arc's idempotency store on Cloudflare Workers, Vercel Edge
|
|
97
|
+
* and Deno Deploy — runtimes that don't support raw TCP (ioredis).
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* import { Redis } from '@upstash/redis';
|
|
102
|
+
* import { RedisIdempotencyStore, upstashAsIdempotencyClient }
|
|
103
|
+
* from '@classytic/arc/idempotency/redis';
|
|
104
|
+
*
|
|
105
|
+
* const redis = Redis.fromEnv();
|
|
106
|
+
* const store = new RedisIdempotencyStore({
|
|
107
|
+
* client: upstashAsIdempotencyClient(redis),
|
|
108
|
+
* });
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
declare function upstashAsIdempotencyClient(client: UpstashRedisLike): RedisClient & {
|
|
112
|
+
eval?: (script: string, numkeys: number, ...args: (string | number)[]) => Promise<unknown>;
|
|
113
|
+
};
|
|
114
|
+
//#endregion
|
|
115
|
+
export { UpstashRedisLike as a, RedisIdempotencyStoreOptions as i, RedisClient as n, ioredisAsIdempotencyClient as o, RedisIdempotencyStore as r, upstashAsIdempotencyClient as s, IoredisLike as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as
|
|
1
|
+
import { i as EventLogger, n as DomainEvent, o as EventTransport, r as EventHandler } from "./EventTransport-CqZ8FyM_.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/events/transports/redis-stream.d.ts
|
|
4
4
|
interface RedisStreamLike {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Gt as
|
|
1
|
+
import { Gt as ResourceRegistry, V as IntrospectionPluginOptions, Wt as RegisterOptions } from "../interface-YrWsmKqE.mjs";
|
|
2
2
|
import { FastifyPluginAsync } from "fastify";
|
|
3
3
|
|
|
4
4
|
//#region src/registry/introspectionPlugin.d.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as BaseController } from "./BaseController-
|
|
2
|
-
import { n as normalizePermissionResult } from "./applyPermissionResult-
|
|
3
|
-
import { t as pluralize } from "./pluralize-
|
|
1
|
+
import { t as BaseController } from "./BaseController-Vu2yc56T.mjs";
|
|
2
|
+
import { n as normalizePermissionResult } from "./applyPermissionResult-bqGpo9ML.mjs";
|
|
3
|
+
import { t as pluralize } from "./pluralize-CWP6MB39.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/integrations/mcp/createMcpServer.ts
|
|
6
6
|
/**
|
|
@@ -568,7 +568,7 @@ function resourceToTools(resource, config = {}) {
|
|
|
568
568
|
const hasSoftDelete = resource._appliedPresets?.includes("softDelete") ?? false;
|
|
569
569
|
const tools = [];
|
|
570
570
|
const prefix = config.toolNamePrefix;
|
|
571
|
-
if (
|
|
571
|
+
if (controller) {
|
|
572
572
|
let ops = ALL_CRUD_OPS.filter((op) => {
|
|
573
573
|
if (resource.disabledRoutes?.includes(op)) return false;
|
|
574
574
|
return true;
|
|
@@ -595,50 +595,51 @@ function resourceToTools(resource, config = {}) {
|
|
|
595
595
|
handler: createHandler(op, controller, resource.name, resource.permissions)
|
|
596
596
|
});
|
|
597
597
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
|
|
598
|
+
}
|
|
599
|
+
for (const route of resource.routes ?? []) {
|
|
600
|
+
if (route.mcp === false) continue;
|
|
601
|
+
const mcpHandler = route.mcpHandler;
|
|
602
|
+
if (!!route.raw && !mcpHandler) continue;
|
|
603
|
+
if (!mcpHandler && ![
|
|
604
|
+
"POST",
|
|
605
|
+
"PUT",
|
|
606
|
+
"PATCH",
|
|
607
|
+
"DELETE"
|
|
608
|
+
].includes(route.method)) continue;
|
|
609
|
+
if (!mcpHandler && typeof route.handler === "string" && !controller) continue;
|
|
610
|
+
const opName = route.operation ?? slugifyRoute(route.method, route.path);
|
|
611
|
+
const hasId = route.path.includes(":id");
|
|
612
|
+
const mcpConfig = typeof route.mcp === "object" && route.mcp !== null ? route.mcp : void 0;
|
|
613
|
+
const toolDescription = mcpConfig?.description ?? route.summary ?? route.description ?? `${opName} on ${resource.displayName}`;
|
|
614
|
+
const toolAnnotations = mcpConfig?.annotations ? { ...mcpConfig.annotations } : { openWorldHint: true };
|
|
615
|
+
const inputShape = {};
|
|
616
|
+
if (hasId) inputShape.id = z.string().describe("Resource ID");
|
|
617
|
+
if (mcpHandler) tools.push({
|
|
618
|
+
name: prefix ? `${prefix}_${opName}_${resource.name}` : `${opName}_${resource.name}`,
|
|
619
|
+
description: toolDescription,
|
|
620
|
+
annotations: toolAnnotations,
|
|
621
|
+
inputSchema: inputShape,
|
|
622
|
+
handler: async (input, _ctx) => {
|
|
623
|
+
try {
|
|
624
|
+
return await mcpHandler(input);
|
|
625
|
+
} catch (err) {
|
|
626
|
+
return {
|
|
627
|
+
content: [{
|
|
628
|
+
type: "text",
|
|
629
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
630
|
+
}],
|
|
631
|
+
isError: true
|
|
632
|
+
};
|
|
632
633
|
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
else tools.push({
|
|
637
|
+
name: prefix ? `${prefix}_${opName}_${resource.name}` : `${opName}_${resource.name}`,
|
|
638
|
+
description: toolDescription,
|
|
639
|
+
annotations: toolAnnotations,
|
|
640
|
+
inputSchema: inputShape,
|
|
641
|
+
handler: createCustomRouteHandler(route, controller, hasId)
|
|
642
|
+
});
|
|
642
643
|
}
|
|
643
644
|
if (resource.actions) for (const [actionName, entry] of Object.entries(resource.actions)) {
|
|
644
645
|
const def = typeof entry === "function" ? { handler: entry } : entry;
|
|
@@ -757,11 +758,27 @@ function createHandler(op, controller, resourceName, permissions) {
|
|
|
757
758
|
}
|
|
758
759
|
};
|
|
759
760
|
}
|
|
760
|
-
function
|
|
761
|
+
function createCustomRouteHandler(route, controller, hasId) {
|
|
761
762
|
const ctrl = controller;
|
|
762
763
|
const handlerName = typeof route.handler === "string" ? route.handler : route.operation ?? slugifyRoute(route.method, route.path);
|
|
763
764
|
return async (input, ctx) => {
|
|
764
765
|
try {
|
|
766
|
+
if (typeof route.handler === "function") {
|
|
767
|
+
const reqCtx = buildRequestContext(input, ctx.session, hasId ? "update" : "create");
|
|
768
|
+
const fn = route.handler;
|
|
769
|
+
const out = await fn(reqCtx);
|
|
770
|
+
return toCallToolResult(out !== null && typeof out === "object" && "success" in out ? out : {
|
|
771
|
+
success: true,
|
|
772
|
+
data: out
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
if (!ctrl) return {
|
|
776
|
+
content: [{
|
|
777
|
+
type: "text",
|
|
778
|
+
text: `Handler "${handlerName}" has no controller available`
|
|
779
|
+
}],
|
|
780
|
+
isError: true
|
|
781
|
+
};
|
|
765
782
|
const method = ctrl[handlerName];
|
|
766
783
|
if (typeof method !== "function") return {
|
|
767
784
|
content: [{
|
package/dist/rpc/index.mjs
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
//#region src/utils/schemaConverter.ts
|
|
2
|
+
/** Default target for Fastify-consumed schemas (matches Fastify v5's default AJV draft). */
|
|
3
|
+
const DEFAULT_FASTIFY_TARGET = "draft-7";
|
|
4
|
+
/** Default target for OpenAPI document generation (matches arc's emitted OpenAPI version). */
|
|
5
|
+
const DEFAULT_OPENAPI_TARGET = "openapi-3.0";
|
|
2
6
|
let _toJSONSchema = null;
|
|
3
7
|
import("zod").then(({ z }) => {
|
|
4
8
|
if (typeof z?.toJSONSchema === "function") _toJSONSchema = (schema, opts) => z.toJSONSchema(schema, opts);
|
|
@@ -26,17 +30,23 @@ function isZodSchema(input) {
|
|
|
26
30
|
* Detection order:
|
|
27
31
|
* 1. `null`/`undefined` → `undefined`
|
|
28
32
|
* 2. Already JSON Schema → pass through as-is (zero overhead)
|
|
29
|
-
* 3. Zod v4 schema → `z.toJSONSchema(schema, { target
|
|
33
|
+
* 3. Zod v4 schema → `z.toJSONSchema(schema, { target })`
|
|
30
34
|
* 4. Unrecognized object → return as-is (treat as opaque schema)
|
|
35
|
+
*
|
|
36
|
+
* @param input Schema (Zod, plain JSON Schema, or opaque object)
|
|
37
|
+
* @param target Output target — defaults to `draft-7` for Fastify compatibility.
|
|
38
|
+
* Pass `openapi-3.0`/`openapi-3.1` for OpenAPI document generation.
|
|
31
39
|
*/
|
|
32
|
-
function toJsonSchema(input) {
|
|
40
|
+
function toJsonSchema(input, target = DEFAULT_FASTIFY_TARGET) {
|
|
33
41
|
if (input == null) return void 0;
|
|
34
42
|
if (typeof input !== "object") return void 0;
|
|
35
43
|
if (isJsonSchema(input)) return input;
|
|
36
44
|
if (isZodSchema(input)) {
|
|
37
45
|
if (!_toJSONSchema) return input;
|
|
38
46
|
try {
|
|
39
|
-
|
|
47
|
+
const converted = _toJSONSchema(input, { target });
|
|
48
|
+
if ("$schema" in converted) delete converted.$schema;
|
|
49
|
+
return converted;
|
|
40
50
|
} catch {
|
|
41
51
|
return { type: "object" };
|
|
42
52
|
}
|
|
@@ -46,8 +56,11 @@ function toJsonSchema(input) {
|
|
|
46
56
|
/**
|
|
47
57
|
* Convert all schema fields in an OpenApiSchemas object.
|
|
48
58
|
* JSON Schema values pass through unchanged. Only Zod schemas are converted.
|
|
59
|
+
*
|
|
60
|
+
* Defaults to the `openapi-3.0` target since this function feeds OpenAPI doc
|
|
61
|
+
* generation, not Fastify route validation.
|
|
49
62
|
*/
|
|
50
|
-
function convertOpenApiSchemas(schemas) {
|
|
63
|
+
function convertOpenApiSchemas(schemas, target = DEFAULT_OPENAPI_TARGET) {
|
|
51
64
|
const result = {};
|
|
52
65
|
const schemaFields = [
|
|
53
66
|
"entity",
|
|
@@ -59,7 +72,7 @@ function convertOpenApiSchemas(schemas) {
|
|
|
59
72
|
];
|
|
60
73
|
for (const field of schemaFields) {
|
|
61
74
|
const value = schemas[field];
|
|
62
|
-
if (value !== void 0) result[field] = toJsonSchema(value) ?? value;
|
|
75
|
+
if (value !== void 0) result[field] = toJsonSchema(value, target) ?? value;
|
|
63
76
|
}
|
|
64
77
|
for (const [key, value] of Object.entries(schemas)) if (!schemaFields.includes(key)) result[key] = value;
|
|
65
78
|
return result;
|
|
@@ -72,20 +85,23 @@ function convertOpenApiSchemas(schemas) {
|
|
|
72
85
|
*
|
|
73
86
|
* JSON Schema values pass through unchanged. Only Zod schemas are converted.
|
|
74
87
|
*
|
|
75
|
-
* Used for both
|
|
88
|
+
* Used for both custom routes and customSchemas (CRUD overrides).
|
|
89
|
+
*
|
|
90
|
+
* Defaults to `draft-7` so Fastify v5's bundled AJV 8 accepts the output.
|
|
91
|
+
* Pass `openapi-3.0` (or `openapi-3.1`) when generating OpenAPI documents.
|
|
76
92
|
*/
|
|
77
|
-
function convertRouteSchema(schema) {
|
|
93
|
+
function convertRouteSchema(schema, target = DEFAULT_FASTIFY_TARGET) {
|
|
78
94
|
const result = { ...schema };
|
|
79
95
|
for (const field of [
|
|
80
96
|
"body",
|
|
81
97
|
"querystring",
|
|
82
98
|
"params",
|
|
83
99
|
"headers"
|
|
84
|
-
]) if (result[field] !== void 0) result[field] = toJsonSchema(result[field]) ?? result[field];
|
|
100
|
+
]) if (result[field] !== void 0) result[field] = toJsonSchema(result[field], target) ?? result[field];
|
|
85
101
|
if (result.response !== void 0 && typeof result.response === "object" && result.response !== null) {
|
|
86
102
|
const responseObj = result.response;
|
|
87
103
|
const convertedResponse = {};
|
|
88
|
-
for (const [statusCode, responseSchema] of Object.entries(responseObj)) convertedResponse[statusCode] = toJsonSchema(responseSchema) ?? responseSchema;
|
|
104
|
+
for (const [statusCode, responseSchema] of Object.entries(responseObj)) convertedResponse[statusCode] = toJsonSchema(responseSchema, target) ?? responseSchema;
|
|
89
105
|
result.response = convertedResponse;
|
|
90
106
|
}
|
|
91
107
|
return result;
|
package/dist/scope/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as isAuthenticated, a as getClientId, b as isOrgInScope, c as getOrgRoles, d as getScopeContextMap, f as getServiceScopes, g as hasOrgAccess, h as getUserRoles, i as getAncestorOrgIds, l as getRequestScope, m as getUserId, n as PUBLIC_SCOPE, o as getOrgContext, p as getTeamId, r as RequestScope, s as getOrgId, t as AUTHENTICATED_SCOPE, u as getScopeContext, v as isElevated, x as isService, y as isMember } from "../types-
|
|
2
|
-
import { i as elevationPlugin, n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-
|
|
1
|
+
import { _ as isAuthenticated, a as getClientId, b as isOrgInScope, c as getOrgRoles, d as getScopeContextMap, f as getServiceScopes, g as hasOrgAccess, h as getUserRoles, i as getAncestorOrgIds, l as getRequestScope, m as getUserId, n as PUBLIC_SCOPE, o as getOrgContext, p as getTeamId, r as RequestScope, s as getOrgId, t as AUTHENTICATED_SCOPE, u as getScopeContext, v as isElevated, x as isService, y as isMember } from "../types-BD85MlEK.mjs";
|
|
2
|
+
import { i as elevationPlugin, n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-B6S5csVA.mjs";
|
|
3
3
|
import { FastifyReply, FastifyRequest } from "fastify";
|
|
4
4
|
|
|
5
5
|
//#region src/scope/rateLimitKey.d.ts
|
package/dist/scope/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _ as isElevated, a as getOrgContext, b as isService, c as getRequestScope, d as getServiceScopes, f as getTeamId, g as isAuthenticated, h as hasOrgAccess, i as getClientId, l as getScopeContext, m as getUserRoles, n as PUBLIC_SCOPE, o as getOrgId, p as getUserId, r as getAncestorOrgIds, s as getOrgRoles, t as AUTHENTICATED_SCOPE, u as getScopeContextMap, v as isMember, y as isOrgInScope } from "../types-AOD8fxIw.mjs";
|
|
2
2
|
import { n as normalizeRoles } from "../types-ZUu_h0jp.mjs";
|
|
3
|
-
import { n as elevation_default, t as elevationPlugin } from "../elevation-
|
|
3
|
+
import { n as elevation_default, t as elevationPlugin } from "../elevation-DxQ6ACbt.mjs";
|
|
4
4
|
//#region src/scope/rateLimitKey.ts
|
|
5
5
|
function createTenantKeyGenerator(opts) {
|
|
6
6
|
if (opts?.strategy) return opts.strategy;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
//#region src/types/storage.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Arc's minimal backend-agnostic storage contract.
|
|
4
|
+
*
|
|
5
|
+
* Implementations live OUTSIDE arc core. This interface is deliberately
|
|
6
|
+
* small — no variants, no hashing, no CDN transforms, no multi-tenancy
|
|
7
|
+
* policy. Those are adapter concerns.
|
|
8
|
+
*
|
|
9
|
+
* Adapter authors should verify their implementation with
|
|
10
|
+
* `runStorageContract()` from `@classytic/arc/testing/storage` —
|
|
11
|
+
* passing the contract guarantees compatibility with every arc preset
|
|
12
|
+
* that consumes `Storage`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import type { Storage } from '@classytic/arc/types/storage';
|
|
17
|
+
*
|
|
18
|
+
* export function memoryStorage(): Storage {
|
|
19
|
+
* const rows = new Map<string, { buffer: Buffer; contentType: string }>();
|
|
20
|
+
* return {
|
|
21
|
+
* async upload(input) {
|
|
22
|
+
* const id = crypto.randomUUID();
|
|
23
|
+
* rows.set(id, { buffer: input.buffer, contentType: input.mimeType });
|
|
24
|
+
* return { id, url: `memory://${id}`, pathname: id, contentType: input.mimeType, bytes: input.size };
|
|
25
|
+
* },
|
|
26
|
+
* async read(id) {
|
|
27
|
+
* const row = rows.get(id);
|
|
28
|
+
* if (!row) throw new Error('Not found');
|
|
29
|
+
* return { kind: 'buffer', buffer: row.buffer, contentType: row.contentType };
|
|
30
|
+
* },
|
|
31
|
+
* async delete(id) { return rows.delete(id); },
|
|
32
|
+
* };
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* Input passed to `Storage.upload()`.
|
|
38
|
+
* The preset populates this from the parsed multipart file.
|
|
39
|
+
*/
|
|
40
|
+
interface StorageUploadInput {
|
|
41
|
+
/** File bytes. */
|
|
42
|
+
buffer: Buffer;
|
|
43
|
+
/** Original filename from the client. */
|
|
44
|
+
filename: string;
|
|
45
|
+
/** IANA media type — preset validates via `multipartBody`. */
|
|
46
|
+
mimeType: string;
|
|
47
|
+
/** Size in bytes. Equals `buffer.length`. */
|
|
48
|
+
size: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Context threaded through every storage call.
|
|
52
|
+
* Adapters decide which keys they care about.
|
|
53
|
+
*/
|
|
54
|
+
interface StorageContext {
|
|
55
|
+
/**
|
|
56
|
+
* App-defined scope. Arc populates this from `RequestScope` via the preset's
|
|
57
|
+
* `contextFrom` option so adapters can isolate per-tenant / per-user / per-project.
|
|
58
|
+
*/
|
|
59
|
+
scope?: Record<string, unknown>;
|
|
60
|
+
/** Optional request correlation id for logging. */
|
|
61
|
+
requestId?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Handle returned by `Storage.upload()`.
|
|
65
|
+
* The adapter owns the id namespace and URL format.
|
|
66
|
+
*/
|
|
67
|
+
interface StorageFile {
|
|
68
|
+
/** Stable ID — the adapter owns the namespace. Used as the route param for GET/DELETE. */
|
|
69
|
+
id: string;
|
|
70
|
+
/** Public or signed URL — what the frontend stores in an `<img src>`. */
|
|
71
|
+
url: string;
|
|
72
|
+
/** Storage-side path / key. Opaque to arc; useful for admin tooling. */
|
|
73
|
+
pathname: string;
|
|
74
|
+
/** IANA media type — usually echoed from `input.mimeType`. */
|
|
75
|
+
contentType: string;
|
|
76
|
+
/** Size in bytes. */
|
|
77
|
+
bytes: number;
|
|
78
|
+
/** Adapter-defined metadata passed through to the response body. */
|
|
79
|
+
metadata?: Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Optional byte range for partial reads.
|
|
83
|
+
* End-inclusive, matching HTTP `Range: bytes=start-end` semantics and
|
|
84
|
+
* media-kit's `StorageDriver.read()` contract.
|
|
85
|
+
*/
|
|
86
|
+
interface StorageReadRange {
|
|
87
|
+
/** First byte offset (inclusive). */
|
|
88
|
+
start: number;
|
|
89
|
+
/** Last byte offset (inclusive). */
|
|
90
|
+
end: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Result of `Storage.read()`.
|
|
94
|
+
* Adapters pick whichever shape is natural; the preset handler branches on `kind`.
|
|
95
|
+
* Large/remote backends should return a stream; small/in-memory backends can
|
|
96
|
+
* return a buffer without wrapping it in a `PassThrough` for no reason.
|
|
97
|
+
*/
|
|
98
|
+
type StorageReadResult = {
|
|
99
|
+
kind: "stream";
|
|
100
|
+
stream: NodeJS.ReadableStream;
|
|
101
|
+
contentType: string; /** Total size of the full object (NOT the ranged slice). Required for `Content-Range`. */
|
|
102
|
+
bytes?: number; /** Actual byte range returned when the caller passed `range`. */
|
|
103
|
+
range?: StorageReadRange;
|
|
104
|
+
} | {
|
|
105
|
+
kind: "buffer";
|
|
106
|
+
buffer: Buffer;
|
|
107
|
+
contentType: string; /** Total size of the full object. Used for `Content-Range` on partial reads. */
|
|
108
|
+
totalBytes?: number; /** Actual byte range returned when the caller passed `range`. */
|
|
109
|
+
range?: StorageReadRange;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Minimal storage contract consumed by `filesUploadPreset`.
|
|
113
|
+
*
|
|
114
|
+
* Adapters are ~50–100 LOC wrappers around S3, GCS, local FS, GridFS,
|
|
115
|
+
* media-kit, or a provider Files API. Ship zero reference adapters in
|
|
116
|
+
* arc core — the one you'd pick is always the wrong default.
|
|
117
|
+
*/
|
|
118
|
+
interface Storage {
|
|
119
|
+
/** Write bytes, return a stable handle. */
|
|
120
|
+
upload(input: StorageUploadInput, ctx: StorageContext): Promise<StorageFile>;
|
|
121
|
+
/**
|
|
122
|
+
* Read bytes by id. Streaming preferred; Buffer fine for small files.
|
|
123
|
+
*
|
|
124
|
+
* If `range` is provided, implementations SHOULD return only the requested
|
|
125
|
+
* slice and set `range` on the result. Implementations that cannot range
|
|
126
|
+
* (e.g. trivial in-memory adapters) MAY return the full object and let the
|
|
127
|
+
* preset slice it — the preset handles both cases.
|
|
128
|
+
*/
|
|
129
|
+
read(id: string, ctx: StorageContext, range?: StorageReadRange): Promise<StorageReadResult>;
|
|
130
|
+
/**
|
|
131
|
+
* Remove bytes by id. MAY be a soft delete — the interface doesn't
|
|
132
|
+
* dictate lifecycle. Return `false` when the id was already absent.
|
|
133
|
+
*/
|
|
134
|
+
delete(id: string, ctx: StorageContext): Promise<boolean>;
|
|
135
|
+
/** Optional — fast existence check without a full read. */
|
|
136
|
+
exists?(id: string, ctx: StorageContext): Promise<boolean>;
|
|
137
|
+
/**
|
|
138
|
+
* Optional — return a fresh URL for an existing id. Called by the preset
|
|
139
|
+
* when the stored `url` may have expired (signed URLs, rotated buckets).
|
|
140
|
+
* Default behavior when omitted: the preset falls back to the url stored
|
|
141
|
+
* on the original `upload()` result.
|
|
142
|
+
*/
|
|
143
|
+
resolveUrl?(id: string, ctx: StorageContext): Promise<string>;
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
export { StorageReadResult as a, StorageReadRange as i, StorageContext as n, StorageUploadInput as o, StorageFile as r, Storage as t };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region src/adapters/store-helpers.ts
|
|
2
|
+
/**
|
|
3
|
+
* Classify an error thrown by `getOne` / `getById` / `update` as a
|
|
4
|
+
* "document not found" miss. Mongokit uses `status: 404`, Prisma uses
|
|
5
|
+
* `code: 'P2025'`, some kits throw `DocumentNotFoundError`. Kits that
|
|
6
|
+
* return `null` on miss never see this predicate fire — it only kicks in
|
|
7
|
+
* when a driver chose to throw.
|
|
8
|
+
*/
|
|
9
|
+
function isNotFoundError(err) {
|
|
10
|
+
if (!err || typeof err !== "object") return false;
|
|
11
|
+
const e = err;
|
|
12
|
+
if (e.status === 404 || e.statusCode === 404) return true;
|
|
13
|
+
if (e.code === "P2025") return true;
|
|
14
|
+
if (e.name === "DocumentNotFoundError") return true;
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Build a `safeGetOne(filter)` that papers over the throw-vs-null split
|
|
19
|
+
* in kit implementations. Real errors propagate; miss returns `null`.
|
|
20
|
+
* Throws if the repository lacks `getOne` — callers must check.
|
|
21
|
+
*/
|
|
22
|
+
function createSafeGetOne(repository) {
|
|
23
|
+
if (typeof repository.getOne !== "function") throw new Error("createSafeGetOne: repository.getOne is required");
|
|
24
|
+
const getOne = repository.getOne.bind(repository);
|
|
25
|
+
return async (filter) => {
|
|
26
|
+
try {
|
|
27
|
+
return await getOne(filter) ?? null;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (isNotFoundError(err)) return null;
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build a dup-key predicate for the given repository. Prefers the kit's
|
|
36
|
+
* own `isDuplicateKeyError` (it knows its driver — Mongo `11000`, Prisma
|
|
37
|
+
* `P2002`, Postgres `23505`, MySQL `1062`, etc.); falls back to a
|
|
38
|
+
* conservative Mongo check so mongokit ≤3.8 keeps working without changes.
|
|
39
|
+
*
|
|
40
|
+
* Non-mongo kits MUST implement the predicate to participate in
|
|
41
|
+
* idempotency/outbox dup-handling semantics.
|
|
42
|
+
*
|
|
43
|
+
* `name === "MongoServerError"` alone is deliberately NOT matched — that
|
|
44
|
+
* also fires on WriteConflict / NotWritablePrimary / transient failures,
|
|
45
|
+
* which must propagate rather than silently become 409s.
|
|
46
|
+
*/
|
|
47
|
+
function createIsDuplicateKeyError(repository) {
|
|
48
|
+
const repoPredicate = typeof repository.isDuplicateKeyError === "function" ? repository.isDuplicateKeyError.bind(repository) : null;
|
|
49
|
+
return (err) => {
|
|
50
|
+
if (repoPredicate) return repoPredicate(err);
|
|
51
|
+
if (!err || typeof err !== "object") return false;
|
|
52
|
+
const e = err;
|
|
53
|
+
return e.code === 11e3 || e.codeName === "DuplicateKey";
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { createSafeGetOne as n, createIsDuplicateKeyError as t };
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { d as ResourceLike, r as CreateAppOptions } from "../types-
|
|
1
|
+
import { Kt as ResourceDefinition, Xt as CrudRepository, p as AnyRecord } from "../interface-YrWsmKqE.mjs";
|
|
2
|
+
import { d as ResourceLike, r as CreateAppOptions } from "../types-CunEX4UX.mjs";
|
|
3
|
+
import { StorageContractSetup, StorageContractSetupResult, runStorageContract } from "./storageContract.mjs";
|
|
3
4
|
import Fastify, { FastifyInstance, FastifyServerOptions } from "fastify";
|
|
4
5
|
import { Connection } from "mongoose";
|
|
5
6
|
import { Mock } from "vitest";
|
|
@@ -630,21 +631,12 @@ declare class TestHarness<T = unknown> {
|
|
|
630
631
|
private _createdIds;
|
|
631
632
|
constructor(resource: ResourceDefinition<unknown>, options: TestHarnessOptions<T>);
|
|
632
633
|
/**
|
|
633
|
-
* Run all baseline tests
|
|
634
|
+
* Run all baseline tests (schema, presets, field permissions, pipeline, events).
|
|
634
635
|
*
|
|
635
|
-
*
|
|
636
|
+
* For HTTP-level CRUD coverage (routes, auth, permissions), use
|
|
637
|
+
* {@link HttpTestHarness} instead.
|
|
636
638
|
*/
|
|
637
639
|
runAll(): void;
|
|
638
|
-
/**
|
|
639
|
-
* Run CRUD operation tests (model-level)
|
|
640
|
-
*
|
|
641
|
-
* Tests: create, read (list + getById), update, delete
|
|
642
|
-
*
|
|
643
|
-
* @deprecated Use `HttpTestHarness.runCrud()` for HTTP-level CRUD tests.
|
|
644
|
-
* This method tests Mongoose models directly and does not exercise
|
|
645
|
-
* HTTP routes, authentication, permissions, or the Arc pipeline.
|
|
646
|
-
*/
|
|
647
|
-
runCrud(): void;
|
|
648
640
|
/**
|
|
649
641
|
* Run validation tests
|
|
650
642
|
*
|
|
@@ -921,4 +913,4 @@ declare class TestDataLoader {
|
|
|
921
913
|
cleanup(): Promise<void>;
|
|
922
914
|
}
|
|
923
915
|
//#endregion
|
|
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 };
|
|
916
|
+
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 StorageContractSetup, type StorageContractSetupResult, 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, runStorageContract, safeParseBody, setupBetterAuthOrg, waitFor, withTestDb };
|