@streamotter/contracts 0.1.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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +329 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +106 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/limits.d.ts +8 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +76 -0
- package/dist/limits.js.map +1 -0
- package/dist/primitives.d.ts +29 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +123 -0
- package/dist/primitives.js.map +1 -0
- package/dist/protocol.d.ts +32 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +37 -0
- package/dist/protocol.js.map +1 -0
- package/dist/schema.d.ts +34 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +267 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +505 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +40 -0
- package/src/config.ts +311 -0
- package/src/errors.ts +118 -0
- package/src/index.ts +7 -0
- package/src/limits.ts +78 -0
- package/src/primitives.ts +112 -0
- package/src/protocol.ts +41 -0
- package/src/schema.ts +255 -0
- package/src/types.ts +312 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEtD,6EAA6E;AAC7E,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAoB1C,CAAC;AAEH,eAAO,MAAM,UAAU,EAAkC,SAAS,CAAC,MAAM,MAAM,CAAC,EAAE,CAAC;AAEnF,wBAAgB,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,GAAG,MAAM,CAE5E;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CA4CxF"}
|
package/dist/limits.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { pointer } from "./schema.js";
|
|
2
|
+
/** Starting bounds from the specification; not published capacity claims. */
|
|
3
|
+
export const DEFAULT_LIMITS = Object.freeze({
|
|
4
|
+
maxConnections: 1_000,
|
|
5
|
+
maxSubscriptionsPerConnection: 50,
|
|
6
|
+
maxSourceRecordBytes: 1_048_576,
|
|
7
|
+
maxDataFrameBytes: 65_536,
|
|
8
|
+
maxParamsBytes: 4_096,
|
|
9
|
+
maxPendingFramesPerSubscription: 100,
|
|
10
|
+
maxPendingBytesPerSubscription: 1_048_576,
|
|
11
|
+
maxPendingBytesPerConnection: 4_194_304,
|
|
12
|
+
maxPendingBytesGateway: 67_108_864,
|
|
13
|
+
maxMapOutputs: 100,
|
|
14
|
+
maxConcurrentSnapshots: 32,
|
|
15
|
+
handlerTimeoutMs: 2_000,
|
|
16
|
+
snapshotTimeoutMs: 10_000,
|
|
17
|
+
receiptTimeoutMs: 5_000,
|
|
18
|
+
maxSyncAttempts: 3,
|
|
19
|
+
maxTraceEntries: 10_000,
|
|
20
|
+
maxTraceBytes: 8_388_608,
|
|
21
|
+
maxControlFrameBytes: 16_384,
|
|
22
|
+
controlRequestsPerSecond: 20
|
|
23
|
+
});
|
|
24
|
+
export const LIMIT_KEYS = Object.keys(DEFAULT_LIMITS);
|
|
25
|
+
export function resolveLimits(overrides) {
|
|
26
|
+
return { ...DEFAULT_LIMITS, ...(overrides ?? {}) };
|
|
27
|
+
}
|
|
28
|
+
/** Validates override values and cross-field consistency of the resolved limits. */
|
|
29
|
+
export function validateLimits(input, path, issues) {
|
|
30
|
+
if (input === undefined)
|
|
31
|
+
return;
|
|
32
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
33
|
+
issues.push({ path, code: "INVALID_TYPE", message: "limits must be an object." });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const overrides = {};
|
|
37
|
+
let valid = true;
|
|
38
|
+
for (const [key, value] of Object.entries(input)) {
|
|
39
|
+
if (!LIMIT_KEYS.includes(key)) {
|
|
40
|
+
issues.push({ path: pointer(path, key), code: "UNKNOWN_KEY", message: `Unknown limit "${key}".` });
|
|
41
|
+
valid = false;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
45
|
+
issues.push({ path: pointer(path, key), code: "INVALID_VALUE", message: `${key} must be a positive integer.` });
|
|
46
|
+
valid = false;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
overrides[key] = value;
|
|
50
|
+
}
|
|
51
|
+
if (!valid)
|
|
52
|
+
return;
|
|
53
|
+
const limits = resolveLimits(overrides);
|
|
54
|
+
const ordered = [
|
|
55
|
+
["maxDataFrameBytes", "maxPendingBytesPerSubscription"],
|
|
56
|
+
["maxPendingBytesPerSubscription", "maxPendingBytesPerConnection"],
|
|
57
|
+
["maxPendingBytesPerConnection", "maxPendingBytesGateway"],
|
|
58
|
+
["maxParamsBytes", "maxControlFrameBytes"]
|
|
59
|
+
];
|
|
60
|
+
for (const [smaller, larger] of ordered) {
|
|
61
|
+
if (limits[smaller] > limits[larger]) {
|
|
62
|
+
issues.push({
|
|
63
|
+
path: pointer(path, smaller),
|
|
64
|
+
code: "INCONSISTENT_LIMITS",
|
|
65
|
+
message: `${smaller} (${limits[smaller]}) must not exceed ${larger} (${limits[larger]}).`
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (limits.maxControlFrameBytes < 1_024) {
|
|
70
|
+
issues.push({ path: pointer(path, "maxControlFrameBytes"), code: "INCONSISTENT_LIMITS", message: "maxControlFrameBytes must be at least 1024." });
|
|
71
|
+
}
|
|
72
|
+
if (limits.maxDataFrameBytes < 1_024) {
|
|
73
|
+
issues.push({ path: pointer(path, "maxDataFrameBytes"), code: "INCONSISTENT_LIMITS", message: "maxDataFrameBytes must be at least 1024." });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Json, Revision } from "./types.ts";
|
|
2
|
+
export declare const MAX_NESTING_DEPTH = 16;
|
|
3
|
+
export declare const IDENTIFIER_PATTERN: RegExp;
|
|
4
|
+
export declare const REVISION_PATTERN: RegExp;
|
|
5
|
+
export declare const UUID_PATTERN: RegExp;
|
|
6
|
+
export declare function isIdentifier(value: unknown): value is string;
|
|
7
|
+
export declare function isUuid(value: unknown): value is string;
|
|
8
|
+
export declare function isRevision(value: unknown): value is Revision;
|
|
9
|
+
/** Numeric comparison of canonical revisions without converting to number. */
|
|
10
|
+
export declare function compareRevisions(a: Revision, b: Revision): -1 | 0 | 1;
|
|
11
|
+
/** Parses a UTC RFC3339 timestamp; returns NaN when malformed. */
|
|
12
|
+
export declare function parseUtcTimestamp(value: unknown): number;
|
|
13
|
+
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
14
|
+
/** UTF-8 byte length without allocating an encoded copy. */
|
|
15
|
+
export declare function utf8ByteLength(text: string): number;
|
|
16
|
+
export declare function codePointLength(text: string): number;
|
|
17
|
+
/**
|
|
18
|
+
* Checks that a value is plain JSON data within the nesting limit: finite
|
|
19
|
+
* numbers, plain objects/arrays, no undefined or functions.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isJsonValue(value: unknown, depth?: number): value is Json;
|
|
22
|
+
/**
|
|
23
|
+
* Canonical JSON: sorted object keys, -0 normalized to 0, no whitespace.
|
|
24
|
+
* Throws on values that are not JSON data.
|
|
25
|
+
*/
|
|
26
|
+
export declare function canonicalJson(value: unknown): string;
|
|
27
|
+
/** Pretty canonical JSON (sorted keys, two-space indent, trailing newline) for files. */
|
|
28
|
+
export declare function canonicalJsonPretty(value: unknown): string;
|
|
29
|
+
//# sourceMappingURL=primitives.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEjD,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,eAAO,MAAM,kBAAkB,QAAkC,CAAC;AAClE,eAAO,MAAM,gBAAgB,QAA6B,CAAC;AAC3D,eAAO,MAAM,YAAY,QAAoE,CAAC;AAG9F,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE5D;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEtD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAIrE;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGxD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI9E;AAED,4DAA4D;AAC5D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYnD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWpD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,KAAK,IAAI,IAAI,CAOpE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD;AAsBD,yFAAyF;AACzF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE1D"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export const MAX_NESTING_DEPTH = 16;
|
|
2
|
+
export const IDENTIFIER_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
3
|
+
export const REVISION_PATTERN = /^(?:0|[1-9][0-9]{0,38})$/;
|
|
4
|
+
export const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
5
|
+
const RFC3339_UTC_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/;
|
|
6
|
+
export function isIdentifier(value) {
|
|
7
|
+
return typeof value === "string" && IDENTIFIER_PATTERN.test(value);
|
|
8
|
+
}
|
|
9
|
+
export function isUuid(value) {
|
|
10
|
+
return typeof value === "string" && UUID_PATTERN.test(value);
|
|
11
|
+
}
|
|
12
|
+
export function isRevision(value) {
|
|
13
|
+
return typeof value === "string" && REVISION_PATTERN.test(value);
|
|
14
|
+
}
|
|
15
|
+
/** Numeric comparison of canonical revisions without converting to number. */
|
|
16
|
+
export function compareRevisions(a, b) {
|
|
17
|
+
if (a.length !== b.length)
|
|
18
|
+
return a.length < b.length ? -1 : 1;
|
|
19
|
+
if (a === b)
|
|
20
|
+
return 0;
|
|
21
|
+
return a < b ? -1 : 1;
|
|
22
|
+
}
|
|
23
|
+
/** Parses a UTC RFC3339 timestamp; returns NaN when malformed. */
|
|
24
|
+
export function parseUtcTimestamp(value) {
|
|
25
|
+
if (typeof value !== "string" || !RFC3339_UTC_PATTERN.test(value))
|
|
26
|
+
return Number.NaN;
|
|
27
|
+
return Date.parse(value);
|
|
28
|
+
}
|
|
29
|
+
export function isPlainObject(value) {
|
|
30
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
31
|
+
return false;
|
|
32
|
+
const proto = Object.getPrototypeOf(value);
|
|
33
|
+
return proto === Object.prototype || proto === null;
|
|
34
|
+
}
|
|
35
|
+
/** UTF-8 byte length without allocating an encoded copy. */
|
|
36
|
+
export function utf8ByteLength(text) {
|
|
37
|
+
let bytes = 0;
|
|
38
|
+
for (let i = 0; i < text.length; i++) {
|
|
39
|
+
const code = text.charCodeAt(i);
|
|
40
|
+
if (code < 0x80)
|
|
41
|
+
bytes += 1;
|
|
42
|
+
else if (code < 0x800)
|
|
43
|
+
bytes += 2;
|
|
44
|
+
else if (code >= 0xd800 && code <= 0xdbff && i + 1 < text.length) {
|
|
45
|
+
const next = text.charCodeAt(i + 1);
|
|
46
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
47
|
+
bytes += 4;
|
|
48
|
+
i++;
|
|
49
|
+
}
|
|
50
|
+
else
|
|
51
|
+
bytes += 3;
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
bytes += 3;
|
|
55
|
+
}
|
|
56
|
+
return bytes;
|
|
57
|
+
}
|
|
58
|
+
export function codePointLength(text) {
|
|
59
|
+
let length = 0;
|
|
60
|
+
for (let i = 0; i < text.length; i++) {
|
|
61
|
+
const code = text.charCodeAt(i);
|
|
62
|
+
if (code >= 0xd800 && code <= 0xdbff && i + 1 < text.length) {
|
|
63
|
+
const next = text.charCodeAt(i + 1);
|
|
64
|
+
if (next >= 0xdc00 && next <= 0xdfff)
|
|
65
|
+
i++;
|
|
66
|
+
}
|
|
67
|
+
length++;
|
|
68
|
+
}
|
|
69
|
+
return length;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Checks that a value is plain JSON data within the nesting limit: finite
|
|
73
|
+
* numbers, plain objects/arrays, no undefined or functions.
|
|
74
|
+
*/
|
|
75
|
+
export function isJsonValue(value, depth = 1) {
|
|
76
|
+
if (depth > MAX_NESTING_DEPTH)
|
|
77
|
+
return false;
|
|
78
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
79
|
+
return true;
|
|
80
|
+
if (typeof value === "number")
|
|
81
|
+
return Number.isFinite(value);
|
|
82
|
+
if (Array.isArray(value))
|
|
83
|
+
return value.every(item => isJsonValue(item, depth + 1));
|
|
84
|
+
if (isPlainObject(value))
|
|
85
|
+
return Object.values(value).every(item => isJsonValue(item, depth + 1));
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Canonical JSON: sorted object keys, -0 normalized to 0, no whitespace.
|
|
90
|
+
* Throws on values that are not JSON data.
|
|
91
|
+
*/
|
|
92
|
+
export function canonicalJson(value) {
|
|
93
|
+
return JSON.stringify(canonicalize(value, 1));
|
|
94
|
+
}
|
|
95
|
+
function canonicalize(value, depth) {
|
|
96
|
+
if (depth > MAX_NESTING_DEPTH)
|
|
97
|
+
throw new TypeError("Value exceeds the maximum nesting depth");
|
|
98
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
99
|
+
return value;
|
|
100
|
+
if (typeof value === "number") {
|
|
101
|
+
if (!Number.isFinite(value))
|
|
102
|
+
throw new TypeError("Non-finite numbers are not JSON data");
|
|
103
|
+
return Object.is(value, -0) ? 0 : value;
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(value))
|
|
106
|
+
return value.map(item => canonicalize(item, depth + 1));
|
|
107
|
+
if (isPlainObject(value)) {
|
|
108
|
+
const sorted = {};
|
|
109
|
+
for (const key of Object.keys(value).sort()) {
|
|
110
|
+
const item = value[key];
|
|
111
|
+
if (item === undefined)
|
|
112
|
+
continue;
|
|
113
|
+
Object.defineProperty(sorted, key, { value: canonicalize(item, depth + 1), enumerable: true, writable: true, configurable: true });
|
|
114
|
+
}
|
|
115
|
+
return sorted;
|
|
116
|
+
}
|
|
117
|
+
throw new TypeError("Value is not JSON data");
|
|
118
|
+
}
|
|
119
|
+
/** Pretty canonical JSON (sorted keys, two-space indent, trailing newline) for files. */
|
|
120
|
+
export function canonicalJsonPretty(value) {
|
|
121
|
+
return `${JSON.stringify(JSON.parse(canonicalJson(value)), null, 2)}\n`;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=primitives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.js","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Capabilities } from "./types.ts";
|
|
2
|
+
export declare const PROTOCOL_VERSION: 1;
|
|
3
|
+
export declare const DEFAULT_SOCKET_PATH = "/streamotter/socket.io";
|
|
4
|
+
export declare const DEFAULT_GATEWAY_PORT = 7400;
|
|
5
|
+
export declare const DEFAULT_MANAGEMENT_PORT = 7401;
|
|
6
|
+
export declare const CAPABILITIES: Capabilities;
|
|
7
|
+
export declare const EVENTS: Readonly<{
|
|
8
|
+
readonly hello: "so:hello";
|
|
9
|
+
readonly state: "so:state";
|
|
10
|
+
readonly data: "so:data";
|
|
11
|
+
readonly error: "so:error";
|
|
12
|
+
readonly subscribe: "so:subscribe";
|
|
13
|
+
readonly unsubscribe: "so:unsubscribe";
|
|
14
|
+
readonly resync: "so:resync";
|
|
15
|
+
readonly receipt: "so:receipt";
|
|
16
|
+
}>;
|
|
17
|
+
/** Protocol timings fixed by the V1 specification. */
|
|
18
|
+
export declare const HELLO_TIMEOUT_MS = 10000;
|
|
19
|
+
export declare const CONTROL_CALLBACK_TIMEOUT_MS = 5000;
|
|
20
|
+
export declare const UNSUBSCRIBE_TIMEOUT_MS = 5000;
|
|
21
|
+
export declare const GET_TOKEN_TIMEOUT_MS = 10000;
|
|
22
|
+
export declare const DEFAULT_READY_TIMEOUT_MS = 30000;
|
|
23
|
+
export declare const TOKEN_REFRESH_LEAD_MS = 30000;
|
|
24
|
+
export declare const RECONNECT_BASE_MS = 500;
|
|
25
|
+
export declare const RECONNECT_CAP_MS = 30000;
|
|
26
|
+
export declare const MAX_TOKEN_BYTES = 8192;
|
|
27
|
+
export declare const REQUEST_CACHE_ENTRIES = 256;
|
|
28
|
+
export declare const REQUEST_CACHE_TTL_MS = 60000;
|
|
29
|
+
export declare const STARTUP_DEADLINE_MS = 30000;
|
|
30
|
+
export declare const DEFAULT_STOP_TIMEOUT_MS = 10000;
|
|
31
|
+
export declare const PREVIEW_TOKEN_TTL_MS: number;
|
|
32
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,eAAO,MAAM,gBAAgB,EAAG,CAAU,CAAC;AAC3C,eAAO,MAAM,mBAAmB,2BAA2B,CAAC;AAC5D,eAAO,MAAM,oBAAoB,OAAO,CAAC;AACzC,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,eAAO,MAAM,YAAY,EAAE,YAMzB,CAAC;AAEH,eAAO,MAAM,MAAM;;;;;;;;;EASR,CAAC;AAEZ,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,QAAS,CAAC;AACvC,eAAO,MAAM,2BAA2B,OAAQ,CAAC;AACjD,eAAO,MAAM,sBAAsB,OAAQ,CAAC;AAC5C,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAC3C,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAC5C,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,gBAAgB,QAAS,CAAC;AACvC,eAAO,MAAM,eAAe,OAAQ,CAAC;AACrC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAC3C,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAC1C,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAC9C,eAAO,MAAM,oBAAoB,QAAa,CAAC"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const PROTOCOL_VERSION = 1;
|
|
2
|
+
export const DEFAULT_SOCKET_PATH = "/streamotter/socket.io";
|
|
3
|
+
export const DEFAULT_GATEWAY_PORT = 7400;
|
|
4
|
+
export const DEFAULT_MANAGEMENT_PORT = 7401;
|
|
5
|
+
export const CAPABILITIES = Object.freeze({
|
|
6
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
7
|
+
configVersions: Object.freeze([1]),
|
|
8
|
+
transport: "socket.io",
|
|
9
|
+
deliveryModes: Object.freeze(["state"]),
|
|
10
|
+
operations: Object.freeze(["subscribe", "unsubscribe", "resync", "receipt"])
|
|
11
|
+
});
|
|
12
|
+
export const EVENTS = Object.freeze({
|
|
13
|
+
hello: "so:hello",
|
|
14
|
+
state: "so:state",
|
|
15
|
+
data: "so:data",
|
|
16
|
+
error: "so:error",
|
|
17
|
+
subscribe: "so:subscribe",
|
|
18
|
+
unsubscribe: "so:unsubscribe",
|
|
19
|
+
resync: "so:resync",
|
|
20
|
+
receipt: "so:receipt"
|
|
21
|
+
});
|
|
22
|
+
/** Protocol timings fixed by the V1 specification. */
|
|
23
|
+
export const HELLO_TIMEOUT_MS = 10_000;
|
|
24
|
+
export const CONTROL_CALLBACK_TIMEOUT_MS = 5_000;
|
|
25
|
+
export const UNSUBSCRIBE_TIMEOUT_MS = 5_000;
|
|
26
|
+
export const GET_TOKEN_TIMEOUT_MS = 10_000;
|
|
27
|
+
export const DEFAULT_READY_TIMEOUT_MS = 30_000;
|
|
28
|
+
export const TOKEN_REFRESH_LEAD_MS = 30_000;
|
|
29
|
+
export const RECONNECT_BASE_MS = 500;
|
|
30
|
+
export const RECONNECT_CAP_MS = 30_000;
|
|
31
|
+
export const MAX_TOKEN_BYTES = 8_192;
|
|
32
|
+
export const REQUEST_CACHE_ENTRIES = 256;
|
|
33
|
+
export const REQUEST_CACHE_TTL_MS = 60_000;
|
|
34
|
+
export const STARTUP_DEADLINE_MS = 30_000;
|
|
35
|
+
export const DEFAULT_STOP_TIMEOUT_MS = 10_000;
|
|
36
|
+
export const PREVIEW_TOKEN_TTL_MS = 5 * 60_000;
|
|
37
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAU,CAAC;AAC3C,MAAM,CAAC,MAAM,mBAAmB,GAAG,wBAAwB,CAAC;AAC5D,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AACzC,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,MAAM,CAAC,MAAM,YAAY,GAAiB,MAAM,CAAC,MAAM,CAAC;IACtD,eAAe,EAAE,gBAAgB;IACjC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAU,CAAC;IAC3C,SAAS,EAAE,WAAW;IACtB,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAU,CAAC;IAChD,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,CAAU,CAAC;CACtF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,KAAK,EAAE,UAAU;IACjB,KAAK,EAAE,UAAU;IACjB,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,UAAU;IACjB,SAAS,EAAE,cAAc;IACzB,WAAW,EAAE,gBAAgB;IAC7B,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;CACb,CAAC,CAAC;AAEZ,sDAAsD;AACtD,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC;AACvC,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,CAAC;AACjD,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,CAAC;AAC5C,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAC3C,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAC/C,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAC5C,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AACrC,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC;AACvC,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AACrC,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AACzC,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAC3C,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAC1C,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAC9C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,GAAG,MAAM,CAAC"}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ConfigIssue, Json, Params, Schema } from "./types.ts";
|
|
2
|
+
/** JSON-Pointer path segment escaping. */
|
|
3
|
+
export declare function pointer(base: string, segment: string | number): string;
|
|
4
|
+
/**
|
|
5
|
+
* Validates a schema definition against the V1 dialect. Unsupported keywords are
|
|
6
|
+
* rejected rather than ignored. Returns true when no issues were added.
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateSchemaDefinition(schema: unknown, path: string, issues: ConfigIssue[], depth?: number): schema is Schema;
|
|
9
|
+
/**
|
|
10
|
+
* Parameter schemas are closed objects whose properties are all required and
|
|
11
|
+
* are strings, booleans, or integers. Call after validateSchemaDefinition.
|
|
12
|
+
*/
|
|
13
|
+
export declare function validateParamsSchema(schema: Schema, path: string, issues: ConfigIssue[]): boolean;
|
|
14
|
+
export interface ValueIssue {
|
|
15
|
+
path: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
/** Validates a value against a schema. Returns the first issue, or null when valid. */
|
|
19
|
+
export declare function validateValue(schema: Schema, value: unknown, path?: string, depth?: number): ValueIssue | null;
|
|
20
|
+
export type CanonicalParamsResult = {
|
|
21
|
+
ok: true;
|
|
22
|
+
params: Params;
|
|
23
|
+
canonical: string;
|
|
24
|
+
} | {
|
|
25
|
+
ok: false;
|
|
26
|
+
issue: ValueIssue;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Validates parameters and produces their canonical encoding: -0 normalized to 0,
|
|
30
|
+
* keys sorted, JSON encoded. String case and Unicode are not normalized.
|
|
31
|
+
*/
|
|
32
|
+
export declare function canonicalizeParams(schema: Schema, params: unknown): CanonicalParamsResult;
|
|
33
|
+
export declare function isJsonData(value: unknown): value is Json;
|
|
34
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAYpE,0CAA0C;AAC1C,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAEtE;AAMD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,KAAK,SAAI,GAAG,MAAM,IAAI,MAAM,CA6G1H;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CA0BjG;AAED,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAE7D,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,SAAM,EAAE,KAAK,SAAI,GAAG,UAAU,GAAG,IAAI,CAkDtG;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC;AAErC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,qBAAqB,CAazF;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,CAOxD"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { canonicalJson, codePointLength, isPlainObject, MAX_NESTING_DEPTH } from "./primitives.js";
|
|
2
|
+
const KEYWORDS = {
|
|
3
|
+
string: ["type", "minLength", "maxLength", "enum"],
|
|
4
|
+
number: ["type", "minimum", "maximum"],
|
|
5
|
+
integer: ["type", "minimum", "maximum"],
|
|
6
|
+
boolean: ["type"],
|
|
7
|
+
null: ["type"],
|
|
8
|
+
array: ["type", "items", "maxItems"],
|
|
9
|
+
object: ["type", "properties", "required", "additionalProperties"]
|
|
10
|
+
};
|
|
11
|
+
/** JSON-Pointer path segment escaping. */
|
|
12
|
+
export function pointer(base, segment) {
|
|
13
|
+
return `${base}/${String(segment).replaceAll("~", "~0").replaceAll("/", "~1")}`;
|
|
14
|
+
}
|
|
15
|
+
function isNonNegativeSafeInteger(value) {
|
|
16
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Validates a schema definition against the V1 dialect. Unsupported keywords are
|
|
20
|
+
* rejected rather than ignored. Returns true when no issues were added.
|
|
21
|
+
*/
|
|
22
|
+
export function validateSchemaDefinition(schema, path, issues, depth = 1) {
|
|
23
|
+
const before = issues.length;
|
|
24
|
+
if (depth > MAX_NESTING_DEPTH) {
|
|
25
|
+
issues.push({ path, code: "SCHEMA_DEPTH_EXCEEDED", message: `Schemas may nest at most ${MAX_NESTING_DEPTH} levels.` });
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (!isPlainObject(schema)) {
|
|
29
|
+
issues.push({ path, code: "INVALID_TYPE", message: "A schema must be an object." });
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const type = schema["type"];
|
|
33
|
+
if (typeof type !== "string" || !Object.hasOwn(KEYWORDS, type)) {
|
|
34
|
+
issues.push({
|
|
35
|
+
path: pointer(path, "type"),
|
|
36
|
+
code: "SCHEMA_UNSUPPORTED_TYPE",
|
|
37
|
+
message: "type must be one of string, number, integer, boolean, null, array, or object (unions are not supported in V1)."
|
|
38
|
+
});
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const allowed = KEYWORDS[type] ?? [];
|
|
42
|
+
for (const key of Object.keys(schema)) {
|
|
43
|
+
if (!allowed.includes(key)) {
|
|
44
|
+
issues.push({
|
|
45
|
+
path: pointer(path, key),
|
|
46
|
+
code: "SCHEMA_UNSUPPORTED_KEYWORD",
|
|
47
|
+
message: `"${key}" is not supported for ${type} schemas in V1.`
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
switch (type) {
|
|
52
|
+
case "string": {
|
|
53
|
+
const { minLength, maxLength } = schema;
|
|
54
|
+
if (minLength !== undefined && !isNonNegativeSafeInteger(minLength)) {
|
|
55
|
+
issues.push({ path: pointer(path, "minLength"), code: "INVALID_VALUE", message: "minLength must be a non-negative integer." });
|
|
56
|
+
}
|
|
57
|
+
if (maxLength !== undefined && !isNonNegativeSafeInteger(maxLength)) {
|
|
58
|
+
issues.push({ path: pointer(path, "maxLength"), code: "INVALID_VALUE", message: "maxLength must be a non-negative integer." });
|
|
59
|
+
}
|
|
60
|
+
if (isNonNegativeSafeInteger(minLength) && isNonNegativeSafeInteger(maxLength) && minLength > maxLength) {
|
|
61
|
+
issues.push({ path: pointer(path, "minLength"), code: "INVALID_VALUE", message: "minLength must not exceed maxLength." });
|
|
62
|
+
}
|
|
63
|
+
const values = schema["enum"];
|
|
64
|
+
if (values !== undefined) {
|
|
65
|
+
if (!Array.isArray(values) || values.length === 0 || !values.every(item => typeof item === "string")) {
|
|
66
|
+
issues.push({ path: pointer(path, "enum"), code: "INVALID_VALUE", message: "enum must be a non-empty array of strings." });
|
|
67
|
+
}
|
|
68
|
+
else if (new Set(values).size !== values.length) {
|
|
69
|
+
issues.push({ path: pointer(path, "enum"), code: "INVALID_VALUE", message: "enum values must be unique." });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case "number":
|
|
75
|
+
case "integer": {
|
|
76
|
+
const { minimum, maximum } = schema;
|
|
77
|
+
for (const [key, bound] of [["minimum", minimum], ["maximum", maximum]]) {
|
|
78
|
+
if (bound === undefined)
|
|
79
|
+
continue;
|
|
80
|
+
if (typeof bound !== "number" || !Number.isFinite(bound) || (type === "integer" && !Number.isSafeInteger(bound))) {
|
|
81
|
+
issues.push({ path: pointer(path, key), code: "INVALID_VALUE", message: `${key} must be a finite ${type === "integer" ? "safe integer" : "number"}.` });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (typeof minimum === "number" && typeof maximum === "number" && minimum > maximum) {
|
|
85
|
+
issues.push({ path: pointer(path, "minimum"), code: "INVALID_VALUE", message: "minimum must not exceed maximum." });
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "array": {
|
|
90
|
+
if (!isNonNegativeSafeInteger(schema["maxItems"])) {
|
|
91
|
+
issues.push({ path: pointer(path, "maxItems"), code: "REQUIRED", message: "Arrays require a non-negative integer maxItems bound." });
|
|
92
|
+
}
|
|
93
|
+
if (schema["items"] === undefined) {
|
|
94
|
+
issues.push({ path: pointer(path, "items"), code: "REQUIRED", message: "Arrays require an items schema." });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
validateSchemaDefinition(schema["items"], pointer(path, "items"), issues, depth + 1);
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "object": {
|
|
102
|
+
if (schema["additionalProperties"] !== false) {
|
|
103
|
+
issues.push({ path: pointer(path, "additionalProperties"), code: "REQUIRED", message: "Objects must declare additionalProperties: false." });
|
|
104
|
+
}
|
|
105
|
+
const properties = schema["properties"];
|
|
106
|
+
if (!isPlainObject(properties)) {
|
|
107
|
+
issues.push({ path: pointer(path, "properties"), code: "REQUIRED", message: "Objects require a properties map." });
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
for (const [name, child] of Object.entries(properties)) {
|
|
111
|
+
validateSchemaDefinition(child, pointer(pointer(path, "properties"), name), issues, depth + 1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const required = schema["required"];
|
|
115
|
+
if (!Array.isArray(required) || !required.every(item => typeof item === "string")) {
|
|
116
|
+
issues.push({ path: pointer(path, "required"), code: "REQUIRED", message: "Objects require a required array of property names." });
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
if (new Set(required).size !== required.length) {
|
|
120
|
+
issues.push({ path: pointer(path, "required"), code: "INVALID_VALUE", message: "required names must be unique." });
|
|
121
|
+
}
|
|
122
|
+
if (isPlainObject(properties)) {
|
|
123
|
+
for (const name of required) {
|
|
124
|
+
if (!Object.hasOwn(properties, name)) {
|
|
125
|
+
issues.push({ path: pointer(path, "required"), code: "UNKNOWN_REFERENCE", message: `required property "${name}" is not declared in properties.` });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
default:
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
return issues.length === before;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Parameter schemas are closed objects whose properties are all required and
|
|
139
|
+
* are strings, booleans, or integers. Call after validateSchemaDefinition.
|
|
140
|
+
*/
|
|
141
|
+
export function validateParamsSchema(schema, path, issues) {
|
|
142
|
+
const before = issues.length;
|
|
143
|
+
if (schema.type !== "object") {
|
|
144
|
+
issues.push({ path, code: "INVALID_PARAMS_SCHEMA", message: "Parameter schemas must be objects." });
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const names = Object.keys(schema.properties);
|
|
148
|
+
for (const name of names) {
|
|
149
|
+
const child = schema.properties[name];
|
|
150
|
+
if (child === undefined)
|
|
151
|
+
continue;
|
|
152
|
+
if (child.type !== "string" && child.type !== "boolean" && child.type !== "integer") {
|
|
153
|
+
issues.push({
|
|
154
|
+
path: pointer(pointer(path, "properties"), name),
|
|
155
|
+
code: "INVALID_PARAMS_SCHEMA",
|
|
156
|
+
message: "Parameters may only be strings, booleans, or integers in V1."
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (!schema.required.includes(name)) {
|
|
160
|
+
issues.push({
|
|
161
|
+
path: pointer(pointer(path, "properties"), name),
|
|
162
|
+
code: "INVALID_PARAMS_SCHEMA",
|
|
163
|
+
message: "Every parameter must be required; optional parameters are not supported in V1."
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return issues.length === before;
|
|
168
|
+
}
|
|
169
|
+
/** Validates a value against a schema. Returns the first issue, or null when valid. */
|
|
170
|
+
export function validateValue(schema, value, path = "$", depth = 1) {
|
|
171
|
+
if (depth > MAX_NESTING_DEPTH)
|
|
172
|
+
return { path, message: `Values may nest at most ${MAX_NESTING_DEPTH} levels.` };
|
|
173
|
+
switch (schema.type) {
|
|
174
|
+
case "string": {
|
|
175
|
+
if (typeof value !== "string")
|
|
176
|
+
return { path, message: "Expected a string." };
|
|
177
|
+
if (schema.enum !== undefined && !schema.enum.includes(value))
|
|
178
|
+
return { path, message: "Value is not one of the allowed strings." };
|
|
179
|
+
const length = (schema.minLength !== undefined || schema.maxLength !== undefined) ? codePointLength(value) : 0;
|
|
180
|
+
if (schema.minLength !== undefined && length < schema.minLength)
|
|
181
|
+
return { path, message: `Expected at least ${schema.minLength} characters.` };
|
|
182
|
+
if (schema.maxLength !== undefined && length > schema.maxLength)
|
|
183
|
+
return { path, message: `Expected at most ${schema.maxLength} characters.` };
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
case "number":
|
|
187
|
+
case "integer": {
|
|
188
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
189
|
+
return { path, message: "Expected a finite number." };
|
|
190
|
+
if (schema.type === "integer" && !Number.isSafeInteger(value))
|
|
191
|
+
return { path, message: "Expected a safe integer." };
|
|
192
|
+
if (schema.minimum !== undefined && value < schema.minimum)
|
|
193
|
+
return { path, message: `Expected a value of at least ${schema.minimum}.` };
|
|
194
|
+
if (schema.maximum !== undefined && value > schema.maximum)
|
|
195
|
+
return { path, message: `Expected a value of at most ${schema.maximum}.` };
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
case "boolean":
|
|
199
|
+
return typeof value === "boolean" ? null : { path, message: "Expected a boolean." };
|
|
200
|
+
case "null":
|
|
201
|
+
return value === null ? null : { path, message: "Expected null." };
|
|
202
|
+
case "array": {
|
|
203
|
+
if (!Array.isArray(value))
|
|
204
|
+
return { path, message: "Expected an array." };
|
|
205
|
+
if (value.length > schema.maxItems)
|
|
206
|
+
return { path, message: `Expected at most ${schema.maxItems} items.` };
|
|
207
|
+
for (let i = 0; i < value.length; i++) {
|
|
208
|
+
const issue = validateValue(schema.items, value[i], `${path}[${i}]`, depth + 1);
|
|
209
|
+
if (issue !== null)
|
|
210
|
+
return issue;
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
case "object": {
|
|
215
|
+
if (!isPlainObject(value))
|
|
216
|
+
return { path, message: "Expected an object." };
|
|
217
|
+
for (const key of Object.keys(value)) {
|
|
218
|
+
if (!Object.hasOwn(schema.properties, key))
|
|
219
|
+
return { path: `${path}.${key}`, message: "Property is not allowed." };
|
|
220
|
+
}
|
|
221
|
+
for (const key of schema.required) {
|
|
222
|
+
if (!Object.hasOwn(value, key))
|
|
223
|
+
return { path: `${path}.${key}`, message: "Required property is missing." };
|
|
224
|
+
}
|
|
225
|
+
for (const [key, child] of Object.entries(schema.properties)) {
|
|
226
|
+
if (!Object.hasOwn(value, key))
|
|
227
|
+
continue;
|
|
228
|
+
const issue = validateValue(child, value[key], `${path}.${key}`, depth + 1);
|
|
229
|
+
if (issue !== null)
|
|
230
|
+
return issue;
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
default:
|
|
235
|
+
return { path, message: "Unsupported schema." };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Validates parameters and produces their canonical encoding: -0 normalized to 0,
|
|
240
|
+
* keys sorted, JSON encoded. String case and Unicode are not normalized.
|
|
241
|
+
*/
|
|
242
|
+
export function canonicalizeParams(schema, params) {
|
|
243
|
+
const issue = validateValue(schema, params);
|
|
244
|
+
if (issue !== null)
|
|
245
|
+
return { ok: false, issue };
|
|
246
|
+
const normalized = {};
|
|
247
|
+
for (const key of Object.keys(params).sort()) {
|
|
248
|
+
const value = params[key];
|
|
249
|
+
if (value === undefined)
|
|
250
|
+
continue;
|
|
251
|
+
Object.defineProperty(normalized, key, {
|
|
252
|
+
value: typeof value === "number" && Object.is(value, -0) ? 0 : value,
|
|
253
|
+
enumerable: true, writable: true, configurable: true
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
return { ok: true, params: normalized, canonical: canonicalJson(normalized) };
|
|
257
|
+
}
|
|
258
|
+
export function isJsonData(value) {
|
|
259
|
+
try {
|
|
260
|
+
canonicalJson(value);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=schema.js.map
|