@classytic/arc 2.16.0 → 2.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/{BaseController-DlCCTIxJ.mjs → BaseController-Vk8I7C0x.mjs} +1 -1
  2. package/dist/{QueryCache-SvmT_9ti.d.mts → QueryCache-8YmAQ-zW.d.mts} +1 -1
  3. package/dist/{ResourceRegistry-f48hFk3m.mjs → ResourceRegistry-CzSzVK1m.mjs} +3 -0
  4. package/dist/audit/index.d.mts +1 -1
  5. package/dist/auth/index.d.mts +4 -4
  6. package/dist/auth/redis-session.d.mts +1 -1
  7. package/dist/{buildHandler-BZX6zzDM.mjs → buildHandler-C_SkBNF3.mjs} +6 -4
  8. package/dist/cache/index.d.mts +3 -3
  9. package/dist/cli/commands/describe.d.mts +1 -1
  10. package/dist/cli/commands/describe.mjs +1 -1
  11. package/dist/cli/commands/docs.mjs +2 -2
  12. package/dist/cli/commands/generate.d.mts +1 -1
  13. package/dist/cli/commands/generate.mjs +1 -1
  14. package/dist/cli/commands/init.d.mts +1 -1
  15. package/dist/cli/commands/init.mjs +1 -1
  16. package/dist/cli/commands/introspect.mjs +1 -1
  17. package/dist/cli/index.d.mts +2 -2
  18. package/dist/cli/index.mjs +2 -2
  19. package/dist/core/index.d.mts +2 -2
  20. package/dist/core/index.mjs +3 -3
  21. package/dist/{core-DBJ_j6rX.mjs → core-wKeGXn38.mjs} +5 -5
  22. package/dist/{createActionRouter-DUpN3Dd1.mjs → createActionRouter-eiOFtnC3.mjs} +2 -2
  23. package/dist/{createAggregationRouter-Dq-TUCuY.mjs → createAggregationRouter-C4unhBnV.mjs} +3 -3
  24. package/dist/{createApp-DNccuhyI.mjs → createApp-DdPBtmzp.mjs} +8 -8
  25. package/dist/{defineEvent-DRwY0fYm.mjs → defineEvent-NPKUJRt8.mjs} +1 -1
  26. package/dist/docs/index.d.mts +2 -2
  27. package/dist/docs/index.mjs +1 -1
  28. package/dist/{eventPlugin-C2cGqtRO.mjs → eventPlugin-CnJCUyM0.mjs} +1 -1
  29. package/dist/{eventPlugin-CtHC_av1.d.mts → eventPlugin-DjEx_J3j.d.mts} +1 -1
  30. package/dist/events/index.d.mts +3 -3
  31. package/dist/events/index.mjs +5 -5
  32. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  33. package/dist/events/transports/redis.d.mts +1 -1
  34. package/dist/factory/index.d.mts +2 -2
  35. package/dist/factory/index.mjs +4 -3
  36. package/dist/fastify-augmentation-CJm0vh-l.d.mts +42 -0
  37. package/dist/{fields-Anj0xdih.d.mts → fields-DqZLp5d6.d.mts} +1 -1
  38. package/dist/hooks/index.d.mts +1 -1
  39. package/dist/idempotency/index.d.mts +2 -2
  40. package/dist/idempotency/index.mjs +1 -1
  41. package/dist/idempotency/redis.d.mts +1 -1
  42. package/dist/{index-3oIimXQn.d.mts → index-3L-adFJO.d.mts} +3 -3
  43. package/dist/{index-B-ulKx5P.d.mts → index-CC_pgZjY.d.mts} +36 -16
  44. package/dist/{index-CkW0flkU.d.mts → index-DBDgy6Hw.d.mts} +79 -7
  45. package/dist/index.d.mts +5 -5
  46. package/dist/index.mjs +4 -4
  47. package/dist/integrations/event-gateway.d.mts +2 -2
  48. package/dist/integrations/event-gateway.mjs +1 -1
  49. package/dist/integrations/index.d.mts +2 -2
  50. package/dist/integrations/mcp/index.d.mts +4 -4
  51. package/dist/integrations/mcp/index.mjs +1 -1
  52. package/dist/integrations/mcp/testing.d.mts +1 -1
  53. package/dist/integrations/mcp/testing.mjs +1 -1
  54. package/dist/integrations/websocket-redis.d.mts +1 -1
  55. package/dist/integrations/websocket.d.mts +1 -1
  56. package/dist/{loadResourcesFromEntry-BLMEI2Xa.mjs → loadResourcesFromEntry-DCjYMgfC.mjs} +1 -1
  57. package/dist/{mcpPlugin-7vGV51ED.mjs → mcpPlugin-Dbgz9_Rj.mjs} +5 -5
  58. package/dist/middleware/index.d.mts +1 -1
  59. package/dist/{openapi-34T9yNwd.mjs → openapi-BRnd3pT7.mjs} +36 -9
  60. package/dist/permissions/index.d.mts +2 -2
  61. package/dist/pipeline/index.d.mts +1 -1
  62. package/dist/plugins/index.d.mts +5 -59
  63. package/dist/plugins/index.mjs +7 -7
  64. package/dist/plugins/tracing-entry.d.mts +1 -1
  65. package/dist/plugins/tracing-entry.mjs +1 -1
  66. package/dist/preloadResources-BOXzPhKU.mjs +45 -0
  67. package/dist/presets/filesUpload.d.mts +4 -4
  68. package/dist/presets/index.d.mts +1 -1
  69. package/dist/presets/multiTenant.d.mts +1 -1
  70. package/dist/presets/search.d.mts +2 -2
  71. package/dist/{queryCachePlugin-Biqzfbi5.d.mts → queryCachePlugin-Do4P_WBC.d.mts} +2 -2
  72. package/dist/{redis-Cyzrz6SX.d.mts → redis-cpqVb85w.d.mts} +1 -1
  73. package/dist/{redis-stream-DT-YjzrB.d.mts → redis-stream-BuiAS3aE.d.mts} +1 -1
  74. package/dist/registry/index.d.mts +30 -8
  75. package/dist/registry/index.mjs +2 -2
  76. package/dist/{registry-BBE23CDj.mjs → registry-BrlHkrRS.mjs} +14 -8
  77. package/dist/{routerShared-CZV5aabX.mjs → routerShared-ClV4cCaa.mjs} +37 -13
  78. package/dist/scope/index.d.mts +2 -2
  79. package/dist/testing/index.d.mts +2 -25
  80. package/dist/testing/index.mjs +2 -45
  81. package/dist/testing/storageContract.d.mts +1 -1
  82. package/dist/types/index.d.mts +4 -4
  83. package/dist/types/storage.d.mts +1 -1
  84. package/dist/{types-BsJMEQ4D.d.mts → types-BD0BCdf6.d.mts} +1 -1
  85. package/dist/{types-D-fYtKjb.d.mts → types-Dw--SyTq.d.mts} +34 -11
  86. package/dist/utils/index.d.mts +5 -4
  87. package/dist/utils/index.mjs +1 -1
  88. package/dist/{utils-DC5ycPfr.mjs → utils-DPXZnZUx.mjs} +69 -1
  89. package/dist/{validate-By96rH0r.mjs → validate-DFIP2VrM.mjs} +65 -3
  90. package/dist/{versioning-ZwX9tmbS.d.mts → versioning-5H4930tj.d.mts} +1 -1
  91. package/package.json +3 -3
  92. /package/dist/{EventTransport-C-2oAHtw.d.mts → EventTransport-B1GFiYUy.d.mts} +0 -0
  93. /package/dist/{EventTransport-Hxvv5QQz.mjs → EventTransport-ClEDfiMS.mjs} +0 -0
  94. /package/dist/{caching-TeHE8G-v.mjs → caching-8MfrrUYU.mjs} +0 -0
  95. /package/dist/{elevation-0YBpa663.d.mts → elevation-DvwtjDwu.d.mts} +0 -0
  96. /package/dist/{errorHandler-DpoXQHZ9.mjs → errorHandler-DNPzqaTT.mjs} +0 -0
  97. /package/dist/{errorHandler-mHuyWzZE.d.mts → errorHandler-DuqTqeqn.d.mts} +0 -0
  98. /package/dist/{errors-C1lX_jlm.d.mts → errors-4ZloYFvm.d.mts} +0 -0
  99. /package/dist/{externalPaths-DFg-2KTp.d.mts → externalPaths-ClykOQqa.d.mts} +0 -0
  100. /package/dist/{generate-CYac-OLv.mjs → generate-CcM24hoq.mjs} +0 -0
  101. /package/dist/{generate-BWFwgcCM.d.mts → generate-cRnsafMS.d.mts} +0 -0
  102. /package/dist/{init-Dv71MsJr.d.mts → init-D-vrTZ1G.d.mts} +0 -0
  103. /package/dist/{init-HDvoO9L5.mjs → init-DYzfniuX.mjs} +0 -0
  104. /package/dist/{interface-CH0OQudo.d.mts → interface-BVkZsvEe.d.mts} +0 -0
  105. /package/dist/{interface-NwJ_qPlY.d.mts → interface-DoE6UoC2.d.mts} +0 -0
  106. /package/dist/{loadResources-ChQEj8ih.mjs → loadResources-fol6JuvW.mjs} +0 -0
  107. /package/dist/{metrics-TuOmguhi.mjs → metrics-B-PU4-Yu.mjs} +0 -0
  108. /package/dist/{replyHelpers-C-gD32oF.mjs → replyHelpers-boS5Rx26.mjs} +0 -0
  109. /package/dist/{schemaIR-Ctc89DSn.mjs → schemaIR-DAOvBWgT.mjs} +0 -0
  110. /package/dist/{sessionManager-BqFegc0W.d.mts → sessionManager-DlmaHUAl.d.mts} +0 -0
  111. /package/dist/{sse-BY6sTy4P.mjs → sse-jh5OqFjR.mjs} +0 -0
  112. /package/dist/{storage-D2KZJAmn.d.mts → storage-HcAgP5Ni.d.mts} +0 -0
  113. /package/dist/{store-helpers-B0sunfZZ.mjs → store-helpers-5JgaxieH.mjs} +0 -0
  114. /package/dist/{tracing-Dm8n7Cnn.d.mts → tracing-bRqqUxFO.d.mts} +0 -0
  115. /package/dist/{types-DVfpSfx2.d.mts → types-CfapW2t8.d.mts} +0 -0
  116. /package/dist/{versioning-B6mimogM.mjs → versioning-CuIwUQZ2.mjs} +0 -0
  117. /package/dist/{websocket-BkjeGZRn.d.mts → websocket-BJ0w-3Dt.d.mts} +0 -0
