@bett3r-dev/pv3-types 1.0.0 → 1.4.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/CHANGELOG.md +305 -0
- package/build/adminPlugin.d.ts +24 -0
- package/build/adminPlugin.d.ts.map +1 -0
- package/build/{eventsourcing/utils.js → adminPlugin.js} +1 -1
- package/build/adminPlugin.js.map +1 -0
- package/build/aggregateSchema.d.ts +94 -0
- package/build/aggregateSchema.d.ts.map +1 -0
- package/build/aggregateSchema.js +287 -0
- package/build/aggregateSchema.js.map +1 -0
- package/build/authentication.d.ts +179 -78
- package/build/authentication.d.ts.map +1 -1
- package/build/authentication.js +62 -19
- package/build/authentication.js.map +1 -1
- package/build/cache.d.ts +50 -4
- package/build/cache.d.ts.map +1 -1
- package/build/clusterAdministration.d.ts +712 -225
- package/build/clusterAdministration.d.ts.map +1 -1
- package/build/clusterAdministration.js +175 -52
- package/build/clusterAdministration.js.map +1 -1
- package/build/configuration.d.ts +26 -5
- package/build/configuration.d.ts.map +1 -1
- package/build/database.d.ts +240 -56
- package/build/database.d.ts.map +1 -1
- package/build/database.js +102 -20
- package/build/database.js.map +1 -1
- package/build/endpoints.d.ts +141 -41
- package/build/endpoints.d.ts.map +1 -1
- package/build/endpoints.js +50 -26
- package/build/endpoints.js.map +1 -1
- package/build/errors.d.ts +166 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +276 -0
- package/build/errors.js.map +1 -0
- package/build/evenstore.d.ts +47 -7
- package/build/evenstore.d.ts.map +1 -1
- package/build/eventsourcing/aggregateBuilder.d.ts +17 -8
- package/build/eventsourcing/aggregateBuilder.d.ts.map +1 -1
- package/build/eventsourcing/commandBuilder.d.ts +20 -13
- package/build/eventsourcing/commandBuilder.d.ts.map +1 -1
- package/build/eventsourcing/commandHandlers.d.ts +155 -33
- package/build/eventsourcing/commandHandlers.d.ts.map +1 -1
- package/build/eventsourcing/commandHandlers.js +62 -14
- package/build/eventsourcing/commandHandlers.js.map +1 -1
- package/build/eventsourcing/eventHandlers.d.ts +173 -44
- package/build/eventsourcing/eventHandlers.d.ts.map +1 -1
- package/build/eventsourcing/eventHandlers.js +29 -21
- package/build/eventsourcing/eventHandlers.js.map +1 -1
- package/build/eventsourcing/eventHandlersBuilder.d.ts +18 -12
- package/build/eventsourcing/eventHandlersBuilder.d.ts.map +1 -1
- package/build/eventsourcing/eventHandlersBuilder.js.map +1 -1
- package/build/eventsourcing/eventsourcing.types.d.ts +322 -0
- package/build/eventsourcing/eventsourcing.types.d.ts.map +1 -0
- package/build/eventsourcing/eventsourcing.types.js +26 -0
- package/build/eventsourcing/eventsourcing.types.js.map +1 -0
- package/build/eventsourcing/externalSystemBuilder.d.ts +8 -7
- package/build/eventsourcing/externalSystemBuilder.d.ts.map +1 -1
- package/build/eventsourcing/index.d.ts +2 -378
- package/build/eventsourcing/index.d.ts.map +1 -1
- package/build/eventsourcing/index.js +2 -23
- package/build/eventsourcing/index.js.map +1 -1
- package/build/eventsourcing/invariants.d.ts +34 -10
- package/build/eventsourcing/invariants.d.ts.map +1 -1
- package/build/eventsourcing/shapeChangeForSubscriber.d.ts +33 -0
- package/build/eventsourcing/shapeChangeForSubscriber.d.ts.map +1 -0
- package/build/eventsourcing/shapeChangeForSubscriber.js +47 -0
- package/build/eventsourcing/shapeChangeForSubscriber.js.map +1 -0
- package/build/index.d.ts +6 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -1
- package/build/index.js.map +1 -1
- package/build/infrastructure.d.ts +9 -1
- package/build/infrastructure.d.ts.map +1 -1
- package/build/infrastructure.js +33 -1
- package/build/infrastructure.js.map +1 -1
- package/build/livenessWatchdog.d.ts +28 -0
- package/build/livenessWatchdog.d.ts.map +1 -0
- package/build/livenessWatchdog.js +43 -0
- package/build/livenessWatchdog.js.map +1 -0
- package/build/logger.d.ts +8 -9
- package/build/logger.d.ts.map +1 -1
- package/build/logger.js +18 -6
- package/build/logger.js.map +1 -1
- package/build/outboxManager.d.ts +858 -14
- package/build/outboxManager.d.ts.map +1 -1
- package/build/outboxManager.js +173 -15
- package/build/outboxManager.js.map +1 -1
- package/build/permissions.d.ts +47 -0
- package/build/permissions.d.ts.map +1 -0
- package/build/permissions.js +3 -0
- package/build/permissions.js.map +1 -0
- package/build/ports.d.ts +10 -10
- package/build/ports.d.ts.map +1 -1
- package/build/routesAuth.d.ts +17 -25
- package/build/routesAuth.d.ts.map +1 -1
- package/build/routesAuth.js +4 -14
- package/build/routesAuth.js.map +1 -1
- package/build/typedEventEmitter.d.ts +7 -13
- package/build/typedEventEmitter.d.ts.map +1 -1
- package/build/typedEventEmitter.js +2 -2
- package/build/typedEventEmitter.js.map +1 -1
- package/build/userSession.d.ts +8 -0
- package/build/userSession.d.ts.map +1 -0
- package/build/userSession.js +3 -0
- package/build/userSession.js.map +1 -0
- package/package.json +22 -7
- package/build/eventsourcing/utils.d.ts +0 -4
- package/build/eventsourcing/utils.d.ts.map +0 -1
- package/build/eventsourcing/utils.js.map +0 -1
- package/build/validation.d.ts +0 -64
- package/build/validation.d.ts.map +0 -1
- package/build/validation.js +0 -57
- package/build/validation.js.map +0 -1
- package/src/authentication.ts +0 -273
- package/src/cache.ts +0 -109
- package/src/clusterAdministration.ts +0 -216
- package/src/configuration.ts +0 -71
- package/src/database.ts +0 -157
- package/src/endpoints.ts +0 -198
- package/src/evenstore.ts +0 -27
- package/src/eventsourcing/aggregateBuilder.ts +0 -11
- package/src/eventsourcing/commandBuilder.ts +0 -64
- package/src/eventsourcing/commandHandlers.ts +0 -137
- package/src/eventsourcing/eventHandlers.ts +0 -118
- package/src/eventsourcing/eventHandlersBuilder.ts +0 -42
- package/src/eventsourcing/externalSystemBuilder.ts +0 -20
- package/src/eventsourcing/index.ts +0 -111
- package/src/eventsourcing/invariants.ts +0 -27
- package/src/eventsourcing/utils.ts +0 -4
- package/src/index.ts +0 -15
- package/src/infrastructure.ts +0 -41
- package/src/logger.ts +0 -45
- package/src/outboxManager.ts +0 -36
- package/src/ports.ts +0 -42
- package/src/routesAuth.ts +0 -58
- package/src/typedEventEmitter.ts +0 -73
- package/src/validation.ts +0 -89
- package/tsconfig.json +0 -15
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AggregateSpecSchema = exports.assertStaticAggregateSpec = exports.assertSafeAggregateIdentifiers = exports.deriveAggregateSchema = exports.AggregateFilterSchema = void 0;
|
|
37
|
+
const jsonschema_definer_1 = __importStar(require("@bett3r-dev/jsonschema-definer"));
|
|
38
|
+
const database_1 = require("./database");
|
|
39
|
+
/**
|
|
40
|
+
* The filter schema embedded under `aggregate.filter`, carrying its OWN `$id` — see
|
|
41
|
+
* `collectionFilterSchemaWithId`. Identical rules to `CollectionFilterSchema`, different identity,
|
|
42
|
+
* because both are materialised in the SAME readmodel query document.
|
|
43
|
+
*/
|
|
44
|
+
exports.AggregateFilterSchema = (0, database_1.collectionFilterSchemaWithId)('AggregateCollectionFilter');
|
|
45
|
+
const errors_1 = require("./errors");
|
|
46
|
+
// `sum`/`avg` are arithmetic: they answer with a total or a mean, which is meaningless over a
|
|
47
|
+
// string or a boolean. `min`/`max`/`count` are NOT arithmetic (they pick an existing value, or
|
|
48
|
+
// tally rows) and stay legal over any type.
|
|
49
|
+
const NUMERIC_TYPES = ['number', 'integer'];
|
|
50
|
+
const isNumericProperty = (property) => {
|
|
51
|
+
const type = property.type;
|
|
52
|
+
// `.nullable()` widens `type` to an ARRAY (`['null','number']`), so both shapes must be read.
|
|
53
|
+
return Array.isArray(type)
|
|
54
|
+
? type.some(t => NUMERIC_TYPES.includes(t))
|
|
55
|
+
: !!type && NUMERIC_TYPES.includes(type);
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* A property with NO `type` at all is a real and confusing case: the message used to read
|
|
59
|
+
* `is of type "undefined"`, which reads like the field is declared as the undefined TYPE rather
|
|
60
|
+
* than like the declaration is MISSING. The two have different fixes, so they get different words.
|
|
61
|
+
*/
|
|
62
|
+
const describeType = (property) => property?.type === undefined
|
|
63
|
+
? 'has no declared type'
|
|
64
|
+
: `is of type "${property.type}"`;
|
|
65
|
+
const reject = (message) => {
|
|
66
|
+
// A ValidationError (400), not a bare Error: under Option B the spec crosses HTTP, so the
|
|
67
|
+
// caller supplying it is a client and a malformed spec is their fault, not a 500.
|
|
68
|
+
throw new errors_1.ValidationError(`deriveAggregateSchema: ${message}`, 'INVALID_AGGREGATE_SPEC');
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Rejects a spec that cannot produce a trustworthy shape, ALWAYS naming the offending field —
|
|
72
|
+
* a bare "invalid spec" leaves the author guessing which of several terms is wrong.
|
|
73
|
+
*
|
|
74
|
+
* This lives INSIDE the derivation rather than at the HTTP boundary on purpose: the derivation
|
|
75
|
+
* is the one thing every consumer goes through (`aggregate()`, the endpoint, teselly's
|
|
76
|
+
* authoring-time validator), so every caller inherits the guarantee. Under Option B that means
|
|
77
|
+
* a bad rule is rejected when it is SAVED, not at 3am when it runs.
|
|
78
|
+
*/
|
|
79
|
+
const validateSpec = (modelProperties, spec) => {
|
|
80
|
+
// CHARSET FIRST, membership second: a hostile value is not a property of the model either, so
|
|
81
|
+
// a membership-first order would report "is not a property of the model" for an injection
|
|
82
|
+
// attempt and hide what actually happened.
|
|
83
|
+
(0, exports.assertSafeAggregateIdentifiers)(spec);
|
|
84
|
+
spec.groupBy.forEach(key => {
|
|
85
|
+
if (!(key in modelProperties))
|
|
86
|
+
reject(`groupBy key "${key}" is not a property of the model.`);
|
|
87
|
+
});
|
|
88
|
+
const groupKeys = new Set(spec.groupBy);
|
|
89
|
+
const seenAliases = new Set();
|
|
90
|
+
spec.aggregates.forEach(term => {
|
|
91
|
+
// `count` is the only function legal without a field; when one IS given it must still exist.
|
|
92
|
+
if (term.field !== undefined && !(term.field in modelProperties))
|
|
93
|
+
reject(`aggregate field "${term.field}" (fn "${term.fn}", as "${term.as}") is not a property of the model.`);
|
|
94
|
+
if ((term.fn === 'sum' || term.fn === 'avg') && !isNumericProperty(modelProperties[term.field]))
|
|
95
|
+
reject(`fn "${term.fn}" requires a numeric field, but "${term.field}" ${describeType(modelProperties[term.field])}.`);
|
|
96
|
+
// THE REGRESSION THIS SLICE CLOSES: the design spike accepted an alias equal to a groupBy
|
|
97
|
+
// key and SILENTLY CLOBBERED the group key in the derived shape, handing downstream a
|
|
98
|
+
// number where an account id was declared.
|
|
99
|
+
if (groupKeys.has(term.as))
|
|
100
|
+
reject(`alias "${term.as}" collides with a groupBy key of the same name.`);
|
|
101
|
+
if (seenAliases.has(term.as))
|
|
102
|
+
reject(`alias "${term.as}" is used by more than one aggregate.`);
|
|
103
|
+
seenAliases.add(term.as);
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Derives the RESPONSE SCHEMA of an aggregate row from the model's schema plus the spec.
|
|
108
|
+
*
|
|
109
|
+
* This is one of the three co-derivations of a single spec value (the other two being the
|
|
110
|
+
* `AggregateRow` TS type and the adapter's SQL). It is deliberately a PURE function of
|
|
111
|
+
* `(modelSchema, spec)` — no ports, no I/O, no dependency on endpoint registration — because
|
|
112
|
+
* the authoring-time consumer (teselly's rule validator) calls it on a spec pv3 never saw at
|
|
113
|
+
* build time. Building it as an internal of endpoint registration is the one decision here
|
|
114
|
+
* that would be expensive to undo.
|
|
115
|
+
*
|
|
116
|
+
* The rules:
|
|
117
|
+
* - a `groupBy` key copies the model's property schema, facets (enum, format) included, made
|
|
118
|
+
* NULLABLE: a row lacking the key groups under SQL NULL, and the adapter projects that group's
|
|
119
|
+
* key as JS `null` (invariant I1 — the schema must accept what the SQL can legitimately hand
|
|
120
|
+
* back);
|
|
121
|
+
* - every OTHER `groupBy` facet survives onto the row rather than being flattened to a loose
|
|
122
|
+
* primitive;
|
|
123
|
+
* - a `count`/`sum` alias is always `number` — neither can be empty (a group exists because a
|
|
124
|
+
* row exists, and the empty sum is COALESCEd to its identity element, 0);
|
|
125
|
+
* - an `avg` alias is `number | null`, and a `min`/`max` alias copies the SOURCE field's
|
|
126
|
+
* property schema verbatim — facets included (invariant I4) — made NULLABLE. This is the
|
|
127
|
+
* half a TS type cannot carry: `AggregateTermType` can say `string`, only the schema can
|
|
128
|
+
* say `format: 'date-time'`, and a consumer generating a client from it would otherwise be
|
|
129
|
+
* handed a looser contract than the readmodel's own. The nullability mirrors
|
|
130
|
+
* `AggregateTermType` exactly: these three functions have no identity element, so a group
|
|
131
|
+
* whose every row lacks the field yields null rather than an invented value. `.nullable()`
|
|
132
|
+
* is the library's own mechanism and preserves the other facets (it widens `type` and, for
|
|
133
|
+
* an enum, appends null to the enum) instead of flattening the schema;
|
|
134
|
+
* - every other model property DISAPPEARS — an aggregate row is a new type, not a partial
|
|
135
|
+
* model.
|
|
136
|
+
*/
|
|
137
|
+
const deriveAggregateSchema = (modelSchema, spec) => {
|
|
138
|
+
const modelProperties = modelSchema.plain.properties || {};
|
|
139
|
+
// Validate BEFORE deriving: a shape derived from a malformed spec is worse than no shape,
|
|
140
|
+
// because it is permissive (an unknown field used to become `raw( undefined )` — a property
|
|
141
|
+
// with no constraints at all) and every downstream consumer believes it.
|
|
142
|
+
validateSpec(modelProperties, spec);
|
|
143
|
+
const groupProperties = spec.groupBy.reduce((acc, key) => ({
|
|
144
|
+
...acc,
|
|
145
|
+
// A row lacking this key groups under SQL NULL (the adapter projects it as JS `null`), so
|
|
146
|
+
// the property must be NULLABLE — mirroring the avg/min/max aliases below, `.nullable()` is
|
|
147
|
+
// used rather than a hand-rolled `anyOf` because it widens `type` and, for an enum, appends
|
|
148
|
+
// `null` to the enum while leaving every other facet (format, etc.) verbatim.
|
|
149
|
+
[key]: new jsonschema_definer_1.BaseSchema().raw(modelProperties[key])
|
|
150
|
+
.nullable()
|
|
151
|
+
}), {});
|
|
152
|
+
const aggregateProperties = spec.aggregates.reduce((acc, term) => ({
|
|
153
|
+
...acc,
|
|
154
|
+
[term.as]: term.fn === 'min' || term.fn === 'max' ? new jsonschema_definer_1.BaseSchema().raw(modelProperties[term.field])
|
|
155
|
+
.nullable()
|
|
156
|
+
: term.fn === 'avg' ? jsonschema_definer_1.default.number().nullable()
|
|
157
|
+
: jsonschema_definer_1.default.number()
|
|
158
|
+
}), {});
|
|
159
|
+
return jsonschema_definer_1.default.shape({ ...groupProperties, ...aggregateProperties });
|
|
160
|
+
};
|
|
161
|
+
exports.deriveAggregateSchema = deriveAggregateSchema;
|
|
162
|
+
/**
|
|
163
|
+
* TV1-2342 slice 7 — THE WIRE BOUNDARY.
|
|
164
|
+
*
|
|
165
|
+
* Everything below exists because of the resolved fork (Option B): the spec is composed at
|
|
166
|
+
* AUTHORING time and travels to pv3 IN THE QUERY STRING, so from here on it is untrusted,
|
|
167
|
+
* attacker-controllable input. In slice 1 the spec was compile-time-bound and none of this was
|
|
168
|
+
* needed; nothing about `translateAggregate` changed, the THREAT MODEL did.
|
|
169
|
+
*/
|
|
170
|
+
/**
|
|
171
|
+
* Every identifier in a spec is INTERPOLATED into the statement, never bound:
|
|
172
|
+
* - `as` -> `... AS "${term.as}"`
|
|
173
|
+
* - `groupBy` -> `data#>>'{${key}}'` and `(ARRAY_AGG(data#>'{${key}}'))[1]`
|
|
174
|
+
* - `field` -> the same accessors
|
|
175
|
+
* A parameter placeholder cannot stand in any of those positions, so the ONLY defence is to
|
|
176
|
+
* refuse anything that is not a plain identifier. This is REJECTION, deliberately, not escaping:
|
|
177
|
+
* an escaping layer is a thing that can be got subtly wrong once and is then trusted forever,
|
|
178
|
+
* whereas a charset that admits no quote, no space, no `;` and no `-` cannot express an injection
|
|
179
|
+
* at all. `maxGroups`/`maxGroupSize` are the other two interpolated values and are guarded by
|
|
180
|
+
* `Number.isSafeInteger` in the adapter plus the wire schema's `integer` below.
|
|
181
|
+
*
|
|
182
|
+
* NOTE ON SCOPE: this admits only TOP-LEVEL identifiers (no dots). That is not a restriction in
|
|
183
|
+
* practice — `deriveAggregateSchema` already requires every key to be a top-level property of the
|
|
184
|
+
* model schema — but it is stated so a future reader adding nested-path support knows this guard
|
|
185
|
+
* is the second place to change.
|
|
186
|
+
*/
|
|
187
|
+
const SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
|
|
188
|
+
const assertSafeIdentifier = (role, value) => {
|
|
189
|
+
if (!SAFE_IDENTIFIER.test(value))
|
|
190
|
+
reject(`${role} "${value}" is not a safe identifier — it must match ${SAFE_IDENTIFIER.source} (it is interpolated into SQL, so it is never escaped or quoted, only rejected).`);
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Charset-checks every value in the spec that reaches SQL by interpolation.
|
|
194
|
+
*
|
|
195
|
+
* Called from `validateSpec` (i.e. from INSIDE `deriveAggregateSchema`), so every caller that
|
|
196
|
+
* DERIVES — the endpoint, teselly's authoring-time validator — inherits it. It is deliberately
|
|
197
|
+
* NOT only there: `aggregate()` does NOT derive, so the Postgres adapter runs this check itself
|
|
198
|
+
* (`assertAggregateBoundsAreValid`, postgres.utils.ts) immediately before interpolating. The
|
|
199
|
+
* derivation is a chokepoint for the HTTP path, not for the port. Exported as well, so the
|
|
200
|
+
* boundary can run it FIRST and
|
|
201
|
+
* report "not a safe identifier" instead of the less useful "not a property of the model" that a
|
|
202
|
+
* membership check would report for the same hostile value.
|
|
203
|
+
*/
|
|
204
|
+
const assertSafeAggregateIdentifiers = (spec) => {
|
|
205
|
+
spec.groupBy.forEach(key => assertSafeIdentifier('groupBy key', String(key)));
|
|
206
|
+
spec.aggregates.forEach(term => {
|
|
207
|
+
if (term.field !== undefined)
|
|
208
|
+
assertSafeIdentifier('aggregate field', String(term.field));
|
|
209
|
+
assertSafeIdentifier('alias "as"', String(term.as));
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
exports.assertSafeAggregateIdentifiers = assertSafeAggregateIdentifiers;
|
|
213
|
+
const STATIC_VIOLATION = 'must be a STATIC string literal';
|
|
214
|
+
/**
|
|
215
|
+
* INVARIANT I5 — the spec must be STATIC.
|
|
216
|
+
*
|
|
217
|
+
* This is the condition the whole design rests on, not a hygiene check. `AggregateRow`, the
|
|
218
|
+
* derived JSON Schema and the SQL are all computed FROM the spec; if a `groupBy` key or an
|
|
219
|
+
* aggregate `field` can arrive from a runtime value (an upstream edge in a rule graph, a
|
|
220
|
+
* template placeholder resolved later) then the row shape is not derivable at authoring time and
|
|
221
|
+
* the entire scheme degrades to `any` — silently, because a spec with a placeholder in it is a
|
|
222
|
+
* perfectly well-formed object.
|
|
223
|
+
*
|
|
224
|
+
* So the two runtime-reference shapes are refused explicitly:
|
|
225
|
+
* - a NON-STRING where an identifier belongs (`{ from: 'input.field' }`, the shape a rules-engine
|
|
226
|
+
* edge would produce);
|
|
227
|
+
* - a string carrying template syntax (`{{...}}`, `${...}`), which would otherwise sail through
|
|
228
|
+
* the charset guard's error path with a confusing message.
|
|
229
|
+
* Run BEFORE the charset guard so the failure names the right problem.
|
|
230
|
+
*/
|
|
231
|
+
const assertStaticAggregateSpec = (spec) => {
|
|
232
|
+
if (!spec || typeof spec !== 'object' || Array.isArray(spec))
|
|
233
|
+
reject('the aggregate spec must be an object with a static `groupBy` and `aggregates`.');
|
|
234
|
+
const candidate = spec;
|
|
235
|
+
const assertStaticString = (role, value) => {
|
|
236
|
+
if (typeof value !== 'string')
|
|
237
|
+
reject(`${role} ${STATIC_VIOLATION}, received ${JSON.stringify(value)} — a runtime-supplied identifier makes the row shape underivable (invariant I5).`);
|
|
238
|
+
if (/\{\{|\}\}|\$\{/.test(value))
|
|
239
|
+
reject(`${role} ${STATIC_VIOLATION}, but "${value}" contains template syntax — a runtime-supplied identifier makes the row shape underivable (invariant I5).`);
|
|
240
|
+
};
|
|
241
|
+
if (!Array.isArray(candidate.groupBy))
|
|
242
|
+
reject(`\`groupBy\` must be a STATIC array of literal keys, received ${JSON.stringify(candidate.groupBy)}.`);
|
|
243
|
+
candidate.groupBy.forEach(key => assertStaticString('groupBy key', key));
|
|
244
|
+
if (!Array.isArray(candidate.aggregates))
|
|
245
|
+
reject(`\`aggregates\` must be a STATIC array of literal terms, received ${JSON.stringify(candidate.aggregates)}.`);
|
|
246
|
+
candidate.aggregates.forEach(term => {
|
|
247
|
+
if (!term || typeof term !== 'object' || Array.isArray(term))
|
|
248
|
+
reject(`each aggregate term ${STATIC_VIOLATION.replace('string literal', 'object literal')}, received ${JSON.stringify(term)}.`);
|
|
249
|
+
const t = term;
|
|
250
|
+
if (t.field !== undefined)
|
|
251
|
+
assertStaticString('aggregate field', t.field);
|
|
252
|
+
assertStaticString('alias "as"', t.as);
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
exports.assertStaticAggregateSpec = assertStaticAggregateSpec;
|
|
256
|
+
/**
|
|
257
|
+
* The WIRE schema of an aggregate spec — what a caller may put in the query string.
|
|
258
|
+
*
|
|
259
|
+
* It validates the spec's SHAPE only (the function vocabulary, the bound types, the filter
|
|
260
|
+
* clauses). The SEMANTIC rules — field exists on the model, `sum` over a numeric field, alias
|
|
261
|
+
* collisions — deliberately stay in `deriveAggregateSchema`, which is the single place every
|
|
262
|
+
* caller passes through. Duplicating them here would create two rule sets that can drift.
|
|
263
|
+
*
|
|
264
|
+
* `filter` reuses the same filter DEFINITION (under its own `$id`) so a filter means exactly the same thing on
|
|
265
|
+
* `aggregate` as it does on `query`.
|
|
266
|
+
*/
|
|
267
|
+
exports.AggregateSpecSchema = jsonschema_definer_1.default.shape({
|
|
268
|
+
groupBy: jsonschema_definer_1.default.array().items(jsonschema_definer_1.default.string()),
|
|
269
|
+
aggregates: jsonschema_definer_1.default.array().items(jsonschema_definer_1.default.shape({
|
|
270
|
+
fn: jsonschema_definer_1.default.string().enum('sum', 'count', 'min', 'max', 'avg'),
|
|
271
|
+
field: jsonschema_definer_1.default.string().optional(),
|
|
272
|
+
as: jsonschema_definer_1.default.string()
|
|
273
|
+
})),
|
|
274
|
+
// Its OWN `$id`, not `CollectionFilterSchema`'s: `ReadmodelDefaultQuerySchema` embeds a filter at
|
|
275
|
+
// `filter` AND at `aggregate.filter`, and two identical `$id`s in one document make ajv refuse to
|
|
276
|
+
// compile the ENTIRE validator. Same rules, same shape, distinct identity.
|
|
277
|
+
filter: jsonschema_definer_1.default.array().items(exports.AggregateFilterSchema)
|
|
278
|
+
.optional(),
|
|
279
|
+
// Interpolated into the statement as a LIMIT, so `integer` here and `Number.isSafeInteger` in
|
|
280
|
+
// the adapter. Both, deliberately: this one gives the caller a 400 with a useful message, the
|
|
281
|
+
// adapter's one is what protects a caller that never came through this boundary.
|
|
282
|
+
maxGroups: jsonschema_definer_1.default.integer().minimum(1)
|
|
283
|
+
.optional(),
|
|
284
|
+
maxGroupSize: jsonschema_definer_1.default.integer().minimum(1)
|
|
285
|
+
.optional()
|
|
286
|
+
});
|
|
287
|
+
//# sourceMappingURL=aggregateSchema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregateSchema.js","sourceRoot":"","sources":["../src/aggregateSchema.ts"],"names":[],"mappings":"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"}
|