@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.
Files changed (169) hide show
  1. package/PROMPT.md +21 -0
  2. package/README.md +13 -0
  3. package/SCHEMA.md +13 -0
  4. package/dist/SchemaValidationError.cjs +41 -0
  5. package/dist/SchemaValidationError.d.ts +20 -0
  6. package/dist/SchemaValidationError.js +7 -0
  7. package/dist/SchemaValidationError.spec.cjs +65 -0
  8. package/dist/SchemaValidationError.spec.d.ts +1 -0
  9. package/dist/SchemaValidationError.spec.js +59 -0
  10. package/dist/after.cjs +36 -0
  11. package/dist/after.d.ts +30 -0
  12. package/dist/after.js +2 -0
  13. package/dist/after.spec.cjs +54 -0
  14. package/dist/after.spec.d.ts +1 -0
  15. package/dist/after.spec.js +48 -0
  16. package/dist/and.cjs +36 -0
  17. package/dist/and.d.ts +26 -0
  18. package/dist/and.js +2 -0
  19. package/dist/and.spec.cjs +57 -0
  20. package/dist/and.spec.d.ts +1 -0
  21. package/dist/and.spec.js +51 -0
  22. package/dist/api.cjs +317 -0
  23. package/dist/api.d.ts +107 -0
  24. package/dist/api.js +277 -0
  25. package/dist/api.spec.cjs +512 -0
  26. package/dist/api.spec.d.ts +1 -0
  27. package/dist/api.spec.js +506 -0
  28. package/dist/array.cjs +74 -0
  29. package/dist/array.d.ts +25 -0
  30. package/dist/array.js +40 -0
  31. package/dist/array.spec.cjs +167 -0
  32. package/dist/array.spec.d.ts +1 -0
  33. package/dist/array.spec.js +161 -0
  34. package/dist/before.cjs +36 -0
  35. package/dist/before.d.ts +30 -0
  36. package/dist/before.js +2 -0
  37. package/dist/before.spec.cjs +54 -0
  38. package/dist/before.spec.d.ts +1 -0
  39. package/dist/before.spec.js +48 -0
  40. package/dist/binary.cjs +53 -0
  41. package/dist/binary.d.ts +24 -0
  42. package/dist/binary.js +19 -0
  43. package/dist/binary.spec.cjs +107 -0
  44. package/dist/binary.spec.d.ts +1 -0
  45. package/dist/binary.spec.js +101 -0
  46. package/dist/boolean.cjs +53 -0
  47. package/dist/boolean.d.ts +24 -0
  48. package/dist/boolean.js +19 -0
  49. package/dist/boolean.spec.cjs +96 -0
  50. package/dist/boolean.spec.d.ts +1 -0
  51. package/dist/boolean.spec.js +90 -0
  52. package/dist/context.cjs +125 -0
  53. package/dist/context.d.ts +101 -0
  54. package/dist/context.js +76 -0
  55. package/dist/context.spec.cjs +244 -0
  56. package/dist/context.spec.d.ts +1 -0
  57. package/dist/context.spec.js +238 -0
  58. package/dist/date.cjs +53 -0
  59. package/dist/date.d.ts +24 -0
  60. package/dist/date.js +19 -0
  61. package/dist/date.spec.cjs +97 -0
  62. package/dist/date.spec.d.ts +1 -0
  63. package/dist/date.spec.js +91 -0
  64. package/dist/doc.cjs +54 -0
  65. package/dist/doc.d.ts +25 -0
  66. package/dist/doc.js +17 -0
  67. package/dist/doc.spec.cjs +99 -0
  68. package/dist/doc.spec.d.ts +1 -0
  69. package/dist/doc.spec.js +93 -0
  70. package/dist/enumeration.cjs +74 -0
  71. package/dist/enumeration.d.ts +50 -0
  72. package/dist/enumeration.js +40 -0
  73. package/dist/enumeration.spec.cjs +110 -0
  74. package/dist/enumeration.spec.d.ts +1 -0
  75. package/dist/enumeration.spec.js +104 -0
  76. package/dist/hydrate.cjs +18 -0
  77. package/dist/hydrate.d.ts +1 -0
  78. package/dist/hydrate.js +0 -0
  79. package/dist/index.cjs +145 -0
  80. package/dist/index.d.ts +34 -0
  81. package/dist/index.js +24 -0
  82. package/dist/index.spec.cjs +43 -0
  83. package/dist/index.spec.d.ts +1 -0
  84. package/dist/index.spec.js +37 -0
  85. package/dist/literal.cjs +55 -0
  86. package/dist/literal.d.ts +25 -0
  87. package/dist/literal.js +21 -0
  88. package/dist/literal.spec.cjs +93 -0
  89. package/dist/literal.spec.d.ts +1 -0
  90. package/dist/literal.spec.js +87 -0
  91. package/dist/number.cjs +89 -0
  92. package/dist/number.d.ts +84 -0
  93. package/dist/number.js +55 -0
  94. package/dist/number.spec.cjs +155 -0
  95. package/dist/number.spec.d.ts +1 -0
  96. package/dist/number.spec.js +149 -0
  97. package/dist/object.cjs +66 -0
  98. package/dist/object.d.ts +37 -0
  99. package/dist/object.js +32 -0
  100. package/dist/object.spec.cjs +171 -0
  101. package/dist/object.spec.d.ts +1 -0
  102. package/dist/object.spec.js +165 -0
  103. package/dist/operation.cjs +182 -0
  104. package/dist/operation.d.ts +197 -0
  105. package/dist/operation.js +133 -0
  106. package/dist/operation.spec.cjs +454 -0
  107. package/dist/operation.spec.d.ts +1 -0
  108. package/dist/operation.spec.js +448 -0
  109. package/dist/or.cjs +85 -0
  110. package/dist/or.d.ts +37 -0
  111. package/dist/or.js +51 -0
  112. package/dist/or.spec.cjs +204 -0
  113. package/dist/or.spec.d.ts +1 -0
  114. package/dist/or.spec.js +198 -0
  115. package/dist/schema.cjs +285 -0
  116. package/dist/schema.d.ts +132 -0
  117. package/dist/schema.js +233 -0
  118. package/dist/schema.spec.cjs +587 -0
  119. package/dist/schema.spec.d.ts +1 -0
  120. package/dist/schema.spec.js +581 -0
  121. package/dist/schemaFactory.cjs +125 -0
  122. package/dist/schemaFactory.d.ts +97 -0
  123. package/dist/schemaFactory.js +88 -0
  124. package/dist/schemaFactory.spec.cjs +197 -0
  125. package/dist/schemaFactory.spec.d.ts +1 -0
  126. package/dist/schemaFactory.spec.js +191 -0
  127. package/dist/schemaModule.cjs +280 -0
  128. package/dist/schemaModule.d.ts +97 -0
  129. package/dist/schemaModule.js +243 -0
  130. package/dist/schemaModule.spec.cjs +355 -0
  131. package/dist/schemaModule.spec.d.ts +1 -0
  132. package/dist/schemaModule.spec.js +349 -0
  133. package/dist/string.cjs +93 -0
  134. package/dist/string.d.ts +85 -0
  135. package/dist/string.js +59 -0
  136. package/dist/string.spec.cjs +158 -0
  137. package/dist/string.spec.d.ts +1 -0
  138. package/dist/string.spec.js +152 -0
  139. package/dist/testing/mocks/assertions.mock.cjs +48 -0
  140. package/dist/testing/mocks/assertions.mock.d.ts +5 -0
  141. package/dist/testing/mocks/assertions.mock.js +14 -0
  142. package/dist/testing/mocks/index.cjs +52 -0
  143. package/dist/testing/mocks/index.d.ts +4 -0
  144. package/dist/testing/mocks/index.js +3 -0
  145. package/dist/testing/mocks/schema.mock.cjs +120 -0
  146. package/dist/testing/mocks/schema.mock.d.ts +37 -0
  147. package/dist/testing/mocks/schema.mock.js +74 -0
  148. package/dist/tuple.cjs +58 -0
  149. package/dist/tuple.d.ts +33 -0
  150. package/dist/tuple.js +24 -0
  151. package/dist/tuple.spec.cjs +162 -0
  152. package/dist/tuple.spec.d.ts +1 -0
  153. package/dist/tuple.spec.js +156 -0
  154. package/dist/typeGuards.cjs +60 -0
  155. package/dist/typeGuards.d.ts +93 -0
  156. package/dist/typeGuards.js +8 -0
  157. package/dist/typeGuards.spec.cjs +101 -0
  158. package/dist/typeGuards.spec.d.ts +1 -0
  159. package/dist/typeGuards.spec.js +95 -0
  160. package/dist/types.cjs +18 -0
  161. package/dist/types.d.ts +289 -0
  162. package/dist/types.js +0 -0
  163. package/dist/union.cjs +74 -0
  164. package/dist/union.d.ts +33 -0
  165. package/dist/union.js +40 -0
  166. package/dist/union.spec.cjs +159 -0
  167. package/dist/union.spec.d.ts +1 -0
  168. package/dist/union.spec.js +153 -0
  169. 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 };