@classytic/arc 2.3.0 → 2.4.2
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 +187 -18
- package/bin/arc.js +11 -3
- package/dist/BaseController-CkM5dUh_.mjs +1031 -0
- package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
- package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
- package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
- package/dist/adapters/index.d.mts +3 -5
- package/dist/adapters/index.mjs +2 -3
- package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
- package/dist/audit/index.d.mts +4 -7
- package/dist/audit/index.mjs +2 -29
- package/dist/audit/mongodb.d.mts +1 -4
- package/dist/audit/mongodb.mjs +2 -3
- package/dist/auth/index.d.mts +7 -9
- package/dist/auth/index.mjs +65 -63
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/auth/redis-session.mjs +1 -2
- package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
- package/dist/cache/index.d.mts +23 -23
- package/dist/cache/index.mjs +4 -6
- package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
- package/dist/chunk-BpYLSNr0.mjs +14 -0
- package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
- package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
- package/dist/cli/commands/describe.mjs +24 -7
- package/dist/cli/commands/docs.mjs +6 -7
- package/dist/cli/commands/doctor.d.mts +10 -0
- package/dist/cli/commands/doctor.mjs +156 -0
- package/dist/cli/commands/generate.mjs +66 -17
- package/dist/cli/commands/init.mjs +315 -45
- package/dist/cli/commands/introspect.mjs +2 -4
- package/dist/cli/index.d.mts +1 -10
- package/dist/cli/index.mjs +4 -153
- package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
- package/dist/core/index.d.mts +3 -5
- package/dist/core/index.mjs +5 -4
- package/dist/core-C1XCMtqM.mjs +185 -0
- package/dist/{createApp-CgKOPhA4.mjs → createApp-ByWNRsZj.mjs} +64 -35
- package/dist/{defineResource-DWbpJYtm.mjs → defineResource-D9aY5Cy6.mjs} +108 -1157
- package/dist/discovery/index.mjs +37 -5
- package/dist/docs/index.d.mts +6 -9
- package/dist/docs/index.mjs +3 -21
- package/dist/dynamic/index.d.mts +93 -0
- package/dist/dynamic/index.mjs +122 -0
- package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
- package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
- package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
- package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
- package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
- package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
- package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
- package/dist/events/index.d.mts +72 -7
- package/dist/events/index.mjs +216 -4
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +19 -7
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +3 -4
- package/dist/factory/index.d.mts +23 -9
- package/dist/factory/index.mjs +48 -3
- package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
- package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
- package/dist/hooks/index.d.mts +1 -3
- package/dist/hooks/index.mjs +2 -3
- package/dist/idempotency/index.d.mts +5 -5
- package/dist/idempotency/index.mjs +3 -7
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/mongodb.mjs +4 -5
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +2 -5
- package/dist/{fastifyAdapter-6b_eRDBw.d.mts → index-BL8CaQih.d.mts} +56 -57
- package/dist/index-Diqcm14c.d.mts +369 -0
- package/dist/{prisma-Dy5S5F5i.d.mts → index-yhxyjqNb.d.mts} +4 -5
- package/dist/index.d.mts +100 -105
- package/dist/index.mjs +85 -58
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +8 -4
- package/dist/integrations/index.d.mts +4 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs.d.mts +2 -2
- package/dist/integrations/jobs.mjs +63 -14
- package/dist/integrations/mcp/index.d.mts +219 -0
- package/dist/integrations/mcp/index.mjs +572 -0
- package/dist/integrations/mcp/testing.d.mts +53 -0
- package/dist/integrations/mcp/testing.mjs +104 -0
- package/dist/integrations/streamline.mjs +39 -19
- package/dist/integrations/webhooks.d.mts +56 -0
- package/dist/integrations/webhooks.mjs +139 -0
- package/dist/integrations/websocket-redis.d.mts +46 -0
- package/dist/integrations/websocket-redis.mjs +50 -0
- package/dist/integrations/websocket.d.mts +68 -2
- package/dist/integrations/websocket.mjs +96 -13
- package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
- package/dist/interface-DGmPxakH.d.mts +2213 -0
- package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
- package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
- package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
- package/dist/metrics-Csh4nsvv.mjs +224 -0
- package/dist/migrations/index.d.mts +113 -44
- package/dist/migrations/index.mjs +84 -102
- package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
- package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
- package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
- package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
- package/dist/org/index.d.mts +12 -14
- package/dist/org/index.mjs +92 -119
- package/dist/org/types.d.mts +2 -2
- package/dist/org/types.mjs +1 -1
- package/dist/permissions/index.d.mts +4 -278
- package/dist/permissions/index.mjs +4 -579
- package/dist/permissions-CA5zg0yK.mjs +751 -0
- package/dist/plugins/index.d.mts +104 -107
- package/dist/plugins/index.mjs +203 -313
- package/dist/plugins/response-cache.mjs +4 -69
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +24 -11
- package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
- package/dist/policies/index.d.mts +2 -2
- package/dist/policies/index.mjs +80 -83
- package/dist/presets/index.d.mts +26 -19
- package/dist/presets/index.mjs +2 -142
- package/dist/presets/multiTenant.d.mts +1 -4
- package/dist/presets/multiTenant.mjs +4 -6
- package/dist/presets-C9QXJV1u.mjs +422 -0
- package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
- package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
- package/dist/queryParser-CgCtsjti.mjs +352 -0
- package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
- package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
- package/dist/registry/index.d.mts +1 -4
- package/dist/registry/index.mjs +3 -4
- package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
- package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
- package/dist/resourceToTools-PMFE8HIv.mjs +533 -0
- package/dist/rpc/index.d.mts +90 -0
- package/dist/rpc/index.mjs +248 -0
- package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
- package/dist/schemas/index.d.mts +30 -30
- package/dist/schemas/index.mjs +2 -4
- package/dist/scope/index.d.mts +13 -2
- package/dist/scope/index.mjs +18 -5
- package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
- package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
- package/dist/testing/index.d.mts +551 -567
- package/dist/testing/index.mjs +1744 -1799
- package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
- package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
- package/dist/types/index.d.mts +4 -946
- package/dist/types/index.mjs +2 -4
- package/dist/types-BJmgxNbF.d.mts +275 -0
- package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
- package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
- package/dist/{types-tKwaViYB.d.mts → types-Dt0-AI6E.d.mts} +68 -27
- package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
- package/dist/utils/index.d.mts +254 -351
- package/dist/utils/index.mjs +7 -6
- package/dist/utils-Dc0WhlIl.mjs +594 -0
- package/dist/versioning-BzfeHmhj.mjs +37 -0
- package/package.json +44 -10
- package/skills/arc/SKILL.md +518 -0
- package/skills/arc/references/auth.md +250 -0
- package/skills/arc/references/events.md +272 -0
- package/skills/arc/references/integrations.md +385 -0
- package/skills/arc/references/mcp.md +431 -0
- package/skills/arc/references/production.md +610 -0
- package/skills/arc/references/testing.md +183 -0
- package/dist/audited-CGdLiSlE.mjs +0 -140
- package/dist/chunk-C7Uep-_p.mjs +0 -20
- package/dist/circuitBreaker-CSS2VvL6.mjs +0 -1109
- package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
- package/dist/interface-BtdYtQUA.d.mts +0 -1114
- package/dist/presets-BTeYbw7h.d.mts +0 -57
- package/dist/presets-CeFtfDR8.mjs +0 -119
- /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
- /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
- /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { n as PUBLIC_SCOPE, u as isElevated } from "./types-C6TQjtdi.mjs";
|
|
2
|
+
import multiTenantPreset from "./presets/multiTenant.mjs";
|
|
3
|
+
import { d as requireRoles, n as allowPublic, s as requireAuth } from "./permissions-CA5zg0yK.mjs";
|
|
4
|
+
//#region src/presets/ownedByUser.ts
|
|
5
|
+
/**
|
|
6
|
+
* Create ownership check middleware.
|
|
7
|
+
* Elevated scope (platform admin) bypasses ownership checks.
|
|
8
|
+
*/
|
|
9
|
+
function createOwnershipCheck(ownerField) {
|
|
10
|
+
return async (request, _reply) => {
|
|
11
|
+
const user = request.user;
|
|
12
|
+
if (!user) return;
|
|
13
|
+
if (isElevated(request.scope ?? PUBLIC_SCOPE)) return;
|
|
14
|
+
const userWithId = user;
|
|
15
|
+
const userId = userWithId._id ?? userWithId.id;
|
|
16
|
+
if (userId) request._ownershipCheck = {
|
|
17
|
+
field: ownerField,
|
|
18
|
+
userId
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function ownedByUserPreset(options = {}) {
|
|
23
|
+
const { ownerField = "userId" } = options;
|
|
24
|
+
const ownershipMiddleware = createOwnershipCheck(ownerField);
|
|
25
|
+
return {
|
|
26
|
+
name: "ownedByUser",
|
|
27
|
+
middlewares: {
|
|
28
|
+
update: [ownershipMiddleware],
|
|
29
|
+
delete: [ownershipMiddleware]
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/presets/slugLookup.ts
|
|
35
|
+
/**
|
|
36
|
+
* Slug Lookup Preset
|
|
37
|
+
*
|
|
38
|
+
* Adds a route to get resource by slug.
|
|
39
|
+
*/
|
|
40
|
+
function slugLookupPreset(options = {}) {
|
|
41
|
+
const { slugField = "slug" } = options;
|
|
42
|
+
return {
|
|
43
|
+
name: "slugLookup",
|
|
44
|
+
additionalRoutes: (permissions) => [{
|
|
45
|
+
method: "GET",
|
|
46
|
+
path: `/slug/:${slugField}`,
|
|
47
|
+
handler: "getBySlug",
|
|
48
|
+
summary: "Get by slug",
|
|
49
|
+
permissions: permissions.get ?? allowPublic(),
|
|
50
|
+
wrapHandler: true,
|
|
51
|
+
operation: "getBySlug"
|
|
52
|
+
}],
|
|
53
|
+
controllerOptions: { slugField }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/presets/softDelete.ts
|
|
58
|
+
/**
|
|
59
|
+
* Soft Delete Preset
|
|
60
|
+
*
|
|
61
|
+
* Adds routes for listing deleted items and restoring them.
|
|
62
|
+
* The actual soft-delete behavior (deletedAt field, query filtering)
|
|
63
|
+
* is handled by the repository/adapter layer (e.g., MongoKit's softDelete plugin).
|
|
64
|
+
*/
|
|
65
|
+
function softDeletePreset() {
|
|
66
|
+
return {
|
|
67
|
+
name: "softDelete",
|
|
68
|
+
additionalRoutes: (permissions) => [{
|
|
69
|
+
method: "GET",
|
|
70
|
+
path: "/deleted",
|
|
71
|
+
handler: "getDeleted",
|
|
72
|
+
summary: "Get soft-deleted items",
|
|
73
|
+
permissions: permissions.list ?? requireRoles(["admin"]),
|
|
74
|
+
wrapHandler: true,
|
|
75
|
+
operation: "listDeleted"
|
|
76
|
+
}, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
path: "/:id/restore",
|
|
79
|
+
handler: "restore",
|
|
80
|
+
summary: "Restore soft-deleted item",
|
|
81
|
+
permissions: permissions.update ?? requireRoles(["admin"]),
|
|
82
|
+
wrapHandler: true,
|
|
83
|
+
operation: "restore"
|
|
84
|
+
}]
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/presets/audited.ts
|
|
89
|
+
/**
|
|
90
|
+
* Audited preset - adds createdBy/updatedBy tracking
|
|
91
|
+
*/
|
|
92
|
+
function auditedPreset(options = {}) {
|
|
93
|
+
const { createdByField = "createdBy", updatedByField = "updatedBy" } = options;
|
|
94
|
+
const injectCreatedBy = async (request, _reply) => {
|
|
95
|
+
const userWithId = request.user;
|
|
96
|
+
if (userWithId?._id || userWithId?.id) {
|
|
97
|
+
const userId = userWithId._id ?? userWithId.id;
|
|
98
|
+
request.body[createdByField] = userId;
|
|
99
|
+
request.body[updatedByField] = userId;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const injectUpdatedBy = async (request, _reply) => {
|
|
103
|
+
const userWithId = request.user;
|
|
104
|
+
if (userWithId?._id || userWithId?.id) request.body[updatedByField] = userWithId._id ?? userWithId.id;
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
name: "audited",
|
|
108
|
+
schemaOptions: { fieldRules: {
|
|
109
|
+
[createdByField]: { systemManaged: true },
|
|
110
|
+
[updatedByField]: { systemManaged: true },
|
|
111
|
+
createdAt: { systemManaged: true },
|
|
112
|
+
updatedAt: { systemManaged: true }
|
|
113
|
+
} },
|
|
114
|
+
middlewares: {
|
|
115
|
+
create: [injectCreatedBy],
|
|
116
|
+
update: [injectUpdatedBy]
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/presets/bulk.ts
|
|
122
|
+
/**
|
|
123
|
+
* Bulk Operations Preset
|
|
124
|
+
*
|
|
125
|
+
* Adds bulk CRUD routes to a resource:
|
|
126
|
+
* - POST /{resource}/bulk → bulkCreate
|
|
127
|
+
* - PATCH /{resource}/bulk → bulkUpdate
|
|
128
|
+
* - DELETE /{resource}/bulk → bulkDelete
|
|
129
|
+
*
|
|
130
|
+
* Permissions inherit from the resource's create/update/delete permissions.
|
|
131
|
+
* Handlers delegate to BaseController.bulkCreate/bulkUpdate/bulkDelete,
|
|
132
|
+
* which call the repository's createMany/updateMany/deleteMany.
|
|
133
|
+
*
|
|
134
|
+
* DB-agnostic — works with any repository that provides these methods.
|
|
135
|
+
* MongoKit provides them via batchOperationsPlugin.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* defineResource({
|
|
140
|
+
* name: 'product',
|
|
141
|
+
* adapter: createMongooseAdapter({ model, repository }),
|
|
142
|
+
* presets: ['bulk'],
|
|
143
|
+
* });
|
|
144
|
+
* // Adds: POST /products/bulk, PATCH /products/bulk, DELETE /products/bulk
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
function bulkPreset(opts) {
|
|
148
|
+
const operations = opts?.operations ?? [
|
|
149
|
+
"createMany",
|
|
150
|
+
"updateMany",
|
|
151
|
+
"deleteMany"
|
|
152
|
+
];
|
|
153
|
+
const maxCreateItems = opts?.maxCreateItems ?? 1e3;
|
|
154
|
+
return {
|
|
155
|
+
name: "bulk",
|
|
156
|
+
additionalRoutes: (permissions) => {
|
|
157
|
+
const routes = [];
|
|
158
|
+
if (operations.includes("createMany")) routes.push({
|
|
159
|
+
method: "POST",
|
|
160
|
+
path: "/bulk",
|
|
161
|
+
handler: "bulkCreate",
|
|
162
|
+
wrapHandler: true,
|
|
163
|
+
operation: "bulkCreate",
|
|
164
|
+
summary: "Create multiple items",
|
|
165
|
+
permissions: permissions.create ?? requireAuth(),
|
|
166
|
+
schema: {
|
|
167
|
+
body: {
|
|
168
|
+
type: "object",
|
|
169
|
+
properties: { items: {
|
|
170
|
+
type: "array",
|
|
171
|
+
maxItems: maxCreateItems,
|
|
172
|
+
minItems: 1
|
|
173
|
+
} },
|
|
174
|
+
required: ["items"]
|
|
175
|
+
},
|
|
176
|
+
response: { 201: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {
|
|
179
|
+
success: { type: "boolean" },
|
|
180
|
+
data: { type: "array" },
|
|
181
|
+
meta: {
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: { count: { type: "number" } }
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} }
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
if (operations.includes("updateMany")) routes.push({
|
|
190
|
+
method: "PATCH",
|
|
191
|
+
path: "/bulk",
|
|
192
|
+
handler: "bulkUpdate",
|
|
193
|
+
wrapHandler: true,
|
|
194
|
+
operation: "bulkUpdate",
|
|
195
|
+
summary: "Update multiple items matching filter",
|
|
196
|
+
permissions: permissions.update ?? requireAuth(),
|
|
197
|
+
schema: {
|
|
198
|
+
body: {
|
|
199
|
+
type: "object",
|
|
200
|
+
properties: {
|
|
201
|
+
filter: { type: "object" },
|
|
202
|
+
data: { type: "object" }
|
|
203
|
+
},
|
|
204
|
+
required: ["filter", "data"]
|
|
205
|
+
},
|
|
206
|
+
response: { 200: {
|
|
207
|
+
type: "object",
|
|
208
|
+
properties: {
|
|
209
|
+
success: { type: "boolean" },
|
|
210
|
+
data: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
matchedCount: { type: "number" },
|
|
214
|
+
modifiedCount: { type: "number" }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} }
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
if (operations.includes("deleteMany")) routes.push({
|
|
222
|
+
method: "DELETE",
|
|
223
|
+
path: "/bulk",
|
|
224
|
+
handler: "bulkDelete",
|
|
225
|
+
wrapHandler: true,
|
|
226
|
+
operation: "bulkDelete",
|
|
227
|
+
summary: "Delete multiple items matching filter",
|
|
228
|
+
permissions: permissions.delete ?? requireAuth(),
|
|
229
|
+
schema: {
|
|
230
|
+
body: {
|
|
231
|
+
type: "object",
|
|
232
|
+
properties: { filter: { type: "object" } },
|
|
233
|
+
required: ["filter"]
|
|
234
|
+
},
|
|
235
|
+
response: { 200: {
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
success: { type: "boolean" },
|
|
239
|
+
data: {
|
|
240
|
+
type: "object",
|
|
241
|
+
properties: { deletedCount: { type: "number" } }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} }
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
return routes;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/presets/tree.ts
|
|
253
|
+
/**
|
|
254
|
+
* Tree Preset
|
|
255
|
+
*
|
|
256
|
+
* Adds routes for hierarchical tree structures.
|
|
257
|
+
*/
|
|
258
|
+
function treePreset(options = {}) {
|
|
259
|
+
const { parentField = "parent" } = options;
|
|
260
|
+
return {
|
|
261
|
+
name: "tree",
|
|
262
|
+
additionalRoutes: (permissions) => [{
|
|
263
|
+
method: "GET",
|
|
264
|
+
path: "/tree",
|
|
265
|
+
handler: "getTree",
|
|
266
|
+
summary: "Get hierarchical tree",
|
|
267
|
+
permissions: permissions.list ?? allowPublic(),
|
|
268
|
+
wrapHandler: true,
|
|
269
|
+
operation: "getTree"
|
|
270
|
+
}, {
|
|
271
|
+
method: "GET",
|
|
272
|
+
path: `/:${parentField}/children`,
|
|
273
|
+
handler: "getChildren",
|
|
274
|
+
summary: "Get children of parent",
|
|
275
|
+
permissions: permissions.list ?? allowPublic(),
|
|
276
|
+
wrapHandler: true,
|
|
277
|
+
operation: "getChildren"
|
|
278
|
+
}],
|
|
279
|
+
controllerOptions: { parentField }
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region src/presets/index.ts
|
|
284
|
+
/**
|
|
285
|
+
* Convenience alias for multiTenantPreset with public list/get routes
|
|
286
|
+
* Equivalent to: multiTenantPreset({ allowPublic: ['list', 'get'] })
|
|
287
|
+
*/
|
|
288
|
+
const flexibleMultiTenantPreset = (options = {}) => multiTenantPreset({
|
|
289
|
+
...options,
|
|
290
|
+
allowPublic: ["list", "get"]
|
|
291
|
+
});
|
|
292
|
+
const presetRegistry = {
|
|
293
|
+
softDelete: softDeletePreset,
|
|
294
|
+
slugLookup: slugLookupPreset,
|
|
295
|
+
ownedByUser: ownedByUserPreset,
|
|
296
|
+
multiTenant: multiTenantPreset,
|
|
297
|
+
tree: treePreset,
|
|
298
|
+
audited: auditedPreset,
|
|
299
|
+
bulk: bulkPreset
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Get preset by name with options
|
|
303
|
+
*/
|
|
304
|
+
function getPreset(nameOrConfig) {
|
|
305
|
+
if (typeof nameOrConfig === "object" && nameOrConfig.name) {
|
|
306
|
+
const { name, ...options } = nameOrConfig;
|
|
307
|
+
return resolvePreset(name, options);
|
|
308
|
+
}
|
|
309
|
+
return resolvePreset(nameOrConfig);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Resolve preset by name
|
|
313
|
+
*/
|
|
314
|
+
function resolvePreset(name, options = {}) {
|
|
315
|
+
const factory = presetRegistry[name];
|
|
316
|
+
if (!factory) {
|
|
317
|
+
const available = Object.keys(presetRegistry).join(", ");
|
|
318
|
+
throw new Error(`Unknown preset: '${name}'\nAvailable presets: ${available}\nDocs: https://github.com/classytic/arc#presets`);
|
|
319
|
+
}
|
|
320
|
+
return factory(options);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Register a custom preset
|
|
324
|
+
*/
|
|
325
|
+
function registerPreset(name, factory, options) {
|
|
326
|
+
if (presetRegistry[name] && !options?.override) throw new Error(`Preset '${name}' already exists. Pass { override: true } to replace.`);
|
|
327
|
+
presetRegistry[name] = factory;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get all available preset names
|
|
331
|
+
*/
|
|
332
|
+
function getAvailablePresets() {
|
|
333
|
+
return Object.keys(presetRegistry);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Validate that preset combinations don't conflict.
|
|
337
|
+
* Detects route collisions (same method + path from different presets).
|
|
338
|
+
*/
|
|
339
|
+
function validatePresetCombination(presets) {
|
|
340
|
+
const conflicts = [];
|
|
341
|
+
const routeMap = /* @__PURE__ */ new Map();
|
|
342
|
+
for (const preset of presets) {
|
|
343
|
+
const name = preset.name ?? "unknown";
|
|
344
|
+
const routes = typeof preset.additionalRoutes === "function" ? preset.additionalRoutes({}) : preset.additionalRoutes ?? [];
|
|
345
|
+
for (const route of routes) {
|
|
346
|
+
const key = `${route.method} ${route.path}`;
|
|
347
|
+
const existing = routeMap.get(key);
|
|
348
|
+
if (existing) conflicts.push({
|
|
349
|
+
presets: [existing, name],
|
|
350
|
+
message: `Both '${existing}' and '${name}' define route ${key}`,
|
|
351
|
+
severity: "error"
|
|
352
|
+
});
|
|
353
|
+
routeMap.set(key, name);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return conflicts;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Apply presets to resource config.
|
|
360
|
+
* Validates preset combinations for conflicts before merging.
|
|
361
|
+
*/
|
|
362
|
+
function applyPresets(config, presets = []) {
|
|
363
|
+
let result = { ...config };
|
|
364
|
+
const resolved = presets.map(resolvePresetInput);
|
|
365
|
+
const errors = validatePresetCombination(resolved).filter((c) => c.severity === "error");
|
|
366
|
+
if (errors.length > 0) throw new Error(`[Arc] Resource '${config.name}' preset conflicts:\n` + errors.map((c) => ` - ${c.message}`).join("\n"));
|
|
367
|
+
for (const preset of resolved) result = mergePreset(result, preset);
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Resolve preset input to PresetResult
|
|
372
|
+
*/
|
|
373
|
+
function resolvePresetInput(preset) {
|
|
374
|
+
if (typeof preset === "object" && ("middlewares" in preset || "additionalRoutes" in preset)) return preset;
|
|
375
|
+
if (typeof preset === "object" && "name" in preset) {
|
|
376
|
+
const { name, ...options } = preset;
|
|
377
|
+
return resolvePreset(name, options);
|
|
378
|
+
}
|
|
379
|
+
return resolvePreset(preset);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Merge preset into config
|
|
383
|
+
*/
|
|
384
|
+
function mergePreset(config, preset) {
|
|
385
|
+
const result = { ...config };
|
|
386
|
+
if (preset.additionalRoutes) {
|
|
387
|
+
const routes = typeof preset.additionalRoutes === "function" ? preset.additionalRoutes(config.permissions ?? {}) : preset.additionalRoutes;
|
|
388
|
+
result.additionalRoutes = [...result.additionalRoutes ?? [], ...routes];
|
|
389
|
+
}
|
|
390
|
+
if (preset.middlewares) {
|
|
391
|
+
result.middlewares = result.middlewares ?? {};
|
|
392
|
+
for (const [op, mws] of Object.entries(preset.middlewares)) {
|
|
393
|
+
const key = op;
|
|
394
|
+
result.middlewares[key] = [...result.middlewares[key] ?? [], ...mws ?? []];
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (preset.schemaOptions) result.schemaOptions = {
|
|
398
|
+
...result.schemaOptions,
|
|
399
|
+
...preset.schemaOptions,
|
|
400
|
+
fieldRules: {
|
|
401
|
+
...result.schemaOptions?.fieldRules,
|
|
402
|
+
...preset.schemaOptions?.fieldRules
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
if (preset.controllerOptions) result._controllerOptions = {
|
|
406
|
+
...result._controllerOptions,
|
|
407
|
+
...preset.controllerOptions
|
|
408
|
+
};
|
|
409
|
+
if (preset.hooks && preset.hooks.length > 0) {
|
|
410
|
+
result._hooks = result._hooks ?? [];
|
|
411
|
+
for (const hook of preset.hooks) result._hooks.push({
|
|
412
|
+
presetName: preset.name,
|
|
413
|
+
operation: hook.operation,
|
|
414
|
+
phase: hook.phase,
|
|
415
|
+
handler: hook.handler,
|
|
416
|
+
priority: hook.priority
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return result;
|
|
420
|
+
}
|
|
421
|
+
//#endregion
|
|
422
|
+
export { registerPreset as a, auditedPreset as c, ownedByUserPreset as d, getPreset as i, softDeletePreset as l, flexibleMultiTenantPreset as n, treePreset as o, getAvailablePresets as r, bulkPreset as s, applyPresets as t, slugLookupPreset as u };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { t as __exportAll } from "./chunk-
|
|
2
|
-
import { i as versionKey, r as tagVersionKey } from "./keys-
|
|
3
|
-
import { t as hasEvents } from "./typeGuards-
|
|
4
|
-
import { t as MemoryCacheStore } from "./memory-
|
|
1
|
+
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
+
import { i as versionKey, r as tagVersionKey } from "./keys-qcD-TVJl.mjs";
|
|
3
|
+
import { t as hasEvents } from "./typeGuards-Cj5Rgvlg.mjs";
|
|
4
|
+
import { t as MemoryCacheStore } from "./memory-Cb_7iy9e.mjs";
|
|
5
5
|
import fp from "fastify-plugin";
|
|
6
|
-
|
|
7
6
|
//#region src/cache/QueryCache.ts
|
|
8
7
|
var QueryCache = class {
|
|
9
8
|
store;
|
|
@@ -12,7 +11,7 @@ var QueryCache = class {
|
|
|
12
11
|
}
|
|
13
12
|
async get(key) {
|
|
14
13
|
const envelope = await this.store.get(key);
|
|
15
|
-
if (!envelope
|
|
14
|
+
if (!envelope?.createdAt) return {
|
|
16
15
|
data: void 0,
|
|
17
16
|
status: "miss"
|
|
18
17
|
};
|
|
@@ -70,27 +69,8 @@ var QueryCache = class {
|
|
|
70
69
|
await this.store.set(key, newVersion, { ttlMs: 1440 * 60 * 1e3 });
|
|
71
70
|
}
|
|
72
71
|
};
|
|
73
|
-
|
|
74
72
|
//#endregion
|
|
75
73
|
//#region src/cache/queryCachePlugin.ts
|
|
76
|
-
/**
|
|
77
|
-
* QueryCache Fastify Plugin
|
|
78
|
-
*
|
|
79
|
-
* Registers QueryCache on `fastify.queryCache` and wires automatic
|
|
80
|
-
* cache invalidation via CRUD events. Zero config for memory mode.
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* ```typescript
|
|
84
|
-
* // Memory mode (default)
|
|
85
|
-
* await fastify.register(queryCachePlugin);
|
|
86
|
-
*
|
|
87
|
-
* // With Redis store
|
|
88
|
-
* await fastify.register(queryCachePlugin, {
|
|
89
|
-
* store: new RedisCacheStore({ client: redis, prefix: 'arc:qc:' }),
|
|
90
|
-
* defaults: { staleTime: 30, gcTime: 300 },
|
|
91
|
-
* });
|
|
92
|
-
* ```
|
|
93
|
-
*/
|
|
94
74
|
var queryCachePlugin_exports = /* @__PURE__ */ __exportAll({ queryCachePlugin: () => queryCachePlugin });
|
|
95
75
|
const CRUD_SUFFIXES = new Set([
|
|
96
76
|
"created",
|
|
@@ -133,6 +113,5 @@ const queryCachePlugin = fp(queryCachePluginImpl, {
|
|
|
133
113
|
name: "arc-query-cache",
|
|
134
114
|
fastify: "5.x"
|
|
135
115
|
});
|
|
136
|
-
|
|
137
116
|
//#endregion
|
|
138
|
-
export { queryCachePlugin_exports as n, QueryCache as r, queryCachePlugin as t };
|
|
117
|
+
export { queryCachePlugin_exports as n, QueryCache as r, queryCachePlugin as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as CacheStore } from "./interface-
|
|
1
|
+
import { i as CacheStore } from "./interface-D_BWALyZ.mjs";
|
|
2
2
|
import { FastifyPluginAsync } from "fastify";
|
|
3
3
|
|
|
4
4
|
//#region src/cache/QueryCache.d.ts
|
|
@@ -18,7 +18,7 @@ interface QueryCacheConfig {
|
|
|
18
18
|
/** Tags for group invalidation */
|
|
19
19
|
tags?: string[];
|
|
20
20
|
}
|
|
21
|
-
type CacheStatus =
|
|
21
|
+
type CacheStatus = "fresh" | "stale" | "miss";
|
|
22
22
|
interface CacheResult<T> {
|
|
23
23
|
data: T;
|
|
24
24
|
status: CacheStatus;
|
|
@@ -58,7 +58,7 @@ interface CrossResourceRule {
|
|
|
58
58
|
pattern: string;
|
|
59
59
|
tags: string[];
|
|
60
60
|
}
|
|
61
|
-
declare module
|
|
61
|
+
declare module "fastify" {
|
|
62
62
|
interface FastifyInstance {
|
|
63
63
|
queryCache: QueryCache;
|
|
64
64
|
queryCacheConfig: QueryCacheDefaults;
|