@classytic/arc 2.41.0 → 2.43.0
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/dist/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
- package/dist/{EventTransport-n7qO5Cz3.mjs → EventTransport-tzA7CAB4.mjs} +22 -17
- package/dist/{HookSystem-CxvZBo77.mjs → HookSystem-B6UaKRbF.mjs} +2 -2
- package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
- package/dist/{ResourceRegistry-Cu7uTJp9.mjs → ResourceRegistry-CuCARu2B.mjs} +44 -13
- package/dist/actionPermissions-BotX1YOa.mjs +776 -0
- package/dist/audit/index.d.mts +43 -5
- package/dist/audit/index.mjs +48 -26
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +43 -13
- package/dist/auth/index.mjs +130 -155
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-2YY_Kaj1.mjs → authorizationDecision-DQYrkl5f.mjs} +7 -102
- package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
- package/dist/boundedMap-BULzfXYU.mjs +57 -0
- package/dist/{buildHandler-CUhjGHGZ.mjs → buildHandler-CRQ8EcX5.mjs} +4 -4
- package/dist/cache/index.d.mts +22 -34
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-DsWDjIxx.mjs → caching-Cc2M1DwO.mjs} +3 -3
- package/dist/claimed-B10RTfkM.mjs +8 -0
- package/dist/cleanup/index.d.mts +6 -10
- package/dist/cleanup/index.mjs +27 -73
- package/dist/cli/commands/describe.d.mts +4 -2
- package/dist/cli/commands/describe.mjs +7 -4
- package/dist/cli/commands/docs.mjs +3 -3
- package/dist/cli/commands/doctor.mjs +1 -1
- package/dist/cli/commands/generate.mjs +2 -2
- package/dist/cli/commands/init.mjs +2 -2
- package/dist/cli/commands/introspect.mjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{constants-BxLgNFF7.mjs → constants-Cu7Mx7_U.mjs} +4 -2
- package/dist/context/index.d.mts +2 -5
- package/dist/context/index.mjs +18 -38
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +8 -8
- package/dist/{core-iNGqYerO.mjs → core-Cb0LjccA.mjs} +5 -4
- package/dist/{createActionRouter-BBlbPs3k.mjs → createActionRouter-xjqonMRO.mjs} +87 -39
- package/dist/{createAggregationRouter-Dv5SuK2x.mjs → createAggregationRouter-zRYWw_5D.mjs} +11 -8
- package/dist/{preloadResources-Bbp4QJ8V.mjs → createApp-D-953ZAq.mjs} +299 -390
- package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-C9cxS8zt.d.mts} +1 -1
- package/dist/{defineAggregation-UyeurGkp.mjs → defineAggregation-Bm8RjMbq.mjs} +1 -1
- package/dist/{defineResource-DzGT-2LP.mjs → defineResource-DIC7JZGU.mjs} +118 -1024
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +10 -4
- package/dist/docs/index.mjs +4 -4
- package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
- package/dist/{elevation-BF-vhGxY.mjs → elevation-DG8SrLN-.mjs} +41 -12
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-CIMkCOWm.mjs → errorHandler-BnfDviL1.mjs} +26 -5
- package/dist/{errors-C_YhELhe.mjs → errors-CtrywMHP.mjs} +58 -23
- package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
- package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
- package/dist/{eventPlugin-CmXaPWLv.mjs → eventPlugin-Q6BhzHzs.mjs} +82 -21
- package/dist/{subscribe-helpers-AVYPgqTl.mjs → eventTypes-Cdhm6ZlV.mjs} +151 -17
- package/dist/events/index.d.mts +33 -8
- package/dist/events/index.mjs +49 -53
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +139 -157
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +2 -2
- package/dist/factory/index.d.mts +32 -17
- package/dist/factory/index.mjs +213 -32
- package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DhUJ-mvo.d.mts} +11 -25
- package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
- package/dist/{fields-BTyPLHK0.mjs → fields-ToPYpMqS.mjs} +1 -1
- package/dist/filter-merge-IpGywvym.mjs +99 -0
- package/dist/{generate-D_YmiCNf.mjs → generate-DRTkLlIl.mjs} +2 -2
- package/dist/{headers-CNECDE6q.mjs → headers-BbkIF6vA.mjs} +2 -2
- package/dist/health-D-Ft6JM6.mjs +22 -0
- package/dist/{health-CxQzbJmr.mjs → health-DqbsG-_w.mjs} +115 -55
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.d.mts +31 -7
- package/dist/idempotency/index.mjs +155 -98
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +48 -11
- package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
- package/dist/{preloadResources-CjS2OfpU.d.mts → index-C8zVGYsk.d.mts} +37 -38
- package/dist/{index-D4u76eQW.d.mts → index-CRoPtISf.d.mts} +23 -73
- package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
- package/dist/index-JdbZAIsm.d.mts +510 -0
- package/dist/{index-DHbyPo5j.d.mts → index-mqaqZNpe.d.mts} +253 -254
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +11 -11
- package/dist/{init-Y2MlSN9n.mjs → init-B_oUYDXI.mjs} +1 -1
- package/dist/integrations/event-gateway.d.mts +8 -3
- package/dist/integrations/event-gateway.mjs +4 -4
- package/dist/integrations/index.d.mts +3 -3
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +18 -5
- package/dist/integrations/mcp/index.d.mts +72 -59
- package/dist/integrations/mcp/index.mjs +19 -4
- package/dist/integrations/mcp/testing.d.mts +2 -2
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.d.mts +78 -136
- package/dist/integrations/streamline.mjs +116 -54
- package/dist/integrations/webhooks.d.mts +60 -120
- package/dist/integrations/webhooks.mjs +36 -80
- package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.d.mts +3 -3
- package/dist/integrations/websocket.mjs +2 -2
- package/dist/{interface-DEWNOMza.d.mts → interface-C0395yUN.d.mts} +13 -1
- package/dist/{jsonBody-Cq9KYl2Z.mjs → jsonBody-B4YVGcql.mjs} +2 -2
- package/dist/{keys-Cmv4cBRa.mjs → keys-B2KtgMLJ.mjs} +7 -18
- package/dist/{loadResources-CblF7xt2.mjs → loadResources-Dn4lalw2.mjs} +3 -3
- package/dist/{loadResourcesFromEntry-99fIMC3p.mjs → loadResourcesFromEntry-3sLYbuse.mjs} +2 -2
- package/dist/logger/index.d.mts +1 -8
- package/dist/logger/index.mjs +3 -153
- package/dist/logger-DXjfIqjV.mjs +160 -0
- package/dist/{mcpPlugin-xF3Gw-HW.mjs → mcpPlugin-C08HalN5.mjs} +433 -170
- package/dist/{memory-CpDZ9NPw.mjs → memory-9Cp7611C.mjs} +36 -34
- package/dist/{metrics-BaKnq8ag.mjs → metrics-CWI11eRu.mjs} +39 -72
- package/dist/middleware/index.d.mts +2 -2
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +2 -2
- package/dist/{module-BkQEesNQ.mjs → module-BSvOQC7E.mjs} +8 -8
- package/dist/multiTenant-cY0FkDiA.d.mts +104 -0
- package/dist/{multipartBody-C92aJ8am.mjs → multipartBody-CXM2a-Dy.mjs} +46 -7
- package/dist/{openapi-C3LFRzPq.mjs → openapi-DT_G4VhY.mjs} +48 -10
- package/dist/outbox-admin/index.d.mts +3 -3
- package/dist/outbox-admin/index.mjs +6 -18
- package/dist/{outbox-module-dsCyLxXH.mjs → outbox-module-Bjgzl6tg.mjs} +203 -33
- package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
- package/dist/permissions/index.d.mts +5 -4
- package/dist/permissions/index.mjs +8 -6
- package/dist/{permissions-DiHXy8Ck.mjs → permissions-Cm0uYHvd.mjs} +149 -33
- package/dist/{pipe-CBVmUJ-j.mjs → pipe-BqeGT6UO.mjs} +2 -2
- package/dist/pipeline/index.d.mts +2 -2
- package/dist/pipeline/index.mjs +2 -2
- package/dist/plan-FVR6LP59.mjs +565 -0
- package/dist/{BaseController-DYJRZ0EJ.mjs → plugin-BccET1tL.mjs} +1256 -344
- package/dist/plugins/index.d.mts +14 -11
- package/dist/plugins/index.mjs +44 -22
- package/dist/plugins/response-cache.mjs +8 -14
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-2_U6QZQg.mjs → pluralize-CVIOcgMF.mjs} +1 -1
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +5 -5
- package/dist/presets/index.d.mts +26 -31
- package/dist/presets/index.mjs +4 -4
- package/dist/presets/multiTenant.d.mts +2 -107
- package/dist/presets/multiTenant.mjs +22 -162
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-BdlugYTc.mjs → presets-Cpvj6fGO.mjs} +99 -107
- package/dist/{projection-Uvgp-EEj.mjs → projection-Ce4aQhQk.mjs} +2 -2
- package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
- package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
- package/dist/{queryCachePlugin-B5F09bYr.mjs → queryCachePlugin-CFWNH8aT.mjs} +12 -14
- package/dist/{rateLimitKey-CK-EU2L8.mjs → rateLimitKey-C4O-kPm0.mjs} +1 -1
- package/dist/{realtime-FnnD0n3Z.mjs → realtime-DmPE9S54.mjs} +25 -21
- package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
- package/dist/{redis-Bzi1y3F9.d.mts → redis-vLvtcGy_.d.mts} +13 -24
- package/dist/redisShapes-CQksY67q.d.mts +55 -0
- package/dist/registry/index.d.mts +9 -4
- package/dist/registry/index.mjs +4 -4
- package/dist/{registry-D3y8VEJ1.mjs → registry-Bcz9G1bh.mjs} +10 -8
- package/dist/{renewingLease-B3VYh5aU.mjs → renewingLease-qODa7KJQ.mjs} +1 -1
- package/dist/{replyHelpers-DPHsdw-5.mjs → replyHelpers-CG_VtVgS.mjs} +2 -2
- package/dist/{requestContext-cSVKnYPk.mjs → requestContext-Dsu9P1bV.mjs} +1 -1
- package/dist/requirement-CbiLdQ1n.d.mts +22 -0
- package/dist/requirement-KDdoRcOP.mjs +35 -0
- package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
- package/dist/resourceRateLimit-dSjVr1pk.mjs +180 -0
- package/dist/{rolldown-runtime-DjDuyglE.mjs → rolldown-runtime-D1w9Y1VJ.mjs} +1 -1
- package/dist/routeValidator-D36SW8qt.mjs +70 -0
- package/dist/{runtimeCapabilities-DSA_dH1_.mjs → runtimeCapabilities-0tccjRlj.mjs} +1 -1
- package/dist/{schedules-BktJb3Y3.mjs → schedules-C-OpmHBq.mjs} +46 -37
- package/dist/{schemaConverter-BLCSmNbr.mjs → schemaConverter-BORek3O-.mjs} +2 -2
- package/dist/{schemaIR-K16Bb5BX.mjs → schemaIR-C7Vbp71I.mjs} +2 -2
- package/dist/schemas/index.d.mts +10 -0
- package/dist/schemas/index.mjs +8 -3
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +6 -6
- package/dist/{scope-jSxuBTFM.mjs → scope-DQthe88b.mjs} +4 -4
- package/dist/shutdown-MqA81z_P.mjs +59 -0
- package/dist/{simpleEqualityMatcher-BiOnyflP.mjs → simpleEqualityMatcher-auW8QjXN.mjs} +1 -1
- package/dist/{sse-CH7Slxc1.mjs → sse-CYzQBVFe.mjs} +11 -15
- package/dist/{sseStream-CN-Lh1Tu.mjs → sseStream-B5_Afiqn.mjs} +2 -2
- package/dist/{store-helpers-9DX9-1j_.mjs → store-helpers-BOzk-CuX.mjs} +1 -1
- package/dist/{streaming-dvppnxsr.mjs → streaming-DjTQhZrN.mjs} +1 -1
- package/dist/subscriberIdentity-CW39uA8G.mjs +28 -0
- package/dist/tenancy--ed9eH_z.mjs +43 -0
- package/dist/tenantPreparation-BKon6Yox.mjs +67 -0
- package/dist/testing/cleanupStoreContract.mjs +1 -1
- package/dist/testing/index.d.mts +193 -175
- package/dist/testing/index.mjs +286 -212
- package/dist/testing/outboxStoreContract.mjs +137 -2
- package/dist/testing/requiredPorts.d.mts +17 -0
- package/dist/testing/requiredPorts.mjs +65 -0
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/timing-qczZ0YC-.mjs +46 -0
- package/dist/{transactionContext-DCWMI6hV.mjs → transactionContext-DjFkrNG6.mjs} +1 -1
- package/dist/{typeGuards-D7tHFdH6.mjs → typeGuards-CySF12G-.mjs} +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-CWQn9-IC.d.mts → types-B4Aq4Sdj.d.mts} +22 -9
- package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
- package/dist/{types-CDy3-zw1.mjs → types-Bk9NOgOC.mjs} +4 -4
- package/dist/usage/index.d.mts +2 -3
- package/dist/usage/index.mjs +5 -5
- package/dist/{userHelpers-DDMOZ_Pm.mjs → userHelpers-aklawRaK.mjs} +1 -1
- package/dist/utils/index.d.mts +84 -223
- package/dist/utils/index.mjs +13 -12
- package/dist/{utils-B-zj_b__.mjs → utils-ec1oTo1n.mjs} +315 -404
- package/dist/{validate-R0G_JrnK.mjs → validate-9yzHC1e2.mjs} +6 -14
- package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
- package/dist/{versioning-DuVWcu-F.mjs → versioning-fhhWHV74.mjs} +4 -4
- package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
- package/dist/{workScope-BhsWde4k.mjs → workScope-zpPyge4t.mjs} +2 -2
- package/package.json +578 -574
- package/skills/arc/SKILL.md +13 -4
- package/skills/arc/references/auth.md +8 -5
- package/skills/arc/references/integrations.md +1 -1
- package/skills/arc/references/mcp.md +8 -6
- package/skills/arc/references/multi-tenancy.md +19 -14
- package/skills/arc/references/production.md +27 -10
- package/dist/actionPermissions-DPwPWPOV.mjs +0 -350
- package/dist/index-CFfLKMH4.d.mts +0 -290
- package/dist/interface-BIpgzbZl.mjs +0 -17
- package/dist/redis-stream-BPKscsTB.d.mts +0 -260
- package/dist/registerRoute-DRsSHPSM.mjs +0 -364
- package/dist/sync/changelog.d.mts +0 -3
- package/dist/sync/changelog.mjs +0 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-D1w9Y1VJ.mjs";
|
|
3
|
+
import { n as evictToSize } from "./boundedMap-BULzfXYU.mjs";
|
|
3
4
|
//#region src/cache/memory.ts
|
|
4
5
|
var memory_exports = /* @__PURE__ */ __exportAll({ MemoryCacheStore: () => MemoryCacheStore });
|
|
5
6
|
/**
|
|
@@ -24,13 +25,13 @@ var MemoryCacheStore = class {
|
|
|
24
25
|
_misses = 0;
|
|
25
26
|
_evictions = 0;
|
|
26
27
|
constructor(options = {}) {
|
|
27
|
-
this.defaultTtlSeconds = options.defaultTtlSeconds ?? 60;
|
|
28
|
-
this.maxEntries = clamp(options.maxEntries ?? 1e3, 1, 1e5);
|
|
29
|
-
this.maxEntryBytes = clamp(options.maxEntryBytes ?? 262144, 1024, 10485760);
|
|
30
|
-
this.maxMemoryBytes = options.maxMemoryBytes ?? 52428800;
|
|
31
|
-
this.evictionWatermark = clamp(options.evictionWatermark ?? .9, .5, 1);
|
|
28
|
+
this.defaultTtlSeconds = finite("defaultTtlSeconds", options.defaultTtlSeconds ?? 60);
|
|
29
|
+
this.maxEntries = clamp("maxEntries", options.maxEntries ?? 1e3, 1, 1e5);
|
|
30
|
+
this.maxEntryBytes = clamp("maxEntryBytes", options.maxEntryBytes ?? 262144, 1024, 10485760);
|
|
31
|
+
this.maxMemoryBytes = clamp("maxMemoryBytes", options.maxMemoryBytes ?? 52428800, 0, Number.MAX_SAFE_INTEGER);
|
|
32
|
+
this.evictionWatermark = clamp("evictionWatermark", options.evictionWatermark ?? .9, .5, 1);
|
|
32
33
|
this.logger = options.logger ?? console;
|
|
33
|
-
const cleanupIntervalMs = clamp(options.cleanupIntervalMs ?? 3e4, 1e3, 6e5);
|
|
34
|
+
const cleanupIntervalMs = clamp("cleanupIntervalMs", options.cleanupIntervalMs ?? 3e4, 1e3, 6e5);
|
|
34
35
|
this.cleanupTimer = setInterval(() => this.cleanupExpired(), cleanupIntervalMs);
|
|
35
36
|
if (this.cleanupTimer.unref) this.cleanupTimer.unref();
|
|
36
37
|
}
|
|
@@ -54,11 +55,12 @@ var MemoryCacheStore = class {
|
|
|
54
55
|
const effectiveTtlSeconds = ttlSeconds ?? this.defaultTtlSeconds;
|
|
55
56
|
if (!Number.isFinite(effectiveTtlSeconds) || effectiveTtlSeconds <= 0) return;
|
|
56
57
|
const ttlMs = effectiveTtlSeconds * 1e3;
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
this.logger.warn(`[MemoryCacheStore] Skipping oversized entry for key '${key}' (${
|
|
58
|
+
const valueBytes = this.estimateSize(value);
|
|
59
|
+
if (valueBytes > this.maxEntryBytes) {
|
|
60
|
+
this.logger.warn(`[MemoryCacheStore] Skipping oversized entry for key '${key}' (${valueBytes} bytes > ${this.maxEntryBytes} bytes)`);
|
|
60
61
|
return;
|
|
61
62
|
}
|
|
63
|
+
const size = valueBytes + keyBytes(key);
|
|
62
64
|
const existing = this.cache.get(key);
|
|
63
65
|
if (existing) {
|
|
64
66
|
this.currentBytes -= existing.size;
|
|
@@ -70,8 +72,7 @@ var MemoryCacheStore = class {
|
|
|
70
72
|
expiresAt: Date.now() + ttlMs
|
|
71
73
|
});
|
|
72
74
|
this.currentBytes += size;
|
|
73
|
-
this.
|
|
74
|
-
if (this.maxMemoryBytes > 0) this.evictToMemoryLimit();
|
|
75
|
+
this.enforceLimits();
|
|
75
76
|
}
|
|
76
77
|
async delete(key) {
|
|
77
78
|
const entry = this.cache.get(key);
|
|
@@ -96,14 +97,14 @@ var MemoryCacheStore = class {
|
|
|
96
97
|
const effectiveTtlSeconds = ttlSeconds ?? this.defaultTtlSeconds;
|
|
97
98
|
if (entry) this.currentBytes -= entry.size;
|
|
98
99
|
this.cache.delete(key);
|
|
99
|
-
const size = 8;
|
|
100
|
+
const size = 8 + keyBytes(key);
|
|
100
101
|
this.cache.set(key, {
|
|
101
102
|
value: next,
|
|
102
103
|
size,
|
|
103
104
|
expiresAt: live ? entry.expiresAt : now + Math.max(1, effectiveTtlSeconds) * 1e3
|
|
104
105
|
});
|
|
105
106
|
this.currentBytes += size;
|
|
106
|
-
this.
|
|
107
|
+
this.enforceLimits();
|
|
107
108
|
return next;
|
|
108
109
|
}
|
|
109
110
|
async clear(pattern) {
|
|
@@ -133,26 +134,19 @@ var MemoryCacheStore = class {
|
|
|
133
134
|
this.cache.delete(key);
|
|
134
135
|
this.currentBytes -= entry.size;
|
|
135
136
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
this.removeEntry(oldestKey, entry);
|
|
143
|
-
this._evictions++;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
evictToMemoryLimit() {
|
|
137
|
+
/** Every write path ends here, so no path can skip a limit. */
|
|
138
|
+
enforceLimits() {
|
|
139
|
+
this._evictions += evictToSize(this.cache, this.maxEntries, (_key, entry) => {
|
|
140
|
+
this.currentBytes -= entry.size;
|
|
141
|
+
});
|
|
142
|
+
if (this.maxMemoryBytes <= 0 || this.currentBytes <= this.maxMemoryBytes) return;
|
|
147
143
|
const target = this.maxMemoryBytes * this.evictionWatermark;
|
|
148
|
-
while (this.currentBytes >
|
|
149
|
-
const
|
|
150
|
-
if (
|
|
151
|
-
const entry =
|
|
152
|
-
if (!entry) break;
|
|
144
|
+
while (this.currentBytes > target) {
|
|
145
|
+
const oldest = this.cache.entries().next();
|
|
146
|
+
if (oldest.done) break;
|
|
147
|
+
const [oldestKey, entry] = oldest.value;
|
|
153
148
|
this.removeEntry(oldestKey, entry);
|
|
154
149
|
this._evictions++;
|
|
155
|
-
if (this.currentBytes <= target) break;
|
|
156
150
|
}
|
|
157
151
|
}
|
|
158
152
|
cleanupExpired() {
|
|
@@ -169,8 +163,16 @@ var MemoryCacheStore = class {
|
|
|
169
163
|
}
|
|
170
164
|
}
|
|
171
165
|
};
|
|
172
|
-
|
|
173
|
-
|
|
166
|
+
/** A non-finite option is a configuration bug — `Math.max(min, NaN)` is NaN, which silently disabled the cap. */
|
|
167
|
+
function finite(name, value) {
|
|
168
|
+
if (!Number.isFinite(value)) throw new Error(`[arc] MemoryCacheStore ${name} must be a finite number, got ${String(value)}.`);
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
171
|
+
function clamp(name, value, min, max) {
|
|
172
|
+
return Math.min(max, Math.max(min, finite(name, value)));
|
|
173
|
+
}
|
|
174
|
+
function keyBytes(key) {
|
|
175
|
+
return Buffer.byteLength(key, "utf8");
|
|
174
176
|
}
|
|
175
177
|
/**
|
|
176
178
|
* Translate a glob pattern (`prefix:*`, `*:tag:v`) into a regex. Only `*`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-D1w9Y1VJ.mjs";
|
|
3
|
+
import { a as MetricHistogram, i as DURATION_BUCKETS_SECONDS, o as MetricSeries, s as renderMetricSample } from "./health-DqbsG-_w.mjs";
|
|
3
4
|
import fp from "fastify-plugin";
|
|
4
5
|
//#region src/plugins/metrics.ts
|
|
5
6
|
var metrics_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -7,99 +8,64 @@ var metrics_exports = /* @__PURE__ */ __exportAll({
|
|
|
7
8
|
metricsPlugin: () => metricsPlugin
|
|
8
9
|
});
|
|
9
10
|
var Counter = class {
|
|
10
|
-
|
|
11
|
+
series;
|
|
12
|
+
constructor(maxSeries) {
|
|
13
|
+
this.series = new MetricSeries(maxSeries);
|
|
14
|
+
}
|
|
11
15
|
inc(labels, value = 1) {
|
|
12
|
-
|
|
13
|
-
const existing = this.data.get(key);
|
|
14
|
-
if (existing) existing.value += value;
|
|
15
|
-
else this.data.set(key, {
|
|
16
|
-
labels: { ...labels },
|
|
17
|
-
value
|
|
18
|
-
});
|
|
16
|
+
this.series.get(labels, () => ({ value: 0 })).value += value;
|
|
19
17
|
}
|
|
20
18
|
values() {
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
reset() {
|
|
24
|
-
this.data.clear();
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
var Histogram = class {
|
|
28
|
-
data = /* @__PURE__ */ new Map();
|
|
29
|
-
observe(labels, value) {
|
|
30
|
-
const key = labelKey(labels);
|
|
31
|
-
const existing = this.data.get(key);
|
|
32
|
-
if (existing) {
|
|
33
|
-
existing.sum += value;
|
|
34
|
-
existing.count += 1;
|
|
35
|
-
} else this.data.set(key, {
|
|
19
|
+
return this.series.entries().map(({ labels, value }) => ({
|
|
36
20
|
labels: { ...labels },
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
values() {
|
|
42
|
-
return [...this.data.values()].flatMap(({ labels, sum, count }) => [{
|
|
43
|
-
labels: {
|
|
44
|
-
...labels,
|
|
45
|
-
le: "sum"
|
|
46
|
-
},
|
|
47
|
-
value: sum
|
|
48
|
-
}, {
|
|
49
|
-
labels: {
|
|
50
|
-
...labels,
|
|
51
|
-
le: "count"
|
|
52
|
-
},
|
|
53
|
-
value: count
|
|
54
|
-
}]);
|
|
21
|
+
value: value.value
|
|
22
|
+
}));
|
|
55
23
|
}
|
|
56
24
|
reset() {
|
|
57
|
-
this.
|
|
25
|
+
this.series.reset();
|
|
58
26
|
}
|
|
59
27
|
};
|
|
60
28
|
var Gauge = class {
|
|
61
|
-
|
|
29
|
+
series;
|
|
30
|
+
constructor(maxSeries) {
|
|
31
|
+
this.series = new MetricSeries(maxSeries);
|
|
32
|
+
}
|
|
62
33
|
set(labels, value) {
|
|
63
|
-
|
|
64
|
-
this.data.set(key, {
|
|
65
|
-
labels: { ...labels },
|
|
66
|
-
value
|
|
67
|
-
});
|
|
34
|
+
this.series.get(labels, () => ({ value: 0 })).value = value;
|
|
68
35
|
}
|
|
69
36
|
values() {
|
|
70
|
-
return
|
|
37
|
+
return this.series.entries().map(({ labels, value }) => ({
|
|
38
|
+
labels: { ...labels },
|
|
39
|
+
value: value.value
|
|
40
|
+
}));
|
|
71
41
|
}
|
|
72
42
|
reset() {
|
|
73
|
-
this.
|
|
43
|
+
this.series.reset();
|
|
74
44
|
}
|
|
75
45
|
};
|
|
76
|
-
function labelKey(labels) {
|
|
77
|
-
return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
|
|
78
|
-
}
|
|
79
46
|
function toPrometheusText(metrics) {
|
|
80
47
|
const lines = [];
|
|
81
48
|
for (const m of metrics) {
|
|
82
49
|
lines.push(`# HELP ${m.name} ${m.help}`);
|
|
83
50
|
lines.push(`# TYPE ${m.name} ${m.type}`);
|
|
84
|
-
for (const v of m.values)
|
|
85
|
-
const labelStr = Object.entries(v.labels).map(([k, val]) => `${k}="${val}"`).join(",");
|
|
86
|
-
lines.push(`${m.name}{${labelStr}} ${v.value}`);
|
|
87
|
-
}
|
|
51
|
+
for (const v of m.values) lines.push(renderMetricSample(m.name, v));
|
|
88
52
|
}
|
|
89
53
|
return `${lines.join("\n")}\n`;
|
|
90
54
|
}
|
|
91
55
|
const metricsPlugin = async (fastify, opts = {}) => {
|
|
92
56
|
const path = opts.path ?? "/_metrics";
|
|
93
57
|
const prefix = opts.prefix ?? "arc";
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
58
|
+
const maxSeries = opts.maxSeries ?? 1e3;
|
|
59
|
+
if (!/^[a-zA-Z_:][a-zA-Z0-9_:]*$/.test(prefix)) throw new Error("metrics.prefix must be a valid Prometheus metric prefix");
|
|
60
|
+
const httpRequestsTotal = new Counter(maxSeries);
|
|
61
|
+
const crudOpsTotal = new Counter(maxSeries);
|
|
62
|
+
const cacheHitsTotal = new Counter(maxSeries);
|
|
63
|
+
const cacheMissesTotal = new Counter(maxSeries);
|
|
64
|
+
const eventsPublishedTotal = new Counter(maxSeries);
|
|
65
|
+
const eventsConsumedTotal = new Counter(maxSeries);
|
|
66
|
+
const httpDuration = new MetricHistogram(DURATION_BUCKETS_SECONDS, maxSeries);
|
|
67
|
+
const crudDuration = new MetricHistogram(DURATION_BUCKETS_SECONDS, maxSeries);
|
|
68
|
+
const circuitBreakerState = new Gauge(maxSeries);
|
|
103
69
|
const collector = {
|
|
104
70
|
collect() {
|
|
105
71
|
const metrics = [
|
|
@@ -196,17 +162,18 @@ const metricsPlugin = async (fastify, opts = {}) => {
|
|
|
196
162
|
eventsConsumedTotal.inc({ event_type: eventType });
|
|
197
163
|
},
|
|
198
164
|
recordCircuitBreakerState(service, state) {
|
|
199
|
-
const
|
|
165
|
+
const normalized = state.toLowerCase().replaceAll("_", "-");
|
|
166
|
+
const stateValue = normalized === "open" ? 1 : normalized === "half-open" ? 2 : 0;
|
|
200
167
|
circuitBreakerState.set({ service }, stateValue);
|
|
201
168
|
}
|
|
202
169
|
};
|
|
203
170
|
fastify.decorate("metrics", collector);
|
|
204
171
|
fastify.addHook("onResponse", async (request, reply) => {
|
|
205
|
-
if (request.url === path) return;
|
|
172
|
+
if (request.routeOptions.url === path) return;
|
|
206
173
|
const duration = reply.elapsedTime / 1e3;
|
|
207
174
|
const labels = {
|
|
208
175
|
method: request.method,
|
|
209
|
-
route: request.routeOptions
|
|
176
|
+
route: request.routeOptions.url ?? "__unmatched__",
|
|
210
177
|
status: String(reply.statusCode)
|
|
211
178
|
};
|
|
212
179
|
httpRequestsTotal.inc(labels);
|
|
@@ -222,4 +189,4 @@ var metrics_default = fp(metricsPlugin, {
|
|
|
222
189
|
fastify: "5.x"
|
|
223
190
|
});
|
|
224
191
|
//#endregion
|
|
225
|
-
export {
|
|
192
|
+
export { metrics_exports as n, metrics_default as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
2
|
+
import { A as RequestWithExtras, J as MiddlewareConfig, k as MiddlewareHandler } from "../index-mqaqZNpe.mjs";
|
|
3
3
|
import { RouteHandlerMethod } from "fastify";
|
|
4
4
|
//#region src/middleware/middleware.d.ts
|
|
5
5
|
interface NamedMiddleware {
|
|
@@ -122,4 +122,4 @@ interface MultipartBodyOptions {
|
|
|
122
122
|
*/
|
|
123
123
|
declare function multipartBody(options?: MultipartBodyOptions): RouteHandlerMethod;
|
|
124
124
|
//#endregion
|
|
125
|
-
export { type MultipartBodyOptions, type NamedMiddleware, type ParsedFile, middleware, multipartBody, sortMiddlewares };
|
|
125
|
+
export { type MiddlewareOptions, type MultipartBodyOptions, type NamedMiddleware, type ParsedFile, middleware, multipartBody, sortMiddlewares };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as CRUD_OPERATIONS } from "../constants-
|
|
3
|
-
import { t as multipartBody } from "../multipartBody-
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as CRUD_OPERATIONS } from "../constants-Cu7Mx7_U.mjs";
|
|
3
|
+
import { t as multipartBody } from "../multipartBody-CXM2a-Dy.mjs";
|
|
4
4
|
//#region src/middleware/middleware.ts
|
|
5
5
|
/**
|
|
6
6
|
* Named Middleware — Priority-based, conditional middleware execution.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as startRenewingLease } from "../renewingLease-
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as startRenewingLease } from "../renewingLease-qODa7KJQ.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
//#region src/migrations/index.ts
|
|
5
5
|
/**
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
2
|
//#region src/factory/module/index.ts
|
|
3
3
|
/**
|
|
4
4
|
* Identity helper for authoring a typed module — mirrors `defineResource`.
|
|
5
5
|
* Gives inference + a single obvious construction site; does no work.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Everything is inferred: the export from `bootstrap`'s return, the port types from
|
|
8
|
+
* `requires` (declare `requires` BEFORE the phases that read `ports`):
|
|
9
9
|
*
|
|
10
10
|
* ```ts
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* defineModule({
|
|
12
|
+
* name: "accounting",
|
|
13
|
+
* requires: { ports: ["outbox"], optionalPorts: ["calendar"] },
|
|
14
|
+
* bootstrap: (f, { ports }) => createAccountingEngine(ports.outbox, ports.calendar),
|
|
15
|
+
* });
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
function defineModule(module) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
|
|
2
|
+
import { G as CrudRouteKey, W as PresetResult } from "./index-mqaqZNpe.mjs";
|
|
3
|
+
//#region src/permissions/tenantPreparation.d.ts
|
|
4
|
+
type TenantFieldSpec = {
|
|
5
|
+
field: string;
|
|
6
|
+
type: "org";
|
|
7
|
+
} | {
|
|
8
|
+
field: string;
|
|
9
|
+
type: "team";
|
|
10
|
+
} | {
|
|
11
|
+
field: string;
|
|
12
|
+
contextKey: string;
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/presets/multiTenant.d.ts
|
|
16
|
+
interface MultiTenantOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Single-field form: name of the database field to filter by.
|
|
19
|
+
* Reads `getOrgId(scope)` as the value source.
|
|
20
|
+
*
|
|
21
|
+
* Mutually exclusive with `tenantFields` — pass one or the other, not both.
|
|
22
|
+
*
|
|
23
|
+
* @default 'organizationId'
|
|
24
|
+
*/
|
|
25
|
+
tenantField?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Multi-field form (2.7.1+): list of tenant dimensions to filter by in
|
|
28
|
+
* lockstep. Use this when a resource is scoped by more than just
|
|
29
|
+
* organization — e.g. organization + branch, organization + project,
|
|
30
|
+
* or organization + team + workspace.
|
|
31
|
+
*
|
|
32
|
+
* Each entry is a discriminated `TenantFieldSpec` declaring where the
|
|
33
|
+
* value comes from. Use `type: 'org'` / `type: 'team'` for built-in scope
|
|
34
|
+
* fields, or `contextKey: '...'` to read from `scope.context` (set by
|
|
35
|
+
* your auth function).
|
|
36
|
+
*
|
|
37
|
+
* Fail-closed: if any required dimension is missing, the request is
|
|
38
|
+
* rejected. Elevated scopes apply whatever resolves and skip the rest.
|
|
39
|
+
*
|
|
40
|
+
* Mutually exclusive with `tenantField`.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* multiTenantPreset({
|
|
45
|
+
* tenantFields: [
|
|
46
|
+
* { field: 'organizationId', type: 'org' },
|
|
47
|
+
* { field: 'branchId', contextKey: 'branchId' },
|
|
48
|
+
* ],
|
|
49
|
+
* })
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
tenantFields?: readonly TenantFieldSpec[];
|
|
53
|
+
/**
|
|
54
|
+
* Routes that allow public access (no auth required)
|
|
55
|
+
* When a route is in this array:
|
|
56
|
+
* - If no org context: allow through without filtering (public data)
|
|
57
|
+
* - If org context present: require auth and apply filter
|
|
58
|
+
*
|
|
59
|
+
* @default [] (strict mode - all routes require auth)
|
|
60
|
+
* @example
|
|
61
|
+
* multiTenantPreset({ allowPublic: ['list', 'get'] })
|
|
62
|
+
*/
|
|
63
|
+
allowPublic?: CrudRouteKey[];
|
|
64
|
+
/**
|
|
65
|
+
* Reads that are NOT scoped to a tenant at all — a marketplace listing.
|
|
66
|
+
*
|
|
67
|
+
* `allowPublic` answers "may an anonymous caller reach this route?". That is a different
|
|
68
|
+
* question from "is this listing scoped to a tenant?", and until this option there was no way
|
|
69
|
+
* to answer the second one with "no". An `allowPublic` route still filters by the caller's
|
|
70
|
+
* organization whenever they have one, and a signed-in user of a multi-org product ALWAYS has
|
|
71
|
+
* one, because choosing an organization writes it onto the session. So a cross-tenant catalog
|
|
72
|
+
* silently became single-tenant the moment one of its own sellers browsed it: the same URL,
|
|
73
|
+
* with no header and no query parameter to explain the difference, returned a different world
|
|
74
|
+
* depending on who was asking.
|
|
75
|
+
*
|
|
76
|
+
* The only escape was to drop this preset from the resource entirely, which also drops tenant
|
|
77
|
+
* INJECTION on create/update — the thing that stops a member moving a document into another
|
|
78
|
+
* organization. Trading a write guarantee for a read behaviour is not a real choice.
|
|
79
|
+
*
|
|
80
|
+
* Listed routes get no tenant middleware: no filter, and nothing stashed for the repo layer.
|
|
81
|
+
* Everything else keeps its filter, so writes are unaffected.
|
|
82
|
+
*
|
|
83
|
+
* READS ONLY, by type. A cross-tenant `create` or `update` is not a listing, it is an escape
|
|
84
|
+
* from tenancy, and it should never be one option away by accident.
|
|
85
|
+
*
|
|
86
|
+
* THIS DOES NOT AUTHORIZE ANYTHING. Removing the tenant filter widens what the query can
|
|
87
|
+
* return, so the resource owes a row-level `policy` from its permission check saying what a
|
|
88
|
+
* stranger may see (`{ status: 'active' }`, `{ visibility: 'public' }`). Declaring this without
|
|
89
|
+
* one publishes every row in every tenant.
|
|
90
|
+
*
|
|
91
|
+
* @default []
|
|
92
|
+
* @example
|
|
93
|
+
* // A public catalog: anyone may browse every seller's LIVE products,
|
|
94
|
+
* // while create/update/delete stay pinned to the caller's own shop.
|
|
95
|
+
* multiTenantPreset({
|
|
96
|
+
* allowPublic: ['list', 'get'],
|
|
97
|
+
* crossTenant: ['list', 'get'],
|
|
98
|
+
* })
|
|
99
|
+
*/
|
|
100
|
+
crossTenant?: readonly Extract<CrudRouteKey, "list" | "get">[];
|
|
101
|
+
}
|
|
102
|
+
declare function multiTenantPreset(options?: MultiTenantOptions): PresetResult;
|
|
103
|
+
//#endregion
|
|
104
|
+
export { multiTenantPreset as n, TenantFieldSpec as r, MultiTenantOptions as t };
|
|
@@ -1,6 +1,41 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { ARC_ERROR_CODES } from "@classytic/repo-core/errors";
|
|
2
3
|
import sjson from "secure-json-parse";
|
|
3
4
|
//#region src/middleware/multipartBody.ts
|
|
5
|
+
/**
|
|
6
|
+
* Multipart Body Middleware — Opt-in multipart/form-data parsing for CRUD routes.
|
|
7
|
+
*
|
|
8
|
+
* Parses multipart form fields into `req.body` and attaches files to `req.body._files`.
|
|
9
|
+
* Use with a `before:create` or `before:update` hook to process files (upload to S3, etc.)
|
|
10
|
+
* before BaseController persists the record.
|
|
11
|
+
*
|
|
12
|
+
* Requires `@fastify/multipart` to be registered on the Fastify instance
|
|
13
|
+
* (Arc registers it by default via `createApp()` unless `multipart: false`).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { defineResource } from '@classytic/arc';
|
|
18
|
+
* import { multipartBody } from '@classytic/arc/middleware';
|
|
19
|
+
*
|
|
20
|
+
* const productResource = defineResource({
|
|
21
|
+
* name: 'product',
|
|
22
|
+
* adapter,
|
|
23
|
+
* middlewares: {
|
|
24
|
+
* create: [multipartBody()],
|
|
25
|
+
* update: [multipartBody()],
|
|
26
|
+
* },
|
|
27
|
+
* hooks: {
|
|
28
|
+
* 'before:create': async (data) => {
|
|
29
|
+
* if (data._files?.image) {
|
|
30
|
+
* data.imageUrl = await uploadToS3(data._files.image);
|
|
31
|
+
* delete data._files;
|
|
32
|
+
* }
|
|
33
|
+
* return data;
|
|
34
|
+
* },
|
|
35
|
+
* },
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
4
39
|
const DEFAULT_MAX_FILE_SIZE = 10485760;
|
|
5
40
|
const DEFAULT_MAX_FILES = 5;
|
|
6
41
|
const DEFAULT_MAX_TOTAL_BYTES = 26214400;
|
|
@@ -78,19 +113,19 @@ function multipartBody(options = {}) {
|
|
|
78
113
|
for await (const part of parts) if (part.type === "file") {
|
|
79
114
|
if (fileCount >= maxFiles) continue;
|
|
80
115
|
if (mimeMatcher && !mimeMatcher.matches(part.mimetype)) return reply.code(415).send({
|
|
81
|
-
code:
|
|
116
|
+
code: ARC_ERROR_CODES.UNSUPPORTED_MEDIA_TYPE,
|
|
82
117
|
message: `File type '${part.mimetype}' not allowed. Accepted: ${mimeMatcher.describe()}`,
|
|
83
118
|
status: 415
|
|
84
119
|
});
|
|
85
120
|
const buffer = await part.toBuffer();
|
|
86
121
|
if (buffer.length > maxFileSize) return reply.code(413).send({
|
|
87
|
-
code:
|
|
122
|
+
code: ARC_ERROR_CODES.PAYLOAD_TOO_LARGE,
|
|
88
123
|
message: `File '${part.filename}' exceeds maximum size of ${Math.round(maxFileSize / 1024 / 1024)}MB`,
|
|
89
124
|
status: 413
|
|
90
125
|
});
|
|
91
126
|
totalBytes += buffer.length;
|
|
92
127
|
if (totalBytes > maxTotalBytes) return reply.code(413).send({
|
|
93
|
-
code:
|
|
128
|
+
code: ARC_ERROR_CODES.PAYLOAD_TOO_LARGE,
|
|
94
129
|
message: `Combined upload size exceeds ${Math.round(maxTotalBytes / 1024 / 1024)}MB per request`,
|
|
95
130
|
status: 413
|
|
96
131
|
});
|
|
@@ -106,7 +141,7 @@ function multipartBody(options = {}) {
|
|
|
106
141
|
} catch (err) {
|
|
107
142
|
request.log.error({ err }, "multipartBody: failed to parse multipart form");
|
|
108
143
|
return reply.code(400).send({
|
|
109
|
-
code:
|
|
144
|
+
code: ARC_ERROR_CODES.BAD_REQUEST,
|
|
110
145
|
message: "Failed to parse multipart form data",
|
|
111
146
|
status: 400
|
|
112
147
|
});
|
|
@@ -114,10 +149,14 @@ function multipartBody(options = {}) {
|
|
|
114
149
|
if (requiredFields) {
|
|
115
150
|
const missing = requiredFields.filter((name) => !(name in files));
|
|
116
151
|
if (missing.length > 0) return reply.code(400).send({
|
|
117
|
-
code:
|
|
152
|
+
code: ARC_ERROR_CODES.VALIDATION_ERROR,
|
|
118
153
|
message: `Missing required file field${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`,
|
|
119
154
|
status: 400,
|
|
120
|
-
details:
|
|
155
|
+
details: missing.map((name) => ({
|
|
156
|
+
path: name,
|
|
157
|
+
code: "required",
|
|
158
|
+
message: `File field "${name}" is required`
|
|
159
|
+
}))
|
|
121
160
|
});
|
|
122
161
|
}
|
|
123
162
|
if (fileCount > 0) body[filesKey] = files;
|