@photon-grove/evt 0.0.0 → 0.1.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 +176 -0
- package/README.md +164 -1
- package/dist/aggregate.d.ts +83 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +92 -0
- package/dist/aggregate.js.map +1 -0
- package/dist/base64.d.ts +8 -0
- package/dist/base64.d.ts.map +1 -0
- package/dist/base64.js +67 -0
- package/dist/base64.js.map +1 -0
- package/dist/codec.d.ts +56 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +90 -0
- package/dist/codec.js.map +1 -0
- package/dist/commit-id.d.ts +3 -0
- package/dist/commit-id.d.ts.map +1 -0
- package/dist/commit-id.js +43 -0
- package/dist/commit-id.js.map +1 -0
- package/dist/dynamodb/client.d.ts +83 -0
- package/dist/dynamodb/client.d.ts.map +1 -0
- package/dist/dynamodb/client.js +6 -0
- package/dist/dynamodb/client.js.map +1 -0
- package/dist/dynamodb/head.d.ts +34 -0
- package/dist/dynamodb/head.d.ts.map +1 -0
- package/dist/dynamodb/head.js +62 -0
- package/dist/dynamodb/head.js.map +1 -0
- package/dist/dynamodb/index.d.ts +6 -0
- package/dist/dynamodb/index.d.ts.map +1 -0
- package/dist/dynamodb/index.js +8 -0
- package/dist/dynamodb/index.js.map +1 -0
- package/dist/dynamodb/item.d.ts +70 -0
- package/dist/dynamodb/item.d.ts.map +1 -0
- package/dist/dynamodb/item.js +264 -0
- package/dist/dynamodb/item.js.map +1 -0
- package/dist/dynamodb/marker.d.ts +11 -0
- package/dist/dynamodb/marker.d.ts.map +1 -0
- package/dist/dynamodb/marker.js +56 -0
- package/dist/dynamodb/marker.js.map +1 -0
- package/dist/dynamodb/repository.d.ts +61 -0
- package/dist/dynamodb/repository.d.ts.map +1 -0
- package/dist/dynamodb/repository.js +731 -0
- package/dist/dynamodb/repository.js.map +1 -0
- package/dist/dynamodb/requests.d.ts +65 -0
- package/dist/dynamodb/requests.d.ts.map +1 -0
- package/dist/dynamodb/requests.js +179 -0
- package/dist/dynamodb/requests.js.map +1 -0
- package/dist/envelope.d.ts +15 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +59 -0
- package/dist/envelope.js.map +1 -0
- package/dist/errors.d.ts +205 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +240 -0
- package/dist/errors.js.map +1 -0
- package/dist/event.d.ts +47 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +64 -0
- package/dist/event.js.map +1 -0
- package/dist/features.d.ts +55 -0
- package/dist/features.d.ts.map +1 -0
- package/dist/features.js +126 -0
- package/dist/features.js.map +1 -0
- package/dist/fields.d.ts +31 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +105 -0
- package/dist/fields.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +47 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +458 -0
- package/dist/json.js.map +1 -0
- package/dist/memory/index.d.ts +2 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +3 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/repository.d.ts +70 -0
- package/dist/memory/repository.d.ts.map +1 -0
- package/dist/memory/repository.js +308 -0
- package/dist/memory/repository.js.map +1 -0
- package/dist/metadata.d.ts +47 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +75 -0
- package/dist/metadata.js.map +1 -0
- package/dist/repository.d.ts +131 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +130 -0
- package/dist/repository.js.map +1 -0
- package/dist/retry.d.ts +25 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +50 -0
- package/dist/retry.js.map +1 -0
- package/dist/snapshots.d.ts +21 -0
- package/dist/snapshots.d.ts.map +1 -0
- package/dist/snapshots.js +35 -0
- package/dist/snapshots.js.map +1 -0
- package/dist/store.d.ts +103 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +246 -0
- package/dist/store.js.map +1 -0
- package/dist/testing/equal.d.ts +22 -0
- package/dist/testing/equal.d.ts.map +1 -0
- package/dist/testing/equal.js +121 -0
- package/dist/testing/equal.js.map +1 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/scenario.d.ts +136 -0
- package/dist/testing/scenario.d.ts.map +1 -0
- package/dist/testing/scenario.js +307 -0
- package/dist/testing/scenario.js.map +1 -0
- package/dist/utf8.d.ts +17 -0
- package/dist/utf8.d.ts.map +1 -0
- package/dist/utf8.js +82 -0
- package/dist/utf8.js.map +1 -0
- package/package.json +58 -2
- package/src/aggregate.ts +196 -0
- package/src/base64.ts +80 -0
- package/src/codec.ts +139 -0
- package/src/commit-id.ts +50 -0
- package/src/dynamodb/client.ts +102 -0
- package/src/dynamodb/head.ts +93 -0
- package/src/dynamodb/index.ts +45 -0
- package/src/dynamodb/item.ts +334 -0
- package/src/dynamodb/marker.ts +64 -0
- package/src/dynamodb/repository.ts +1030 -0
- package/src/dynamodb/requests.ts +256 -0
- package/src/envelope.ts +67 -0
- package/src/errors.ts +365 -0
- package/src/event.ts +117 -0
- package/src/features.ts +175 -0
- package/src/fields.ts +134 -0
- package/src/globals.d.ts +39 -0
- package/src/index.ts +98 -0
- package/src/json.ts +546 -0
- package/src/memory/index.ts +11 -0
- package/src/memory/repository.ts +463 -0
- package/src/metadata.ts +120 -0
- package/src/repository.ts +275 -0
- package/src/retry.ts +74 -0
- package/src/snapshots.ts +42 -0
- package/src/store.ts +377 -0
- package/src/testing/equal.ts +146 -0
- package/src/testing/index.ts +14 -0
- package/src/testing/scenario.ts +406 -0
- package/src/utf8.ts +92 -0
package/dist/fields.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Lenient decoding of engine documents by Go's rules: a missing field or null is the zero value,
|
|
2
|
+
// unknown fields are ignored, and a value of the wrong JSON type is an error. Field names match
|
|
3
|
+
// exactly, as in Rust (Go also matches them case-insensitively).
|
|
4
|
+
import { base64Decode } from './base64.js';
|
|
5
|
+
import { EvtError } from './errors.js';
|
|
6
|
+
import { JsonNumber } from './json.js';
|
|
7
|
+
const INT64_MIN = -(2n ** 63n);
|
|
8
|
+
const INT64_MAX = 2n ** 63n - 1n;
|
|
9
|
+
/** How evt parses its own documents: a repeated key is refused. */
|
|
10
|
+
export const DOCUMENT = { duplicateKeys: 'refuse' };
|
|
11
|
+
export function invalid(message) {
|
|
12
|
+
return new EvtError('invalidRecord', message);
|
|
13
|
+
}
|
|
14
|
+
/** An object, or null for JSON null. */
|
|
15
|
+
export function objectOrNull(value, what) {
|
|
16
|
+
if (value === undefined || value === null) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (!(value instanceof Map)) {
|
|
20
|
+
throw invalid(`${what} is not an object`);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
/** A string field, `""` when missing or null. */
|
|
25
|
+
export function stringField(object, name, what) {
|
|
26
|
+
return nullableString(object, name, what) ?? '';
|
|
27
|
+
}
|
|
28
|
+
/** A string field that may be null (a Go `*string`). */
|
|
29
|
+
export function nullableString(object, name, what) {
|
|
30
|
+
const value = object.get(name);
|
|
31
|
+
if (value === undefined || value === null) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (typeof value !== 'string') {
|
|
35
|
+
throw invalid(`${what}.${name} is not a string`);
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
/** An int64 field, `0n` when missing or null. `1.0`, `1e3` and `"1"` are refused, as in Go. */
|
|
40
|
+
export function int64Field(object, name, what) {
|
|
41
|
+
const value = object.get(name);
|
|
42
|
+
if (value === undefined || value === null) {
|
|
43
|
+
return 0n;
|
|
44
|
+
}
|
|
45
|
+
return int64(value, `${what}.${name}`);
|
|
46
|
+
}
|
|
47
|
+
/** A small integer field, `0` when missing or null, refused if it isn't a safe integer. */
|
|
48
|
+
export function safeIntegerField(object, name, what) {
|
|
49
|
+
const value = int64Field(object, name, what);
|
|
50
|
+
if (value < BigInt(Number.MIN_SAFE_INTEGER) || value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
51
|
+
throw invalid(`${what}.${name} is ${value}, past the integers this field holds`);
|
|
52
|
+
}
|
|
53
|
+
return Number(value);
|
|
54
|
+
}
|
|
55
|
+
/** Reads a JSON number as an int64. */
|
|
56
|
+
export function int64(value, what) {
|
|
57
|
+
if (!(value instanceof JsonNumber) || !value.isInteger) {
|
|
58
|
+
throw invalid(`${what} is not an integer`);
|
|
59
|
+
}
|
|
60
|
+
return checkInt64(BigInt(value.text), what);
|
|
61
|
+
}
|
|
62
|
+
/** Refuses an integer outside int64. */
|
|
63
|
+
export function checkInt64(value, what) {
|
|
64
|
+
if (value < INT64_MIN || value > INT64_MAX) {
|
|
65
|
+
throw invalid(`${what} is ${value}, outside int64`);
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
/** Refuses to write an integer that no reader would read back as an int64. */
|
|
70
|
+
export function int64Argument(value, what) {
|
|
71
|
+
if (value < INT64_MIN || value > INT64_MAX) {
|
|
72
|
+
throw new EvtError('invalidArgument', `${what} is ${value}, outside int64`);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
/** Refuses to write a small integer that isn't a safe integer, such as 1.5 or NaN. */
|
|
77
|
+
export function safeIntegerArgument(value, what) {
|
|
78
|
+
if (!Number.isSafeInteger(value)) {
|
|
79
|
+
throw new EvtError('invalidArgument', `${what} is ${value}, not a safe integer`);
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
/** A base64 field (a Go `[]byte`), null when missing or null. */
|
|
84
|
+
export function bytesField(object, name, what) {
|
|
85
|
+
const text = nullableString(object, name, what);
|
|
86
|
+
return text === null ? null : base64Decode(text);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A Go `map[string]string`: null for a missing or null map, and `""` for a null value.
|
|
90
|
+
*/
|
|
91
|
+
export function stringMap(value, what) {
|
|
92
|
+
const object = objectOrNull(value, what);
|
|
93
|
+
if (object === null) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const map = new Map();
|
|
97
|
+
for (const [key, entry] of object) {
|
|
98
|
+
if (entry !== null && typeof entry !== 'string') {
|
|
99
|
+
throw invalid(`${what}.${key} is not a string`);
|
|
100
|
+
}
|
|
101
|
+
map.set(key, entry ?? '');
|
|
102
|
+
}
|
|
103
|
+
return map;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fields.js","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { type Aggregate, type AggregateDefinition, type Decision, type EventDefinition, type EventDefinitions, type EventOf, type Rejection, type Upcaster, defineAggregate, defineEvents, jsonUpcaster, } from './aggregate.js';
|
|
2
|
+
export { base64Decode, base64Encode } from './base64.js';
|
|
3
|
+
export { type Codec, type JsonOptions, type StandardResult, type StandardSchemaV1, json, } from './codec.js';
|
|
4
|
+
export { encodeEnvelope, decodeEnvelope, messageAttributes } from './envelope.js';
|
|
5
|
+
export { CommitTooLargeError, CompactionUncoveredError, ConflictError, EvtError, type EvtErrorCode, FeatureManifestChangedError, HistoryDependentProjectionError, RebuildInProgressError, ReplayError, ReservedEntityIdError, SequenceGapError, SnapshotError, StoreNotInitializedError, StreamHeadDriftError, StreamHeadsNotMigratedError, TransientError, UnsupportedFeatureError, type FeaturePair, isConflict, isRetryable, } from './errors.js';
|
|
6
|
+
export { type SerializedEvent, type SerializedSnapshot, decodeEvent, decodeSnapshot, encodeEvent, encodeSnapshot, eventId, } from './event.js';
|
|
7
|
+
export { type FeatureManifest, type ManifestTexts, STREAM_HEADS_FEATURE, STREAM_HEADS_VERSION, SUPPORTED_FEATURES, decodeManifest, encodeManifest, } from './features.js';
|
|
8
|
+
export { JsonNumber, type JsonValue, parseJson, stringifyJson } from './json.js';
|
|
9
|
+
export { type Metadata, type Origin, decodeMetadata, effectiveCausationId, emptyMetadata, encodeMetadata, } from './metadata.js';
|
|
10
|
+
export type { CallOptions, Commit, NewSnapshot, Repository, StoreInitializer, } from './repository.js';
|
|
11
|
+
export { DEFAULT_RETRY_POLICY, type RetryPolicy, retryDelay, } from './retry.js';
|
|
12
|
+
export { MAX_COMMIT_ITEM_BYTES, snapshotCommitSize, snapshotEventsDue } from './snapshots.js';
|
|
13
|
+
export { compareUtf8, utf8Length } from './utf8.js';
|
|
14
|
+
export { DEFAULT_SNAPSHOT_INTERVAL, type ExecuteOptions, type ExecuteResult, type Loaded, type RetryOptions, Store, type StoreOptions, } from './store.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAC,YAAY,EAAE,YAAY,EAAC,MAAM,aAAa,CAAA;AACtD,OAAO,EACL,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,IAAI,GACL,MAAM,YAAY,CAAA;AACnB,OAAO,EAAC,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAA;AAC/E,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,QAAQ,EACR,KAAK,YAAY,EACjB,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,EACtB,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,wBAAwB,EACxB,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EACd,uBAAuB,EACvB,KAAK,WAAW,EAChB,UAAU,EACV,WAAW,GACZ,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,WAAW,EACX,cAAc,EACd,WAAW,EACX,cAAc,EACd,OAAO,GACR,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,EACd,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,OAAO,EAAC,UAAU,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,aAAa,EAAC,MAAM,WAAW,CAAA;AAC9E,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,WAAW,EACX,MAAM,EACN,WAAW,EACX,UAAU,EACV,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,oBAAoB,EACpB,KAAK,WAAW,EAChB,UAAU,GACX,MAAM,YAAY,CAAA;AACnB,OAAO,EAAC,qBAAqB,EAAE,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,gBAAgB,CAAA;AAC3F,OAAO,EAAC,WAAW,EAAE,UAAU,EAAC,MAAM,WAAW,CAAA;AACjD,OAAO,EACL,yBAAyB,EACzB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,EACL,KAAK,YAAY,GAClB,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// @photon-grove/evt: event sourcing for TypeScript, wire-compatible with the Go and Rust evt
|
|
2
|
+
// implementations (docs/adr/0009-typescript-implementation.md). The storage contract is
|
|
3
|
+
// docs/reference/storage-format.md, and compat/fixtures holds its executable form.
|
|
4
|
+
export { defineAggregate, defineEvents, jsonUpcaster, } from './aggregate.js';
|
|
5
|
+
export { base64Decode, base64Encode } from './base64.js';
|
|
6
|
+
export { json, } from './codec.js';
|
|
7
|
+
export { encodeEnvelope, decodeEnvelope, messageAttributes } from './envelope.js';
|
|
8
|
+
export { CommitTooLargeError, CompactionUncoveredError, ConflictError, EvtError, FeatureManifestChangedError, HistoryDependentProjectionError, RebuildInProgressError, ReplayError, ReservedEntityIdError, SequenceGapError, SnapshotError, StoreNotInitializedError, StreamHeadDriftError, StreamHeadsNotMigratedError, TransientError, UnsupportedFeatureError, isConflict, isRetryable, } from './errors.js';
|
|
9
|
+
export { decodeEvent, decodeSnapshot, encodeEvent, encodeSnapshot, eventId, } from './event.js';
|
|
10
|
+
export { STREAM_HEADS_FEATURE, STREAM_HEADS_VERSION, SUPPORTED_FEATURES, decodeManifest, encodeManifest, } from './features.js';
|
|
11
|
+
export { JsonNumber, parseJson, stringifyJson } from './json.js';
|
|
12
|
+
export { decodeMetadata, effectiveCausationId, emptyMetadata, encodeMetadata, } from './metadata.js';
|
|
13
|
+
export { DEFAULT_RETRY_POLICY, retryDelay, } from './retry.js';
|
|
14
|
+
export { MAX_COMMIT_ITEM_BYTES, snapshotCommitSize, snapshotEventsDue } from './snapshots.js';
|
|
15
|
+
export { compareUtf8, utf8Length } from './utf8.js';
|
|
16
|
+
export { DEFAULT_SNAPSHOT_INTERVAL, Store, } from './store.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,wFAAwF;AACxF,mFAAmF;AAEnF,OAAO,EASL,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAC,YAAY,EAAE,YAAY,EAAC,MAAM,aAAa,CAAA;AACtD,OAAO,EAKL,IAAI,GACL,MAAM,YAAY,CAAA;AACnB,OAAO,EAAC,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAA;AAC/E,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,QAAQ,EAER,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,EACtB,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,wBAAwB,EACxB,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EACd,uBAAuB,EAEvB,UAAU,EACV,WAAW,GACZ,MAAM,aAAa,CAAA;AACpB,OAAO,EAGL,WAAW,EACX,cAAc,EACd,WAAW,EACX,cAAc,EACd,OAAO,GACR,MAAM,YAAY,CAAA;AACnB,OAAO,EAGL,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,EACd,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,OAAO,EAAC,UAAU,EAAkB,SAAS,EAAE,aAAa,EAAC,MAAM,WAAW,CAAA;AAC9E,OAAO,EAGL,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAA;AAQtB,OAAO,EACL,oBAAoB,EAEpB,UAAU,GACX,MAAM,YAAY,CAAA;AACnB,OAAO,EAAC,qBAAqB,EAAE,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,gBAAgB,CAAA;AAC3F,OAAO,EAAC,WAAW,EAAE,UAAU,EAAC,MAAM,WAAW,CAAA;AACjD,OAAO,EACL,yBAAyB,EAKzB,KAAK,GAEN,MAAM,YAAY,CAAA"}
|
package/dist/json.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** A value read by {@link parseJson}. Objects are Maps, so integer-like keys keep their order. */
|
|
2
|
+
export type JsonValue = null | boolean | string | JsonNumber | JsonValue[] | Map<string, JsonValue>;
|
|
3
|
+
/** A JSON number kept as its literal text, so 2^53 + 1 or a 40-digit decimal stays exact. */
|
|
4
|
+
export declare class JsonNumber {
|
|
5
|
+
readonly text: string;
|
|
6
|
+
constructor(text: string);
|
|
7
|
+
/** Whether the literal is an integer: no fraction and no exponent, as Go's int64 requires. */
|
|
8
|
+
get isInteger(): boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Already-encoded JSON the writer copies as it is. The caller vouches for its bytes. */
|
|
11
|
+
export declare class RawJson {
|
|
12
|
+
readonly text: string;
|
|
13
|
+
constructor(text: string);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Encodes a value the way Go's `json.Marshal` encodes the same shape:
|
|
17
|
+
*
|
|
18
|
+
* - A plain object is a struct: its own properties in order, skipping `undefined` ones. An
|
|
19
|
+
* integer-like key (`"10"`) is refused, because the engine has already moved it to the front.
|
|
20
|
+
* - A `Map` is a Go map, written with its keys sorted by their UTF-8 bytes. Keys are strings, or
|
|
21
|
+
* integers (`number` or `bigint`), which are written as text and sorted as text, like Go's.
|
|
22
|
+
* - A `bigint` is an integer, a `Uint8Array` is a `[]byte` (padded base64), and a
|
|
23
|
+
* {@link JsonNumber} is written as its text.
|
|
24
|
+
* - Strings escape `<`, `>`, `&`, U+2028 and U+2029, and a lone surrogate becomes `\ufffd`, as Go
|
|
25
|
+
* writes invalid UTF-8.
|
|
26
|
+
*
|
|
27
|
+
* Anything else, such as a `Date`, a class instance or a number that isn't finite, is refused.
|
|
28
|
+
*/
|
|
29
|
+
export declare function stringifyJson(value: unknown): string;
|
|
30
|
+
/** Quotes a string with Go's escaping. */
|
|
31
|
+
export declare function quote(text: string): string;
|
|
32
|
+
/** How {@link parseJson} treats an object that repeats a key. */
|
|
33
|
+
export interface ParseOptions {
|
|
34
|
+
/**
|
|
35
|
+
* `last` (the default) keeps the key's first place and its last value. `refuse` refuses the
|
|
36
|
+
* document, which is how evt reads its own documents: Go merges a repeated key into the earlier
|
|
37
|
+
* value and Rust refuses a repeated field, so no reading of one matches both.
|
|
38
|
+
*/
|
|
39
|
+
duplicateKeys?: 'last' | 'refuse';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parses JSON without losing anything `JSON.parse` loses. Objects come back as Maps in written
|
|
43
|
+
* order, and numbers come back as {@link JsonNumber}. An escaped lone surrogate reads as U+FFFD,
|
|
44
|
+
* as it does in Go.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseJson(text: string, options?: ParseOptions): JsonValue;
|
|
47
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AAQA,kGAAkG;AAClG,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAMnG,6FAA6F;AAC7F,qBAAa,UAAU;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB,YAAY,IAAI,EAAE,MAAM,EAMvB;IAED,8FAA8F;IAC9F,IAAI,SAAS,IAAI,OAAO,CAEvB;CACF;AAED,yFAAyF;AACzF,qBAAa,OAAO;IACN,QAAQ,CAAC,IAAI,EAAE,MAAM;IAAjC,YAAqB,IAAI,EAAE,MAAM,EAAI;CACtC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA8CpD;AAqJD,0CAA0C;AAC1C,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsB1C;AAMD,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;CAClC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,SAAS,CAU7E"}
|
package/dist/json.js
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
// evt's JSON reader and writer. The writer produces Go's encoding/json bytes, which every stored
|
|
2
|
+
// document must match, and the reader keeps what JSON.parse loses: each number's text, and object
|
|
3
|
+
// keys in the order they were written.
|
|
4
|
+
import { base64Encode } from './base64.js';
|
|
5
|
+
import { EvtError } from './errors.js';
|
|
6
|
+
import { compareUtf8, isHighSurrogate, isLowSurrogate } from './utf8.js';
|
|
7
|
+
const NUMBER = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
8
|
+
const INTEGER = /^-?(?:0|[1-9]\d*)$/;
|
|
9
|
+
/** A JSON number kept as its literal text, so 2^53 + 1 or a 40-digit decimal stays exact. */
|
|
10
|
+
export class JsonNumber {
|
|
11
|
+
text;
|
|
12
|
+
constructor(text) {
|
|
13
|
+
if (!NUMBER.test(text)) {
|
|
14
|
+
throw new EvtError('invalidArgument', `not a JSON number: ${text}`);
|
|
15
|
+
}
|
|
16
|
+
this.text = text;
|
|
17
|
+
}
|
|
18
|
+
/** Whether the literal is an integer: no fraction and no exponent, as Go's int64 requires. */
|
|
19
|
+
get isInteger() {
|
|
20
|
+
return INTEGER.test(this.text);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Already-encoded JSON the writer copies as it is. The caller vouches for its bytes. */
|
|
24
|
+
export class RawJson {
|
|
25
|
+
text;
|
|
26
|
+
constructor(text) {
|
|
27
|
+
this.text = text;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Encodes a value the way Go's `json.Marshal` encodes the same shape:
|
|
32
|
+
*
|
|
33
|
+
* - A plain object is a struct: its own properties in order, skipping `undefined` ones. An
|
|
34
|
+
* integer-like key (`"10"`) is refused, because the engine has already moved it to the front.
|
|
35
|
+
* - A `Map` is a Go map, written with its keys sorted by their UTF-8 bytes. Keys are strings, or
|
|
36
|
+
* integers (`number` or `bigint`), which are written as text and sorted as text, like Go's.
|
|
37
|
+
* - A `bigint` is an integer, a `Uint8Array` is a `[]byte` (padded base64), and a
|
|
38
|
+
* {@link JsonNumber} is written as its text.
|
|
39
|
+
* - Strings escape `<`, `>`, `&`, U+2028 and U+2029, and a lone surrogate becomes `\ufffd`, as Go
|
|
40
|
+
* writes invalid UTF-8.
|
|
41
|
+
*
|
|
42
|
+
* Anything else, such as a `Date`, a class instance or a number that isn't finite, is refused.
|
|
43
|
+
*/
|
|
44
|
+
export function stringifyJson(value) {
|
|
45
|
+
const out = [];
|
|
46
|
+
// The arrays and objects being written, innermost last. The writer keeps its own stack, as the
|
|
47
|
+
// reader does, so a deep value is written without overflowing the call stack.
|
|
48
|
+
const open = [];
|
|
49
|
+
const writing = new Set();
|
|
50
|
+
let next = value;
|
|
51
|
+
for (;;) {
|
|
52
|
+
const opened = writeValue(next, out);
|
|
53
|
+
if (opened !== undefined) {
|
|
54
|
+
if (writing.has(opened.value)) {
|
|
55
|
+
throw new EvtError('invalidArgument', 'cannot encode a cyclic value as JSON');
|
|
56
|
+
}
|
|
57
|
+
writing.add(opened.value);
|
|
58
|
+
open.push(opened);
|
|
59
|
+
}
|
|
60
|
+
// Move to the next entry of the innermost open container, closing each one that is done.
|
|
61
|
+
for (;;) {
|
|
62
|
+
const top = open.at(-1);
|
|
63
|
+
if (top === undefined) {
|
|
64
|
+
return out.join('');
|
|
65
|
+
}
|
|
66
|
+
const entry = top.entries[top.next];
|
|
67
|
+
if (entry === undefined) {
|
|
68
|
+
out.push(top.close);
|
|
69
|
+
writing.delete(top.value);
|
|
70
|
+
open.pop();
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (top.next > 0) {
|
|
74
|
+
out.push(',');
|
|
75
|
+
}
|
|
76
|
+
top.next++;
|
|
77
|
+
const [key, entryValue] = entry;
|
|
78
|
+
if (key !== undefined) {
|
|
79
|
+
out.push(quote(key), ':');
|
|
80
|
+
}
|
|
81
|
+
next = entryValue;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Writes a scalar, or the opening of an array or object, which it returns for the caller to fill.
|
|
87
|
+
function writeValue(value, out) {
|
|
88
|
+
switch (typeof value) {
|
|
89
|
+
case 'string':
|
|
90
|
+
out.push(quote(value));
|
|
91
|
+
return undefined;
|
|
92
|
+
case 'boolean':
|
|
93
|
+
out.push(value ? 'true' : 'false');
|
|
94
|
+
return undefined;
|
|
95
|
+
case 'bigint':
|
|
96
|
+
out.push(value.toString());
|
|
97
|
+
return undefined;
|
|
98
|
+
case 'number':
|
|
99
|
+
out.push(formatNumber(value));
|
|
100
|
+
return undefined;
|
|
101
|
+
case 'object':
|
|
102
|
+
if (value === null) {
|
|
103
|
+
out.push('null');
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return writeObject(value, out);
|
|
107
|
+
default:
|
|
108
|
+
throw new EvtError('invalidArgument', `cannot encode a ${typeof value} as JSON`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function writeObject(value, out) {
|
|
112
|
+
if (value instanceof JsonNumber || value instanceof RawJson) {
|
|
113
|
+
out.push(value.text);
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
if (value instanceof Uint8Array) {
|
|
117
|
+
out.push('"', base64Encode(value), '"');
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
out.push('[');
|
|
122
|
+
return { value, entries: arrayEntries(value), next: 0, close: ']' };
|
|
123
|
+
}
|
|
124
|
+
const entries = value instanceof Map ? mapEntries(value) : structEntries(value);
|
|
125
|
+
out.push('{');
|
|
126
|
+
return { value, entries, next: 0, close: '}' };
|
|
127
|
+
}
|
|
128
|
+
function arrayEntries(value) {
|
|
129
|
+
return Array.from(value, (entry) => {
|
|
130
|
+
if (entry === undefined) {
|
|
131
|
+
throw new EvtError('invalidArgument', 'cannot encode undefined in an array as JSON');
|
|
132
|
+
}
|
|
133
|
+
return [undefined, entry];
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function mapEntries(value) {
|
|
137
|
+
const entries = [];
|
|
138
|
+
const seen = new Set();
|
|
139
|
+
for (const [key, entry] of value) {
|
|
140
|
+
const text = mapKey(key);
|
|
141
|
+
if (seen.has(text)) {
|
|
142
|
+
throw new EvtError('invalidArgument', `map key ${text} appears twice once written as text`);
|
|
143
|
+
}
|
|
144
|
+
seen.add(text);
|
|
145
|
+
entries.push([text, entry]);
|
|
146
|
+
}
|
|
147
|
+
entries.sort(([a], [b]) => compareUtf8(a, b));
|
|
148
|
+
return entries;
|
|
149
|
+
}
|
|
150
|
+
function mapKey(key) {
|
|
151
|
+
if (typeof key === 'string') {
|
|
152
|
+
return key;
|
|
153
|
+
}
|
|
154
|
+
if (typeof key === 'bigint' || (typeof key === 'number' && Number.isSafeInteger(key))) {
|
|
155
|
+
return key.toString();
|
|
156
|
+
}
|
|
157
|
+
throw new EvtError('invalidArgument', 'map keys must be strings or integers');
|
|
158
|
+
}
|
|
159
|
+
function structEntries(value) {
|
|
160
|
+
const prototype = Object.getPrototypeOf(value);
|
|
161
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
162
|
+
const name = value.constructor.name || 'object';
|
|
163
|
+
throw new EvtError('invalidArgument', `cannot encode a ${name} as JSON`);
|
|
164
|
+
}
|
|
165
|
+
const entries = [];
|
|
166
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
167
|
+
if (isArrayIndex(key)) {
|
|
168
|
+
throw new EvtError('invalidArgument', `object key "${key}" is integer-like, so its order is lost: use a Map`);
|
|
169
|
+
}
|
|
170
|
+
if (entry !== undefined) {
|
|
171
|
+
entries.push([key, entry]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return entries;
|
|
175
|
+
}
|
|
176
|
+
// The keys an engine enumerates first, in numeric order: canonical integers below 2^32 - 1.
|
|
177
|
+
function isArrayIndex(key) {
|
|
178
|
+
return /^(?:0|[1-9]\d{0,9})$/.test(key) && Number(key) < 4294967295;
|
|
179
|
+
}
|
|
180
|
+
function formatNumber(value) {
|
|
181
|
+
if (!Number.isFinite(value)) {
|
|
182
|
+
throw new EvtError('invalidArgument', `cannot encode ${value} as JSON`);
|
|
183
|
+
}
|
|
184
|
+
// Both write the shortest text that reads back, with an exponent below 1e-6 and from 1e21. A
|
|
185
|
+
// number is usually an integer in a payload, so negative zero (from 0 * -1, say) is written as
|
|
186
|
+
// 0, as a Go int is, not as the -0 of a Go float64.
|
|
187
|
+
return value.toString();
|
|
188
|
+
}
|
|
189
|
+
const SHORT_ESCAPES = {
|
|
190
|
+
0x08: '\\b',
|
|
191
|
+
0x09: '\\t',
|
|
192
|
+
0x0a: '\\n',
|
|
193
|
+
0x0c: '\\f',
|
|
194
|
+
0x0d: '\\r',
|
|
195
|
+
0x22: '\\"',
|
|
196
|
+
0x5c: '\\\\',
|
|
197
|
+
};
|
|
198
|
+
// <, > and &, which Go escapes so the output is safe in HTML, and the two line separators.
|
|
199
|
+
const HTML_ESCAPES = new Set([0x26, 0x3c, 0x3e, 0x2028, 0x2029]);
|
|
200
|
+
/** Quotes a string with Go's escaping. */
|
|
201
|
+
export function quote(text) {
|
|
202
|
+
let out = '"';
|
|
203
|
+
let start = 0;
|
|
204
|
+
for (let i = 0; i < text.length; i++) {
|
|
205
|
+
const unit = text.charCodeAt(i);
|
|
206
|
+
let escaped;
|
|
207
|
+
if (isHighSurrogate(unit) && isLowSurrogate(text.charCodeAt(i + 1))) {
|
|
208
|
+
i++;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
else if (unit >= 0xd800 && unit <= 0xdfff) {
|
|
212
|
+
escaped = '\\ufffd';
|
|
213
|
+
}
|
|
214
|
+
else if (unit < 0x20 || unit === 0x22 || unit === 0x5c || HTML_ESCAPES.has(unit)) {
|
|
215
|
+
escaped = SHORT_ESCAPES[unit] ?? `\\u${unit.toString(16).padStart(4, '0')}`;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
out += text.slice(start, i) + escaped;
|
|
221
|
+
start = i + 1;
|
|
222
|
+
}
|
|
223
|
+
return out + text.slice(start) + '"';
|
|
224
|
+
}
|
|
225
|
+
// Go's decoder refuses documents nested deeper than this. The parser keeps its own stack, so a
|
|
226
|
+
// deep document is refused cleanly instead of overflowing the call stack.
|
|
227
|
+
const MAX_DEPTH = 10_000;
|
|
228
|
+
/**
|
|
229
|
+
* Parses JSON without losing anything `JSON.parse` loses. Objects come back as Maps in written
|
|
230
|
+
* order, and numbers come back as {@link JsonNumber}. An escaped lone surrogate reads as U+FFFD,
|
|
231
|
+
* as it does in Go.
|
|
232
|
+
*/
|
|
233
|
+
export function parseJson(text, options = {}) {
|
|
234
|
+
const parser = new Parser(text, options.duplicateKeys === 'refuse');
|
|
235
|
+
parser.skipWhitespace();
|
|
236
|
+
const value = parser.value();
|
|
237
|
+
parser.skipWhitespace();
|
|
238
|
+
if (parser.pos < text.length) {
|
|
239
|
+
throw parser.error('unexpected text after the value');
|
|
240
|
+
}
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
class Parser {
|
|
244
|
+
text;
|
|
245
|
+
refuseDuplicates;
|
|
246
|
+
pos = 0;
|
|
247
|
+
constructor(text, refuseDuplicates) {
|
|
248
|
+
this.text = text;
|
|
249
|
+
this.refuseDuplicates = refuseDuplicates;
|
|
250
|
+
}
|
|
251
|
+
value() {
|
|
252
|
+
const open = [];
|
|
253
|
+
for (;;) {
|
|
254
|
+
let value;
|
|
255
|
+
const unit = this.text.charCodeAt(this.pos);
|
|
256
|
+
if (unit === 0x7b || unit === 0x5b) {
|
|
257
|
+
// { or [
|
|
258
|
+
if (open.length >= MAX_DEPTH) {
|
|
259
|
+
throw this.error('exceeded max depth');
|
|
260
|
+
}
|
|
261
|
+
this.pos++;
|
|
262
|
+
this.skipWhitespace();
|
|
263
|
+
const empty = this.text.charCodeAt(this.pos) === unit + 2;
|
|
264
|
+
if (!empty) {
|
|
265
|
+
open.push(unit === 0x5b ? { array: [] } : { object: new Map(), key: this.key() });
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
this.pos++;
|
|
269
|
+
value = unit === 0x5b ? [] : new Map();
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
value = this.scalar(unit);
|
|
273
|
+
}
|
|
274
|
+
// Add the value to the innermost open container, and close each container that ends here.
|
|
275
|
+
for (;;) {
|
|
276
|
+
const top = open.at(-1);
|
|
277
|
+
if (top === undefined) {
|
|
278
|
+
return value;
|
|
279
|
+
}
|
|
280
|
+
if ('array' in top) {
|
|
281
|
+
top.array.push(value);
|
|
282
|
+
}
|
|
283
|
+
else if (this.refuseDuplicates && top.object.has(top.key)) {
|
|
284
|
+
throw this.error(`the key "${top.key}" appears twice`);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
top.object.set(top.key, value);
|
|
288
|
+
}
|
|
289
|
+
this.skipWhitespace();
|
|
290
|
+
if (this.text.charCodeAt(this.pos) === 0x2c) {
|
|
291
|
+
this.pos++;
|
|
292
|
+
this.skipWhitespace();
|
|
293
|
+
if ('object' in top) {
|
|
294
|
+
top.key = this.key();
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
if ('array' in top) {
|
|
299
|
+
this.expect(0x5d, "',' or ']'");
|
|
300
|
+
value = top.array;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
this.expect(0x7d, "',' or '}'");
|
|
304
|
+
value = top.object;
|
|
305
|
+
}
|
|
306
|
+
open.pop();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
scalar(unit) {
|
|
311
|
+
switch (unit) {
|
|
312
|
+
case 0x22: // "
|
|
313
|
+
return this.string();
|
|
314
|
+
case 0x74: // t
|
|
315
|
+
return this.literal('true', true);
|
|
316
|
+
case 0x66: // f
|
|
317
|
+
return this.literal('false', false);
|
|
318
|
+
case 0x6e: // n
|
|
319
|
+
return this.literal('null', null);
|
|
320
|
+
default:
|
|
321
|
+
return this.number();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// An object key and its colon, leaving the position at the value.
|
|
325
|
+
key() {
|
|
326
|
+
if (this.text.charCodeAt(this.pos) !== 0x22) {
|
|
327
|
+
throw this.error('expected an object key');
|
|
328
|
+
}
|
|
329
|
+
const key = this.string();
|
|
330
|
+
this.skipWhitespace();
|
|
331
|
+
this.expect(0x3a, "':'");
|
|
332
|
+
this.skipWhitespace();
|
|
333
|
+
return key;
|
|
334
|
+
}
|
|
335
|
+
string() {
|
|
336
|
+
let out = '';
|
|
337
|
+
this.pos++;
|
|
338
|
+
let start = this.pos;
|
|
339
|
+
for (;;) {
|
|
340
|
+
if (this.pos >= this.text.length) {
|
|
341
|
+
throw this.error('unterminated string');
|
|
342
|
+
}
|
|
343
|
+
const unit = this.text.charCodeAt(this.pos);
|
|
344
|
+
if (unit === 0x22) {
|
|
345
|
+
out += this.text.slice(start, this.pos);
|
|
346
|
+
this.pos++;
|
|
347
|
+
return out;
|
|
348
|
+
}
|
|
349
|
+
if (unit < 0x20) {
|
|
350
|
+
throw this.error('control character in string');
|
|
351
|
+
}
|
|
352
|
+
if (unit === 0x5c) {
|
|
353
|
+
out += this.text.slice(start, this.pos) + this.escape();
|
|
354
|
+
start = this.pos;
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (unit >= 0xd800 && unit <= 0xdfff) {
|
|
358
|
+
// A raw surrogate that isn't half of a pair is invalid UTF-8 to Go.
|
|
359
|
+
if (isHighSurrogate(unit) && isLowSurrogate(this.text.charCodeAt(this.pos + 1))) {
|
|
360
|
+
this.pos += 2;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
out += this.text.slice(start, this.pos) + '\ufffd';
|
|
364
|
+
this.pos++;
|
|
365
|
+
start = this.pos;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
this.pos++;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
escape() {
|
|
372
|
+
const unit = this.text.charCodeAt(this.pos + 1);
|
|
373
|
+
this.pos += 2;
|
|
374
|
+
switch (unit) {
|
|
375
|
+
case 0x22:
|
|
376
|
+
return '"';
|
|
377
|
+
case 0x5c:
|
|
378
|
+
return '\\';
|
|
379
|
+
case 0x2f:
|
|
380
|
+
return '/';
|
|
381
|
+
case 0x62:
|
|
382
|
+
return '\b';
|
|
383
|
+
case 0x66:
|
|
384
|
+
return '\f';
|
|
385
|
+
case 0x6e:
|
|
386
|
+
return '\n';
|
|
387
|
+
case 0x72:
|
|
388
|
+
return '\r';
|
|
389
|
+
case 0x74:
|
|
390
|
+
return '\t';
|
|
391
|
+
case 0x75: {
|
|
392
|
+
const high = this.hex();
|
|
393
|
+
if (!isHighSurrogate(high)) {
|
|
394
|
+
return isLowSurrogate(high) ? '\ufffd' : String.fromCharCode(high);
|
|
395
|
+
}
|
|
396
|
+
// A high surrogate pairs only with an escaped low surrogate right after it.
|
|
397
|
+
if (this.text.startsWith('\\u', this.pos)) {
|
|
398
|
+
const save = this.pos;
|
|
399
|
+
this.pos += 2;
|
|
400
|
+
const low = this.hex();
|
|
401
|
+
if (isLowSurrogate(low)) {
|
|
402
|
+
return String.fromCharCode(high, low);
|
|
403
|
+
}
|
|
404
|
+
this.pos = save;
|
|
405
|
+
}
|
|
406
|
+
return '\ufffd';
|
|
407
|
+
}
|
|
408
|
+
default:
|
|
409
|
+
this.pos -= 2;
|
|
410
|
+
throw this.error('invalid escape');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
hex() {
|
|
414
|
+
const digits = this.text.slice(this.pos, this.pos + 4);
|
|
415
|
+
if (!/^[0-9a-fA-F]{4}$/.test(digits)) {
|
|
416
|
+
throw this.error('invalid \\u escape');
|
|
417
|
+
}
|
|
418
|
+
this.pos += 4;
|
|
419
|
+
return Number.parseInt(digits, 16);
|
|
420
|
+
}
|
|
421
|
+
number() {
|
|
422
|
+
const match = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
|
|
423
|
+
match.lastIndex = this.pos;
|
|
424
|
+
const found = match.exec(this.text);
|
|
425
|
+
if (found === null) {
|
|
426
|
+
throw this.error('unexpected character');
|
|
427
|
+
}
|
|
428
|
+
this.pos += found[0].length;
|
|
429
|
+
return new JsonNumber(found[0]);
|
|
430
|
+
}
|
|
431
|
+
literal(word, value) {
|
|
432
|
+
if (!this.text.startsWith(word, this.pos)) {
|
|
433
|
+
throw this.error('unexpected character');
|
|
434
|
+
}
|
|
435
|
+
this.pos += word.length;
|
|
436
|
+
return value;
|
|
437
|
+
}
|
|
438
|
+
expect(unit, what) {
|
|
439
|
+
if (this.text.charCodeAt(this.pos) !== unit) {
|
|
440
|
+
throw this.error(`expected ${what}`);
|
|
441
|
+
}
|
|
442
|
+
this.pos++;
|
|
443
|
+
}
|
|
444
|
+
skipWhitespace() {
|
|
445
|
+
for (;;) {
|
|
446
|
+
const unit = this.text.charCodeAt(this.pos);
|
|
447
|
+
if (unit !== 0x20 && unit !== 0x09 && unit !== 0x0a && unit !== 0x0d) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
this.pos++;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
error(message) {
|
|
454
|
+
const where = this.pos >= this.text.length ? 'at the end' : `at offset ${this.pos}`;
|
|
455
|
+
return new EvtError('invalidJson', `invalid JSON ${where}: ${message}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
//# sourceMappingURL=json.js.map
|