@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,352 @@
|
|
|
1
|
+
import { m as RESERVED_QUERY_PARAMS } from "./constants-Cxde4rpC.mjs";
|
|
2
|
+
//#region src/utils/queryParser.ts
|
|
3
|
+
/**
|
|
4
|
+
* Arc Query Parser - Default URL-to-Query Parser
|
|
5
|
+
*
|
|
6
|
+
* Framework-agnostic query parser that converts URL parameters to query options.
|
|
7
|
+
* This is Arc's built-in parser; users can swap in MongoKit's QueryParser,
|
|
8
|
+
* pgkit's parser, or any custom parser implementing QueryParserInterface.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // Use Arc default parser (auto-applied if no queryParser option)
|
|
12
|
+
* defineResource({ name: 'product', adapter: ... });
|
|
13
|
+
*
|
|
14
|
+
* // Use MongoKit's QueryParser (recommended for MongoDB - has $lookup, aggregations, etc.)
|
|
15
|
+
* import { QueryParser } from '@classytic/mongokit';
|
|
16
|
+
* defineResource({
|
|
17
|
+
* name: 'product',
|
|
18
|
+
* adapter: ...,
|
|
19
|
+
* queryParser: new QueryParser(),
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Use custom parser for SQL databases
|
|
23
|
+
* defineResource({
|
|
24
|
+
* name: 'user',
|
|
25
|
+
* adapter: ...,
|
|
26
|
+
* queryParser: new PgQueryParser(),
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Regex patterns that can cause catastrophic backtracking (ReDoS attacks)
|
|
31
|
+
* Detects:
|
|
32
|
+
* - Quantifiers: {n,m}
|
|
33
|
+
* - Possessive quantifiers: *+, ++, ?+
|
|
34
|
+
* - Nested quantifiers: (a+)+, (a*)*
|
|
35
|
+
* - Backreferences: \1, \2, etc.
|
|
36
|
+
*/
|
|
37
|
+
const DANGEROUS_REGEX_PATTERNS = /(\{[0-9,]+\}|\*\+|\+\+|\?\+|(\(.+\))\+|\(\?:|\\[0-9]|(\[.+\]).+(\[.+\]))/;
|
|
38
|
+
/**
|
|
39
|
+
* Arc's default query parser
|
|
40
|
+
*
|
|
41
|
+
* Converts URL query parameters to a structured query format:
|
|
42
|
+
* - Pagination: ?page=1&limit=20
|
|
43
|
+
* - Sorting: ?sort=-createdAt,name (- prefix = descending)
|
|
44
|
+
* - Filtering: ?status=active&price[gte]=100&price[lte]=500
|
|
45
|
+
* - Search: ?search=keyword
|
|
46
|
+
* - Populate: ?populate=author,category
|
|
47
|
+
* - Field selection: ?select=name,price,status
|
|
48
|
+
* - Keyset pagination: ?after=cursor_value
|
|
49
|
+
*
|
|
50
|
+
* For advanced MongoDB features ($lookup, aggregations), use MongoKit's QueryParser.
|
|
51
|
+
*/
|
|
52
|
+
var ArcQueryParser = class {
|
|
53
|
+
maxLimit;
|
|
54
|
+
defaultLimit;
|
|
55
|
+
maxRegexLength;
|
|
56
|
+
maxSearchLength;
|
|
57
|
+
maxFilterDepth;
|
|
58
|
+
_allowedFilterFields;
|
|
59
|
+
_allowedSortFields;
|
|
60
|
+
_allowedOperators;
|
|
61
|
+
/** Allowed filter fields (used by MCP for auto-derive) */
|
|
62
|
+
allowedFilterFields;
|
|
63
|
+
/** Allowed sort fields (used by MCP for sort descriptions) */
|
|
64
|
+
allowedSortFields;
|
|
65
|
+
/** Allowed operators (used by MCP for operator descriptions) */
|
|
66
|
+
allowedOperators;
|
|
67
|
+
/** Supported filter operators */
|
|
68
|
+
operators = {
|
|
69
|
+
eq: "$eq",
|
|
70
|
+
ne: "$ne",
|
|
71
|
+
gt: "$gt",
|
|
72
|
+
gte: "$gte",
|
|
73
|
+
lt: "$lt",
|
|
74
|
+
lte: "$lte",
|
|
75
|
+
in: "$in",
|
|
76
|
+
nin: "$nin",
|
|
77
|
+
like: "$regex",
|
|
78
|
+
contains: "$regex",
|
|
79
|
+
regex: "$regex",
|
|
80
|
+
exists: "$exists"
|
|
81
|
+
};
|
|
82
|
+
constructor(options = {}) {
|
|
83
|
+
this.maxLimit = options.maxLimit ?? 1e3;
|
|
84
|
+
this.defaultLimit = options.defaultLimit ?? 20;
|
|
85
|
+
this.maxRegexLength = options.maxRegexLength ?? 200;
|
|
86
|
+
this.maxSearchLength = options.maxSearchLength ?? 200;
|
|
87
|
+
this.maxFilterDepth = options.maxFilterDepth ?? 10;
|
|
88
|
+
if (options.allowedFilterFields) {
|
|
89
|
+
this._allowedFilterFields = new Set(options.allowedFilterFields);
|
|
90
|
+
this.allowedFilterFields = options.allowedFilterFields;
|
|
91
|
+
}
|
|
92
|
+
if (options.allowedSortFields) {
|
|
93
|
+
this._allowedSortFields = new Set(options.allowedSortFields);
|
|
94
|
+
this.allowedSortFields = options.allowedSortFields;
|
|
95
|
+
}
|
|
96
|
+
if (options.allowedOperators) {
|
|
97
|
+
this._allowedOperators = new Set(options.allowedOperators);
|
|
98
|
+
this.allowedOperators = options.allowedOperators;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Parse URL query parameters into structured query options
|
|
103
|
+
*/
|
|
104
|
+
parse(query) {
|
|
105
|
+
const q = query ?? {};
|
|
106
|
+
const page = this.parseNumber(q.page, 1);
|
|
107
|
+
const limit = Math.min(this.parseNumber(q.limit, this.defaultLimit), this.maxLimit);
|
|
108
|
+
const after = this.parseString(q.after ?? q.cursor);
|
|
109
|
+
const sort = this.parseSort(q.sort);
|
|
110
|
+
const { populate, populateOptions } = this.parsePopulate(q.populate);
|
|
111
|
+
const search = this.parseSearch(q.search);
|
|
112
|
+
const select = this.parseSelect(q.select);
|
|
113
|
+
return {
|
|
114
|
+
filters: this.parseFilters(q),
|
|
115
|
+
limit,
|
|
116
|
+
sort,
|
|
117
|
+
populate,
|
|
118
|
+
populateOptions,
|
|
119
|
+
search,
|
|
120
|
+
page: after ? void 0 : page,
|
|
121
|
+
after,
|
|
122
|
+
select
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
parseNumber(value, defaultValue) {
|
|
126
|
+
if (value === void 0 || value === null) return defaultValue;
|
|
127
|
+
const num = parseInt(String(value), 10);
|
|
128
|
+
return Number.isNaN(num) ? defaultValue : Math.max(1, num);
|
|
129
|
+
}
|
|
130
|
+
parseString(value) {
|
|
131
|
+
if (value === void 0 || value === null) return void 0;
|
|
132
|
+
const str = String(value).trim();
|
|
133
|
+
return str.length > 0 ? str : void 0;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Parse populate parameter — handles both simple string and bracket notation.
|
|
137
|
+
*
|
|
138
|
+
* Simple: ?populate=author,category → { populate: 'author,category' }
|
|
139
|
+
* Bracket: ?populate[author][select]=name,email → { populateOptions: [{ path: 'author', select: 'name email' }] }
|
|
140
|
+
*/
|
|
141
|
+
parsePopulate(value) {
|
|
142
|
+
if (value === void 0 || value === null) return {};
|
|
143
|
+
if (typeof value === "string") {
|
|
144
|
+
const trimmed = value.trim();
|
|
145
|
+
return trimmed.length > 0 ? { populate: trimmed } : {};
|
|
146
|
+
}
|
|
147
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
148
|
+
const obj = value;
|
|
149
|
+
const keys = Object.keys(obj);
|
|
150
|
+
if (keys.length === 0) return {};
|
|
151
|
+
const options = [];
|
|
152
|
+
for (const path of keys) {
|
|
153
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(path)) continue;
|
|
154
|
+
const config = obj[path];
|
|
155
|
+
if (typeof config === "object" && config !== null && !Array.isArray(config)) {
|
|
156
|
+
const cfg = config;
|
|
157
|
+
const option = { path };
|
|
158
|
+
if (typeof cfg.select === "string") option.select = cfg.select.split(",").map((s) => s.trim()).filter(Boolean).join(" ");
|
|
159
|
+
if (typeof cfg.match === "object" && cfg.match !== null) option.match = cfg.match;
|
|
160
|
+
options.push(option);
|
|
161
|
+
} else options.push({ path });
|
|
162
|
+
}
|
|
163
|
+
return options.length > 0 ? { populateOptions: options } : {};
|
|
164
|
+
}
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
167
|
+
parseSort(value) {
|
|
168
|
+
if (!value) return void 0;
|
|
169
|
+
const sortStr = String(value);
|
|
170
|
+
const result = {};
|
|
171
|
+
for (const field of sortStr.split(",")) {
|
|
172
|
+
const trimmed = field.trim();
|
|
173
|
+
if (!trimmed) continue;
|
|
174
|
+
if (!/^-?[a-zA-Z_][a-zA-Z0-9_.]*$/.test(trimmed)) continue;
|
|
175
|
+
const fieldName = trimmed.startsWith("-") ? trimmed.slice(1) : trimmed;
|
|
176
|
+
if (this._allowedSortFields && !this._allowedSortFields.has(fieldName)) continue;
|
|
177
|
+
if (trimmed.startsWith("-")) result[fieldName] = -1;
|
|
178
|
+
else result[fieldName] = 1;
|
|
179
|
+
}
|
|
180
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
181
|
+
}
|
|
182
|
+
parseSearch(value) {
|
|
183
|
+
if (!value) return void 0;
|
|
184
|
+
const search = String(value).trim();
|
|
185
|
+
if (search.length === 0) return void 0;
|
|
186
|
+
if (search.length > this.maxSearchLength) return search.slice(0, this.maxSearchLength);
|
|
187
|
+
return search;
|
|
188
|
+
}
|
|
189
|
+
parseSelect(value) {
|
|
190
|
+
if (!value) return void 0;
|
|
191
|
+
const selectStr = String(value);
|
|
192
|
+
const result = {};
|
|
193
|
+
for (const field of selectStr.split(",")) {
|
|
194
|
+
const trimmed = field.trim();
|
|
195
|
+
if (!trimmed) continue;
|
|
196
|
+
if (!/^-?[a-zA-Z_][a-zA-Z0-9_.]*$/.test(trimmed)) continue;
|
|
197
|
+
if (trimmed.startsWith("-")) result[trimmed.slice(1)] = 0;
|
|
198
|
+
else result[trimmed] = 1;
|
|
199
|
+
}
|
|
200
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Check if a value exceeds the maximum nesting depth.
|
|
204
|
+
* Prevents filter bombs where deeply nested objects consume excessive memory/CPU.
|
|
205
|
+
*/
|
|
206
|
+
exceedsDepth(obj, currentDepth = 0) {
|
|
207
|
+
if (currentDepth > this.maxFilterDepth) return true;
|
|
208
|
+
if (obj === null || obj === void 0) return false;
|
|
209
|
+
if (Array.isArray(obj)) return obj.some((v) => this.exceedsDepth(v, currentDepth));
|
|
210
|
+
if (typeof obj !== "object") return false;
|
|
211
|
+
return Object.values(obj).some((v) => this.exceedsDepth(v, currentDepth + 1));
|
|
212
|
+
}
|
|
213
|
+
parseFilters(query) {
|
|
214
|
+
const filters = {};
|
|
215
|
+
for (const [key, value] of Object.entries(query)) {
|
|
216
|
+
if (RESERVED_QUERY_PARAMS.has(key)) continue;
|
|
217
|
+
if (value === void 0 || value === null) continue;
|
|
218
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(key)) continue;
|
|
219
|
+
if (this._allowedFilterFields && !this._allowedFilterFields.has(key)) continue;
|
|
220
|
+
if (this.exceedsDepth(value)) continue;
|
|
221
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
222
|
+
const operatorObj = value;
|
|
223
|
+
const operatorKeys = Object.keys(operatorObj);
|
|
224
|
+
const allOperators = operatorKeys.every((op) => this.operators[op] && (!this._allowedOperators || this._allowedOperators.has(op)));
|
|
225
|
+
const allKnownOperators = operatorKeys.every((op) => this.operators[op]);
|
|
226
|
+
if (allOperators && operatorKeys.length > 0) {
|
|
227
|
+
const mongoFilters = {};
|
|
228
|
+
for (const [op, opValue] of Object.entries(operatorObj)) {
|
|
229
|
+
const mongoOp = this.operators[op];
|
|
230
|
+
if (mongoOp) mongoFilters[mongoOp] = this.parseFilterValue(opValue, op);
|
|
231
|
+
}
|
|
232
|
+
filters[key] = mongoFilters;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (allKnownOperators && this._allowedOperators) continue;
|
|
236
|
+
}
|
|
237
|
+
const match = key.match(/^([a-zA-Z_][a-zA-Z0-9_.]*)(?:\[([a-z]+)\])?$/);
|
|
238
|
+
if (!match) continue;
|
|
239
|
+
const [, fieldName, operator] = match;
|
|
240
|
+
if (!fieldName) continue;
|
|
241
|
+
if (operator && this.operators[operator] && (!this._allowedOperators || this._allowedOperators.has(operator))) {
|
|
242
|
+
const mongoOp = this.operators[operator];
|
|
243
|
+
const parsedValue = this.parseFilterValue(value, operator);
|
|
244
|
+
if (!filters[fieldName]) filters[fieldName] = {};
|
|
245
|
+
filters[fieldName][mongoOp] = parsedValue;
|
|
246
|
+
} else if (!operator) filters[fieldName] = this.parseFilterValue(value);
|
|
247
|
+
}
|
|
248
|
+
return filters;
|
|
249
|
+
}
|
|
250
|
+
parseFilterValue(value, operator) {
|
|
251
|
+
if (operator === "in" || operator === "nin") {
|
|
252
|
+
if (Array.isArray(value)) return value.map((v) => this.coerceValue(v));
|
|
253
|
+
if (typeof value === "string" && value.includes(",")) return value.split(",").map((v) => this.coerceValue(v.trim()));
|
|
254
|
+
return [this.coerceValue(value)];
|
|
255
|
+
}
|
|
256
|
+
if (operator === "like" || operator === "contains" || operator === "regex") return this.sanitizeRegex(String(value));
|
|
257
|
+
if (operator === "exists") {
|
|
258
|
+
const str = String(value).toLowerCase();
|
|
259
|
+
return str === "true" || str === "1";
|
|
260
|
+
}
|
|
261
|
+
return this.coerceValue(value);
|
|
262
|
+
}
|
|
263
|
+
coerceValue(value) {
|
|
264
|
+
if (value === "true") return true;
|
|
265
|
+
if (value === "false") return false;
|
|
266
|
+
if (value === "null") return null;
|
|
267
|
+
if (typeof value === "string") {
|
|
268
|
+
const num = Number(value);
|
|
269
|
+
if (!Number.isNaN(num) && value.trim() !== "") return num;
|
|
270
|
+
}
|
|
271
|
+
return value;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Generate OpenAPI-compatible JSON Schema for query parameters.
|
|
275
|
+
* Arc's defineResource() auto-detects this method and uses it
|
|
276
|
+
* to document list endpoint query parameters in OpenAPI/Swagger.
|
|
277
|
+
*/
|
|
278
|
+
getQuerySchema() {
|
|
279
|
+
const operatorLines = Object.entries(this.operators).map(([op, mongoOp]) => {
|
|
280
|
+
return ` ${op} → ${mongoOp}: ${{
|
|
281
|
+
eq: "Equal (default when no operator specified)",
|
|
282
|
+
ne: "Not equal",
|
|
283
|
+
gt: "Greater than",
|
|
284
|
+
gte: "Greater than or equal",
|
|
285
|
+
lt: "Less than",
|
|
286
|
+
lte: "Less than or equal",
|
|
287
|
+
in: "In list (comma-separated values)",
|
|
288
|
+
nin: "Not in list",
|
|
289
|
+
like: "Pattern match (case-insensitive)",
|
|
290
|
+
contains: "Contains substring (case-insensitive)",
|
|
291
|
+
regex: "Regex pattern",
|
|
292
|
+
exists: "Field exists (true/false)"
|
|
293
|
+
}[op] || op}`;
|
|
294
|
+
});
|
|
295
|
+
return {
|
|
296
|
+
type: "object",
|
|
297
|
+
properties: {
|
|
298
|
+
page: {
|
|
299
|
+
type: "integer",
|
|
300
|
+
description: "Page number for offset pagination",
|
|
301
|
+
default: 1,
|
|
302
|
+
minimum: 1
|
|
303
|
+
},
|
|
304
|
+
limit: {
|
|
305
|
+
type: "integer",
|
|
306
|
+
description: "Number of items per page",
|
|
307
|
+
default: this.defaultLimit,
|
|
308
|
+
minimum: 1,
|
|
309
|
+
maximum: this.maxLimit
|
|
310
|
+
},
|
|
311
|
+
sort: {
|
|
312
|
+
type: "string",
|
|
313
|
+
description: "Sort fields (comma-separated). Prefix with - for descending. Example: -createdAt,name"
|
|
314
|
+
},
|
|
315
|
+
search: {
|
|
316
|
+
type: "string",
|
|
317
|
+
description: "Full-text search query",
|
|
318
|
+
maxLength: this.maxSearchLength
|
|
319
|
+
},
|
|
320
|
+
select: {
|
|
321
|
+
type: "string",
|
|
322
|
+
description: "Fields to include/exclude (comma-separated). Prefix with - to exclude. Example: name,email,-password"
|
|
323
|
+
},
|
|
324
|
+
populate: {
|
|
325
|
+
type: "string",
|
|
326
|
+
description: "Fields to populate/join (comma-separated). Example: author,category"
|
|
327
|
+
},
|
|
328
|
+
after: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: "Cursor value for keyset pagination"
|
|
331
|
+
},
|
|
332
|
+
_filterOperators: {
|
|
333
|
+
type: "string",
|
|
334
|
+
description: ["Available filter operators (use as field[operator]=value):", ...operatorLines].join("\n")
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
sanitizeRegex(pattern) {
|
|
340
|
+
let sanitized = pattern.slice(0, this.maxRegexLength);
|
|
341
|
+
if (DANGEROUS_REGEX_PATTERNS.test(sanitized)) sanitized = sanitized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
342
|
+
return sanitized;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* Create a new ArcQueryParser instance
|
|
347
|
+
*/
|
|
348
|
+
function createQueryParser(options) {
|
|
349
|
+
return new ArcQueryParser(options);
|
|
350
|
+
}
|
|
351
|
+
//#endregion
|
|
352
|
+
export { createQueryParser as n, ArcQueryParser as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-
|
|
1
|
+
import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-B4awm1RJ.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/idempotency/stores/redis.d.ts
|
|
4
4
|
interface RedisClient {
|
|
@@ -34,7 +34,7 @@ declare class RedisIdempotencyStore implements IdempotencyStore {
|
|
|
34
34
|
private resultKey;
|
|
35
35
|
private lockKey;
|
|
36
36
|
get(key: string): Promise<IdempotencyResult | undefined>;
|
|
37
|
-
set(key: string, result: Omit<IdempotencyResult,
|
|
37
|
+
set(key: string, result: Omit<IdempotencyResult, "key">): Promise<void>;
|
|
38
38
|
tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean>;
|
|
39
39
|
unlock(key: string, requestId: string): Promise<void>;
|
|
40
40
|
isLocked(key: string): Promise<boolean>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "./EventTransport-
|
|
1
|
+
import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "./EventTransport-wc5hSLik.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/events/transports/redis-stream.d.ts
|
|
4
4
|
interface RedisStreamLike {
|
|
5
5
|
xadd(key: string, id: string, ...fieldValues: string[]): Promise<string | null>;
|
|
6
|
-
xreadgroup(command:
|
|
6
|
+
xreadgroup(command: "GROUP", group: string, consumer: string, ...args: (string | number)[]): Promise<Array<[string, Array<[string, string[]]>]> | null>;
|
|
7
7
|
xack(key: string, group: string, ...ids: string[]): Promise<number>;
|
|
8
8
|
xgroup(command: string, key: string, group: string, ...args: string[]): Promise<unknown>;
|
|
9
9
|
xpending(key: string, group: string, ...args: (string | number)[]): Promise<Array<[string, string, number, number]>>;
|
|
@@ -62,6 +62,12 @@ interface RedisStreamTransportOptions {
|
|
|
62
62
|
* @default 10000
|
|
63
63
|
*/
|
|
64
64
|
maxLen?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Max event payload size in bytes. Publish rejects events exceeding this limit
|
|
67
|
+
* to prevent Redis memory exhaustion from oversized payloads.
|
|
68
|
+
* @default 1_000_000 (1 MB)
|
|
69
|
+
*/
|
|
70
|
+
maxPayloadBytes?: number;
|
|
65
71
|
/**
|
|
66
72
|
* Logger for error messages (default: console).
|
|
67
73
|
* Pass `fastify.log` to integrate with your application logger.
|
|
@@ -80,6 +86,7 @@ declare class RedisStreamTransport implements EventTransport {
|
|
|
80
86
|
private claimTimeoutMs;
|
|
81
87
|
private deadLetterStream;
|
|
82
88
|
private maxLen;
|
|
89
|
+
private maxPayloadBytes;
|
|
83
90
|
private logger;
|
|
84
91
|
private handlers;
|
|
85
92
|
private running;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import { C as RegisterOptions, w as ResourceRegistry } from "../interface-BtdYtQUA.mjs";
|
|
3
|
-
import "../types-RLkFVgaw.mjs";
|
|
4
|
-
import { IntrospectionPluginOptions } from "../types/index.mjs";
|
|
1
|
+
import { Ft as RegisterOptions, I as IntrospectionPluginOptions, It as ResourceRegistry } from "../interface-DGmPxakH.mjs";
|
|
5
2
|
import { FastifyPluginAsync } from "fastify";
|
|
6
3
|
|
|
7
4
|
//#region src/registry/introspectionPlugin.d.ts
|
package/dist/registry/index.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
export { ResourceRegistry, introspectionPlugin_default as introspectionPlugin, introspectionPlugin as introspectionPluginFn };
|
|
1
|
+
import { n as introspectionPlugin_default, t as introspectionPlugin } from "../registry-I-ogLgL9.mjs";
|
|
2
|
+
import { t as ResourceRegistry } from "../ResourceRegistry-DeCIFlix.mjs";
|
|
3
|
+
export { ResourceRegistry, introspectionPlugin_default as introspectionPlugin, introspectionPlugin as introspectionPluginFn };
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import fp from "fastify-plugin";
|
|
2
|
-
|
|
3
2
|
//#region src/registry/introspectionPlugin.ts
|
|
4
|
-
/**
|
|
5
|
-
* Introspection Plugin
|
|
6
|
-
*
|
|
7
|
-
* Exposes resource registry via API endpoints.
|
|
8
|
-
*/
|
|
9
3
|
const introspectionPlugin = async (fastify, opts = {}) => {
|
|
10
4
|
const { prefix = "/_resources", authRoles = ["superadmin"], enabled = true } = opts;
|
|
11
5
|
if (!enabled) {
|
|
@@ -48,6 +42,5 @@ const introspectionPlugin = async (fastify, opts = {}) => {
|
|
|
48
42
|
fastify.log?.debug?.(`Introspection API at ${prefix}`);
|
|
49
43
|
};
|
|
50
44
|
var introspectionPlugin_default = fp(introspectionPlugin, { name: "arc-introspection" });
|
|
51
|
-
|
|
52
45
|
//#endregion
|
|
53
|
-
export { introspectionPlugin_default as n, introspectionPlugin as t };
|
|
46
|
+
export { introspectionPlugin_default as n, introspectionPlugin as t };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
|
|
3
2
|
//#region src/context/requestContext.ts
|
|
4
3
|
/**
|
|
5
4
|
* Request Context via AsyncLocalStorage
|
|
@@ -50,6 +49,5 @@ const requestContext = {
|
|
|
50
49
|
},
|
|
51
50
|
storage
|
|
52
51
|
};
|
|
53
|
-
|
|
54
52
|
//#endregion
|
|
55
|
-
export { requestContext as t };
|
|
53
|
+
export { requestContext as t };
|