velocious 1.0.494 → 1.0.496

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 (60) hide show
  1. package/build/database/record/index.js +2 -0
  2. package/build/database/record/validation-messages.js +50 -0
  3. package/build/database/record/validators/length.js +52 -0
  4. package/build/database/record/validators/presence.js +4 -1
  5. package/build/database/record/validators/uniqueness.js +4 -1
  6. package/build/frontend-model-resource/base-resource.js +175 -5
  7. package/build/frontend-models/resource-definition.js +49 -1
  8. package/build/src/database/record/index.d.ts.map +1 -1
  9. package/build/src/database/record/index.js +3 -1
  10. package/build/src/database/record/validation-messages.d.ts +38 -0
  11. package/build/src/database/record/validation-messages.d.ts.map +1 -0
  12. package/build/src/database/record/validation-messages.js +45 -0
  13. package/build/src/database/record/validators/length.d.ts +12 -0
  14. package/build/src/database/record/validators/length.d.ts.map +1 -0
  15. package/build/src/database/record/validators/length.js +45 -0
  16. package/build/src/database/record/validators/presence.d.ts.map +1 -1
  17. package/build/src/database/record/validators/presence.js +4 -2
  18. package/build/src/database/record/validators/uniqueness.d.ts.map +1 -1
  19. package/build/src/database/record/validators/uniqueness.js +4 -2
  20. package/build/src/frontend-model-resource/base-resource.d.ts +184 -5
  21. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  22. package/build/src/frontend-model-resource/base-resource.js +155 -6
  23. package/build/src/frontend-models/resource-definition.d.ts +41 -0
  24. package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
  25. package/build/src/frontend-models/resource-definition.js +45 -2
  26. package/build/src/sync/sync-envelope-replay-service.d.ts +166 -3
  27. package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
  28. package/build/src/sync/sync-envelope-replay-service.js +304 -12
  29. package/build/src/sync/sync-replay-upsert-applier.d.ts +1 -0
  30. package/build/src/sync/sync-replay-upsert-applier.d.ts.map +1 -1
  31. package/build/src/sync/sync-replay-upsert-applier.js +2 -1
  32. package/build/src/sync/sync-resource-base.d.ts +16 -32
  33. package/build/src/sync/sync-resource-base.d.ts.map +1 -1
  34. package/build/src/sync/sync-resource-base.js +31 -43
  35. package/build/src/testing/test-runner.d.ts +24 -0
  36. package/build/src/testing/test-runner.d.ts.map +1 -1
  37. package/build/src/testing/test-runner.js +99 -9
  38. package/build/sync/sync-envelope-replay-service.js +350 -10
  39. package/build/sync/sync-replay-upsert-applier.js +1 -0
  40. package/build/sync/sync-resource-base.js +32 -45
  41. package/build/testing/test-runner.js +104 -8
  42. package/build/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +1 -1
  44. package/scripts/run-tests.js +6 -0
  45. package/src/database/record/index.js +2 -0
  46. package/src/database/record/validation-messages.js +50 -0
  47. package/src/database/record/validators/length.js +52 -0
  48. package/src/database/record/validators/presence.js +4 -1
  49. package/src/database/record/validators/uniqueness.js +4 -1
  50. package/src/frontend-model-resource/base-resource.js +175 -5
  51. package/src/frontend-models/resource-definition.js +49 -1
  52. package/src/sync/sync-envelope-replay-service.js +350 -10
  53. package/src/sync/sync-replay-upsert-applier.js +1 -0
  54. package/src/sync/sync-resource-base.js +32 -45
  55. package/src/testing/test-runner.js +104 -8
  56. package/build/src/sync/sync-attribute-normalizer.d.ts +0 -105
  57. package/build/src/sync/sync-attribute-normalizer.d.ts.map +0 -1
  58. package/build/src/sync/sync-attribute-normalizer.js +0 -172
  59. package/build/sync/sync-attribute-normalizer.js +0 -186
  60. package/src/sync/sync-attribute-normalizer.js +0 -186
@@ -187,6 +187,8 @@ export default class TestRunner {
187
187
  this._testsCount = 0
188
188
  this._activeAfterAllScopes = []
189
189
  this._failedTestDetails = []
190
+ /** @type {{fullDescription: string, filePath: string, line: number} | null} */
191
+ this._lastTestContext = null
190
192
  }
191
193
 
192
194
  /**
@@ -638,16 +640,102 @@ export default class TestRunner {
638
640
  * Runs run.
639
641
  * @returns {Promise<void>} - Resolves when complete.
640
642
  */