@@ -11,6 +11,13 @@ var ArcAggregationConfigError = class extends Error {
11
11
  name = "ArcAggregationConfigError";
12
12
  };
13
13
  /**
14
+ * Framework cap on URL-driven aggregation limits when the host opts
15
+ * in to `defaultLimit` without explicit `maxLimit`. Set to the same
16
+ * order of magnitude as a "large dashboard page" — bigger than every
17
+ * realistic grouped tile, smaller than "we returned the whole table."
18
+ */
19
+ const AGG_FRAMEWORK_MAX_LIMIT = 1e3;
20
+ /**
14
21
  * Validate + normalize all aggregations on a resource. Throws on first
15
22
  * misconfig with the offending aggregation name in the message — hosts
16
23
  * see exactly which entry needs fixing.
@@ -53,6 +60,8 @@ function validateAggregations(resourceName, aggregations, schemaOptions) {
53
60
  bucketAliases
54
61
  });
55
62
  if (config.topN) validateTopNConfig(resourceName, name, config.topN, groupBy, measures, bucketAliases);
63
+ validateLimitConfig(resourceName, name, config);
64
+ const limitPolicy = resolveLimitPolicy(config);
56
65
  out.push({
57
66
  name,
58
67
  base: config,
@@ -65,13 +74,47 @@ function validateAggregations(resourceName, aggregations, schemaOptions) {
65
74
  having: config.having,
66
75
  sort: config.sort,
67
76
  limit: config.limit,
68
- topN: config.topN
77
+ topN: config.topN,
78
+ ...limitPolicy ? { limitPolicy } : {}
69
79
  }
70
80
  });
71
81
  }
72
82
  return out;
73
83
  }
74
84
  /**
85
+ * Validate `limit` / `defaultLimit` / `maxLimit` triad. Throws on
86
+ * misconfig at boot — boundary value, hosts see exactly what to fix.
87
+ */
88
+ function validateLimitConfig(resourceName, aggName, config) {
89
+ const hasStatic = config.limit !== void 0;
90
+ const hasDynamic = config.defaultLimit !== void 0;
91
+ if (hasStatic && hasDynamic) throw new ArcAggregationConfigError(`Resource "${resourceName}" aggregation "${aggName}" sets both \`limit\` (static cap) and \`defaultLimit\` (URL-driven cap). Pick one — static \`limit\` ignores the URL; \`defaultLimit\` reads \`?limit=N\` and caps it at \`maxLimit\`.`);
92
+ if (config.maxLimit !== void 0 && !hasDynamic) throw new ArcAggregationConfigError(`Resource "${resourceName}" aggregation "${aggName}" sets \`maxLimit\` without \`defaultLimit\`. \`maxLimit\` only applies to URL-driven limits — set \`defaultLimit\` or remove \`maxLimit\`.`);
93
+ if (hasStatic) {
94
+ if (!Number.isInteger(config.limit) || config.limit <= 0) throw new ArcAggregationConfigError(`Resource "${resourceName}" aggregation "${aggName}" \`limit\` must be a positive integer — got ${String(config.limit)}.`);
95
+ }
96
+ if (hasDynamic) {
97
+ if (!Number.isInteger(config.defaultLimit) || config.defaultLimit <= 0) throw new ArcAggregationConfigError(`Resource "${resourceName}" aggregation "${aggName}" \`defaultLimit\` must be a positive integer — got ${String(config.defaultLimit)}.`);
98
+ if (config.maxLimit !== void 0) {
99
+ if (!Number.isInteger(config.maxLimit) || config.maxLimit <= 0) throw new ArcAggregationConfigError(`Resource "${resourceName}" aggregation "${aggName}" \`maxLimit\` must be a positive integer — got ${String(config.maxLimit)}.`);
100
+ if (config.maxLimit < config.defaultLimit) throw new ArcAggregationConfigError(`Resource "${resourceName}" aggregation "${aggName}" \`maxLimit\` (${config.maxLimit}) is less than \`defaultLimit\` (${config.defaultLimit}). The ceiling must be ≥ the default.`);
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * Resolve the URL-driven limit policy into a concrete `{ default, max }`
106
+ * pair. Returns `undefined` when the host uses the static `limit` form
107
+ * (or no limit at all) — the request handler reads the absence to know
108
+ * "do not parse `?limit=N` from the URL."
109
+ */
110
+ function resolveLimitPolicy(config) {
111
+ if (config.defaultLimit === void 0) return void 0;
112
+ return {
113
+ defaultLimit: config.defaultLimit,
114
+ maxLimit: config.maxLimit ?? AGG_FRAMEWORK_MAX_LIMIT
115
+ };
116
+ }
117
+ /**
75
118
  * Adapter feature-detect for `aggregate()`. Called at boot when the
76
119
  * repository instance is available. Returns `true` when the kit ships
77
120
  * `aggregate`; `false` when missing.
@@ -83,14 +126,33 @@ function adapterSupportsAggregate(repo) {
83
126
  if (!repo || typeof repo !== "object") return false;
84
127
  return typeof repo.aggregate === "function";
85
128
  }
129
+ /**
130
+ * Resolve the effective per-request `limit` — three cases:
131
+ *
132
+ * 1. Host declared static `limit` → URL `?limit` ignored, static wins.
133
+ * 2. Host declared `defaultLimit` → parse `?limit=N`, cap at
134
+ * `maxLimit`, fall back to default on absent / invalid input.
135
+ * 3. Host declared neither → returns `undefined` (no limit applied,
136
+ * preserves pre-2.16.1 behavior).
137
+ */
138
+ function resolveAggRequestLimit(normalized, query) {
139
+ const policy = normalized.compiled.limitPolicy;
140
+ if (!policy) return normalized.compiled.limit;
141
+ const raw = query["limit"];
142
+ if (raw === void 0 || raw === "") return policy.defaultLimit;
143
+ const parsed = typeof raw === "number" ? raw : Number.parseInt(String(raw), 10);
144
+ if (!Number.isFinite(parsed) || parsed <= 0) return policy.defaultLimit;
145
+ return Math.min(Math.floor(parsed), policy.maxLimit);
146
+ }
86
147
  /** Compile to canonical `AggRequest` for `repo.aggregate()` at request time. */
87
- function compileAggRequest(normalized, callerFilter, tenantOptions) {
148
+ function compileAggRequest(normalized, callerFilter, tenantOptions, query) {
88
149
  const filter = {
89
150
  ...normalized.compiled.filter ?? {},
90
151
  ...callerFilter
91
152
  };
92
153
  const executionHints = buildExecutionHints(normalized.base);
93
154
  const cache = buildCacheOptions(normalized.base);
155
+ const limit = query ? resolveAggRequestLimit(normalized, query) : normalized.compiled.limit;
94
156
  return {
95
157
  measures: normalized.compiled.measures,
96
158
  ...Object.keys(filter).length > 0 ? { filter } : {},
@@ -99,7 +161,7 @@ function compileAggRequest(normalized, callerFilter, tenantOptions) {
99
161
  ...normalized.compiled.dateBuckets ? { dateBuckets: normalized.compiled.dateBuckets } : {},
100
162
  ...normalized.compiled.having ? { having: normalized.compiled.having } : {},
101
163
  ...normalized.compiled.sort ? { sort: normalized.compiled.sort } : {},
102
- ...normalized.compiled.limit !== void 0 ? { limit: normalized.compiled.limit } : {},
164
+ ...limit !== void 0 ? { limit } : {},
103
165
  ...normalized.compiled.topN ? { topN: normalized.compiled.topN } : {},
104
166
  ...executionHints ? { executionHints } : {},
105
167
  ...cache ? { cache } : {}
@@ -1,4 +1,4 @@
1
- import { n as DomainEvent } from "./EventTransport-C-2oAHtw.mjs";
1
+ import { n as DomainEvent } from "./EventTransport-B1GFiYUy.mjs";
2
2
  import { FastifyPluginAsync, FastifyRequest } from "fastify";
3
3
 
4
4
  //#region src/plugins/health.d.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.16.0",
3
+ "version": "2.16.1",
4
4
  "description": "Resource-oriented backend framework for Fastify - clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {
@@ -231,7 +231,7 @@
231
231
  "push": "classytic-push",
232
232
  "release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
233
233
  "release": "npm run push -- main && npm run release:tag && npm publish",
234
- "prepublishOnly": "npm run typecheck && npm run lint && npm run build && npm run test:ci && npm run smoke"
234
+ "prepublishOnly": "npm run typecheck && npm run lint && npm run build && npm run test:main && npm run smoke"
235
235
  },
236
236
  "engines": {
237
237
  "node": ">=22"
@@ -352,7 +352,7 @@
352
352
  "@classytic/mongokit": "^3.14.0",
353
353
  "@classytic/primitives": "^0.6.0",
354
354
  "@classytic/repo-core": "^0.5.0",
355
- "@classytic/sqlitekit": "^0.4.0",
355
+ "@classytic/sqlitekit": "^0.5.0",
356
356
  "@classytic/streamline": "^2.3.3",
357
357
  "@fastify/cors": "^11.2.0",
358
358
  "@fastify/helmet": "^13.0.2",
File without changes
File without changes