@livon/schema 0.27.0-rc.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.
- package/PROMPT.md +21 -0
- package/README.md +13 -0
- package/SCHEMA.md +13 -0
- package/dist/SchemaValidationError.cjs +41 -0
- package/dist/SchemaValidationError.d.ts +20 -0
- package/dist/SchemaValidationError.js +7 -0
- package/dist/SchemaValidationError.spec.cjs +65 -0
- package/dist/SchemaValidationError.spec.d.ts +1 -0
- package/dist/SchemaValidationError.spec.js +59 -0
- package/dist/after.cjs +36 -0
- package/dist/after.d.ts +30 -0
- package/dist/after.js +2 -0
- package/dist/after.spec.cjs +54 -0
- package/dist/after.spec.d.ts +1 -0
- package/dist/after.spec.js +48 -0
- package/dist/and.cjs +36 -0
- package/dist/and.d.ts +26 -0
- package/dist/and.js +2 -0
- package/dist/and.spec.cjs +57 -0
- package/dist/and.spec.d.ts +1 -0
- package/dist/and.spec.js +51 -0
- package/dist/api.cjs +317 -0
- package/dist/api.d.ts +107 -0
- package/dist/api.js +277 -0
- package/dist/api.spec.cjs +512 -0
- package/dist/api.spec.d.ts +1 -0
- package/dist/api.spec.js +506 -0
- package/dist/array.cjs +74 -0
- package/dist/array.d.ts +25 -0
- package/dist/array.js +40 -0
- package/dist/array.spec.cjs +167 -0
- package/dist/array.spec.d.ts +1 -0
- package/dist/array.spec.js +161 -0
- package/dist/before.cjs +36 -0
- package/dist/before.d.ts +30 -0
- package/dist/before.js +2 -0
- package/dist/before.spec.cjs +54 -0
- package/dist/before.spec.d.ts +1 -0
- package/dist/before.spec.js +48 -0
- package/dist/binary.cjs +53 -0
- package/dist/binary.d.ts +24 -0
- package/dist/binary.js +19 -0
- package/dist/binary.spec.cjs +107 -0
- package/dist/binary.spec.d.ts +1 -0
- package/dist/binary.spec.js +101 -0
- package/dist/boolean.cjs +53 -0
- package/dist/boolean.d.ts +24 -0
- package/dist/boolean.js +19 -0
- package/dist/boolean.spec.cjs +96 -0
- package/dist/boolean.spec.d.ts +1 -0
- package/dist/boolean.spec.js +90 -0
- package/dist/context.cjs +125 -0
- package/dist/context.d.ts +101 -0
- package/dist/context.js +76 -0
- package/dist/context.spec.cjs +244 -0
- package/dist/context.spec.d.ts +1 -0
- package/dist/context.spec.js +238 -0
- package/dist/date.cjs +53 -0
- package/dist/date.d.ts +24 -0
- package/dist/date.js +19 -0
- package/dist/date.spec.cjs +97 -0
- package/dist/date.spec.d.ts +1 -0
- package/dist/date.spec.js +91 -0
- package/dist/doc.cjs +54 -0
- package/dist/doc.d.ts +25 -0
- package/dist/doc.js +17 -0
- package/dist/doc.spec.cjs +99 -0
- package/dist/doc.spec.d.ts +1 -0
- package/dist/doc.spec.js +93 -0
- package/dist/enumeration.cjs +74 -0
- package/dist/enumeration.d.ts +50 -0
- package/dist/enumeration.js +40 -0
- package/dist/enumeration.spec.cjs +110 -0
- package/dist/enumeration.spec.d.ts +1 -0
- package/dist/enumeration.spec.js +104 -0
- package/dist/hydrate.cjs +18 -0
- package/dist/hydrate.d.ts +1 -0
- package/dist/hydrate.js +0 -0
- package/dist/index.cjs +145 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +24 -0
- package/dist/index.spec.cjs +43 -0
- package/dist/index.spec.d.ts +1 -0
- package/dist/index.spec.js +37 -0
- package/dist/literal.cjs +55 -0
- package/dist/literal.d.ts +25 -0
- package/dist/literal.js +21 -0
- package/dist/literal.spec.cjs +93 -0
- package/dist/literal.spec.d.ts +1 -0
- package/dist/literal.spec.js +87 -0
- package/dist/number.cjs +89 -0
- package/dist/number.d.ts +84 -0
- package/dist/number.js +55 -0
- package/dist/number.spec.cjs +155 -0
- package/dist/number.spec.d.ts +1 -0
- package/dist/number.spec.js +149 -0
- package/dist/object.cjs +66 -0
- package/dist/object.d.ts +37 -0
- package/dist/object.js +32 -0
- package/dist/object.spec.cjs +171 -0
- package/dist/object.spec.d.ts +1 -0
- package/dist/object.spec.js +165 -0
- package/dist/operation.cjs +182 -0
- package/dist/operation.d.ts +197 -0
- package/dist/operation.js +133 -0
- package/dist/operation.spec.cjs +454 -0
- package/dist/operation.spec.d.ts +1 -0
- package/dist/operation.spec.js +448 -0
- package/dist/or.cjs +85 -0
- package/dist/or.d.ts +37 -0
- package/dist/or.js +51 -0
- package/dist/or.spec.cjs +204 -0
- package/dist/or.spec.d.ts +1 -0
- package/dist/or.spec.js +198 -0
- package/dist/schema.cjs +285 -0
- package/dist/schema.d.ts +132 -0
- package/dist/schema.js +233 -0
- package/dist/schema.spec.cjs +587 -0
- package/dist/schema.spec.d.ts +1 -0
- package/dist/schema.spec.js +581 -0
- package/dist/schemaFactory.cjs +125 -0
- package/dist/schemaFactory.d.ts +97 -0
- package/dist/schemaFactory.js +88 -0
- package/dist/schemaFactory.spec.cjs +197 -0
- package/dist/schemaFactory.spec.d.ts +1 -0
- package/dist/schemaFactory.spec.js +191 -0
- package/dist/schemaModule.cjs +280 -0
- package/dist/schemaModule.d.ts +97 -0
- package/dist/schemaModule.js +243 -0
- package/dist/schemaModule.spec.cjs +355 -0
- package/dist/schemaModule.spec.d.ts +1 -0
- package/dist/schemaModule.spec.js +349 -0
- package/dist/string.cjs +93 -0
- package/dist/string.d.ts +85 -0
- package/dist/string.js +59 -0
- package/dist/string.spec.cjs +158 -0
- package/dist/string.spec.d.ts +1 -0
- package/dist/string.spec.js +152 -0
- package/dist/testing/mocks/assertions.mock.cjs +48 -0
- package/dist/testing/mocks/assertions.mock.d.ts +5 -0
- package/dist/testing/mocks/assertions.mock.js +14 -0
- package/dist/testing/mocks/index.cjs +52 -0
- package/dist/testing/mocks/index.d.ts +4 -0
- package/dist/testing/mocks/index.js +3 -0
- package/dist/testing/mocks/schema.mock.cjs +120 -0
- package/dist/testing/mocks/schema.mock.d.ts +37 -0
- package/dist/testing/mocks/schema.mock.js +74 -0
- package/dist/tuple.cjs +58 -0
- package/dist/tuple.d.ts +33 -0
- package/dist/tuple.js +24 -0
- package/dist/tuple.spec.cjs +162 -0
- package/dist/tuple.spec.d.ts +1 -0
- package/dist/tuple.spec.js +156 -0
- package/dist/typeGuards.cjs +60 -0
- package/dist/typeGuards.d.ts +93 -0
- package/dist/typeGuards.js +8 -0
- package/dist/typeGuards.spec.cjs +101 -0
- package/dist/typeGuards.spec.d.ts +1 -0
- package/dist/typeGuards.spec.js +95 -0
- package/dist/types.cjs +18 -0
- package/dist/types.d.ts +289 -0
- package/dist/types.js +0 -0
- package/dist/union.cjs +74 -0
- package/dist/union.d.ts +33 -0
- package/dist/union.js +40 -0
- package/dist/union.spec.cjs +159 -0
- package/dist/union.spec.d.ts +1 -0
- package/dist/union.spec.js +153 -0
- package/package.json +47 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
schemaModule: ()=>schemaModule,
|
|
28
|
+
createSchemaModuleInput: ()=>createSchemaModuleInput
|
|
29
|
+
});
|
|
30
|
+
const external_context_cjs_namespaceObject = require("./context.cjs");
|
|
31
|
+
const external_operation_cjs_namespaceObject = require("./operation.cjs");
|
|
32
|
+
const external_msgpackr_namespaceObject = require("msgpackr");
|
|
33
|
+
const createSchemaModuleInput = (input)=>({
|
|
34
|
+
operations: input.operations,
|
|
35
|
+
fieldOperations: input.fieldOperations,
|
|
36
|
+
subscriptions: input.subscriptions,
|
|
37
|
+
ast: input.ast
|
|
38
|
+
});
|
|
39
|
+
const isRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
|
|
40
|
+
const defaultEncode = (value)=>(0, external_msgpackr_namespaceObject.pack)(value);
|
|
41
|
+
const defaultDecode = (payload)=>payload instanceof Uint8Array ? (0, external_msgpackr_namespaceObject.unpack)(payload) : payload;
|
|
42
|
+
const stableStringify = (value)=>{
|
|
43
|
+
if (null === value || 'object' != typeof value) return JSON.stringify(value);
|
|
44
|
+
if (Array.isArray(value)) return `[${value.map((entry)=>stableStringify(entry)).join(',')}]`;
|
|
45
|
+
const record = value;
|
|
46
|
+
const keys = Object.keys(record).sort();
|
|
47
|
+
return `{${keys.map((key)=>`${JSON.stringify(key)}:${stableStringify(record[key])}`).join(',')}}`;
|
|
48
|
+
};
|
|
49
|
+
const hashString = (input)=>Array.from(input).reduce((hash, char)=>{
|
|
50
|
+
const next = (hash ^ char.charCodeAt(0)) >>> 0;
|
|
51
|
+
return Math.imul(next, 16777619) >>> 0;
|
|
52
|
+
}, 2166136261).toString(16).padStart(8, '0');
|
|
53
|
+
const getEnvelopeMetadata = (envelope)=>{
|
|
54
|
+
if (envelope.metadata) return envelope.metadata;
|
|
55
|
+
return envelope.meta;
|
|
56
|
+
};
|
|
57
|
+
const normalizeFieldPayload = (payload)=>isRecord(payload) && 'dependsOn' in payload ? payload : {
|
|
58
|
+
dependsOn: payload
|
|
59
|
+
};
|
|
60
|
+
const splitFieldEvent = (event)=>{
|
|
61
|
+
if (!event.startsWith('$')) return;
|
|
62
|
+
const body = event.slice(1);
|
|
63
|
+
const dotIndex = body.indexOf('.');
|
|
64
|
+
if (dotIndex <= 0 || dotIndex === body.length - 1) return;
|
|
65
|
+
return {
|
|
66
|
+
owner: body.slice(0, dotIndex),
|
|
67
|
+
field: body.slice(dotIndex + 1)
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
const mergeMetadata = (base, extra)=>{
|
|
71
|
+
if (!base && !extra) return;
|
|
72
|
+
return {
|
|
73
|
+
...base ?? {},
|
|
74
|
+
...extra ?? {}
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
const buildExplainPayload = (input)=>({
|
|
78
|
+
ast: input.ast,
|
|
79
|
+
checksum: input.checksum,
|
|
80
|
+
schemaVersion: input.schemaVersion,
|
|
81
|
+
generatedAt: new Date(input.now()).toISOString(),
|
|
82
|
+
etag: input.checksum,
|
|
83
|
+
...input.notModified ? {
|
|
84
|
+
notModified: true
|
|
85
|
+
} : {}
|
|
86
|
+
});
|
|
87
|
+
const eventErrorFromUnknown = (error, info)=>{
|
|
88
|
+
if (error instanceof Error) return {
|
|
89
|
+
message: error.message,
|
|
90
|
+
name: error.name,
|
|
91
|
+
stack: error.stack,
|
|
92
|
+
context: info
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
message: 'string' == typeof error ? error : 'Unknown error',
|
|
96
|
+
context: info
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
const normalizeAckConfig = (ack)=>{
|
|
100
|
+
if (void 0 === ack || false === ack) return;
|
|
101
|
+
if (true === ack) return {
|
|
102
|
+
required: true,
|
|
103
|
+
mode: 'received'
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
required: ack.required ?? true,
|
|
107
|
+
mode: ack.mode ?? 'received',
|
|
108
|
+
...'number' == typeof ack.timeoutMs ? {
|
|
109
|
+
timeoutMs: ack.timeoutMs
|
|
110
|
+
} : {},
|
|
111
|
+
...'number' == typeof ack.retries ? {
|
|
112
|
+
retries: ack.retries
|
|
113
|
+
} : {}
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
const resolveSubscriptionPayload = async (input)=>{
|
|
117
|
+
const parsedInput = input.subscription.input ? input.subscription.input.parse(input.input, input.ctx) : input.input;
|
|
118
|
+
const parsedPayload = input.subscription.payload.parse(input.payload, input.ctx);
|
|
119
|
+
if (input.subscription.filter) {
|
|
120
|
+
const allowed = await input.subscription.filter(parsedInput, parsedPayload, input.ctx);
|
|
121
|
+
if (!allowed) return {
|
|
122
|
+
skip: true
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const executed = input.subscription.exec ? await input.subscription.exec(parsedInput, parsedPayload, input.ctx) : parsedPayload;
|
|
126
|
+
const output = input.subscription.output ? input.subscription.output.parse(executed, input.ctx) : executed;
|
|
127
|
+
return {
|
|
128
|
+
skip: false,
|
|
129
|
+
payload: output
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
const emitErrorEvent = async (input)=>input.ctx.emitError({
|
|
133
|
+
id: input.envelope.id,
|
|
134
|
+
event: input.envelope.event,
|
|
135
|
+
status: input.envelope.status,
|
|
136
|
+
...void 0 !== input.envelope.payload ? {
|
|
137
|
+
payload: input.envelope.payload
|
|
138
|
+
} : {},
|
|
139
|
+
error: eventErrorFromUnknown(input.error, input.info),
|
|
140
|
+
metadata: input.metadata,
|
|
141
|
+
context: input.envelope.context ? {
|
|
142
|
+
...input.envelope.context
|
|
143
|
+
} : void 0
|
|
144
|
+
});
|
|
145
|
+
const schemaModule = (schema, options = {})=>{
|
|
146
|
+
const encode = options.encoder ?? defaultEncode;
|
|
147
|
+
const decode = options.decoder ?? defaultDecode;
|
|
148
|
+
const now = options.now ?? Date.now;
|
|
149
|
+
const ast = schema.ast();
|
|
150
|
+
const checksum = hashString(stableStringify(ast));
|
|
151
|
+
const onReceive = async (envelope, ctx, next)=>{
|
|
152
|
+
if ('$explain' === envelope.event) {
|
|
153
|
+
if (!options.explain) return next();
|
|
154
|
+
const metadata = getEnvelopeMetadata(envelope);
|
|
155
|
+
const ifNoneMatch = 'string' == typeof metadata?.ifNoneMatch ? metadata.ifNoneMatch : void 0;
|
|
156
|
+
const notModified = Boolean(ifNoneMatch && ifNoneMatch === checksum);
|
|
157
|
+
const payload = buildExplainPayload({
|
|
158
|
+
notModified,
|
|
159
|
+
ast,
|
|
160
|
+
checksum,
|
|
161
|
+
schemaVersion: options.schemaVersion,
|
|
162
|
+
now
|
|
163
|
+
});
|
|
164
|
+
await ctx.emitEvent({
|
|
165
|
+
event: envelope.event,
|
|
166
|
+
payload: encode(payload),
|
|
167
|
+
metadata,
|
|
168
|
+
context: envelope.context ? {
|
|
169
|
+
...envelope.context
|
|
170
|
+
} : void 0
|
|
171
|
+
});
|
|
172
|
+
return envelope;
|
|
173
|
+
}
|
|
174
|
+
const op = schema.operations[envelope.event];
|
|
175
|
+
const fieldInfo = op ? void 0 : splitFieldEvent(envelope.event);
|
|
176
|
+
const fieldKey = fieldInfo ? `${fieldInfo.owner}.${fieldInfo.field}` : void 0;
|
|
177
|
+
const fieldOp = fieldInfo && !op ? schema.fieldOperations[fieldKey] ?? schema.fieldOperations[fieldInfo.field] : void 0;
|
|
178
|
+
if (!op && !fieldOp) return next();
|
|
179
|
+
const metadata = getEnvelopeMetadata(envelope);
|
|
180
|
+
const requestOverrides = options.getRequestContext ? options.getRequestContext(envelope, ctx) : void 0;
|
|
181
|
+
const externalPublisher = requestOverrides?.publisher;
|
|
182
|
+
const externalOnPublishError = requestOverrides?.onPublishError;
|
|
183
|
+
const schemaContext = (0, external_context_cjs_namespaceObject.createSchemaContext)();
|
|
184
|
+
const publisher = async (input)=>{
|
|
185
|
+
const subscription = schema.subscriptions[input.topic];
|
|
186
|
+
if (!subscription) throw new Error(`schemaModule: publish topic "${input.topic}" has no matching subscription.`);
|
|
187
|
+
const resolved = await resolveSubscriptionPayload({
|
|
188
|
+
subscription,
|
|
189
|
+
input: input.input,
|
|
190
|
+
payload: input.payload,
|
|
191
|
+
ctx: schemaContext
|
|
192
|
+
});
|
|
193
|
+
if (resolved.skip) return;
|
|
194
|
+
const keyMeta = input.key ? {
|
|
195
|
+
key: input.key
|
|
196
|
+
} : void 0;
|
|
197
|
+
const ackMeta = normalizeAckConfig(input.ack);
|
|
198
|
+
const ackWrapper = ackMeta ? {
|
|
199
|
+
ack: ackMeta
|
|
200
|
+
} : void 0;
|
|
201
|
+
const publishMeta = mergeMetadata(metadata, mergeMetadata(input.meta, mergeMetadata(keyMeta, ackWrapper)));
|
|
202
|
+
await ctx.emitEvent({
|
|
203
|
+
event: input.topic,
|
|
204
|
+
payload: encode(resolved.payload),
|
|
205
|
+
metadata: publishMeta,
|
|
206
|
+
context: envelope.context ? {
|
|
207
|
+
...envelope.context
|
|
208
|
+
} : void 0
|
|
209
|
+
});
|
|
210
|
+
if (externalPublisher) await externalPublisher({
|
|
211
|
+
...input,
|
|
212
|
+
payload: resolved.payload,
|
|
213
|
+
...ackMeta ? {
|
|
214
|
+
ack: ackMeta
|
|
215
|
+
} : {}
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
const handlePublishError = (error, info)=>{
|
|
219
|
+
if (externalOnPublishError) externalOnPublishError(error, info);
|
|
220
|
+
emitErrorEvent({
|
|
221
|
+
ctx,
|
|
222
|
+
envelope,
|
|
223
|
+
metadata,
|
|
224
|
+
error,
|
|
225
|
+
info
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
const requestContext = {
|
|
229
|
+
...requestOverrides,
|
|
230
|
+
metadata: mergeMetadata(metadata, requestOverrides?.metadata),
|
|
231
|
+
publisher,
|
|
232
|
+
onPublishError: handlePublishError,
|
|
233
|
+
logger: requestOverrides?.logger ?? options.logger
|
|
234
|
+
};
|
|
235
|
+
schemaContext.setRequestContext(requestContext);
|
|
236
|
+
const input = decode(envelope.payload);
|
|
237
|
+
const fieldPayload = op ? void 0 : normalizeFieldPayload(input);
|
|
238
|
+
let result;
|
|
239
|
+
try {
|
|
240
|
+
result = op ? await (0, external_operation_cjs_namespaceObject.runOperation)(op, input, schemaContext) : await (0, external_operation_cjs_namespaceObject.runFieldOperation)(fieldOp, fieldPayload?.dependsOn, fieldPayload?.input, schemaContext);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
await emitErrorEvent({
|
|
243
|
+
ctx,
|
|
244
|
+
envelope,
|
|
245
|
+
metadata,
|
|
246
|
+
error,
|
|
247
|
+
info: {
|
|
248
|
+
phase: 'execute',
|
|
249
|
+
event: envelope.event
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
return envelope;
|
|
253
|
+
}
|
|
254
|
+
await ctx.emitEvent({
|
|
255
|
+
event: envelope.event,
|
|
256
|
+
payload: encode(result),
|
|
257
|
+
metadata,
|
|
258
|
+
context: envelope.context ? {
|
|
259
|
+
...envelope.context
|
|
260
|
+
} : void 0
|
|
261
|
+
});
|
|
262
|
+
return envelope;
|
|
263
|
+
};
|
|
264
|
+
const register = (registry)=>{
|
|
265
|
+
registry.onReceive(onReceive);
|
|
266
|
+
};
|
|
267
|
+
return {
|
|
268
|
+
name: 'schema',
|
|
269
|
+
register
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
exports.createSchemaModuleInput = __webpack_exports__.createSchemaModuleInput;
|
|
273
|
+
exports.schemaModule = __webpack_exports__.schemaModule;
|
|
274
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
275
|
+
"createSchemaModuleInput",
|
|
276
|
+
"schemaModule"
|
|
277
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
278
|
+
Object.defineProperty(exports, '__esModule', {
|
|
279
|
+
value: true
|
|
280
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type Operation, type FieldOperation } from './operation.js';
|
|
2
|
+
import type { AstNode, Logger, SchemaRequestContextInput, Schema } from './types.js';
|
|
3
|
+
import type { Subscription } from './api.js';
|
|
4
|
+
import type { EventEnvelope, RuntimeContext, RuntimeModule } from '@livon/runtime';
|
|
5
|
+
type AnySchema = Schema<any>;
|
|
6
|
+
type AnyInput = any;
|
|
7
|
+
type AnyResult = any;
|
|
8
|
+
type AnyOperation = Operation<AnySchema, AnySchema | undefined, AnyResult>;
|
|
9
|
+
type AnyFieldOperation = FieldOperation<AnySchema, AnyInput, AnySchema | undefined, AnyResult>;
|
|
10
|
+
type AnySubscription = Subscription<AnySchema | undefined, AnySchema, AnySchema | undefined, unknown>;
|
|
11
|
+
export type SchemaModuleInput = {
|
|
12
|
+
operations: Record<string, AnyOperation>;
|
|
13
|
+
fieldOperations: Record<string, AnyFieldOperation>;
|
|
14
|
+
subscriptions: Record<string, AnySubscription>;
|
|
15
|
+
ast: () => AstNode;
|
|
16
|
+
};
|
|
17
|
+
export interface SchemaModuleLike {
|
|
18
|
+
operations: Record<string, AnyOperation>;
|
|
19
|
+
fieldOperations: Record<string, AnyFieldOperation>;
|
|
20
|
+
subscriptions: Record<string, AnySubscription>;
|
|
21
|
+
ast: () => AstNode;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* createSchemaModuleInput is part of the public LIVON API.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* Parameter and return types are defined in the TypeScript signature.
|
|
28
|
+
*
|
|
29
|
+
* @see https://live-input-vector-output-node.github.io/livon-ts/docs/packages/schema
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const result = createSchemaModuleInput(undefined as never);
|
|
33
|
+
*/
|
|
34
|
+
export declare const createSchemaModuleInput: (input: SchemaModuleLike) => SchemaModuleInput;
|
|
35
|
+
export type SchemaModuleOptions = {
|
|
36
|
+
explain?: boolean;
|
|
37
|
+
schemaVersion?: string;
|
|
38
|
+
now?: SchemaModuleNow;
|
|
39
|
+
encoder?: SchemaModuleEncoder;
|
|
40
|
+
decoder?: SchemaModuleDecoder;
|
|
41
|
+
logger?: Logger;
|
|
42
|
+
getRequestContext?: SchemaModuleGetRequestContext;
|
|
43
|
+
};
|
|
44
|
+
export interface SchemaModuleNow {
|
|
45
|
+
(): number;
|
|
46
|
+
}
|
|
47
|
+
export interface SchemaModuleEncoder {
|
|
48
|
+
(value: unknown): Uint8Array;
|
|
49
|
+
}
|
|
50
|
+
export interface SchemaModuleDecoder {
|
|
51
|
+
(payload: Uint8Array | unknown): unknown;
|
|
52
|
+
}
|
|
53
|
+
export interface SchemaModuleGetRequestContext {
|
|
54
|
+
(envelope: EventEnvelope, ctx: RuntimeContext): SchemaRequestContextInput | undefined;
|
|
55
|
+
}
|
|
56
|
+
export interface ExplainPayload {
|
|
57
|
+
ast: AstNode;
|
|
58
|
+
checksum: string;
|
|
59
|
+
schemaVersion?: string;
|
|
60
|
+
generatedAt: string;
|
|
61
|
+
etag: string;
|
|
62
|
+
notModified?: boolean;
|
|
63
|
+
}
|
|
64
|
+
export interface FieldPayload {
|
|
65
|
+
dependsOn: unknown;
|
|
66
|
+
input?: unknown;
|
|
67
|
+
}
|
|
68
|
+
export interface BuildExplainPayloadInput {
|
|
69
|
+
notModified?: boolean;
|
|
70
|
+
ast: AstNode;
|
|
71
|
+
checksum: string;
|
|
72
|
+
schemaVersion?: string;
|
|
73
|
+
now: () => number;
|
|
74
|
+
}
|
|
75
|
+
export interface EmitErrorEventInput {
|
|
76
|
+
ctx: RuntimeContext;
|
|
77
|
+
envelope: EventEnvelope;
|
|
78
|
+
metadata?: Readonly<Record<string, unknown>>;
|
|
79
|
+
error: unknown;
|
|
80
|
+
info?: Readonly<Record<string, unknown>>;
|
|
81
|
+
}
|
|
82
|
+
export type EnvelopeWithMeta = EventEnvelope & {
|
|
83
|
+
meta?: Readonly<Record<string, unknown>>;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* schemaModule is part of the public LIVON API.
|
|
87
|
+
*
|
|
88
|
+
* @remarks
|
|
89
|
+
* Parameter and return types are defined in the TypeScript signature.
|
|
90
|
+
*
|
|
91
|
+
* @see https://live-input-vector-output-node.github.io/livon-ts/docs/packages/schema
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const result = schemaModule(undefined as never);
|
|
95
|
+
*/
|
|
96
|
+
export declare const schemaModule: (schema: SchemaModuleInput, options?: SchemaModuleOptions) => RuntimeModule;
|
|
97
|
+
export {};
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { createSchemaContext } from "./context.js";
|
|
2
|
+
import { runFieldOperation, runOperation } from "./operation.js";
|
|
3
|
+
import { pack, unpack } from "msgpackr";
|
|
4
|
+
const createSchemaModuleInput = (input)=>({
|
|
5
|
+
operations: input.operations,
|
|
6
|
+
fieldOperations: input.fieldOperations,
|
|
7
|
+
subscriptions: input.subscriptions,
|
|
8
|
+
ast: input.ast
|
|
9
|
+
});
|
|
10
|
+
const isRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
|
|
11
|
+
const defaultEncode = (value)=>pack(value);
|
|
12
|
+
const defaultDecode = (payload)=>payload instanceof Uint8Array ? unpack(payload) : payload;
|
|
13
|
+
const stableStringify = (value)=>{
|
|
14
|
+
if (null === value || 'object' != typeof value) return JSON.stringify(value);
|
|
15
|
+
if (Array.isArray(value)) return `[${value.map((entry)=>stableStringify(entry)).join(',')}]`;
|
|
16
|
+
const record = value;
|
|
17
|
+
const keys = Object.keys(record).sort();
|
|
18
|
+
return `{${keys.map((key)=>`${JSON.stringify(key)}:${stableStringify(record[key])}`).join(',')}}`;
|
|
19
|
+
};
|
|
20
|
+
const hashString = (input)=>Array.from(input).reduce((hash, char)=>{
|
|
21
|
+
const next = (hash ^ char.charCodeAt(0)) >>> 0;
|
|
22
|
+
return Math.imul(next, 16777619) >>> 0;
|
|
23
|
+
}, 2166136261).toString(16).padStart(8, '0');
|
|
24
|
+
const getEnvelopeMetadata = (envelope)=>{
|
|
25
|
+
if (envelope.metadata) return envelope.metadata;
|
|
26
|
+
return envelope.meta;
|
|
27
|
+
};
|
|
28
|
+
const normalizeFieldPayload = (payload)=>isRecord(payload) && 'dependsOn' in payload ? payload : {
|
|
29
|
+
dependsOn: payload
|
|
30
|
+
};
|
|
31
|
+
const splitFieldEvent = (event)=>{
|
|
32
|
+
if (!event.startsWith('$')) return;
|
|
33
|
+
const body = event.slice(1);
|
|
34
|
+
const dotIndex = body.indexOf('.');
|
|
35
|
+
if (dotIndex <= 0 || dotIndex === body.length - 1) return;
|
|
36
|
+
return {
|
|
37
|
+
owner: body.slice(0, dotIndex),
|
|
38
|
+
field: body.slice(dotIndex + 1)
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const mergeMetadata = (base, extra)=>{
|
|
42
|
+
if (!base && !extra) return;
|
|
43
|
+
return {
|
|
44
|
+
...base ?? {},
|
|
45
|
+
...extra ?? {}
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
const buildExplainPayload = (input)=>({
|
|
49
|
+
ast: input.ast,
|
|
50
|
+
checksum: input.checksum,
|
|
51
|
+
schemaVersion: input.schemaVersion,
|
|
52
|
+
generatedAt: new Date(input.now()).toISOString(),
|
|
53
|
+
etag: input.checksum,
|
|
54
|
+
...input.notModified ? {
|
|
55
|
+
notModified: true
|
|
56
|
+
} : {}
|
|
57
|
+
});
|
|
58
|
+
const eventErrorFromUnknown = (error, info)=>{
|
|
59
|
+
if (error instanceof Error) return {
|
|
60
|
+
message: error.message,
|
|
61
|
+
name: error.name,
|
|
62
|
+
stack: error.stack,
|
|
63
|
+
context: info
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
message: 'string' == typeof error ? error : 'Unknown error',
|
|
67
|
+
context: info
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
const normalizeAckConfig = (ack)=>{
|
|
71
|
+
if (void 0 === ack || false === ack) return;
|
|
72
|
+
if (true === ack) return {
|
|
73
|
+
required: true,
|
|
74
|
+
mode: 'received'
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
required: ack.required ?? true,
|
|
78
|
+
mode: ack.mode ?? 'received',
|
|
79
|
+
...'number' == typeof ack.timeoutMs ? {
|
|
80
|
+
timeoutMs: ack.timeoutMs
|
|
81
|
+
} : {},
|
|
82
|
+
...'number' == typeof ack.retries ? {
|
|
83
|
+
retries: ack.retries
|
|
84
|
+
} : {}
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
const resolveSubscriptionPayload = async (input)=>{
|
|
88
|
+
const parsedInput = input.subscription.input ? input.subscription.input.parse(input.input, input.ctx) : input.input;
|
|
89
|
+
const parsedPayload = input.subscription.payload.parse(input.payload, input.ctx);
|
|
90
|
+
if (input.subscription.filter) {
|
|
91
|
+
const allowed = await input.subscription.filter(parsedInput, parsedPayload, input.ctx);
|
|
92
|
+
if (!allowed) return {
|
|
93
|
+
skip: true
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const executed = input.subscription.exec ? await input.subscription.exec(parsedInput, parsedPayload, input.ctx) : parsedPayload;
|
|
97
|
+
const output = input.subscription.output ? input.subscription.output.parse(executed, input.ctx) : executed;
|
|
98
|
+
return {
|
|
99
|
+
skip: false,
|
|
100
|
+
payload: output
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
const emitErrorEvent = async (input)=>input.ctx.emitError({
|
|
104
|
+
id: input.envelope.id,
|
|
105
|
+
event: input.envelope.event,
|
|
106
|
+
status: input.envelope.status,
|
|
107
|
+
...void 0 !== input.envelope.payload ? {
|
|
108
|
+
payload: input.envelope.payload
|
|
109
|
+
} : {},
|
|
110
|
+
error: eventErrorFromUnknown(input.error, input.info),
|
|
111
|
+
metadata: input.metadata,
|
|
112
|
+
context: input.envelope.context ? {
|
|
113
|
+
...input.envelope.context
|
|
114
|
+
} : void 0
|
|
115
|
+
});
|
|
116
|
+
const schemaModule = (schema, options = {})=>{
|
|
117
|
+
const encode = options.encoder ?? defaultEncode;
|
|
118
|
+
const decode = options.decoder ?? defaultDecode;
|
|
119
|
+
const now = options.now ?? Date.now;
|
|
120
|
+
const ast = schema.ast();
|
|
121
|
+
const checksum = hashString(stableStringify(ast));
|
|
122
|
+
const onReceive = async (envelope, ctx, next)=>{
|
|
123
|
+
if ('$explain' === envelope.event) {
|
|
124
|
+
if (!options.explain) return next();
|
|
125
|
+
const metadata = getEnvelopeMetadata(envelope);
|
|
126
|
+
const ifNoneMatch = 'string' == typeof metadata?.ifNoneMatch ? metadata.ifNoneMatch : void 0;
|
|
127
|
+
const notModified = Boolean(ifNoneMatch && ifNoneMatch === checksum);
|
|
128
|
+
const payload = buildExplainPayload({
|
|
129
|
+
notModified,
|
|
130
|
+
ast,
|
|
131
|
+
checksum,
|
|
132
|
+
schemaVersion: options.schemaVersion,
|
|
133
|
+
now
|
|
134
|
+
});
|
|
135
|
+
await ctx.emitEvent({
|
|
136
|
+
event: envelope.event,
|
|
137
|
+
payload: encode(payload),
|
|
138
|
+
metadata,
|
|
139
|
+
context: envelope.context ? {
|
|
140
|
+
...envelope.context
|
|
141
|
+
} : void 0
|
|
142
|
+
});
|
|
143
|
+
return envelope;
|
|
144
|
+
}
|
|
145
|
+
const op = schema.operations[envelope.event];
|
|
146
|
+
const fieldInfo = op ? void 0 : splitFieldEvent(envelope.event);
|
|
147
|
+
const fieldKey = fieldInfo ? `${fieldInfo.owner}.${fieldInfo.field}` : void 0;
|
|
148
|
+
const fieldOp = fieldInfo && !op ? schema.fieldOperations[fieldKey] ?? schema.fieldOperations[fieldInfo.field] : void 0;
|
|
149
|
+
if (!op && !fieldOp) return next();
|
|
150
|
+
const metadata = getEnvelopeMetadata(envelope);
|
|
151
|
+
const requestOverrides = options.getRequestContext ? options.getRequestContext(envelope, ctx) : void 0;
|
|
152
|
+
const externalPublisher = requestOverrides?.publisher;
|
|
153
|
+
const externalOnPublishError = requestOverrides?.onPublishError;
|
|
154
|
+
const schemaContext = createSchemaContext();
|
|
155
|
+
const publisher = async (input)=>{
|
|
156
|
+
const subscription = schema.subscriptions[input.topic];
|
|
157
|
+
if (!subscription) throw new Error(`schemaModule: publish topic "${input.topic}" has no matching subscription.`);
|
|
158
|
+
const resolved = await resolveSubscriptionPayload({
|
|
159
|
+
subscription,
|
|
160
|
+
input: input.input,
|
|
161
|
+
payload: input.payload,
|
|
162
|
+
ctx: schemaContext
|
|
163
|
+
});
|
|
164
|
+
if (resolved.skip) return;
|
|
165
|
+
const keyMeta = input.key ? {
|
|
166
|
+
key: input.key
|
|
167
|
+
} : void 0;
|
|
168
|
+
const ackMeta = normalizeAckConfig(input.ack);
|
|
169
|
+
const ackWrapper = ackMeta ? {
|
|
170
|
+
ack: ackMeta
|
|
171
|
+
} : void 0;
|
|
172
|
+
const publishMeta = mergeMetadata(metadata, mergeMetadata(input.meta, mergeMetadata(keyMeta, ackWrapper)));
|
|
173
|
+
await ctx.emitEvent({
|
|
174
|
+
event: input.topic,
|
|
175
|
+
payload: encode(resolved.payload),
|
|
176
|
+
metadata: publishMeta,
|
|
177
|
+
context: envelope.context ? {
|
|
178
|
+
...envelope.context
|
|
179
|
+
} : void 0
|
|
180
|
+
});
|
|
181
|
+
if (externalPublisher) await externalPublisher({
|
|
182
|
+
...input,
|
|
183
|
+
payload: resolved.payload,
|
|
184
|
+
...ackMeta ? {
|
|
185
|
+
ack: ackMeta
|
|
186
|
+
} : {}
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
const handlePublishError = (error, info)=>{
|
|
190
|
+
if (externalOnPublishError) externalOnPublishError(error, info);
|
|
191
|
+
emitErrorEvent({
|
|
192
|
+
ctx,
|
|
193
|
+
envelope,
|
|
194
|
+
metadata,
|
|
195
|
+
error,
|
|
196
|
+
info
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
const requestContext = {
|
|
200
|
+
...requestOverrides,
|
|
201
|
+
metadata: mergeMetadata(metadata, requestOverrides?.metadata),
|
|
202
|
+
publisher,
|
|
203
|
+
onPublishError: handlePublishError,
|
|
204
|
+
logger: requestOverrides?.logger ?? options.logger
|
|
205
|
+
};
|
|
206
|
+
schemaContext.setRequestContext(requestContext);
|
|
207
|
+
const input = decode(envelope.payload);
|
|
208
|
+
const fieldPayload = op ? void 0 : normalizeFieldPayload(input);
|
|
209
|
+
let result;
|
|
210
|
+
try {
|
|
211
|
+
result = op ? await runOperation(op, input, schemaContext) : await runFieldOperation(fieldOp, fieldPayload?.dependsOn, fieldPayload?.input, schemaContext);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
await emitErrorEvent({
|
|
214
|
+
ctx,
|
|
215
|
+
envelope,
|
|
216
|
+
metadata,
|
|
217
|
+
error,
|
|
218
|
+
info: {
|
|
219
|
+
phase: 'execute',
|
|
220
|
+
event: envelope.event
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
return envelope;
|
|
224
|
+
}
|
|
225
|
+
await ctx.emitEvent({
|
|
226
|
+
event: envelope.event,
|
|
227
|
+
payload: encode(result),
|
|
228
|
+
metadata,
|
|
229
|
+
context: envelope.context ? {
|
|
230
|
+
...envelope.context
|
|
231
|
+
} : void 0
|
|
232
|
+
});
|
|
233
|
+
return envelope;
|
|
234
|
+
};
|
|
235
|
+
const register = (registry)=>{
|
|
236
|
+
registry.onReceive(onReceive);
|
|
237
|
+
};
|
|
238
|
+
return {
|
|
239
|
+
name: 'schema',
|
|
240
|
+
register
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
export { createSchemaModuleInput, schemaModule };
|