643
+ /**
644
+ * Records an asynchronous crash (an unhandled promise rejection detached from
645
+ * any await, e.g. a `void connection.afterCommit(async () => broadcast(...))`
646
+ * frontend-model publish — or a synchronous throw inside a detached callback
647
+ * such as a driver socket or timer callback) as a real, visible, attributed
648
+ * test failure.
649
+ *
650
+ * Without this, such a rejection/exception has no handler, so on modern Node
651
+ * the process is TERMINATED — the run ends with no reported failures and CI
652
+ * just sees a crashed/retried shard with an empty result (the recurring
653
+ * "silent test-runner death": invisible and impossible to diagnose). Turning
654
+ * it into a failure makes the run go red with something debuggable instead of
655
+ * vanishing.
656
+ * @param {"uncaughtException" | "unhandledRejection"} kind - Async-crash kind.
657
+ * @param {unknown} reason - Rejection reason or thrown error.
658
+ * @returns {void}
659
+ */
660
+ recordAsyncCrash(kind, reason) {
661
+ const error = reason instanceof Error ? reason : new Error(`${kind}: ${String(reason)}`)
662
+ const near = this._lastTestContext
663
+ const attribution = near ? `, near test: ${near.fullDescription} (${near.filePath}:${near.line})` : ""
664
+
665
+ this._failedTests = (this._failedTests || 0) + 1
666
+ this._failedTestDetails.push({
667
+ fullDescription: `<${kind} during test run${attribution}>`,
668
+ filePath: near ? near.filePath : "<test runner>",
669
+ line: near ? near.line : 0,
670
+ error,
671
+ consoleOutput: undefined
672
+ })
673
+
674
+ console.error(picocolors.red(`\n[test-runner] ${kind} during the test run — this would otherwise terminate the process silently and surface only as a crashed/retried shard with zero reported failures.${attribution}`))
675
+ console.error(error)
676
+ }
677
+
641
678
  async run() {
642
- await this.getConfiguration().ensureConnections({name: "Test runner suite"}, async () => {
643
- await this.runTests({
644
- afterEaches: [],
645
- beforeEaches: [],
646
- tests,
647
- descriptions: [],
648
- indentLevel: 0
679
+ /**
680
+ * Handles a process-level unhandled rejection during the run.
681
+ * @param {unknown} reason - Rejection reason.
682
+ * @returns {void}
683
+ */
684
+ const onUnhandledRejection = (reason) => {
685
+ // If a test attached its OWN unhandledRejection listener, it is
686
+ // intentionally observing/triggering the rejection (e.g. beacon
687
+ // error-reporting-spec.js) — Node dispatches to EVERY listener, so also
688
+ // failing the suite here would break those tests. Defer to the test's
689
+ // handler; only treat a rejection as a silent-death crash when ours is the
690
+ // sole listener (no persistent framework listener exists to mask this).
691
+ if (process.listenerCount("unhandledRejection") > 1) return
692
+
693
+ this.recordAsyncCrash("unhandledRejection", reason)
694
+ }
695
+
696
+ /**
697
+ * Handles a process-level uncaught exception during the run — a
698
+ * synchronous throw inside a detached callback (driver socket, timer,
699
+ * event emitter) that no test await observes. Same silent-death mode as
700
+ * unhandled rejections: without a handler the process dies mid-run and CI
701
+ * sees a crashed shard with zero reported failures.
702
+ * @param {unknown} error - Thrown error.
703
+ * @returns {void}
704
+ */
705
+ const onUncaughtException = (error) => {
706
+ // Mirror the unhandledRejection deferral: a test observing/triggering
707
+ // uncaught exceptions with its own listener owns them.
708
+ if (process.listenerCount("uncaughtException") > 1) return
709
+
710
+ this.recordAsyncCrash("uncaughtException", error)
711
+ }
712
+
713
+ process.on("unhandledRejection", onUnhandledRejection)
714
+ process.on("uncaughtException", onUncaughtException)
715
+
716
+ try {
717
+ await this.getConfiguration().ensureConnections({name: "Test runner suite"}, async () => {
718
+ await this.runTests({
719
+ afterEaches: [],
720
+ beforeEaches: [],
721
+ tests,
722
+ descriptions: [],
723
+ indentLevel: 0
724
+ })
725
+
726
+ // A rejection scheduled by the final test (a detached rejected promise,
727
+ // or an afterCommit callback rejecting as the suite drains) is reported
728
+ // by Node on a LATER turn. Drain a few turns while the handler is still
729
+ // attached — and connections still open — so those late rejections are
730
+ // recorded instead of escaping to the default crash path after cleanup.
731
+ for (let drainTurn = 0; drainTurn < 3; drainTurn++) {
732
+ await new Promise((resolve) => setImmediate(resolve))
733
+ }
649
734
  })
650
- })
735
+ } finally {
736
+ process.off("unhandledRejection", onUnhandledRejection)
737
+ process.off("uncaughtException", onUncaughtException)
738
+ }
651
739
  }
652
740
 
653
741
  /**
@@ -798,6 +886,14 @@ export default class TestRunner {
798
886
  await beforeEachData.callback({configuration: this.getConfiguration(), testArgs, testData})
799
887
  }
800
888
 
889
+ // Record which test is running so an async crash (an unhandled
890
+ // rejection detached from any await) that fires during or shortly
891
+ // after this test can be attributed to it in run()'s handler.
892
+ this._lastTestContext = {
893
+ fullDescription: this.buildFullDescription(descriptions, testDescription),
894
+ filePath: testData.filePath ?? "<unknown>",
895
+ line: testData.line ?? 0
896
+ }
801
897
  await testData.function(testArgs)
802
898
  this._successfulTests++
803
899
  } finally {
@@ -1,105 +0,0 @@
1
- /**
2
- * Builds the error thrown for a failed sync attribute validation.
3
- * @callback SyncAttributeErrorFactory
4
- * @param {string} message - Human-readable validation message.
5
- * @param {{cause?: Error, code: string}} details - Stable machine-readable code and optional cause.
6
- * @returns {Error} Error to throw.
7
- */
8
- /**
9
- * Declarative validation schema for one writable sync attribute.
10
- *
11
- * Keys are camelCase attribute names; each attribute also accepts its
12
- * snake_case column key on input (the camelCase key wins when both are given)
13
- * and is written under the snake_case column key on output. Error messages
14
- * default to `<label> ...` phrasings with `sync-<label>-...` codes, where the
15
- * label defaults to the camelCase attribute name; apps pin exact messages per
16
- * attribute through the `*Message` overrides.
17
- * @typedef {object} SyncAttributeSchemaEntry
18
- * @property {"date" | "json" | "raw" | "string"} type - Attribute value type.
19
- * @property {boolean} [required] - Whether present blank/absent values are rejected (strings reject blank-after-trim, dates reject null).
20
- * @property {number} [maxLength] - Maximum string length, checked after trimming.
21
- * @property {string} [label] - Label used in derived messages and codes.
22
- * @property {string} [column] - Output column key. Defaults to the snake_cased attribute name.
23
- * @property {string} [requiredMessage] - Pinned message for required rejections.
24
- * @property {string} [tooLongMessage] - Pinned message for too-long rejections.
25
- * @property {string} [invalidMessage] - Pinned message for invalid dates and non-object/non-string json values.
26
- * @property {string} [invalidJsonMessage] - Pinned message for unparseable json strings.
27
- */
28
- /**
29
- * Normalizes writable sync attributes against a declarative schema.
30
- *
31
- * Only present keys are validated, so required attributes may be absent —
32
- * create defaults own that concern. Input keys not declared in the schema are
33
- * rejected by default so misspelled or read-only attributes fail loudly
34
- * instead of being silently discarded (`unknownAttributes: "ignore"` opts
35
- * out). Validation failures throw through the given error factory so apps
36
- * control the raised error class.
37
- * @param {object} args - Options.
38
- * @param {Record<string, ?>} args.attributes - Raw incoming attributes (camelCase and/or snake_case keys).
39
- * @param {SyncAttributeErrorFactory} args.errorFactory - Maps validation failures to thrown errors.
40
- * @param {Record<string, SyncAttributeSchemaEntry>} args.schema - Attribute schema keyed by camelCase attribute name.
41
- * @param {"error" | "ignore"} [args.unknownAttributes] - Unknown input-key handling. Defaults to "error".
42
- * @returns {Record<string, ?>} Normalized values keyed by snake_case column names.
43
- */
44
- export default function normalizeAttributesWithSchema({ attributes, errorFactory, schema, unknownAttributes }: {
45
- attributes: Record<string, unknown>;
46
- errorFactory: SyncAttributeErrorFactory;
47
- schema: Record<string, SyncAttributeSchemaEntry>;
48
- unknownAttributes?: "error" | "ignore" | undefined;
49
- }): Record<string, unknown>;
50
- /**
51
- * Builds the error thrown for a failed sync attribute validation.
52
- */
53
- export type SyncAttributeErrorFactory = (message: string, details: {
54
- cause?: Error;
55
- code: string;
56
- }) => Error;
57
- /**
58
- * Declarative validation schema for one writable sync attribute.
59
- *
60
- * Keys are camelCase attribute names; each attribute also accepts its
61
- * snake_case column key on input (the camelCase key wins when both are given)
62
- * and is written under the snake_case column key on output. Error messages
63
- * default to `<label> ...` phrasings with `sync-<label>-...` codes, where the
64
- * label defaults to the camelCase attribute name; apps pin exact messages per
65
- * attribute through the `*Message` overrides.
66
- */
67
- export type SyncAttributeSchemaEntry = {
68
- /**
69
- * - Attribute value type.
70
- */
71
- type: "date" | "json" | "raw" | "string";
72
- /**
73
- * - Whether present blank/absent values are rejected (strings reject blank-after-trim, dates reject null).
74
- */
75
- required?: boolean | undefined;
76
- /**
77
- * - Maximum string length, checked after trimming.
78
- */
79
- maxLength?: number | undefined;
80
- /**
81
- * - Label used in derived messages and codes.
82
- */
83
- label?: string | undefined;
84
- /**
85
- * - Output column key. Defaults to the snake_cased attribute name.
86
- */
87
- column?: string | undefined;
88
- /**
89
- * - Pinned message for required rejections.
90
- */
91
- requiredMessage?: string | undefined;
92
- /**
93
- * - Pinned message for too-long rejections.
94
- */
95
- tooLongMessage?: string | undefined;
96
- /**
97
- * - Pinned message for invalid dates and non-object/non-string json values.
98
- */
99
- invalidMessage?: string | undefined;
100
- /**
101
- * - Pinned message for unparseable json strings.
102
- */
103
- invalidJsonMessage?: string | undefined;
104
- };
105
- //# sourceMappingURL=sync-attribute-normalizer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sync-attribute-normalizer.d.ts","sourceRoot":"","sources":["../../../src/sync/sync-attribute-normalizer.js"],"names":[],"mappings":"AAIA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,+GANG;IAAgC,UAAU,EAAlC,MAAM,CAAC,MAAM,EAAE,OAAC,CAAC;IACe,YAAY,EAA5C,yBAAyB;IACsB,MAAM,EAArD,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC;IACd,iBAAiB;CACnD,GAAU,MAAM,CAAC,MAAM,EAAE,OAAC,CAAC,CAqC7B;;;;kDA7EU,MAAM,WACN;IAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,KAC3B,KAAK;;;;;;;;;;;;;;;UAaJ,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ"}
@@ -1,172 +0,0 @@
1
- // @ts-check
2
- import { snakeCaseKey } from "typanic";
3
- /**
4
- * Builds the error thrown for a failed sync attribute validation.
5
- * @callback SyncAttributeErrorFactory
6
- * @param {string} message - Human-readable validation message.
7
- * @param {{cause?: Error, code: string}} details - Stable machine-readable code and optional cause.
8
- * @returns {Error} Error to throw.
9
- */
10
- /**
11
- * Declarative validation schema for one writable sync attribute.
12
- *
13
- * Keys are camelCase attribute names; each attribute also accepts its
14
- * snake_case column key on input (the camelCase key wins when both are given)
15
- * and is written under the snake_case column key on output. Error messages
16
- * default to `<label> ...` phrasings with `sync-<label>-...` codes, where the
17
- * label defaults to the camelCase attribute name; apps pin exact messages per
18
- * attribute through the `*Message` overrides.
19
- * @typedef {object} SyncAttributeSchemaEntry
20
- * @property {"date" | "json" | "raw" | "string"} type - Attribute value type.
21
- * @property {boolean} [required] - Whether present blank/absent values are rejected (strings reject blank-after-trim, dates reject null).
22
- * @property {number} [maxLength] - Maximum string length, checked after trimming.
23
- * @property {string} [label] - Label used in derived messages and codes.
24
- * @property {string} [column] - Output column key. Defaults to the snake_cased attribute name.
25
- * @property {string} [requiredMessage] - Pinned message for required rejections.
26
- * @property {string} [tooLongMessage] - Pinned message for too-long rejections.
27
- * @property {string} [invalidMessage] - Pinned message for invalid dates and non-object/non-string json values.
28
- * @property {string} [invalidJsonMessage] - Pinned message for unparseable json strings.
29
- */
30
- /**
31
- * Normalizes writable sync attributes against a declarative schema.
32
- *
33
- * Only present keys are validated, so required attributes may be absent —
34
- * create defaults own that concern. Input keys not declared in the schema are
35
- * rejected by default so misspelled or read-only attributes fail loudly
36
- * instead of being silently discarded (`unknownAttributes: "ignore"` opts
37
- * out). Validation failures throw through the given error factory so apps
38
- * control the raised error class.
39
- * @param {object} args - Options.
40
- * @param {Record<string, ?>} args.attributes - Raw incoming attributes (camelCase and/or snake_case keys).
41
- * @param {SyncAttributeErrorFactory} args.errorFactory - Maps validation failures to thrown errors.
42
- * @param {Record<string, SyncAttributeSchemaEntry>} args.schema - Attribute schema keyed by camelCase attribute name.
43
- * @param {"error" | "ignore"} [args.unknownAttributes] - Unknown input-key handling. Defaults to "error".
44
- * @returns {Record<string, ?>} Normalized values keyed by snake_case column names.
45
- */
46
- export default function normalizeAttributesWithSchema({ attributes, errorFactory, schema, unknownAttributes = "error" }) {
47
- if (!attributes || typeof attributes != "object")
48
- throw new Error(`normalizeAttributesWithSchema requires an attributes object, got: ${String(attributes)}`);
49
- if (typeof errorFactory != "function")
50
- throw new Error("normalizeAttributesWithSchema requires an errorFactory function");
51
- if (!schema || typeof schema != "object")
52
- throw new Error(`normalizeAttributesWithSchema requires a schema object, got: ${String(schema)}`);
53
- if (unknownAttributes != "error" && unknownAttributes != "ignore") {
54
- throw new Error(`normalizeAttributesWithSchema unknownAttributes must be "error" or "ignore", got: ${String(unknownAttributes)}`);
55
- }
56
- /** @type {Record<string, ?>} */
57
- const normalized = {};
58
- /** @type {Set<string>} */
59
- const knownInputKeys = new Set();
60
- for (const [attributeName, entry] of Object.entries(schema)) {
61
- const column = entry.column ?? snakeCaseKey(attributeName);
62
- const inputKeys = column == attributeName ? [attributeName] : [column, attributeName];
63
- for (const inputKey of inputKeys) {
64
- knownInputKeys.add(inputKey);
65
- if (!Object.prototype.hasOwnProperty.call(attributes, inputKey))
66
- continue;
67
- normalized[column] = normalizeAttributeValue({ attributeName, entry, errorFactory, value: attributes[inputKey] });
68
- }
69
- }
70
- if (unknownAttributes == "error") {
71
- for (const inputKey of Object.keys(attributes)) {
72
- if (!knownInputKeys.has(inputKey)) {
73
- throw errorFactory(`Unknown attribute: ${inputKey}.`, { code: "sync-unknown-attribute" });
74
- }
75
- }
76
- }
77
- return normalized;
78
- }
79
- /**
80
- * Normalizes one present attribute value per its schema entry.
81
- * @param {object} args - Options.
82
- * @param {string} args.attributeName - camelCase attribute name.
83
- * @param {SyncAttributeSchemaEntry} args.entry - Schema entry.
84
- * @param {SyncAttributeErrorFactory} args.errorFactory - Error factory.
85
- * @param {?} args.value - Raw value.
86
- * @returns {?} Normalized value.
87
- */
88
- function normalizeAttributeValue({ attributeName, entry, errorFactory, value }) {
89
- const label = entry.label ?? attributeName;
90
- if (entry.type == "string")
91
- return normalizeString({ entry, errorFactory, label, value });
92
- if (entry.type == "date")
93
- return normalizeDate({ entry, errorFactory, label, value });
94
- if (entry.type == "json")
95
- return normalizeJson({ entry, errorFactory, label, value });
96
- if (entry.type == "raw")
97
- return value;
98
- throw new Error(`Unknown sync attribute schema type for ${attributeName}: ${String(entry.type)}`);
99
- }
100
- /**
101
- * Normalizes a string attribute: trims, rejects blank required values after trimming, maps empty optional values to null and bounds the trimmed length.
102
- * @param {object} args - Options.
103
- * @param {SyncAttributeSchemaEntry} args.entry - Schema entry.
104
- * @param {SyncAttributeErrorFactory} args.errorFactory - Error factory.
105
- * @param {string} args.label - Message/code label.
106
- * @param {?} args.value - Raw value.
107
- * @returns {string | null} Normalized string.
108
- */
109
- function normalizeString({ entry, errorFactory, label, value }) {
110
- const requiredError = () => errorFactory(entry.requiredMessage ?? `${label} is required.`, { code: `sync-${label}-required` });
111
- if (value === null || value === undefined || value === "") {
112
- if (entry.required)
113
- throw requiredError();
114
- return null;
115
- }
116
- const stringValue = String(value).trim();
117
- if (stringValue === "") {
118
- if (entry.required)
119
- throw requiredError();
120
- return null;
121
- }
122
- if (entry.maxLength !== undefined && stringValue.length > entry.maxLength) {
123
- throw errorFactory(entry.tooLongMessage ?? `${label} is too long (maximum is ${entry.maxLength} characters).`, { code: `sync-${label}-too-long` });
124
- }
125
- return stringValue;
126
- }
127
- /**
128
- * Normalizes a date attribute: Date instances pass through, strings/numbers are parsed and invalid or missing-required values are rejected.
129
- * @param {object} args - Options.
130
- * @param {SyncAttributeSchemaEntry} args.entry - Schema entry.
131
- * @param {SyncAttributeErrorFactory} args.errorFactory - Error factory.
132
- * @param {string} args.label - Message/code label.
133
- * @param {?} args.value - Raw value.
134
- * @returns {Date | null} Normalized date.
135
- */
136
- function normalizeDate({ entry, errorFactory, label, value }) {
137
- if (!entry.required && (value === null || value === undefined || value === ""))
138
- return null;
139
- const dateValue = value instanceof Date ? value : typeof value == "string" || typeof value == "number" ? new Date(value) : null;
140
- if (!dateValue || Number.isNaN(dateValue.getTime())) {
141
- throw errorFactory(entry.invalidMessage ?? `${label} must be a valid datetime.`, { code: `sync-${label}-invalid-datetime` });
142
- }
143
- return dateValue;
144
- }
145
- /**
146
- * Normalizes a json attribute: empty values become null, objects pass through, strings must parse as JSON and other types are rejected.
147
- * @param {object} args - Options.
148
- * @param {SyncAttributeSchemaEntry} args.entry - Schema entry.
149
- * @param {SyncAttributeErrorFactory} args.errorFactory - Error factory.
150
- * @param {string} args.label - Message/code label.
151
- * @param {?} args.value - Raw value.
152
- * @returns {?} Normalized json value.
153
- */
154
- function normalizeJson({ entry, errorFactory, label, value }) {
155
- if (value === null || value === undefined || value === "")
156
- return null;
157
- if (typeof value == "object")
158
- return value;
159
- if (typeof value == "string") {
160
- try {
161
- return JSON.parse(value);
162
- }
163
- catch (error) {
164
- throw errorFactory(entry.invalidJsonMessage ?? `${label} must be valid JSON.`, {
165
- cause: /** @type {Error} */ (error),
166
- code: `sync-${label}-invalid-json`
167
- });
168
- }
169
- }
170
- throw errorFactory(entry.invalidMessage ?? `${label} must be an object or JSON string.`, { code: `sync-${label}-invalid` });
171
- }
172
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy1hdHRyaWJ1dGUtbm9ybWFsaXplci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zeW5jL3N5bmMtYXR0cmlidXRlLW5vcm1hbGl6ZXIuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE9BQU8sRUFBQyxZQUFZLEVBQUMsTUFBTSxTQUFTLENBQUE7QUFFcEM7Ozs7OztHQU1HO0FBRUg7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFFSDs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxNQUFNLENBQUMsT0FBTyxVQUFVLDZCQUE2QixDQUFDLEVBQUMsVUFBVSxFQUFFLFlBQVksRUFBRSxNQUFNLEVBQUUsaUJBQWlCLEdBQUcsT0FBTyxFQUFDO0lBQ25ILElBQUksQ0FBQyxVQUFVLElBQUksT0FBTyxVQUFVLElBQUksUUFBUTtRQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMscUVBQXFFLE1BQU0sQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDLENBQUE7SUFDNUosSUFBSSxPQUFPLFlBQVksSUFBSSxVQUFVO1FBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyxpRUFBaUUsQ0FBQyxDQUFBO0lBQ3pILElBQUksQ0FBQyxNQUFNLElBQUksT0FBTyxNQUFNLElBQUksUUFBUTtRQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsZ0VBQWdFLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUE7SUFDM0ksSUFBSSxpQkFBaUIsSUFBSSxPQUFPLElBQUksaUJBQWlCLElBQUksUUFBUSxFQUFFLENBQUM7UUFDbEUsTUFBTSxJQUFJLEtBQUssQ0FBQyxxRkFBcUYsTUFBTSxDQUFDLGlCQUFpQixDQUFDLEVBQUUsQ0FBQyxDQUFBO0lBQ25JLENBQUM7SUFFRCxnQ0FBZ0M7SUFDaEMsTUFBTSxVQUFVLEdBQUcsRUFBRSxDQUFBO0lBQ3JCLDBCQUEwQjtJQUMxQixNQUFNLGNBQWMsR0FBRyxJQUFJLEdBQUcsRUFBRSxDQUFBO0lBRWhDLEtBQUssTUFBTSxDQUFDLGFBQWEsRUFBRSxLQUFLLENBQUMsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUM7UUFDNUQsTUFBTSxNQUFNLEdBQUcsS0FBSyxDQUFDLE1BQU0sSUFBSSxZQUFZLENBQUMsYUFBYSxDQUFDLENBQUE7UUFDMUQsTUFBTSxTQUFTLEdBQUcsTUFBTSxJQUFJLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsYUFBYSxDQUFDLENBQUE7UUFFckYsS0FBSyxNQUFNLFFBQVEsSUFBSSxTQUFTLEVBQUUsQ0FBQztZQUNqQyxjQUFjLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFBO1lBRTVCLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLFFBQVEsQ0FBQztnQkFBRSxTQUFRO1lBRXpFLFVBQVUsQ0FBQyxNQUFNLENBQUMsR0FBRyx1QkFBdUIsQ0FBQyxFQUFDLGFBQWEsRUFBRSxLQUFLLEVBQUUsWUFBWSxFQUFFLEtBQUssRUFBRSxVQUFVLENBQUMsUUFBUSxDQUFDLEVBQUMsQ0FBQyxDQUFBO1FBQ2pILENBQUM7SUFDSCxDQUFDO0lBRUQsSUFBSSxpQkFBaUIsSUFBSSxPQUFPLEVBQUUsQ0FBQztRQUNqQyxLQUFLLE1BQU0sUUFBUSxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztZQUMvQyxJQUFJLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO2dCQUNsQyxNQUFNLFlBQVksQ0FBQyxzQkFBc0IsUUFBUSxHQUFHLEVBQUUsRUFBQyxJQUFJLEVBQUUsd0JBQXdCLEVBQUMsQ0FBQyxDQUFBO1lBQ3pGLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUVELE9BQU8sVUFBVSxDQUFBO0FBQ25CLENBQUM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILFNBQVMsdUJBQXVCLENBQUMsRUFBQyxhQUFhLEVBQUUsS0FBSyxFQUFFLFlBQVksRUFBRSxLQUFLLEVBQUM7SUFDMUUsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLEtBQUssSUFBSSxhQUFhLENBQUE7SUFFMUMsSUFBSSxLQUFLLENBQUMsSUFBSSxJQUFJLFFBQVE7UUFBRSxPQUFPLGVBQWUsQ0FBQyxFQUFDLEtBQUssRUFBRSxZQUFZLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBQyxDQUFDLENBQUE7SUFDdkYsSUFBSSxLQUFLLENBQUMsSUFBSSxJQUFJLE1BQU07UUFBRSxPQUFPLGFBQWEsQ0FBQyxFQUFDLEtBQUssRUFBRSxZQUFZLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBQyxDQUFDLENBQUE7SUFDbkYsSUFBSSxLQUFLLENBQUMsSUFBSSxJQUFJLE1BQU07UUFBRSxPQUFPLGFBQWEsQ0FBQyxFQUFDLEtBQUssRUFBRSxZQUFZLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBQyxDQUFDLENBQUE7SUFDbkYsSUFBSSxLQUFLLENBQUMsSUFBSSxJQUFJLEtBQUs7UUFBRSxPQUFPLEtBQUssQ0FBQTtJQUVyQyxNQUFNLElBQUksS0FBSyxDQUFDLDBDQUEwQyxhQUFhLEtBQUssTUFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUE7QUFDbkcsQ0FBQztBQUVEOzs7Ozs7OztHQVFHO0FBQ0gsU0FBUyxlQUFlLENBQUMsRUFBQyxLQUFLLEVBQUUsWUFBWSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUM7SUFDMUQsTUFBTSxhQUFhLEdBQUcsR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLEtBQUssQ0FBQyxlQUFlLElBQUksR0FBRyxLQUFLLGVBQWUsRUFBRSxFQUFDLElBQUksRUFBRSxRQUFRLEtBQUssV0FBVyxFQUFDLENBQUMsQ0FBQTtJQUU1SCxJQUFJLEtBQUssS0FBSyxJQUFJLElBQUksS0FBSyxLQUFLLFNBQVMsSUFBSSxLQUFLLEtBQUssRUFBRSxFQUFFLENBQUM7UUFDMUQsSUFB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LWNoZWNrXG5cbmltcG9ydCB7c25ha2VDYXNlS2V5fSBmcm9tIFwidHlwYW5pY1wiXG5cbi8qKlxuICogQnVpbGRzIHRoZSBlcnJvciB0aHJvd24gZm9yIGEgZmFpbGVkIHN5bmMgYXR0cmlidXRlIHZhbGlkYXRpb24uXG4gKiBAY2FsbGJhY2sgU3luY0F0dHJpYnV0ZUVycm9yRmFjdG9yeVxuICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2UgLSBIdW1hbi1yZWFkYWJsZSB2YWxpZGF0aW9uIG1lc3NhZ2UuXG4gKiBAcGFyYW0ge3tjYXVzZT86IEVycm9yLCBjb2RlOiBzdHJpbmd9fSBkZXRhaWxzIC0gU3RhYmxlIG1hY2hpbmUtcmVhZGFibGUgY29kZSBhbmQgb3B0aW9uYWwgY2F1c2UuXG4gKiBAcmV0dXJucyB7RXJyb3J9IEVycm9yIHRvIHRocm93LlxuICovXG5cbi8qKlxuICogRGVjbGFyYXRpdmUgdmFsaWRhdGlvbiBzY2hlbWEgZm9yIG9uZSB3cml0YWJsZSBzeW5jIGF0dHJpYnV0ZS5cbiAqXG4gKiBLZXlzIGFyZSBjYW1lbENhc2UgYXR0cmlidXRlIG5hbWVzOyBlYWNoIGF0dHJpYnV0ZSBhbHNvIGFjY2VwdHMgaXRzXG4gKiBzbmFrZV9jYXNlIGNvbHVtbiBrZXkgb24gaW5wdXQgKHRoZSBjYW1lbENhc2Uga2V5IHdpbnMgd2hlbiBib3RoIGFyZSBnaXZlbilcbiAqIGFuZCBpcyB3cml0dGVuIHVuZGVyIHRoZSBzbmFrZV9jYXNlIGNvbHVtbiBrZXkgb24gb3V0cHV0LiBFcnJvciBtZXNzYWdlc1xuICogZGVmYXVsdCB0byBgPGxhYmVsPiAuLi5gIHBocmFzaW5ncyB3aXRoIGBzeW5jLTxsYWJlbD4tLi4uYCBjb2Rlcywgd2hlcmUgdGhlXG4gKiBsYWJlbCBkZWZhdWx0cyB0byB0aGUgY2FtZWxDYXNlIGF0dHJpYnV0ZSBuYW1lOyBhcHBzIHBpbiBleGFjdCBtZXNzYWdlcyBwZXJcbiAqIGF0dHJpYnV0ZSB0aHJvdWdoIHRoZSBgKk1lc3NhZ2VgIG92ZXJyaWRlcy5cbiAqIEB0eXBlZGVmIHtvYmplY3R9IFN5bmNBdHRyaWJ1dGVTY2hlbWFFbnRyeVxuICogQHByb3BlcnR5IHtcImRhdGVcIiB8IFwianNvblwiIHwgXCJyYXdcIiB8IFwic3RyaW5nXCJ9IHR5cGUgLSBBdHRyaWJ1dGUgdmFsdWUgdHlwZS5cbiAqIEBwcm9wZXJ0eSB7Ym9vbGVhbn0gW3JlcXVpcmVkXSAtIFdoZXRoZXIgcHJlc2VudCBibGFuay9hYnNlbnQgdmFsdWVzIGFyZSByZWplY3RlZCAoc3RyaW5ncyByZWplY3QgYmxhbmstYWZ0ZXItdHJpbSwgZGF0ZXMgcmVqZWN0IG51bGwpLlxuICogQHByb3BlcnR5IHtudW1iZXJ9IFttYXhMZW5ndGhdIC0gTWF4aW11bSBzdHJpbmcgbGVuZ3RoLCBjaGVja2VkIGFmdGVyIHRyaW1taW5nLlxuICogQHByb3BlcnR5IHtzdHJpbmd9IFtsYWJlbF0gLSBMYWJlbCB1c2VkIGluIGRlcml2ZWQgbWVzc2FnZXMgYW5kIGNvZGVzLlxuICogQHByb3BlcnR5IHtzdHJpbmd9IFtjb2x1bW5dIC0gT3V0cHV0IGNvbHVtbiBrZXkuIERlZmF1bHRzIHRvIHRoZSBzbmFrZV9jYXNlZCBhdHRyaWJ1dGUgbmFtZS5cbiAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBbcmVxdWlyZWRNZXNzYWdlXSAtIFBpbm5lZCBtZXNzYWdlIGZvciByZXF1aXJlZCByZWplY3Rpb25zLlxuICogQHByb3BlcnR5IHtzdHJpbmd9IFt0b29Mb25nTWVzc2FnZV0gLSBQaW5uZWQgbWVzc2FnZSBmb3IgdG9vLWxvbmcgcmVqZWN0aW9ucy5cbiAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBbaW52YWxpZE1lc3NhZ2VdIC0gUGlubmVkIG1lc3NhZ2UgZm9yIGludmFsaWQgZGF0ZXMgYW5kIG5vbi1vYmplY3Qvbm9uLXN0cmluZyBqc29uIHZhbHVlcy5cbiAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBbaW52YWxpZEpzb25NZXNzYWdlXSAtIFBpbm5lZCBtZXNzYWdlIGZvciB1bnBhcnNlYWJsZSBqc29uIHN0cmluZ3MuXG4gKi9cblxuLyoqXG4gKiBOb3JtYWxpemVzIHdyaXRhYmxlIHN5bmMgYXR0cmlidXRlcyBhZ2FpbnN0IGEgZGVjbGFyYXRpdmUgc2NoZW1hLlxuICpcbiAqIE9ubHkgcHJlc2VudCBrZXlzIGFyZSB2YWxpZGF0ZWQsIHNvIHJlcXVpcmVkIGF0dHJpYnV0ZXMgbWF5IGJlIGFic2VudCDigJRcbiAqIGNyZWF0ZSBkZWZhdWx0cyBvd24gdGhhdCBjb25jZXJuLiBJbnB1dCBrZXlzIG5vdCBkZWNsYXJlZCBpbiB0aGUgc2NoZW1hIGFyZVxuICogcmVqZWN0ZWQgYnkgZGVmYXVsdCBzbyBtaXNzcGVsbGVkIG9yIHJlYWQtb25seSBhdHRyaWJ1dGVzIGZhaWwgbG91ZGx5XG4gKiBpbnN0ZWFkIG9mIGJlaW5nIHNpbGVudGx5IGRpc2NhcmRlZCAoYHVua25vd25BdHRyaWJ1dGVzOiBcImlnbm9yZVwiYCBvcHRzXG4gKiBvdXQpLiBWYWxpZGF0aW9uIGZhaWx1cmVzIHRocm93IHRocm91Z2ggdGhlIGdpdmVuIGVycm9yIGZhY3Rvcnkgc28gYXBwc1xuICogY29udHJvbCB0aGUgcmFpc2VkIGVycm9yIGNsYXNzLlxuICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBPcHRpb25zLlxuICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCA/Pn0gYXJncy5hdHRyaWJ1dGVzIC0gUmF3IGluY29taW5nIGF0dHJpYnV0ZXMgKGNhbWVsQ2FzZSBhbmQvb3Igc25ha2VfY2FzZSBrZXlzKS5cbiAqIEBwYXJhbSB7U3luY0F0dHJpYnV0ZUVycm9yRmFjdG9yeX0gYXJncy5lcnJvckZhY3RvcnkgLSBNYXBzIHZhbGlkYXRpb24gZmFpbHVyZXMgdG8gdGhyb3duIGVycm9ycy5cbiAqIEBwYXJhbSB7UmVjb3JkPHN0cmluZywgU3luY0F0dHJpYnV0ZVNjaGVtYUVudHJ5Pn0gYXJncy5zY2hlbWEgLSBBdHRyaWJ1dGUgc2NoZW1hIGtleWVkIGJ5IGNhbWVsQ2FzZSBhdHRyaWJ1dGUgbmFtZS5cbiAqIEBwYXJhbSB7XCJlcnJvclwiIHwgXCJpZ25vcmVcIn0gW2FyZ3MudW5rbm93bkF0dHJpYnV0ZXNdIC0gVW5rbm93biBpbnB1dC1rZXkgaGFuZGxpbmcuIERlZmF1bHRzIHRvIFwiZXJyb3JcIi5cbiAqIEByZXR1cm5zIHtSZWNvcmQ8c3RyaW5nLCA/Pn0gTm9ybWFsaXplZCB2YWx1ZXMga2V5ZWQgYnkgc25ha2VfY2FzZSBjb2x1bW4gbmFtZXMuXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIG5vcm1hbGl6ZUF0dHJpYnV0ZXNXaXRoU2NoZW1hKHthdHRyaWJ1dGVzLCBlcnJvckZhY3RvcnksIHNjaGVtYSwgdW5rbm93bkF0dHJpYnV0ZXMgPSBcImVycm9yXCJ9KSB7XG4gIGlmICghYXR0cmlidXRlcyB8fCB0eXBlb2YgYXR0cmlidXRlcyAhPSBcIm9iamVjdFwiKSB0aHJvdyBuZXcgRXJyb3IoYG5vcm1hbGl6ZUF0dHJpYnV0ZXNXaXRoU2NoZW1hIHJlcXVpcmVzIGFuIGF0dHJpYnV0ZXMgb2JqZWN0LCBnb3Q6ICR7U3RyaW5nKGF0dHJpYnV0ZXMpfWApXG4gIGlmICh0eXBlb2YgZXJyb3JGYWN0b3J5ICE9IFwiZnVuY3Rpb25cIikgdGhyb3cgbmV3IEVycm9yKFwibm9ybWFsaXplQXR0cmlidXRlc1dpdGhTY2hlbWEgcmVxdWlyZXMgYW4gZXJyb3JGYWN0b3J5IGZ1bmN0aW9uXCIpXG4gIGlmICghc2NoZW1hIHx8IHR5cGVvZiBzY2hlbWEgIT0gXCJvYmplY3RcIikgdGhyb3cgbmV3IEVycm9yKGBub3JtYWxpemVBdHRyaWJ1dGVzV2l0aFNjaGVtYSByZXF1aXJlcyBhIHNjaGVtYSBvYmplY3QsIGdvdDogJHtTdHJpbmcoc2NoZW1hKX1gKVxuICBpZiAodW5rbm93bkF0dHJpYnV0ZXMgIT0gXCJlcnJvclwiICYmIHVua25vd25BdHRyaWJ1dGVzICE9IFwiaWdub3JlXCIpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYG5vcm1hbGl6ZUF0dHJpYnV0ZXNXaXRoU2NoZW1hIHVua25vd25BdHRyaWJ1dGVzIG11c3QgYmUgXCJlcnJvclwiIG9yIFwiaWdub3JlXCIsIGdvdDogJHtTdHJpbmcodW5rbm93bkF0dHJpYnV0ZXMpfWApXG4gIH1cblxuICAvKiogQHR5cGUge1JlY29yZDxzdHJpbmcsID8+fSAqL1xuICBjb25zdCBub3JtYWxpemVkID0ge31cbiAgLyoqIEB0eXBlIHtTZXQ8c3RyaW5nPn0gKi9cbiAgY29uc3Qga25vd25JbnB1dEtleXMgPSBuZXcgU2V0KClcblxuICBmb3IgKGNvbnN0IFthdHRyaWJ1dGVOYW1lLCBlbnRyeV0gb2YgT2JqZWN0LmVudHJpZXMoc2NoZW1hKSkge1xuICAgIGNvbnN0IGNvbHVtbiA9IGVudHJ5LmNvbHVtbiA/PyBzbmFrZUNhc2VLZXkoYXR0cmlidXRlTmFtZSlcbiAgICBjb25zdCBpbnB1dEtleXMgPSBjb2x1bW4gPT0gYXR0cmlidXRlTmFtZSA/IFthdHRyaWJ1dGVOYW1lXSA6IFtjb2x1bW4sIGF0dHJpYnV0ZU5hbWVdXG5cbiAgICBmb3IgKGNvbnN0IGlucHV0S2V5IG9mIGlucHV0S2V5cykge1xuICAgICAga25vd25JbnB1dEtleXMuYWRkKGlucHV0S2V5KVxuXG4gICAgICBpZiAoIU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChhdHRyaWJ1dGVzLCBpbnB1dEtleSkpIGNvbnRpbnVlXG5cbiAgICAgIG5vcm1hbGl6ZWRbY29sdW1uXSA9IG5vcm1hbGl6ZUF0dHJpYnV0ZVZhbHVlKHthdHRyaWJ1dGVOYW1lLCBlbnRyeSwgZXJyb3JGYWN0b3J5LCB2YWx1ZTogYXR0cmlidXRlc1tpbnB1dEtleV19KVxuICAgIH1cbiAgfVxuXG4gIGlmICh1bmtub3duQXR0cmlidXRlcyA9PSBcImVycm9yXCIpIHtcbiAgICBmb3IgKGNvbnN0IGlucHV0S2V5IG9mIE9iamVjdC5rZXlzKGF0dHJpYnV0ZXMpKSB7XG4gICAgICBpZiAoIWtub3duSW5wdXRLZXlzLmhhcyhpbnB1dEtleSkpIHtcbiAgICAgICAgdGhyb3cgZXJyb3JGYWN0b3J5KGBVbmtub3duIGF0dHJpYnV0ZTogJHtpbnB1dEtleX0uYCwge2NvZGU6IFwic3luYy11bmtub3duLWF0dHJpYnV0ZVwifSlcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICByZXR1cm4gbm9ybWFsaXplZFxufVxuXG4vKipcbiAqIE5vcm1hbGl6ZXMgb25lIHByZXNlbnQgYXR0cmlidXRlIHZhbHVlIHBlciBpdHMgc2NoZW1hIGVudHJ5LlxuICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBPcHRpb25zLlxuICogQHBhcmFtIHtzdHJpbmd9IGFyZ3MuYXR0cmlidXRlTmFtZSAtIGNhbWVsQ2FzZSBhdHRyaWJ1dGUgbmFtZS5cbiAqIEBwYXJhbSB7U3luY0F0dHJpYnV0ZVNjaGVtYUVudHJ5fSBhcmdzLmVudHJ5IC0gU2NoZW1hIGVudHJ5LlxuICogQHBhcmFtIHtTeW5jQXR0cmlidXRlRXJyb3JGYWN0b3J5fSBhcmdzLmVycm9yRmFjdG9yeSAtIEVycm9yIGZhY3RvcnkuXG4gKiBAcGFyYW0gez99IGFyZ3MudmFsdWUgLSBSYXcgdmFsdWUuXG4gKiBAcmV0dXJucyB7P30gTm9ybWFsaXplZCB2YWx1ZS5cbiAqL1xuZnVuY3Rpb24gbm9ybWFsaXplQXR0cmlidXRlVmFsdWUoe2F0dHJpYnV0ZU5hbWUsIGVudHJ5LCBlcnJvckZhY3RvcnksIHZhbHVlfSkge1xuICBjb25zdCBsYWJlbCA9IGVudHJ5LmxhYmVsID8/IGF0dHJpYnV0ZU5hbWVcblxuICBpZiAoZW50cnkudHlwZSA9PSBcInN0cmluZ1wiKSByZXR1cm4gbm9ybWFsaXplU3RyaW5nKHtlbnRyeSwgZXJyb3JGYWN0b3J5LCBsYWJlbCwgdmFsdWV9KVxuICBpZiAoZW50cnkudHlwZSA9PSBcImRhdGVcIikgcmV0dXJuIG5vcm1hbGl6ZURhdGUoe2VudHJ5LCBlcnJvckZhY3RvcnksIGxhYmVsLCB2YWx1ZX0pXG4gIGlmIChlbnRyeS50eXBlID09IFwianNvblwiKSByZXR1cm4gbm9ybWFsaXplSnNvbih7ZW50cnksIGVycm9yRmFjdG9yeSwgbGFiZWwsIHZhbHVlfSlcbiAgaWYgKGVudHJ5LnR5cGUgPT0gXCJyYXdcIikgcmV0dXJuIHZhbHVlXG5cbiAgdGhyb3cgbmV3IEVycm9yKGBVbmtub3duIHN5bmMgYXR0cmlidXRlIHNjaGVtYSB0eXBlIGZvciAke2F0dHJpYnV0ZU5hbWV9OiAke1N0cmluZyhlbnRyeS50eXBlKX1gKVxufVxuXG4vKipcbiAqIE5vcm1hbGl6ZXMgYSBzdHJpbmcgYXR0cmlidXRlOiB0cmltcywgcmVqZWN0cyBibGFuayByZXF1aXJlZCB2YWx1ZXMgYWZ0ZXIgdHJpbW1pbmcsIG1hcHMgZW1wdHkgb3B0aW9uYWwgdmFsdWVzIHRvIG51bGwgYW5kIGJvdW5kcyB0aGUgdHJpbW1lZCBsZW5ndGguXG4gKiBAcGFyYW0ge29iamVjdH0gYXJncyAtIE9wdGlvbnMuXG4gKiBAcGFyYW0ge1N5bmNBdHRyaWJ1dGVTY2hlbWFFbnRyeX0gYXJncy5lbnRyeSAtIFNjaGVtYSBlbnRyeS5cbiAqIEBwYXJhbSB7U3luY0F0dHJpYnV0ZUVycm9yRmFjdG9yeX0gYXJncy5lcnJvckZhY3RvcnkgLSBFcnJvciBmYWN0b3J5LlxuICogQHBhcmFtIHtzdHJpbmd9IGFyZ3MubGFiZWwgLSBNZXNzYWdlL2NvZGUgbGFiZWwuXG4gKiBAcGFyYW0gez99IGFyZ3MudmFsdWUgLSBSYXcgdmFsdWUuXG4gKiBAcmV0dXJucyB7c3RyaW5nIHwgbnVsbH0gTm9ybWFsaXplZCBzdHJpbmcuXG4gKi9cbmZ1bmN0aW9uIG5vcm1hbGl6ZVN0cmluZyh7ZW50cnksIGVycm9yRmFjdG9yeSwgbGFiZWwsIHZhbHVlfSkge1xuICBjb25zdCByZXF1aXJlZEVycm9yID0gKCkgPT4gZXJyb3JGYWN0b3J5KGVudHJ5LnJlcXVpcmVkTWVzc2FnZSA/PyBgJHtsYWJlbH0gaXMgcmVxdWlyZWQuYCwge2NvZGU6IGBzeW5jLSR7bGFiZWx9LXJlcXVpcmVkYH0pXG5cbiAgaWYgKHZhbHVlID09PSBudWxsIHx8IHZhbHVlID09PSB1bmRlZmluZWQgfHwgdmFsdWUgPT09IFwiXCIpIHtcbiAgICBpZiAoZW50cnkucmVxdWlyZWQpIHRocm93IHJlcXVpcmVkRXJyb3IoKVxuXG4gICAgcmV0dXJuIG51bGxcbiAgfVxuXG4gIGNvbnN0IHN0cmluZ1ZhbHVlID0gU3RyaW5nKHZhbHVlKS50cmltKClcblxuICBpZiAoc3RyaW5nVmFsdWUgPT09IFwiXCIpIHtcbiAgICBpZiAoZW50cnkucmVxdWlyZWQpIHRocm93IHJlcXVpcmVkRXJyb3IoKVxuXG4gICAgcmV0dXJuIG51bGxcbiAgfVxuXG4gIGlmIChlbnRyeS5tYXhMZW5ndGggIT09IHVuZGVmaW5lZCAmJiBzdHJpbmdWYWx1ZS5sZW5ndGggPiBlbnRyeS5tYXhMZW5ndGgpIHtcbiAgICB0aHJvdyBlcnJvckZhY3RvcnkoZW50cnkudG9vTG9uZ01lc3NhZ2UgPz8gYCR7bGFiZWx9IGlzIHRvbyBsb25nIChtYXhpbXVtIGlzICR7ZW50cnkubWF4TGVuZ3RofSBjaGFyYWN0ZXJzKS5gLCB7Y29kZTogYHN5bmMtJHtsYWJlbH0tdG9vLWxvbmdgfSlcbiAgfVxuXG4gIHJldHVybiBzdHJpbmdWYWx1ZVxufVxuXG4vKipcbiAqIE5vcm1hbGl6ZXMgYSBkYXRlIGF0dHJpYnV0ZTogRGF0ZSBpbnN0YW5jZXMgcGFzcyB0aHJvdWdoLCBzdHJpbmdzL251bWJlcnMgYXJlIHBhcnNlZCBhbmQgaW52YWxpZCBvciBtaXNzaW5nLXJlcXVpcmVkIHZhbHVlcyBhcmUgcmVqZWN0ZWQuXG4gKiBAcGFyYW0ge29iamVjdH0gYXJncyAtIE9wdGlvbnMuXG4gKiBAcGFyYW0ge1N5bmNBdHRyaWJ1dGVTY2hlbWFFbnRyeX0gYXJncy5lbnRyeSAtIFNjaGVtYSBlbnRyeS5cbiAqIEBwYXJhbSB7U3luY0F0dHJpYnV0ZUVycm9yRmFjdG9yeX0gYXJncy5lcnJvckZhY3RvcnkgLSBFcnJvciBmYWN0b3J5LlxuICogQHBhcmFtIHtzdHJpbmd9IGFyZ3MubGFiZWwgLSBNZXNzYWdlL2NvZGUgbGFiZWwuXG4gKiBAcGFyYW0gez99IGFyZ3MudmFsdWUgLSBSYXcgdmFsdWUuXG4gKiBAcmV0dXJucyB7RGF0ZSB8IG51bGx9IE5vcm1hbGl6ZWQgZGF0ZS5cbiAqL1xuZnVuY3Rpb24gbm9ybWFsaXplRGF0ZSh7ZW50cnksIGVycm9yRmFjdG9yeSwgbGFiZWwsIHZhbHVlfSkge1xuICBpZiAoIWVudHJ5LnJlcXVpcmVkICYmICh2YWx1ZSA9PT0gbnVsbCB8fCB2YWx1ZSA9PT0gdW5kZWZpbmVkIHx8IHZhbHVlID09PSBcIlwiKSkgcmV0dXJuIG51bGxcblxuICBjb25zdCBkYXRlVmFsdWUgPSB2YWx1ZSBpbnN0YW5jZW9mIERhdGUgPyB2YWx1ZSA6IHR5cGVvZiB2YWx1ZSA9PSBcInN0cmluZ1wiIHx8IHR5cGVvZiB2YWx1ZSA9PSBcIm51bWJlclwiID8gbmV3IERhdGUodmFsdWUpIDogbnVsbFxuXG4gIGlmICghZGF0ZVZhbHVlIHx8IE51bWJlci5pc05hTihkYXRlVmFsdWUuZ2V0VGltZSgpKSkge1xuICAgIHRocm93IGVycm9yRmFjdG9yeShlbnRyeS5pbnZhbGlkTWVzc2FnZSA/PyBgJHtsYWJlbH0gbXVzdCBiZSBhIHZhbGlkIGRhdGV0aW1lLmAsIHtjb2RlOiBgc3luYy0ke2xhYmVsfS1pbnZhbGlkLWRhdGV0aW1lYH0pXG4gIH1cblxuICByZXR1cm4gZGF0ZVZhbHVlXG59XG5cbi8qKlxuICogTm9ybWFsaXplcyBhIGpzb24gYXR0cmlidXRlOiBlbXB0eSB2YWx1ZXMgYmVjb21lIG51bGwsIG9iamVjdHMgcGFzcyB0aHJvdWdoLCBzdHJpbmdzIG11c3QgcGFyc2UgYXMgSlNPTiBhbmQgb3RoZXIgdHlwZXMgYXJlIHJlamVjdGVkLlxuICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBPcHRpb25zLlxuICogQHBhcmFtIHtTeW5jQXR0cmlidXRlU2NoZW1hRW50cnl9IGFyZ3MuZW50cnkgLSBTY2hlbWEgZW50cnkuXG4gKiBAcGFyYW0ge1N5bmNBdHRyaWJ1dGVFcnJvckZhY3Rvcnl9IGFyZ3MuZXJyb3JGYWN0b3J5IC0gRXJyb3IgZmFjdG9yeS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBhcmdzLmxhYmVsIC0gTWVzc2FnZS9jb2RlIGxhYmVsLlxuICogQHBhcmFtIHs/fSBhcmdzLnZhbHVlIC0gUmF3IHZhbHVlLlxuICogQHJldHVybnMgez99IE5vcm1hbGl6ZWQganNvbiB2YWx1ZS5cbiAqL1xuZnVuY3Rpb24gbm9ybWFsaXplSnNvbih7ZW50cnksIGVycm9yRmFjdG9yeSwgbGFiZWwsIHZhbHVlfSkge1xuICBpZiAodmFsdWUgPT09IG51bGwgfHwgdmFsdWUgPT09IHVuZGVmaW5lZCB8fCB2YWx1ZSA9PT0gXCJcIikgcmV0dXJuIG51bGxcbiAgaWYgKHR5cGVvZiB2YWx1ZSA9PSBcIm9iamVjdFwiKSByZXR1cm4gdmFsdWVcblxuICBpZiAodHlwZW9mIHZhbHVlID09IFwic3RyaW5nXCIpIHtcbiAgICB0cnkge1xuICAgICAgcmV0dXJuIEpTT04ucGFyc2UodmFsdWUpXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIHRocm93IGVycm9yRmFjdG9yeShlbnRyeS5pbnZhbGlkSnNvbk1lc3NhZ2UgPz8gYCR7bGFiZWx9IG11c3QgYmUgdmFsaWQgSlNPTi5gLCB7XG4gICAgICAgIGNhdXNlOiAvKiogQHR5cGUge0Vycm9yfSAqLyAoZXJyb3IpLFxuICAgICAgICBjb2RlOiBgc3luYy0ke2xhYmVsfS1pbnZhbGlkLWpzb25gXG4gICAgICB9KVxuICAgIH1cbiAgfVxuXG4gIHRocm93IGVycm9yRmFjdG9yeShlbnRyeS5pbnZhbGlkTWVzc2FnZSA/PyBgJHtsYWJlbH0gbXVzdCBiZSBhbiBvYmplY3Qgb3IgSlNPTiBzdHJpbmcuYCwge2NvZGU6IGBzeW5jLSR7bGFiZWx9LWludmFsaWRgfSlcbn1cbiJdfQ==
@@ -1,186 +0,0 @@
1
- // @ts-check
2
-
3
- import {snakeCaseKey} from "typanic"
4
-
5
- /**
6
- * Builds the error thrown for a failed sync attribute validation.
7
- * @callback SyncAttributeErrorFactory
8
- * @param {string} message - Human-readable validation message.
9
- * @param {{cause?: Error, code: string}} details - Stable machine-readable code and optional cause.
10
- * @returns {Error} Error to throw.
11
- */
12
-
13
- /**
14
- * Declarative validation schema for one writable sync attribute.
15
- *
16
- * Keys are camelCase attribute names; each attribute also accepts its
17
- * snake_case column key on input (the camelCase key wins when both are given)
18
- * and is written under the snake_case column key on output. Error messages
19
- * default to `<label> ...` phrasings with `sync-<label>-...` codes, where the
20
- * label defaults to the camelCase attribute name; apps pin exact messages per
21
- * attribute through the `*Message` overrides.
22
- * @typedef {object} SyncAttributeSchemaEntry
23
- * @property {"date" | "json" | "raw" | "string"} type - Attribute value type.
24
- * @property {boolean} [required] - Whether present blank/absent values are rejected (strings reject blank-after-trim, dates reject null).
25
- * @property {number} [maxLength] - Maximum string length, checked after trimming.
26
- * @property {string} [label] - Label used in derived messages and codes.
27
- * @property {string} [column] - Output column key. Defaults to the snake_cased attribute name.
28
- * @property {string} [requiredMessage] - Pinned message for required rejections.
29
- * @property {string} [tooLongMessage] - Pinned message for too-long rejections.
30
- * @property {string} [invalidMessage] - Pinned message for invalid dates and non-object/non-string json values.
31
- * @property {string} [invalidJsonMessage] - Pinned message for unparseable json strings.
32
- */
33
-
34
- /**
35
- * Normalizes writable sync attributes against a declarative schema.
36
- *
37
- * Only present keys are validated, so required attributes may be absent —
38
- * create defaults own that concern. Input keys not declared in the schema are
39
- * rejected by default so misspelled or read-only attributes fail loudly
40
- * instead of being silently discarded (`unknownAttributes: "ignore"` opts
41
- * out). Validation failures throw through the given error factory so apps
42
- * control the raised error class.
43
- * @param {object} args - Options.
44
- * @param {Record<string, ?>} args.attributes - Raw incoming attributes (camelCase and/or snake_case keys).
45
- * @param {SyncAttributeErrorFactory} args.errorFactory - Maps validation failures to thrown errors.
46
- * @param {Record<string, SyncAttributeSchemaEntry>} args.schema - Attribute schema keyed by camelCase attribute name.
47
- * @param {"error" | "ignore"} [args.unknownAttributes] - Unknown input-key handling. Defaults to "error".
48
- * @returns {Record<string, ?>} Normalized values keyed by snake_case column names.
49
- */
50
- export default function normalizeAttributesWithSchema({attributes, errorFactory, schema, unknownAttributes = "error"}) {
51
- if (!attributes || typeof attributes != "object") throw new Error(`normalizeAttributesWithSchema requires an attributes object, got: ${String(attributes)}`)
52
- if (typeof errorFactory != "function") throw new Error("normalizeAttributesWithSchema requires an errorFactory function")
53
- if (!schema || typeof schema != "object") throw new Error(`normalizeAttributesWithSchema requires a schema object, got: ${String(schema)}`)
54
- if (unknownAttributes != "error" && unknownAttributes != "ignore") {
55
- throw new Error(`normalizeAttributesWithSchema unknownAttributes must be "error" or "ignore", got: ${String(unknownAttributes)}`)
56
- }
57
-
58
- /** @type {Record<string, ?>} */
59
- const normalized = {}
60
- /** @type {Set<string>} */
61
- const knownInputKeys = new Set()
62
-
63
- for (const [attributeName, entry] of Object.entries(schema)) {
64
- const column = entry.column ?? snakeCaseKey(attributeName)
65
- const inputKeys = column == attributeName ? [attributeName] : [column, attributeName]
66
-
67
- for (const inputKey of inputKeys) {
68
- knownInputKeys.add(inputKey)
69
-
70
- if (!Object.prototype.hasOwnProperty.call(attributes, inputKey)) continue
71
-
72
- normalized[column] = normalizeAttributeValue({attributeName, entry, errorFactory, value: attributes[inputKey]})
73
- }
74
- }
75
-
76
- if (unknownAttributes == "error") {
77
- for (const inputKey of Object.keys(attributes)) {
78
- if (!knownInputKeys.has(inputKey)) {
79
- throw errorFactory(`Unknown attribute: ${inputKey}.`, {code: "sync-unknown-attribute"})
80
- }
81
- }
82
- }
83
-
84
- return normalized
85
- }
86
-
87
- /**
88
- * Normalizes one present attribute value per its schema entry.
89
- * @param {object} args - Options.
90
- * @param {string} args.attributeName - camelCase attribute name.
91
- * @param {SyncAttributeSchemaEntry} args.entry - Schema entry.
92
- * @param {SyncAttributeErrorFactory} args.errorFactory - Error factory.
93
- * @param {?} args.value - Raw value.
94
- * @returns {?} Normalized value.
95
- */
96
- function normalizeAttributeValue({attributeName, entry, errorFactory, value}) {
97
- const label = entry.label ?? attributeName
98
-
99
- if (entry.type == "string") return normalizeString({entry, errorFactory, label, value})
100
- if (entry.type == "date") return normalizeDate({entry, errorFactory, label, value})
101
- if (entry.type == "json") return normalizeJson({entry, errorFactory, label, value})
102
- if (entry.type == "raw") return value
103
-
104
- throw new Error(`Unknown sync attribute schema type for ${attributeName}: ${String(entry.type)}`)
105
- }
106
-
107
- /**
108
- * Normalizes a string attribute: trims, rejects blank required values after trimming, maps empty optional values to null and bounds the trimmed length.
109
- * @param {object} args - Options.
110
- * @param {SyncAttributeSchemaEntry} args.entry - Schema entry.
111
- * @param {SyncAttributeErrorFactory} args.errorFactory - Error factory.
112
- * @param {string} args.label - Message/code label.
113
- * @param {?} args.value - Raw value.
114
- * @returns {string | null} Normalized string.
115
- */
116
- function normalizeString({entry, errorFactory, label, value}) {
117
- const requiredError = () => errorFactory(entry.requiredMessage ?? `${label} is required.`, {code: `sync-${label}-required`})
118
-
119
- if (value === null || value === undefined || value === "") {
120
- if (entry.required) throw requiredError()
121
-
122
- return null
123
- }
124
-
125
- const stringValue = String(value).trim()
126
-
127
- if (stringValue === "") {
128
- if (entry.required) throw requiredError()
129
-
130
- return null
131
- }
132
-
133
- if (entry.maxLength !== undefined && stringValue.length > entry.maxLength) {
134
- throw errorFactory(entry.tooLongMessage ?? `${label} is too long (maximum is ${entry.maxLength} characters).`, {code: `sync-${label}-too-long`})
135
- }
136
-
137
- return stringValue
138
- }
139
-
140
- /**
141
- * Normalizes a date attribute: Date instances pass through, strings/numbers are parsed and invalid or missing-required values are rejected.
142
- * @param {object} args - Options.
143
- * @param {SyncAttributeSchemaEntry} args.entry - Schema entry.
144
- * @param {SyncAttributeErrorFactory} args.errorFactory - Error factory.
145
- * @param {string} args.label - Message/code label.
146
- * @param {?} args.value - Raw value.
147
- * @returns {Date | null} Normalized date.
148
- */
149
- function normalizeDate({entry, errorFactory, label, value}) {
150
- if (!entry.required && (value === null || value === undefined || value === "")) return null
151
-
152
- const dateValue = value instanceof Date ? value : typeof value == "string" || typeof value == "number" ? new Date(value) : null
153
-
154
- if (!dateValue || Number.isNaN(dateValue.getTime())) {
155
- throw errorFactory(entry.invalidMessage ?? `${label} must be a valid datetime.`, {code: `sync-${label}-invalid-datetime`})
156
- }
157
-
158
- return dateValue
159
- }
160
-
161
- /**
162
- * Normalizes a json attribute: empty values become null, objects pass through, strings must parse as JSON and other types are rejected.
163
- * @param {object} args - Options.
164
- * @param {SyncAttributeSchemaEntry} args.entry - Schema entry.
165
- * @param {SyncAttributeErrorFactory} args.errorFactory - Error factory.
166
- * @param {string} args.label - Message/code label.
167
- * @param {?} args.value - Raw value.
168
- * @returns {?} Normalized json value.
169
- */
170
- function normalizeJson({entry, errorFactory, label, value}) {
171
- if (value === null || value === undefined || value === "") return null
172
- if (typeof value == "object") return value
173
-
174
- if (typeof value == "string") {
175
- try {
176
- return JSON.parse(value)
177
- } catch (error) {
178
- throw errorFactory(entry.invalidJsonMessage ?? `${label} must be valid JSON.`, {
179
- cause: /** @type {Error} */ (error),
180
- code: `sync-${label}-invalid-json`
181
- })
182
- }
183
- }
184
-
185
- throw errorFactory(entry.invalidMessage ?? `${label} must be an object or JSON string.`, {code: `sync-${label}-invalid`})
186
- }