@classytic/arc 1.1.0 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BkUDYZEb.d.mts +99 -0
- package/dist/HookSystem-BsGV-j2l.mjs +404 -0
- package/dist/ResourceRegistry-7Ic20ZMw.mjs +249 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +81 -0
- package/dist/audit/index.mjs +275 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-CGdLiSlE.mjs +140 -0
- package/dist/auth/index.d.mts +188 -0
- package/dist/auth/index.mjs +1096 -0
- package/dist/auth/redis-session.d.mts +43 -0
- package/dist/auth/redis-session.mjs +75 -0
- package/dist/betterAuthOpenApi-DjWDddNc.mjs +249 -0
- package/dist/cache/index.d.mts +145 -0
- package/dist/cache/index.mjs +91 -0
- package/dist/caching-GSDJcA6-.mjs +93 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DYhWBW_D.mjs +1096 -0
- package/dist/cli/commands/describe.d.mts +18 -0
- package/dist/cli/commands/describe.mjs +238 -0
- package/dist/cli/commands/docs.d.mts +13 -0
- package/dist/cli/commands/docs.mjs +52 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -2
- package/dist/cli/commands/generate.mjs +357 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +11 -8
- package/dist/cli/commands/{init.js → init.mjs} +807 -617
- package/dist/cli/commands/introspect.d.mts +10 -0
- package/dist/cli/commands/introspect.mjs +75 -0
- package/dist/cli/index.d.mts +16 -0
- package/dist/cli/index.mjs +156 -0
- package/dist/constants-DdXFXQtN.mjs +84 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-D2D5XXaV.mjs +559 -0
- package/dist/defineResource-PXzSJ15_.mjs +2197 -0
- package/dist/discovery/index.d.mts +46 -0
- package/dist/discovery/index.mjs +109 -0
- package/dist/docs/index.d.mts +162 -0
- package/dist/docs/index.mjs +74 -0
- package/dist/elevation-DGo5shaX.d.mts +87 -0
- package/dist/elevation-DSTbVvYj.mjs +113 -0
- package/dist/errorHandler-C3GY3_ow.mjs +108 -0
- package/dist/errorHandler-CW3OOeYq.d.mts +72 -0
- package/dist/errors-DAWRdiYP.d.mts +124 -0
- package/dist/errors-DBANPbGr.mjs +211 -0
- package/dist/eventPlugin-BEOvaDqo.mjs +229 -0
- package/dist/eventPlugin-H6wDDjGO.d.mts +124 -0
- package/dist/events/index.d.mts +53 -0
- package/dist/events/index.mjs +51 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +177 -0
- package/dist/events/transports/redis.d.mts +76 -0
- package/dist/events/transports/redis.mjs +124 -0
- package/dist/externalPaths-SyPF2tgK.d.mts +50 -0
- package/dist/factory/index.d.mts +63 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-C8DlE0YH.d.mts +216 -0
- package/dist/fields-Bi_AVKSo.d.mts +109 -0
- package/dist/fields-CTd_CrKr.mjs +114 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +96 -0
- package/dist/idempotency/index.mjs +319 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +114 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +103 -0
- package/dist/index.d.mts +260 -0
- package/dist/index.mjs +104 -0
- package/dist/integrations/event-gateway.d.mts +46 -0
- package/dist/integrations/event-gateway.mjs +43 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +103 -0
- package/dist/integrations/jobs.mjs +123 -0
- package/dist/integrations/streamline.d.mts +60 -0
- package/dist/integrations/streamline.mjs +125 -0
- package/dist/integrations/websocket.d.mts +82 -0
- package/dist/integrations/websocket.mjs +288 -0
- package/dist/interface-CSNjltAc.d.mts +77 -0
- package/dist/interface-DTbsvIWe.d.mts +54 -0
- package/dist/interface-e9XfSsUV.d.mts +1097 -0
- package/dist/introspectionPlugin-B3JkrjwU.mjs +53 -0
- package/dist/keys-DhqDRxv3.mjs +42 -0
- package/dist/logger-ByrvQWZO.mjs +78 -0
- package/dist/memory-B2v7KrCB.mjs +143 -0
- package/dist/migrations/index.d.mts +156 -0
- package/dist/migrations/index.mjs +260 -0
- package/dist/mongodb-ClykrfGo.d.mts +118 -0
- package/dist/mongodb-DNKEExbf.mjs +93 -0
- package/dist/mongodb-Dg8O_gvd.d.mts +71 -0
- package/dist/openapi-9nB_kiuR.mjs +525 -0
- package/dist/org/index.d.mts +68 -0
- package/dist/org/index.mjs +513 -0
- package/dist/org/types.d.mts +82 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +278 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/plugins/index.d.mts +172 -0
- package/dist/plugins/index.mjs +522 -0
- package/dist/plugins/response-cache.d.mts +87 -0
- package/dist/plugins/response-cache.mjs +283 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +185 -0
- package/dist/pluralize-CM-jZg7p.mjs +86 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -170
- package/dist/policies/index.mjs +321 -0
- package/dist/presets/{index.d.ts → index.d.mts} +62 -131
- package/dist/presets/index.mjs +143 -0
- package/dist/presets/multiTenant.d.mts +24 -0
- package/dist/presets/multiTenant.mjs +113 -0
- package/dist/presets-BTeYbw7h.d.mts +57 -0
- package/dist/presets-CeFtfDR8.mjs +119 -0
- package/dist/prisma-C3iornoK.d.mts +274 -0
- package/dist/prisma-DJbMt3yf.mjs +627 -0
- package/dist/queryCachePlugin-B6R0d4av.mjs +138 -0
- package/dist/queryCachePlugin-Q6SYuHZ6.d.mts +71 -0
- package/dist/redis-UwjEp8Ea.d.mts +49 -0
- package/dist/redis-stream-CBg0upHI.d.mts +103 -0
- package/dist/registry/index.d.mts +11 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-xi6OKBL-.mjs +55 -0
- package/dist/schemaConverter-Dtg0Kt9T.mjs +98 -0
- package/dist/schemas/index.d.mts +63 -0
- package/dist/schemas/index.mjs +82 -0
- package/dist/scope/index.d.mts +21 -0
- package/dist/scope/index.mjs +65 -0
- package/dist/sessionManager-D_iEHjQl.d.mts +186 -0
- package/dist/sse-DkqQ1uxb.mjs +123 -0
- package/dist/testing/index.d.mts +907 -0
- package/dist/testing/index.mjs +1976 -0
- package/dist/tracing-8CEbhF0w.d.mts +70 -0
- package/dist/typeGuards-DwxA1t_L.mjs +9 -0
- package/dist/types/index.d.mts +946 -0
- package/dist/types/index.mjs +14 -0
- package/dist/types-B0dhNrnd.d.mts +445 -0
- package/dist/types-Beqn1Un7.mjs +38 -0
- package/dist/types-DelU6kln.mjs +25 -0
- package/dist/types-RLkFVgaw.d.mts +101 -0
- package/dist/utils/index.d.mts +747 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
//#region src/hooks/HookSystem.ts
|
|
2
|
+
var HookSystem = class {
|
|
3
|
+
hooks;
|
|
4
|
+
logger;
|
|
5
|
+
warn;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.hooks = /* @__PURE__ */ new Map();
|
|
8
|
+
this.logger = options?.logger ?? { error: (...args) => console.error(...args) };
|
|
9
|
+
this.warn = options?.logger?.warn ?? ((...args) => console.warn(...args));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generate hook key
|
|
13
|
+
*/
|
|
14
|
+
getKey(resource, operation, phase) {
|
|
15
|
+
return `${resource}:${operation}:${phase}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Register a hook
|
|
19
|
+
* Supports both object parameter and positional arguments
|
|
20
|
+
*/
|
|
21
|
+
register(resourceOrOptions, operation, phase, handler, priority = 10) {
|
|
22
|
+
let hookName;
|
|
23
|
+
let resource;
|
|
24
|
+
let finalOperation;
|
|
25
|
+
let finalPhase;
|
|
26
|
+
let finalHandler;
|
|
27
|
+
let finalPriority;
|
|
28
|
+
let dependsOn;
|
|
29
|
+
if (typeof resourceOrOptions === "object") {
|
|
30
|
+
hookName = resourceOrOptions.name;
|
|
31
|
+
resource = resourceOrOptions.resource;
|
|
32
|
+
finalOperation = resourceOrOptions.operation;
|
|
33
|
+
finalPhase = resourceOrOptions.phase;
|
|
34
|
+
finalHandler = resourceOrOptions.handler;
|
|
35
|
+
finalPriority = resourceOrOptions.priority ?? 10;
|
|
36
|
+
dependsOn = resourceOrOptions.dependsOn;
|
|
37
|
+
} else {
|
|
38
|
+
resource = resourceOrOptions;
|
|
39
|
+
finalOperation = operation;
|
|
40
|
+
finalPhase = phase;
|
|
41
|
+
finalHandler = handler;
|
|
42
|
+
finalPriority = priority;
|
|
43
|
+
}
|
|
44
|
+
const key = this.getKey(resource, finalOperation, finalPhase);
|
|
45
|
+
if (!this.hooks.has(key)) this.hooks.set(key, []);
|
|
46
|
+
const registration = {
|
|
47
|
+
name: hookName,
|
|
48
|
+
resource,
|
|
49
|
+
operation: finalOperation,
|
|
50
|
+
phase: finalPhase,
|
|
51
|
+
handler: finalHandler,
|
|
52
|
+
priority: finalPriority,
|
|
53
|
+
dependsOn
|
|
54
|
+
};
|
|
55
|
+
const hooks = this.hooks.get(key);
|
|
56
|
+
hooks.push(registration);
|
|
57
|
+
hooks.sort((a, b) => a.priority - b.priority);
|
|
58
|
+
return () => {
|
|
59
|
+
const idx = hooks.indexOf(registration);
|
|
60
|
+
if (idx !== -1) hooks.splice(idx, 1);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Register before hook
|
|
65
|
+
*/
|
|
66
|
+
before(resource, operation, handler, priority = 10) {
|
|
67
|
+
return this.register(resource, operation, "before", handler, priority);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Register after hook
|
|
71
|
+
*/
|
|
72
|
+
after(resource, operation, handler, priority = 10) {
|
|
73
|
+
return this.register(resource, operation, "after", handler, priority);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Register around hook — wraps the core operation.
|
|
77
|
+
* Call `next()` inside the handler to proceed.
|
|
78
|
+
*/
|
|
79
|
+
around(resource, operation, handler, priority = 10) {
|
|
80
|
+
return this.register(resource, operation, "around", handler, priority);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Execute around hooks as a nested middleware chain.
|
|
84
|
+
* Each around hook receives `next()` to call the next hook or the core operation.
|
|
85
|
+
*/
|
|
86
|
+
async executeAround(resource, operation, data, execute, options) {
|
|
87
|
+
const key = this.getKey(resource, operation, "around");
|
|
88
|
+
const hooks = [...this.hooks.get(key) ?? []];
|
|
89
|
+
const wildcardKey = this.getKey("*", operation, "around");
|
|
90
|
+
const allHooks = [...this.hooks.get(wildcardKey) ?? [], ...hooks];
|
|
91
|
+
allHooks.sort((a, b) => a.priority - b.priority);
|
|
92
|
+
if (allHooks.length === 0) return execute();
|
|
93
|
+
let index = 0;
|
|
94
|
+
const next = async () => {
|
|
95
|
+
if (index < allHooks.length) {
|
|
96
|
+
const hook = allHooks[index++];
|
|
97
|
+
const ctx = {
|
|
98
|
+
resource,
|
|
99
|
+
operation,
|
|
100
|
+
phase: "around",
|
|
101
|
+
data,
|
|
102
|
+
user: options?.user,
|
|
103
|
+
context: options?.context,
|
|
104
|
+
meta: options?.meta
|
|
105
|
+
};
|
|
106
|
+
return hook.handler(ctx, next);
|
|
107
|
+
}
|
|
108
|
+
return execute();
|
|
109
|
+
};
|
|
110
|
+
return next();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Execute hooks for a given context
|
|
114
|
+
*/
|
|
115
|
+
async execute(ctx) {
|
|
116
|
+
const key = this.getKey(ctx.resource, ctx.operation, ctx.phase);
|
|
117
|
+
const hooks = this.hooks.get(key) ?? [];
|
|
118
|
+
const wildcardKey = this.getKey("*", ctx.operation, ctx.phase);
|
|
119
|
+
let allHooks = [...this.hooks.get(wildcardKey) ?? [], ...hooks];
|
|
120
|
+
allHooks.sort((a, b) => a.priority - b.priority);
|
|
121
|
+
if (allHooks.some((h) => h.dependsOn?.length)) allHooks = this.topologicalSort(allHooks);
|
|
122
|
+
let result = ctx.data;
|
|
123
|
+
for (const hook of allHooks) {
|
|
124
|
+
const handlerContext = {
|
|
125
|
+
resource: ctx.resource,
|
|
126
|
+
operation: ctx.operation,
|
|
127
|
+
phase: ctx.phase,
|
|
128
|
+
data: result,
|
|
129
|
+
result: ctx.result,
|
|
130
|
+
user: ctx.user,
|
|
131
|
+
context: ctx.context,
|
|
132
|
+
meta: ctx.meta
|
|
133
|
+
};
|
|
134
|
+
const hookResult = await hook.handler(handlerContext);
|
|
135
|
+
if (hookResult !== void 0 && hookResult !== null) result = hookResult;
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Execute before hooks
|
|
141
|
+
*/
|
|
142
|
+
async executeBefore(resource, operation, data, options) {
|
|
143
|
+
return await this.execute({
|
|
144
|
+
resource,
|
|
145
|
+
operation,
|
|
146
|
+
phase: "before",
|
|
147
|
+
data,
|
|
148
|
+
user: options?.user,
|
|
149
|
+
context: options?.context,
|
|
150
|
+
meta: options?.meta
|
|
151
|
+
}) ?? data;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Execute after hooks
|
|
155
|
+
* Errors in after hooks are logged but don't fail the request
|
|
156
|
+
*/
|
|
157
|
+
async executeAfter(resource, operation, result, options) {
|
|
158
|
+
try {
|
|
159
|
+
await this.execute({
|
|
160
|
+
resource,
|
|
161
|
+
operation,
|
|
162
|
+
phase: "after",
|
|
163
|
+
result,
|
|
164
|
+
user: options?.user,
|
|
165
|
+
context: options?.context,
|
|
166
|
+
meta: options?.meta
|
|
167
|
+
});
|
|
168
|
+
} catch (error) {
|
|
169
|
+
this.logger.error(`[HookSystem] Error in after hook for ${resource}:${operation}:`, error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Topological sort with Kahn's algorithm.
|
|
174
|
+
* Hooks with `dependsOn` are ordered after their dependencies.
|
|
175
|
+
* Within the same dependency level, priority ordering is preserved.
|
|
176
|
+
* Hooks without names or dependencies pass through in their original order.
|
|
177
|
+
*/
|
|
178
|
+
topologicalSort(hooks) {
|
|
179
|
+
const byName = /* @__PURE__ */ new Map();
|
|
180
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
181
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
182
|
+
for (const hook of hooks) {
|
|
183
|
+
inDegree.set(hook, 0);
|
|
184
|
+
if (hook.name) byName.set(hook.name, hook);
|
|
185
|
+
}
|
|
186
|
+
for (const hook of hooks) if (hook.dependsOn) {
|
|
187
|
+
let resolvedDeps = 0;
|
|
188
|
+
for (const dep of hook.dependsOn) if (byName.has(dep)) {
|
|
189
|
+
resolvedDeps++;
|
|
190
|
+
if (!dependents.has(dep)) dependents.set(dep, []);
|
|
191
|
+
dependents.get(dep).push(hook);
|
|
192
|
+
} else this.warn(`[HookSystem] Hook '${hook.name ?? "<unnamed>"}' depends on '${dep}' which is not registered in the same phase/resource. Dependency will be ignored.`);
|
|
193
|
+
inDegree.set(hook, resolvedDeps);
|
|
194
|
+
}
|
|
195
|
+
const queue = [];
|
|
196
|
+
const result = [];
|
|
197
|
+
for (const hook of hooks) if (inDegree.get(hook) === 0) queue.push(hook);
|
|
198
|
+
queue.sort((a, b) => a.priority - b.priority);
|
|
199
|
+
while (queue.length > 0) {
|
|
200
|
+
const current = queue.shift();
|
|
201
|
+
result.push(current);
|
|
202
|
+
if (current.name && dependents.has(current.name)) {
|
|
203
|
+
const deps = dependents.get(current.name);
|
|
204
|
+
for (const dep of deps) {
|
|
205
|
+
const newDegree = inDegree.get(dep) - 1;
|
|
206
|
+
inDegree.set(dep, newDegree);
|
|
207
|
+
if (newDegree === 0) {
|
|
208
|
+
const insertIdx = queue.findIndex((q) => q.priority > dep.priority);
|
|
209
|
+
if (insertIdx === -1) queue.push(dep);
|
|
210
|
+
else queue.splice(insertIdx, 0, dep);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (result.length < hooks.length) {
|
|
216
|
+
const missing = hooks.filter((h) => !result.includes(h));
|
|
217
|
+
const names = missing.map((h) => h.name ?? "<unnamed>").join(", ");
|
|
218
|
+
this.logger.error(`[HookSystem] Circular dependency detected in hooks: ${names}. These hooks will be appended in priority order.`);
|
|
219
|
+
missing.sort((a, b) => a.priority - b.priority);
|
|
220
|
+
result.push(...missing);
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get all registered hooks
|
|
226
|
+
*/
|
|
227
|
+
getAll() {
|
|
228
|
+
const all = [];
|
|
229
|
+
for (const hooks of this.hooks.values()) all.push(...hooks);
|
|
230
|
+
return all;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get hooks for a specific resource
|
|
234
|
+
*/
|
|
235
|
+
getForResource(resource) {
|
|
236
|
+
const all = [];
|
|
237
|
+
for (const [key, hooks] of this.hooks.entries()) if (key.startsWith(`${resource}:`)) all.push(...hooks);
|
|
238
|
+
return all;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get hooks matching filter criteria.
|
|
242
|
+
* Useful for debugging and testing specific hook combinations.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* // Find all before-create hooks for products (including wildcards)
|
|
247
|
+
* const hooks = hookSystem.getRegistered({
|
|
248
|
+
* resource: 'product',
|
|
249
|
+
* operation: 'create',
|
|
250
|
+
* phase: 'before',
|
|
251
|
+
* });
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
getRegistered(filter) {
|
|
255
|
+
let results = this.getAll();
|
|
256
|
+
if (filter?.resource) results = results.filter((h) => h.resource === filter.resource || h.resource === "*");
|
|
257
|
+
if (filter?.operation) results = results.filter((h) => h.operation === filter.operation);
|
|
258
|
+
if (filter?.phase) results = results.filter((h) => h.phase === filter.phase);
|
|
259
|
+
return results;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Get a structured summary of all registered hooks for debugging.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* const info = hookSystem.inspect();
|
|
267
|
+
* // { total: 12, resources: { product: [...], '*': [...] }, summary: [...] }
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
inspect() {
|
|
271
|
+
const all = this.getAll();
|
|
272
|
+
const byResource = /* @__PURE__ */ new Map();
|
|
273
|
+
for (const hook of all) {
|
|
274
|
+
const arr = byResource.get(hook.resource) ?? [];
|
|
275
|
+
arr.push(hook);
|
|
276
|
+
byResource.set(hook.resource, arr);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
total: all.length,
|
|
280
|
+
resources: Object.fromEntries(byResource),
|
|
281
|
+
summary: all.map((h) => ({
|
|
282
|
+
name: h.name,
|
|
283
|
+
key: `${h.resource}:${h.operation}:${h.phase}`,
|
|
284
|
+
priority: h.priority,
|
|
285
|
+
...h.dependsOn?.length ? { dependsOn: h.dependsOn } : {}
|
|
286
|
+
}))
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Check if any hooks exist for a specific resource/operation/phase combination.
|
|
291
|
+
*/
|
|
292
|
+
has(resource, operation, phase) {
|
|
293
|
+
const key = this.getKey(resource, operation, phase);
|
|
294
|
+
return (this.hooks.get(key)?.length ?? 0) > 0;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Clear all hooks
|
|
298
|
+
*/
|
|
299
|
+
clear() {
|
|
300
|
+
this.hooks.clear();
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Clear hooks for a specific resource
|
|
304
|
+
*/
|
|
305
|
+
clearResource(resource) {
|
|
306
|
+
for (const key of this.hooks.keys()) if (key.startsWith(`${resource}:`)) this.hooks.delete(key);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
/**
|
|
310
|
+
* Create a new isolated HookSystem instance
|
|
311
|
+
*
|
|
312
|
+
* Use this for:
|
|
313
|
+
* - Test isolation (parallel test suites)
|
|
314
|
+
* - Multiple app instances with independent hooks
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* const hooks = createHookSystem();
|
|
318
|
+
* await app.register(arcCorePlugin, { hookSystem: hooks });
|
|
319
|
+
*
|
|
320
|
+
* @example With custom logger
|
|
321
|
+
* const hooks = createHookSystem({ logger: fastify.log });
|
|
322
|
+
*/
|
|
323
|
+
function createHookSystem(options) {
|
|
324
|
+
return new HookSystem(options);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Define a named hook with optional dependencies.
|
|
328
|
+
* Returns a registration object — call `register(hookSystem)` to activate.
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```typescript
|
|
332
|
+
* const generateSlug = defineHook({
|
|
333
|
+
* name: 'generateSlug',
|
|
334
|
+
* resource: 'product', operation: 'create', phase: 'before',
|
|
335
|
+
* handler: (ctx) => ({ ...ctx.data, slug: slugify(ctx.data.name) }),
|
|
336
|
+
* });
|
|
337
|
+
*
|
|
338
|
+
* const validateUniqueSlug = defineHook({
|
|
339
|
+
* name: 'validateUniqueSlug',
|
|
340
|
+
* resource: 'product', operation: 'create', phase: 'before',
|
|
341
|
+
* dependsOn: ['generateSlug'],
|
|
342
|
+
* handler: async (ctx) => { // check uniqueness },
|
|
343
|
+
* });
|
|
344
|
+
*
|
|
345
|
+
* // Register on a hook system
|
|
346
|
+
* generateSlug.register(hooks);
|
|
347
|
+
* validateUniqueSlug.register(hooks);
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
function defineHook(options) {
|
|
351
|
+
return {
|
|
352
|
+
...options,
|
|
353
|
+
register(hooks) {
|
|
354
|
+
return hooks.register({
|
|
355
|
+
name: options.name,
|
|
356
|
+
resource: options.resource,
|
|
357
|
+
operation: options.operation,
|
|
358
|
+
phase: options.phase,
|
|
359
|
+
handler: options.handler,
|
|
360
|
+
priority: options.priority,
|
|
361
|
+
dependsOn: options.dependsOn
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Create a before-create hook registration for a given hook system
|
|
368
|
+
*/
|
|
369
|
+
function beforeCreate(hooks, resource, handler, priority = 10) {
|
|
370
|
+
return hooks.before(resource, "create", handler, priority);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Create an after-create hook registration for a given hook system
|
|
374
|
+
*/
|
|
375
|
+
function afterCreate(hooks, resource, handler, priority = 10) {
|
|
376
|
+
return hooks.after(resource, "create", handler, priority);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Create a before-update hook registration for a given hook system
|
|
380
|
+
*/
|
|
381
|
+
function beforeUpdate(hooks, resource, handler, priority = 10) {
|
|
382
|
+
return hooks.before(resource, "update", handler, priority);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Create an after-update hook registration for a given hook system
|
|
386
|
+
*/
|
|
387
|
+
function afterUpdate(hooks, resource, handler, priority = 10) {
|
|
388
|
+
return hooks.after(resource, "update", handler, priority);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Create a before-delete hook registration for a given hook system
|
|
392
|
+
*/
|
|
393
|
+
function beforeDelete(hooks, resource, handler, priority = 10) {
|
|
394
|
+
return hooks.before(resource, "delete", handler, priority);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Create an after-delete hook registration for a given hook system
|
|
398
|
+
*/
|
|
399
|
+
function afterDelete(hooks, resource, handler, priority = 10) {
|
|
400
|
+
return hooks.after(resource, "delete", handler, priority);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
//#endregion
|
|
404
|
+
export { beforeCreate as a, createHookSystem as c, afterUpdate as i, defineHook as l, afterCreate as n, beforeDelete as o, afterDelete as r, beforeUpdate as s, HookSystem as t };
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-DdXFXQtN.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/registry/ResourceRegistry.ts
|
|
4
|
+
var ResourceRegistry = class {
|
|
5
|
+
_resources;
|
|
6
|
+
_frozen;
|
|
7
|
+
constructor() {
|
|
8
|
+
this._resources = /* @__PURE__ */ new Map();
|
|
9
|
+
this._frozen = false;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Register a resource
|
|
13
|
+
*/
|
|
14
|
+
register(resource, options = {}) {
|
|
15
|
+
if (this._frozen) throw new Error(`Registry frozen. Cannot register '${resource.name}' after startup.`);
|
|
16
|
+
if (this._resources.has(resource.name)) throw new Error(`Resource '${resource.name}' already registered.`);
|
|
17
|
+
const entry = {
|
|
18
|
+
name: resource.name,
|
|
19
|
+
displayName: resource.displayName,
|
|
20
|
+
tag: resource.tag,
|
|
21
|
+
prefix: resource.prefix,
|
|
22
|
+
module: options.module ?? void 0,
|
|
23
|
+
adapter: resource.adapter ? {
|
|
24
|
+
type: resource.adapter.type,
|
|
25
|
+
name: resource.adapter.name
|
|
26
|
+
} : null,
|
|
27
|
+
permissions: resource.permissions,
|
|
28
|
+
presets: resource._appliedPresets ?? [],
|
|
29
|
+
routes: [],
|
|
30
|
+
additionalRoutes: resource.additionalRoutes.map((r) => ({
|
|
31
|
+
method: r.method,
|
|
32
|
+
path: r.path,
|
|
33
|
+
handler: typeof r.handler === "string" ? r.handler : r.handler.name || "anonymous",
|
|
34
|
+
operation: r.operation,
|
|
35
|
+
summary: r.summary,
|
|
36
|
+
description: r.description,
|
|
37
|
+
permissions: r.permissions,
|
|
38
|
+
wrapHandler: r.wrapHandler,
|
|
39
|
+
schema: r.schema
|
|
40
|
+
})),
|
|
41
|
+
events: Object.keys(resource.events ?? {}),
|
|
42
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
43
|
+
disableDefaultRoutes: resource.disableDefaultRoutes,
|
|
44
|
+
updateMethod: resource.updateMethod,
|
|
45
|
+
disabledRoutes: resource.disabledRoutes,
|
|
46
|
+
openApiSchemas: options.openApiSchemas,
|
|
47
|
+
fieldPermissions: extractFieldPermissions(resource.fields),
|
|
48
|
+
pipelineSteps: extractPipelineSteps(resource.pipe),
|
|
49
|
+
rateLimit: resource.rateLimit,
|
|
50
|
+
plugin: resource.toPlugin()
|
|
51
|
+
};
|
|
52
|
+
this._resources.set(resource.name, entry);
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get resource by name
|
|
57
|
+
*/
|
|
58
|
+
get(name) {
|
|
59
|
+
return this._resources.get(name);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get all resources
|
|
63
|
+
*/
|
|
64
|
+
getAll() {
|
|
65
|
+
return Array.from(this._resources.values());
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get resources by module
|
|
69
|
+
*/
|
|
70
|
+
getByModule(moduleName) {
|
|
71
|
+
return this.getAll().filter((r) => r.module === moduleName);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get resources by preset
|
|
75
|
+
*/
|
|
76
|
+
getByPreset(presetName) {
|
|
77
|
+
return this.getAll().filter((r) => r.presets.includes(presetName));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if resource exists
|
|
81
|
+
*/
|
|
82
|
+
has(name) {
|
|
83
|
+
return this._resources.has(name);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get registry statistics
|
|
87
|
+
*/
|
|
88
|
+
getStats() {
|
|
89
|
+
const resources = this.getAll();
|
|
90
|
+
const presetCounts = {};
|
|
91
|
+
for (const r of resources) for (const preset of r.presets) presetCounts[preset] = (presetCounts[preset] ?? 0) + 1;
|
|
92
|
+
return {
|
|
93
|
+
totalResources: resources.length,
|
|
94
|
+
byModule: this._groupBy(resources, "module"),
|
|
95
|
+
presetUsage: presetCounts,
|
|
96
|
+
totalRoutes: resources.reduce((sum, r) => {
|
|
97
|
+
if (r.disableDefaultRoutes) return sum + (r.additionalRoutes?.length ?? 0);
|
|
98
|
+
const disabledSet = new Set(r.disabledRoutes ?? []);
|
|
99
|
+
let defaultCount = CRUD_OPERATIONS.filter((route) => !disabledSet.has(route)).length;
|
|
100
|
+
if (!disabledSet.has("update") && r.updateMethod === "both") defaultCount += 1;
|
|
101
|
+
return sum + defaultCount + (r.additionalRoutes?.length ?? 0);
|
|
102
|
+
}, 0),
|
|
103
|
+
totalEvents: resources.reduce((sum, r) => sum + (r.events?.length ?? 0), 0)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get full introspection data
|
|
108
|
+
*/
|
|
109
|
+
getIntrospection() {
|
|
110
|
+
return {
|
|
111
|
+
resources: this.getAll().map((r) => {
|
|
112
|
+
const disabledSet = new Set(r.disabledRoutes ?? []);
|
|
113
|
+
const updateMethod = r.updateMethod ?? DEFAULT_UPDATE_METHOD;
|
|
114
|
+
const defaultRoutes = r.disableDefaultRoutes ? [] : [
|
|
115
|
+
...!disabledSet.has("list") ? [{
|
|
116
|
+
method: "GET",
|
|
117
|
+
path: r.prefix,
|
|
118
|
+
operation: "list"
|
|
119
|
+
}] : [],
|
|
120
|
+
...!disabledSet.has("get") ? [{
|
|
121
|
+
method: "GET",
|
|
122
|
+
path: `${r.prefix}/:id`,
|
|
123
|
+
operation: "get"
|
|
124
|
+
}] : [],
|
|
125
|
+
...!disabledSet.has("create") ? [{
|
|
126
|
+
method: "POST",
|
|
127
|
+
path: r.prefix,
|
|
128
|
+
operation: "create"
|
|
129
|
+
}] : [],
|
|
130
|
+
...!disabledSet.has("update") ? updateMethod === "both" ? [{
|
|
131
|
+
method: "PUT",
|
|
132
|
+
path: `${r.prefix}/:id`,
|
|
133
|
+
operation: "update"
|
|
134
|
+
}, {
|
|
135
|
+
method: "PATCH",
|
|
136
|
+
path: `${r.prefix}/:id`,
|
|
137
|
+
operation: "update"
|
|
138
|
+
}] : [{
|
|
139
|
+
method: updateMethod,
|
|
140
|
+
path: `${r.prefix}/:id`,
|
|
141
|
+
operation: "update"
|
|
142
|
+
}] : [],
|
|
143
|
+
...!disabledSet.has("delete") ? [{
|
|
144
|
+
method: "DELETE",
|
|
145
|
+
path: `${r.prefix}/:id`,
|
|
146
|
+
operation: "delete"
|
|
147
|
+
}] : []
|
|
148
|
+
];
|
|
149
|
+
return {
|
|
150
|
+
name: r.name,
|
|
151
|
+
displayName: r.displayName,
|
|
152
|
+
prefix: r.prefix,
|
|
153
|
+
module: r.module,
|
|
154
|
+
presets: r.presets,
|
|
155
|
+
permissions: r.permissions,
|
|
156
|
+
routes: [...defaultRoutes, ...r.additionalRoutes?.map((ar) => ({
|
|
157
|
+
method: ar.method,
|
|
158
|
+
path: `${r.prefix}${ar.path}`,
|
|
159
|
+
operation: ar.operation ?? (typeof ar.handler === "string" ? ar.handler : "custom"),
|
|
160
|
+
handler: typeof ar.handler === "string" ? ar.handler : void 0,
|
|
161
|
+
summary: ar.summary
|
|
162
|
+
})) ?? []],
|
|
163
|
+
events: r.events
|
|
164
|
+
};
|
|
165
|
+
}),
|
|
166
|
+
stats: this.getStats(),
|
|
167
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Freeze registry (prevent further registrations)
|
|
172
|
+
*/
|
|
173
|
+
freeze() {
|
|
174
|
+
this._frozen = true;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Check if frozen
|
|
178
|
+
*/
|
|
179
|
+
isFrozen() {
|
|
180
|
+
return this._frozen;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Unfreeze registry (allow new registrations)
|
|
184
|
+
*/
|
|
185
|
+
unfreeze() {
|
|
186
|
+
this._frozen = false;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Reset registry — clear all resources and unfreeze
|
|
190
|
+
*/
|
|
191
|
+
reset() {
|
|
192
|
+
this._resources.clear();
|
|
193
|
+
this._frozen = false;
|
|
194
|
+
}
|
|
195
|
+
/** @internal Alias for unfreeze() */
|
|
196
|
+
_unfreeze() {
|
|
197
|
+
this.unfreeze();
|
|
198
|
+
}
|
|
199
|
+
/** @internal Alias for reset() */
|
|
200
|
+
_clear() {
|
|
201
|
+
this.reset();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Group by key
|
|
205
|
+
*/
|
|
206
|
+
_groupBy(arr, key) {
|
|
207
|
+
const result = {};
|
|
208
|
+
for (const item of arr) {
|
|
209
|
+
const k = String(item[key] ?? "uncategorized");
|
|
210
|
+
result[k] = (result[k] ?? 0) + 1;
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
function extractFieldPermissions(fields) {
|
|
216
|
+
if (!fields || Object.keys(fields).length === 0) return void 0;
|
|
217
|
+
const result = {};
|
|
218
|
+
for (const [field, perm] of Object.entries(fields)) {
|
|
219
|
+
const entry = { type: perm._type };
|
|
220
|
+
if (perm.roles?.length) entry.roles = perm.roles;
|
|
221
|
+
if (perm.redactValue !== void 0) entry.redactValue = perm.redactValue;
|
|
222
|
+
result[field] = entry;
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
function extractPipelineSteps(pipe) {
|
|
227
|
+
if (!pipe) return void 0;
|
|
228
|
+
const steps = [];
|
|
229
|
+
if (Array.isArray(pipe)) steps.push(...pipe);
|
|
230
|
+
else {
|
|
231
|
+
const seen = /* @__PURE__ */ new Set();
|
|
232
|
+
for (const opSteps of Object.values(pipe)) if (Array.isArray(opSteps)) for (const step of opSteps) {
|
|
233
|
+
const key = `${step._type}:${step.name}`;
|
|
234
|
+
if (!seen.has(key)) {
|
|
235
|
+
seen.add(key);
|
|
236
|
+
steps.push(step);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (steps.length === 0) return void 0;
|
|
241
|
+
return steps.map((s) => ({
|
|
242
|
+
type: s._type,
|
|
243
|
+
name: s.name,
|
|
244
|
+
operations: s.operations ? [...s.operations] : void 0
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
//#endregion
|
|
249
|
+
export { ResourceRegistry as t };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import "../elevation-DGo5shaX.mjs";
|
|
2
|
+
import { a as RepositoryLike, i as RelationMetadata, n as DataAdapter, o as SchemaMetadata, r as FieldMetadata, s as ValidationResult, t as AdapterFactory } from "../interface-e9XfSsUV.mjs";
|
|
3
|
+
import "../types-RLkFVgaw.mjs";
|
|
4
|
+
import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter } from "../prisma-C3iornoK.mjs";
|
|
5
|
+
export { type AdapterFactory, type DataAdapter, type FieldMetadata, MongooseAdapter, type MongooseAdapterOptions, PrismaAdapter, type PrismaAdapterOptions, type PrismaQueryOptions, PrismaQueryParser, type PrismaQueryParserOptions, type RelationMetadata, type RepositoryLike, type SchemaMetadata, type ValidationResult, createMongooseAdapter, createPrismaAdapter };
|