@effect-dynamodb/schema 1.24.0 → 1.26.0
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/dist/Compression.d.ts +82 -0
- package/dist/Compression.d.ts.map +1 -0
- package/dist/Compression.js +80 -0
- package/dist/Compression.js.map +1 -0
- package/dist/DynamoModel.d.ts +107 -3
- package/dist/DynamoModel.d.ts.map +1 -1
- package/dist/DynamoModel.js +118 -0
- package/dist/DynamoModel.js.map +1 -1
- package/dist/Entity.d.ts +19 -3
- package/dist/Entity.d.ts.map +1 -1
- package/dist/Entity.js +30 -3
- package/dist/Entity.js.map +1 -1
- package/dist/Errors.d.ts +85 -12
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +70 -7
- package/dist/Errors.js.map +1 -1
- package/dist/VectorIndex.d.ts +32 -10
- package/dist/VectorIndex.d.ts.map +1 -1
- package/dist/VectorIndex.js +37 -25
- package/dist/VectorIndex.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/DefaultCrypto.d.ts +4 -4
- package/dist/internal/DefaultCrypto.js +4 -4
- package/dist/internal/EntityPacked.d.ts +64 -0
- package/dist/internal/EntityPacked.d.ts.map +1 -0
- package/dist/internal/EntityPacked.js +105 -0
- package/dist/internal/EntityPacked.js.map +1 -0
- package/dist/internal/PackedCodec.d.ts +186 -0
- package/dist/internal/PackedCodec.d.ts.map +1 -0
- package/dist/internal/PackedCodec.js +454 -0
- package/dist/internal/PackedCodec.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal Pure codec for packed storage (`DynamoModel.Packed`): the stored
|
|
3
|
+
* form of one attribute value, independent of DynamoDB's attribute-value types.
|
|
4
|
+
*
|
|
5
|
+
* Packing turns an attribute's **wire form** (the value the write path would
|
|
6
|
+
* otherwise marshal as a native map) into the JS value that marshals to its
|
|
7
|
+
* stored form — `string` → `S`, `Uint8Array` → `B`, `null` → `NULL`,
|
|
8
|
+
* `undefined` → no attribute. Unpacking dispatches on the stored value, never on
|
|
9
|
+
* the current config:
|
|
10
|
+
*
|
|
11
|
+
* | Stored (unmarshalled) | Read as |
|
|
12
|
+
* |-----------------------|-------------------------------------------------------------|
|
|
13
|
+
* | `undefined` / `null` | as is |
|
|
14
|
+
* | `string` (`S`) | `JSON.parse` |
|
|
15
|
+
* | `Uint8Array` (`B`) | header byte → decompress per codec → UTF-8 (fatal) → `JSON.parse` |
|
|
16
|
+
* | anything else | a legacy native value (map, list, …), unchanged |
|
|
17
|
+
*
|
|
18
|
+
* ## `B` header (byte 0): `vvv ff ccc`
|
|
19
|
+
*
|
|
20
|
+
* - bits 7–5, layout version: `001` = v1. `000` invalid; `010`–`111` reserved
|
|
21
|
+
* for future layouts, which may add bytes after byte 0.
|
|
22
|
+
* - bits 4–3, format: `00` json; `01` binary — reserved for SchemaBinary;
|
|
23
|
+
* `10`, `11` reserved.
|
|
24
|
+
* - bits 2–0, codec: `000` none, `001` gzip, `010` brotli, `011` zstd;
|
|
25
|
+
* `100`–`111` reserved.
|
|
26
|
+
*
|
|
27
|
+
* v1 writers emit `0x21` (gzip), `0x22` (brotli), `0x23` (zstd); v1 readers also
|
|
28
|
+
* accept `0x20` (json, uncompressed). Every v1 header byte lies in `0x20`–`0x3F`,
|
|
29
|
+
* so the base64 text of a v1 `B` value starts with one of `I`–`P` — a character
|
|
30
|
+
* no `JSON.stringify` output starts with. A future layout version must keep that
|
|
31
|
+
* property (see {@link isBase64PackedText}).
|
|
32
|
+
*
|
|
33
|
+
* No `node:zlib` and no AWS SDK here: compression goes through a
|
|
34
|
+
* {@link CompressionRegistry}.
|
|
35
|
+
*/
|
|
36
|
+
import { Effect, Schema, SchemaAST } from "effect";
|
|
37
|
+
import type { CompressionRegistry } from "../Compression.js";
|
|
38
|
+
import type { CompressionCodec, PackedConfig } from "../DynamoModel.js";
|
|
39
|
+
import { ValidationError } from "../Errors.js";
|
|
40
|
+
/** Layout version of every value this version writes (bits 7–5). */
|
|
41
|
+
export declare const LAYOUT_VERSION_V1 = 1;
|
|
42
|
+
/** Format codes (bits 4–3). */
|
|
43
|
+
export declare const FORMAT_JSON = 0;
|
|
44
|
+
/** Reserved for a future SchemaBinary encoding; not read or written by this version. */
|
|
45
|
+
export declare const FORMAT_BINARY = 1;
|
|
46
|
+
/** Codec codes (bits 2–0). */
|
|
47
|
+
export declare const CODEC_NONE = 0;
|
|
48
|
+
export declare const CODEC_GZIP = 1;
|
|
49
|
+
export declare const CODEC_BROTLI = 2;
|
|
50
|
+
export declare const CODEC_ZSTD = 3;
|
|
51
|
+
/** The header byte of a v1 value of `format` compressed with `codec`. */
|
|
52
|
+
export declare const headerByte: (codec: CompressionCodec | "none", format?: number) => number;
|
|
53
|
+
/** A header this version can read. */
|
|
54
|
+
export interface PackedHeader {
|
|
55
|
+
readonly version: 1;
|
|
56
|
+
readonly format: "json";
|
|
57
|
+
readonly codec: CompressionCodec | "none";
|
|
58
|
+
}
|
|
59
|
+
/** Parse byte 0 of a `B` value. Returns a reason string when this version cannot read it. */
|
|
60
|
+
export declare const parseHeader: (byte: number) => PackedHeader | string;
|
|
61
|
+
/**
|
|
62
|
+
* True iff `text` is the base64 encoding of a v1 `B` value: its first
|
|
63
|
+
* character encodes the top six bits of a header byte in `0x20`–`0x3F`
|
|
64
|
+
* (`I`–`P`). JSON text never starts with one of those characters, so a string
|
|
65
|
+
* on a packed attribute of an unmarshalled stream image is base64 exactly when
|
|
66
|
+
* this holds.
|
|
67
|
+
*/
|
|
68
|
+
export declare const isBase64PackedText: (text: string) => boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Who is packing or unpacking, for error attribution. `operation` is used
|
|
71
|
+
* verbatim — by convention `<op>.packed` on writes and `<op>.decode` (or the
|
|
72
|
+
* store's decode operation) on reads.
|
|
73
|
+
*/
|
|
74
|
+
export interface PackedContext extends OperationContext {
|
|
75
|
+
readonly attribute: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Who is reading or writing, without the attribute: the synchronous codec
|
|
79
|
+
* takes the attribute separately so a hot read loop builds no context object
|
|
80
|
+
* per value — one is built only when a value fails.
|
|
81
|
+
*/
|
|
82
|
+
export interface OperationContext {
|
|
83
|
+
readonly entityType: string;
|
|
84
|
+
readonly operation: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A missing compressor, surfaced as a `ValidationError` whose `cause` is a
|
|
88
|
+
* {@link CompressorNotRegistered} naming the codec, the direction and the
|
|
89
|
+
* attribute. Packed storage is opt-in, so it never widens an operation's error
|
|
90
|
+
* channel: a caller inspects `cause` (`error.cause instanceof
|
|
91
|
+
* CompressorNotRegistered`) to tell a misconfigured deployment from an
|
|
92
|
+
* undecodable value.
|
|
93
|
+
*/
|
|
94
|
+
export declare const notRegistered: (ctx: OperationContext, attribute: string, codec: CompressionCodec, direction: "compress" | "decompress") => ValidationError;
|
|
95
|
+
/**
|
|
96
|
+
* `JSON.stringify` that refuses anything JSON cannot represent faithfully:
|
|
97
|
+
* bigint, binary (`Uint8Array`, `ArrayBuffer`, typed arrays), `Set`, `Map`,
|
|
98
|
+
* `Date`, any other non-plain object (including one with `toJSON`), non-finite
|
|
99
|
+
* numbers, `undefined` array elements, symbols, functions and cycles.
|
|
100
|
+
* `undefined` object properties are omitted.
|
|
101
|
+
*
|
|
102
|
+
* @throws an `Error` naming the path (rooted at `root`) and the JS type.
|
|
103
|
+
*/
|
|
104
|
+
export declare const stringifyStrict: (value: unknown, root?: string) => string;
|
|
105
|
+
/** The JS value a packed attribute marshals to: `S`, `B`, `NULL`, or absent. */
|
|
106
|
+
export type PackedStored = string | Uint8Array | null | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* {@link packValue} without an Effect: the stored value, or the
|
|
109
|
+
* `ValidationError` it fails with (a returned value, never thrown). The
|
|
110
|
+
* synchronous core every pack site runs, wrapped in an Effect once per item or
|
|
111
|
+
* operation by the caller.
|
|
112
|
+
*/
|
|
113
|
+
export declare const packValueSync: (config: PackedConfig, wire: unknown, registry: CompressionRegistry, ctx: OperationContext, attribute: string) => PackedStored | ValidationError;
|
|
114
|
+
/**
|
|
115
|
+
* Pack an attribute's wire form per `config` (see the module header).
|
|
116
|
+
*
|
|
117
|
+
* - `undefined` → `undefined` (leave the attribute absent); `null` → `null`.
|
|
118
|
+
* - Otherwise the strict JSON. Without compression, or when its UTF-8 length is
|
|
119
|
+
* under `threshold`, the JSON string (`S`).
|
|
120
|
+
* - At or over the threshold the codec's compressor runs, and the header plus
|
|
121
|
+
* compressed bytes are kept only when strictly smaller than the JSON (`B`);
|
|
122
|
+
* otherwise the JSON string.
|
|
123
|
+
*
|
|
124
|
+
* Fails with `ValidationError` on non-JSON input or a throwing compressor, and
|
|
125
|
+
* when the value crosses the threshold and `registry` has no compressor for the
|
|
126
|
+
* codec — then its `cause` is a `CompressorNotRegistered` (direction
|
|
127
|
+
* `"compress"`).
|
|
128
|
+
*/
|
|
129
|
+
export declare const packValue: (config: PackedConfig, wire: unknown, registry: CompressionRegistry, ctx: PackedContext) => Effect.Effect<PackedStored, ValidationError>;
|
|
130
|
+
/**
|
|
131
|
+
* {@link unpackBytes} without an Effect: the JSON wire form, or the
|
|
132
|
+
* `ValidationError` it fails with (returned, never thrown). A parsed JSON value
|
|
133
|
+
* is never a `ValidationError`, so `instanceof ValidationError` tells them apart.
|
|
134
|
+
*/
|
|
135
|
+
export declare const unpackBytesSync: (stored: Uint8Array, registry: CompressionRegistry, ctx: OperationContext, attribute: string) => unknown;
|
|
136
|
+
/**
|
|
137
|
+
* {@link unpackValue} of a stored `S` (JSON text) or `B` (header + payload)
|
|
138
|
+
* without an Effect: the JSON wire form, or the `ValidationError` it fails with
|
|
139
|
+
* (returned, never thrown — tell them apart with `instanceof ValidationError`).
|
|
140
|
+
* Callers pass only strings and bytes; every other stored value is native and
|
|
141
|
+
* read as is, without a call.
|
|
142
|
+
*/
|
|
143
|
+
export declare const unpackStoredSync: (stored: string | Uint8Array, registry: CompressionRegistry, ctx: OperationContext, attribute: string) => unknown;
|
|
144
|
+
/**
|
|
145
|
+
* Decode a `B` value (header byte + payload) to its JSON wire form.
|
|
146
|
+
*/
|
|
147
|
+
export declare const unpackBytes: (stored: Uint8Array, registry: CompressionRegistry, ctx: PackedContext) => Effect.Effect<unknown, ValidationError>;
|
|
148
|
+
/**
|
|
149
|
+
* Unpack a stored (unmarshalled) value by its form — see the module header.
|
|
150
|
+
* A `string` is JSON, a `Uint8Array` is a header + payload, and anything else
|
|
151
|
+
* (absent, `null`, a legacy native map, list, number, …) is returned unchanged.
|
|
152
|
+
*/
|
|
153
|
+
export declare const unpackValue: (stored: unknown, registry: CompressionRegistry, ctx: PackedContext) => Effect.Effect<unknown, ValidationError>;
|
|
154
|
+
/**
|
|
155
|
+
* The shape of a schema's **encoded** side, for packed storage:
|
|
156
|
+
*
|
|
157
|
+
* - `Structured` — object- or array-shaped (`Struct`, `Class`, `Record`,
|
|
158
|
+
* `Array`, `Tuple`, a union of those, optionally with `null` / `undefined`).
|
|
159
|
+
* A native value of it is never `S` or `B`, so a reader may dispatch on the
|
|
160
|
+
* stored form even when the field is not configured packed.
|
|
161
|
+
* - `Opaque` — `Unknown`, `Any`, `Json`, `object`: may be packed (a native
|
|
162
|
+
* value of it could be a string or bytes, so switching an existing native
|
|
163
|
+
* field of this type to packed misreads such values), but is not
|
|
164
|
+
* dispatch-eligible unless configured packed.
|
|
165
|
+
* - `Refused` — scalar, date, binary, set, map, or a union with such a member:
|
|
166
|
+
* cannot be packed (`reason` says why).
|
|
167
|
+
*/
|
|
168
|
+
export type PackedShape = {
|
|
169
|
+
readonly _tag: "Structured";
|
|
170
|
+
} | {
|
|
171
|
+
readonly _tag: "Opaque";
|
|
172
|
+
} | {
|
|
173
|
+
readonly _tag: "Refused";
|
|
174
|
+
readonly reason: string;
|
|
175
|
+
};
|
|
176
|
+
/** Classify a schema's encoded side for packed storage (see {@link PackedShape}). */
|
|
177
|
+
export declare const packedShape: (schema: Schema.Top | SchemaAST.AST) => PackedShape;
|
|
178
|
+
/** True iff a schema may be stored packed (its encoded side is not refused). */
|
|
179
|
+
export declare const isPackedShape: (schema: Schema.Top | SchemaAST.AST) => boolean;
|
|
180
|
+
/**
|
|
181
|
+
* True iff readers may dispatch on the stored form of an attribute of this
|
|
182
|
+
* schema even when it is not configured packed: its encoded side is
|
|
183
|
+
* `Structured`, so a native value is never `S` or `B`.
|
|
184
|
+
*/
|
|
185
|
+
export declare const isDispatchEligible: (schema: Schema.Top | SchemaAST.AST) => boolean;
|
|
186
|
+
//# sourceMappingURL=PackedCodec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PackedCodec.d.ts","sourceRoot":"","sources":["../../src/internal/PackedCodec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal Pure codec for packed storage (`DynamoModel.Packed`): the stored
|
|
3
|
+
* form of one attribute value, independent of DynamoDB's attribute-value types.
|
|
4
|
+
*
|
|
5
|
+
* Packing turns an attribute's **wire form** (the value the write path would
|
|
6
|
+
* otherwise marshal as a native map) into the JS value that marshals to its
|
|
7
|
+
* stored form — `string` → `S`, `Uint8Array` → `B`, `null` → `NULL`,
|
|
8
|
+
* `undefined` → no attribute. Unpacking dispatches on the stored value, never on
|
|
9
|
+
* the current config:
|
|
10
|
+
*
|
|
11
|
+
* | Stored (unmarshalled) | Read as |
|
|
12
|
+
* |-----------------------|-------------------------------------------------------------|
|
|
13
|
+
* | `undefined` / `null` | as is |
|
|
14
|
+
* | `string` (`S`) | `JSON.parse` |
|
|
15
|
+
* | `Uint8Array` (`B`) | header byte → decompress per codec → UTF-8 (fatal) → `JSON.parse` |
|
|
16
|
+
* | anything else | a legacy native value (map, list, …), unchanged |
|
|
17
|
+
*
|
|
18
|
+
* ## `B` header (byte 0): `vvv ff ccc`
|
|
19
|
+
*
|
|
20
|
+
* - bits 7–5, layout version: `001` = v1. `000` invalid; `010`–`111` reserved
|
|
21
|
+
* for future layouts, which may add bytes after byte 0.
|
|
22
|
+
* - bits 4–3, format: `00` json; `01` binary — reserved for SchemaBinary;
|
|
23
|
+
* `10`, `11` reserved.
|
|
24
|
+
* - bits 2–0, codec: `000` none, `001` gzip, `010` brotli, `011` zstd;
|
|
25
|
+
* `100`–`111` reserved.
|
|
26
|
+
*
|
|
27
|
+
* v1 writers emit `0x21` (gzip), `0x22` (brotli), `0x23` (zstd); v1 readers also
|
|
28
|
+
* accept `0x20` (json, uncompressed). Every v1 header byte lies in `0x20`–`0x3F`,
|
|
29
|
+
* so the base64 text of a v1 `B` value starts with one of `I`–`P` — a character
|
|
30
|
+
* no `JSON.stringify` output starts with. A future layout version must keep that
|
|
31
|
+
* property (see {@link isBase64PackedText}).
|
|
32
|
+
*
|
|
33
|
+
* No `node:zlib` and no AWS SDK here: compression goes through a
|
|
34
|
+
* {@link CompressionRegistry}.
|
|
35
|
+
*/
|
|
36
|
+
import { Effect, Schema, SchemaAST } from "effect";
|
|
37
|
+
import { CompressorNotRegistered, ValidationError } from "../Errors.js";
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Header layout
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
/** Layout version of every value this version writes (bits 7–5). */
|
|
42
|
+
export const LAYOUT_VERSION_V1 = 0b001;
|
|
43
|
+
/** Format codes (bits 4–3). */
|
|
44
|
+
export const FORMAT_JSON = 0b00;
|
|
45
|
+
/** Reserved for a future SchemaBinary encoding; not read or written by this version. */
|
|
46
|
+
export const FORMAT_BINARY = 0b01;
|
|
47
|
+
/** Codec codes (bits 2–0). */
|
|
48
|
+
export const CODEC_NONE = 0b000;
|
|
49
|
+
export const CODEC_GZIP = 0b001;
|
|
50
|
+
export const CODEC_BROTLI = 0b010;
|
|
51
|
+
export const CODEC_ZSTD = 0b011;
|
|
52
|
+
const codecCodes = {
|
|
53
|
+
gzip: CODEC_GZIP,
|
|
54
|
+
brotli: CODEC_BROTLI,
|
|
55
|
+
zstd: CODEC_ZSTD,
|
|
56
|
+
};
|
|
57
|
+
const codecByCode = [
|
|
58
|
+
"none",
|
|
59
|
+
"gzip",
|
|
60
|
+
"brotli",
|
|
61
|
+
"zstd",
|
|
62
|
+
];
|
|
63
|
+
/** The header byte of a v1 value of `format` compressed with `codec`. */
|
|
64
|
+
export const headerByte = (codec, format = FORMAT_JSON) => (LAYOUT_VERSION_V1 << 5) | (format << 3) | (codec === "none" ? CODEC_NONE : codecCodes[codec]);
|
|
65
|
+
const hex = (byte) => `0x${byte.toString(16).padStart(2, "0")}`;
|
|
66
|
+
/** Parse byte 0 of a `B` value. Returns a reason string when this version cannot read it. */
|
|
67
|
+
export const parseHeader = (byte) => {
|
|
68
|
+
const version = byte >>> 5;
|
|
69
|
+
const format = (byte >>> 3) & 0b11;
|
|
70
|
+
const codec = byte & 0b111;
|
|
71
|
+
if (version === 0)
|
|
72
|
+
return `header byte ${hex(byte)} has layout version 0, which is invalid`;
|
|
73
|
+
if (version !== LAYOUT_VERSION_V1) {
|
|
74
|
+
return (`header byte ${hex(byte)} has layout version ${version}, which this version does not ` +
|
|
75
|
+
`support (written by a newer effect-dynamodb?)`);
|
|
76
|
+
}
|
|
77
|
+
if (format === FORMAT_BINARY) {
|
|
78
|
+
return (`header byte ${hex(byte)} declares format "binary", which is reserved and not supported ` +
|
|
79
|
+
`by this version`);
|
|
80
|
+
}
|
|
81
|
+
if (format !== FORMAT_JSON)
|
|
82
|
+
return `header byte ${hex(byte)} has unknown format code ${format}`;
|
|
83
|
+
const name = codecByCode[codec];
|
|
84
|
+
if (name === undefined)
|
|
85
|
+
return `header byte ${hex(byte)} has unknown codec code ${codec}`;
|
|
86
|
+
return { version: 1, format: "json", codec: name };
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* True iff `text` is the base64 encoding of a v1 `B` value: its first
|
|
90
|
+
* character encodes the top six bits of a header byte in `0x20`–`0x3F`
|
|
91
|
+
* (`I`–`P`). JSON text never starts with one of those characters, so a string
|
|
92
|
+
* on a packed attribute of an unmarshalled stream image is base64 exactly when
|
|
93
|
+
* this holds.
|
|
94
|
+
*/
|
|
95
|
+
export const isBase64PackedText = (text) => {
|
|
96
|
+
const first = text.charCodeAt(0);
|
|
97
|
+
// "I" (0x49) .. "P" (0x50)
|
|
98
|
+
return first >= 0x49 && first <= 0x50;
|
|
99
|
+
};
|
|
100
|
+
const invalid = (ctx, attribute, message, cause) => new ValidationError({
|
|
101
|
+
entityType: ctx.entityType,
|
|
102
|
+
operation: ctx.operation,
|
|
103
|
+
cause: cause === undefined
|
|
104
|
+
? new Error(`Packed attribute "${attribute}": ${message}`)
|
|
105
|
+
: new Error(`Packed attribute "${attribute}": ${message}`, { cause }),
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* A missing compressor, surfaced as a `ValidationError` whose `cause` is a
|
|
109
|
+
* {@link CompressorNotRegistered} naming the codec, the direction and the
|
|
110
|
+
* attribute. Packed storage is opt-in, so it never widens an operation's error
|
|
111
|
+
* channel: a caller inspects `cause` (`error.cause instanceof
|
|
112
|
+
* CompressorNotRegistered`) to tell a misconfigured deployment from an
|
|
113
|
+
* undecodable value.
|
|
114
|
+
*/
|
|
115
|
+
export const notRegistered = (ctx, attribute, codec, direction) => new ValidationError({
|
|
116
|
+
entityType: ctx.entityType,
|
|
117
|
+
operation: ctx.operation,
|
|
118
|
+
cause: new CompressorNotRegistered({
|
|
119
|
+
codec,
|
|
120
|
+
direction,
|
|
121
|
+
entityType: ctx.entityType,
|
|
122
|
+
attribute,
|
|
123
|
+
operation: ctx.operation,
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Strict JSON serialisation
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
130
|
+
const describe = (value) => {
|
|
131
|
+
if (value === undefined)
|
|
132
|
+
return "undefined";
|
|
133
|
+
if (typeof value === "bigint")
|
|
134
|
+
return "a bigint";
|
|
135
|
+
if (typeof value === "symbol")
|
|
136
|
+
return "a symbol";
|
|
137
|
+
if (typeof value === "function")
|
|
138
|
+
return "a function";
|
|
139
|
+
if (typeof value === "number")
|
|
140
|
+
return `the non-finite number ${String(value)}`;
|
|
141
|
+
if (value instanceof Date)
|
|
142
|
+
return "a Date";
|
|
143
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
|
|
144
|
+
return `binary data (${value.constructor?.name ?? "ArrayBuffer"})`;
|
|
145
|
+
}
|
|
146
|
+
if (value instanceof Set)
|
|
147
|
+
return "a Set";
|
|
148
|
+
if (value instanceof Map)
|
|
149
|
+
return "a Map";
|
|
150
|
+
const name = value.constructor?.name;
|
|
151
|
+
return name ? `an instance of ${name}` : "a non-plain object";
|
|
152
|
+
};
|
|
153
|
+
class NotJson extends Error {
|
|
154
|
+
path;
|
|
155
|
+
what;
|
|
156
|
+
constructor(path, what) {
|
|
157
|
+
super(`the value at ${path} is ${what}, which JSON cannot represent. Packed storage holds ` +
|
|
158
|
+
`JSON only: store this field natively, or give it a JSON-shaped schema ` +
|
|
159
|
+
`(e.g. DateTimeUtcFromString, BigIntFromString, Uint8ArrayFromBase64, an array instead ` +
|
|
160
|
+
`of a set).`);
|
|
161
|
+
this.path = path;
|
|
162
|
+
this.what = what;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const isPlainObject = (value) => {
|
|
166
|
+
const proto = Object.getPrototypeOf(value);
|
|
167
|
+
return proto === Object.prototype || proto === null;
|
|
168
|
+
};
|
|
169
|
+
const check = (value, path, stack) => {
|
|
170
|
+
switch (typeof value) {
|
|
171
|
+
case "string":
|
|
172
|
+
case "boolean":
|
|
173
|
+
return;
|
|
174
|
+
case "number":
|
|
175
|
+
if (!Number.isFinite(value))
|
|
176
|
+
throw new NotJson(path, describe(value));
|
|
177
|
+
return;
|
|
178
|
+
case "object": {
|
|
179
|
+
if (value === null)
|
|
180
|
+
return;
|
|
181
|
+
if (stack.has(value))
|
|
182
|
+
throw new NotJson(path, "a circular reference");
|
|
183
|
+
if (Array.isArray(value)) {
|
|
184
|
+
stack.add(value);
|
|
185
|
+
for (let i = 0; i < value.length; i++) {
|
|
186
|
+
const element = value[i];
|
|
187
|
+
if (element === undefined)
|
|
188
|
+
throw new NotJson(`${path}[${i}]`, "undefined");
|
|
189
|
+
check(element, `${path}[${i}]`, stack);
|
|
190
|
+
}
|
|
191
|
+
stack.delete(value);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (!isPlainObject(value) || typeof value.toJSON === "function") {
|
|
195
|
+
throw new NotJson(path, describe(value));
|
|
196
|
+
}
|
|
197
|
+
stack.add(value);
|
|
198
|
+
for (const key of Object.keys(value)) {
|
|
199
|
+
const v = value[key];
|
|
200
|
+
// Omitted by JSON.stringify, as removeUndefinedValues omits it from a map.
|
|
201
|
+
if (v === undefined)
|
|
202
|
+
continue;
|
|
203
|
+
check(v, IDENTIFIER.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`, stack);
|
|
204
|
+
}
|
|
205
|
+
stack.delete(value);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
default:
|
|
209
|
+
throw new NotJson(path, describe(value));
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* `JSON.stringify` that refuses anything JSON cannot represent faithfully:
|
|
214
|
+
* bigint, binary (`Uint8Array`, `ArrayBuffer`, typed arrays), `Set`, `Map`,
|
|
215
|
+
* `Date`, any other non-plain object (including one with `toJSON`), non-finite
|
|
216
|
+
* numbers, `undefined` array elements, symbols, functions and cycles.
|
|
217
|
+
* `undefined` object properties are omitted.
|
|
218
|
+
*
|
|
219
|
+
* @throws an `Error` naming the path (rooted at `root`) and the JS type.
|
|
220
|
+
*/
|
|
221
|
+
export const stringifyStrict = (value, root = "value") => {
|
|
222
|
+
check(value, root, new Set());
|
|
223
|
+
return JSON.stringify(value);
|
|
224
|
+
};
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Pack / unpack
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
const encoder = new TextEncoder();
|
|
229
|
+
// Non-fatal decoding takes the runtime's fast UTF-8 path; a payload that
|
|
230
|
+
// decodes with a replacement character is re-decoded fatally, so invalid
|
|
231
|
+
// UTF-8 still fails (and a payload that legitimately holds U+FFFD reads).
|
|
232
|
+
const looseDecoder = new TextDecoder("utf-8");
|
|
233
|
+
const fatalDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
234
|
+
/**
|
|
235
|
+
* {@link packValue} without an Effect: the stored value, or the
|
|
236
|
+
* `ValidationError` it fails with (a returned value, never thrown). The
|
|
237
|
+
* synchronous core every pack site runs, wrapped in an Effect once per item or
|
|
238
|
+
* operation by the caller.
|
|
239
|
+
*/
|
|
240
|
+
export const packValueSync = (config, wire, registry, ctx, attribute) => {
|
|
241
|
+
if (wire === undefined)
|
|
242
|
+
return undefined;
|
|
243
|
+
if (wire === null)
|
|
244
|
+
return null;
|
|
245
|
+
let json;
|
|
246
|
+
try {
|
|
247
|
+
json = stringifyStrict(wire, attribute);
|
|
248
|
+
}
|
|
249
|
+
catch (cause) {
|
|
250
|
+
return invalid(ctx, attribute, cause instanceof NotJson ? cause.message : "cannot be serialised", cause);
|
|
251
|
+
}
|
|
252
|
+
const compression = config.compression;
|
|
253
|
+
if (compression === undefined)
|
|
254
|
+
return json;
|
|
255
|
+
// UTF-8 is at most 3 bytes per UTF-16 code unit: skip the encode when the
|
|
256
|
+
// value is certainly under the threshold.
|
|
257
|
+
if (json.length * 3 < compression.threshold)
|
|
258
|
+
return json;
|
|
259
|
+
const bytes = encoder.encode(json);
|
|
260
|
+
if (bytes.length < compression.threshold)
|
|
261
|
+
return json;
|
|
262
|
+
const compressor = registry.get(compression.codec);
|
|
263
|
+
if (compressor === undefined) {
|
|
264
|
+
return notRegistered(ctx, attribute, compression.codec, "compress");
|
|
265
|
+
}
|
|
266
|
+
let compressed;
|
|
267
|
+
try {
|
|
268
|
+
compressed = compressor.compress(bytes);
|
|
269
|
+
}
|
|
270
|
+
catch (cause) {
|
|
271
|
+
return invalid(ctx, attribute, `the "${compression.codec}" compressor failed`, cause);
|
|
272
|
+
}
|
|
273
|
+
if (!(compressed instanceof Uint8Array)) {
|
|
274
|
+
return invalid(ctx, attribute, `the "${compression.codec}" compressor did not return a Uint8Array`);
|
|
275
|
+
}
|
|
276
|
+
if (1 + compressed.length >= bytes.length)
|
|
277
|
+
return json;
|
|
278
|
+
const out = new Uint8Array(1 + compressed.length);
|
|
279
|
+
out[0] = headerByte(compression.codec);
|
|
280
|
+
out.set(compressed, 1);
|
|
281
|
+
return out;
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Pack an attribute's wire form per `config` (see the module header).
|
|
285
|
+
*
|
|
286
|
+
* - `undefined` → `undefined` (leave the attribute absent); `null` → `null`.
|
|
287
|
+
* - Otherwise the strict JSON. Without compression, or when its UTF-8 length is
|
|
288
|
+
* under `threshold`, the JSON string (`S`).
|
|
289
|
+
* - At or over the threshold the codec's compressor runs, and the header plus
|
|
290
|
+
* compressed bytes are kept only when strictly smaller than the JSON (`B`);
|
|
291
|
+
* otherwise the JSON string.
|
|
292
|
+
*
|
|
293
|
+
* Fails with `ValidationError` on non-JSON input or a throwing compressor, and
|
|
294
|
+
* when the value crosses the threshold and `registry` has no compressor for the
|
|
295
|
+
* codec — then its `cause` is a `CompressorNotRegistered` (direction
|
|
296
|
+
* `"compress"`).
|
|
297
|
+
*/
|
|
298
|
+
export const packValue = (config, wire, registry, ctx) => {
|
|
299
|
+
const stored = packValueSync(config, wire, registry, ctx, ctx.attribute);
|
|
300
|
+
return stored instanceof ValidationError ? Effect.fail(stored) : Effect.succeed(stored);
|
|
301
|
+
};
|
|
302
|
+
const parseJsonSync = (text, ctx, attribute) => {
|
|
303
|
+
try {
|
|
304
|
+
return JSON.parse(text);
|
|
305
|
+
}
|
|
306
|
+
catch (cause) {
|
|
307
|
+
return invalid(ctx, attribute, "the stored value is not valid JSON", cause);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* {@link unpackBytes} without an Effect: the JSON wire form, or the
|
|
312
|
+
* `ValidationError` it fails with (returned, never thrown). A parsed JSON value
|
|
313
|
+
* is never a `ValidationError`, so `instanceof ValidationError` tells them apart.
|
|
314
|
+
*/
|
|
315
|
+
export const unpackBytesSync = (stored, registry, ctx, attribute) => {
|
|
316
|
+
if (stored.length === 0)
|
|
317
|
+
return invalid(ctx, attribute, "the stored binary value is empty");
|
|
318
|
+
const header = parseHeader(stored[0]);
|
|
319
|
+
if (typeof header === "string")
|
|
320
|
+
return invalid(ctx, attribute, header);
|
|
321
|
+
let payload = stored.subarray(1);
|
|
322
|
+
if (header.codec !== "none") {
|
|
323
|
+
const compressor = registry.get(header.codec);
|
|
324
|
+
if (compressor === undefined)
|
|
325
|
+
return notRegistered(ctx, attribute, header.codec, "decompress");
|
|
326
|
+
try {
|
|
327
|
+
payload = compressor.decompress(payload);
|
|
328
|
+
}
|
|
329
|
+
catch (cause) {
|
|
330
|
+
return invalid(ctx, attribute, `the "${header.codec}" decompressor failed`, cause);
|
|
331
|
+
}
|
|
332
|
+
if (!(payload instanceof Uint8Array)) {
|
|
333
|
+
return invalid(ctx, attribute, `the "${header.codec}" decompressor did not return a Uint8Array`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
let text = looseDecoder.decode(payload);
|
|
337
|
+
if (text.includes("\uFFFD")) {
|
|
338
|
+
try {
|
|
339
|
+
text = fatalDecoder.decode(payload);
|
|
340
|
+
}
|
|
341
|
+
catch (cause) {
|
|
342
|
+
return invalid(ctx, attribute, "the stored payload is not valid UTF-8", cause);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return parseJsonSync(text, ctx, attribute);
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* {@link unpackValue} of a stored `S` (JSON text) or `B` (header + payload)
|
|
349
|
+
* without an Effect: the JSON wire form, or the `ValidationError` it fails with
|
|
350
|
+
* (returned, never thrown — tell them apart with `instanceof ValidationError`).
|
|
351
|
+
* Callers pass only strings and bytes; every other stored value is native and
|
|
352
|
+
* read as is, without a call.
|
|
353
|
+
*/
|
|
354
|
+
export const unpackStoredSync = (stored, registry, ctx, attribute) => typeof stored === "string"
|
|
355
|
+
? parseJsonSync(stored, ctx, attribute)
|
|
356
|
+
: unpackBytesSync(stored, registry, ctx, attribute);
|
|
357
|
+
const lift = (value) => value instanceof ValidationError ? Effect.fail(value) : Effect.succeed(value);
|
|
358
|
+
/**
|
|
359
|
+
* Decode a `B` value (header byte + payload) to its JSON wire form.
|
|
360
|
+
*/
|
|
361
|
+
export const unpackBytes = (stored, registry, ctx) => lift(unpackBytesSync(stored, registry, ctx, ctx.attribute));
|
|
362
|
+
/**
|
|
363
|
+
* Unpack a stored (unmarshalled) value by its form — see the module header.
|
|
364
|
+
* A `string` is JSON, a `Uint8Array` is a header + payload, and anything else
|
|
365
|
+
* (absent, `null`, a legacy native map, list, number, …) is returned unchanged.
|
|
366
|
+
*/
|
|
367
|
+
export const unpackValue = (stored, registry, ctx) => typeof stored === "string" || stored instanceof Uint8Array
|
|
368
|
+
? lift(unpackStoredSync(stored, registry, ctx, ctx.attribute))
|
|
369
|
+
: Effect.succeed(stored);
|
|
370
|
+
const STRUCTURED = { _tag: "Structured" };
|
|
371
|
+
const OPAQUE = { _tag: "Opaque" };
|
|
372
|
+
const refused = (reason) => ({ _tag: "Refused", reason });
|
|
373
|
+
const OPAQUE_DECLARATIONS = new Set(["effect/schema/Json", "effect/schema/MutableJson"]);
|
|
374
|
+
const representationId = (ast) => {
|
|
375
|
+
const representation = ast.annotations?.representation;
|
|
376
|
+
const id = representation?.id;
|
|
377
|
+
return typeof id === "string" ? id : undefined;
|
|
378
|
+
};
|
|
379
|
+
const scalarReason = {
|
|
380
|
+
String: "a string",
|
|
381
|
+
Number: "a number",
|
|
382
|
+
Boolean: "a boolean",
|
|
383
|
+
BigInt: "a bigint",
|
|
384
|
+
Symbol: "a symbol",
|
|
385
|
+
UniqueSymbol: "a unique symbol",
|
|
386
|
+
Literal: "a literal",
|
|
387
|
+
Enum: "an enum",
|
|
388
|
+
TemplateLiteral: "a template literal",
|
|
389
|
+
Never: "never",
|
|
390
|
+
Null: "null",
|
|
391
|
+
Undefined: "undefined",
|
|
392
|
+
Void: "void",
|
|
393
|
+
};
|
|
394
|
+
const classify = (ast, seen) => {
|
|
395
|
+
const encoded = SchemaAST.toEncoded(ast);
|
|
396
|
+
switch (encoded._tag) {
|
|
397
|
+
case "Objects":
|
|
398
|
+
case "Arrays":
|
|
399
|
+
return STRUCTURED;
|
|
400
|
+
case "Unknown":
|
|
401
|
+
case "Any":
|
|
402
|
+
case "ObjectKeyword":
|
|
403
|
+
return OPAQUE;
|
|
404
|
+
case "Declaration": {
|
|
405
|
+
const id = representationId(encoded);
|
|
406
|
+
if (id !== undefined && OPAQUE_DECLARATIONS.has(id))
|
|
407
|
+
return OPAQUE;
|
|
408
|
+
const name = id?.replace(/^effect\/schema\//, "");
|
|
409
|
+
return refused(name
|
|
410
|
+
? `its encoded side is ${name}, which is not object- or array-shaped (dates, binary, ` +
|
|
411
|
+
`sets and maps cannot be packed)`
|
|
412
|
+
: "its encoded side is a declared type that is not object- or array-shaped");
|
|
413
|
+
}
|
|
414
|
+
case "Suspend": {
|
|
415
|
+
if (seen.has(encoded))
|
|
416
|
+
return refused("its encoded side is an unresolvable recursive type");
|
|
417
|
+
seen.add(encoded);
|
|
418
|
+
return classify(encoded.thunk(), seen);
|
|
419
|
+
}
|
|
420
|
+
case "Union": {
|
|
421
|
+
let opaque = false;
|
|
422
|
+
let members = 0;
|
|
423
|
+
for (const member of encoded.types) {
|
|
424
|
+
const tag = SchemaAST.toEncoded(member)._tag;
|
|
425
|
+
if (tag === "Null" || tag === "Undefined" || tag === "Void")
|
|
426
|
+
continue;
|
|
427
|
+
members++;
|
|
428
|
+
const shape = classify(member, seen);
|
|
429
|
+
if (shape._tag === "Refused") {
|
|
430
|
+
return refused(`a member of its union is not packable: ${shape.reason}`);
|
|
431
|
+
}
|
|
432
|
+
if (shape._tag === "Opaque")
|
|
433
|
+
opaque = true;
|
|
434
|
+
}
|
|
435
|
+
if (members === 0)
|
|
436
|
+
return refused("its encoded side is only null / undefined");
|
|
437
|
+
return opaque ? OPAQUE : STRUCTURED;
|
|
438
|
+
}
|
|
439
|
+
default:
|
|
440
|
+
return refused(`its encoded side is ${scalarReason[encoded._tag] ?? encoded._tag}, which is not ` +
|
|
441
|
+
`object- or array-shaped`);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
/** Classify a schema's encoded side for packed storage (see {@link PackedShape}). */
|
|
445
|
+
export const packedShape = (schema) => classify(Schema.isSchema(schema) ? schema.ast : schema, new Set());
|
|
446
|
+
/** True iff a schema may be stored packed (its encoded side is not refused). */
|
|
447
|
+
export const isPackedShape = (schema) => packedShape(schema)._tag !== "Refused";
|
|
448
|
+
/**
|
|
449
|
+
* True iff readers may dispatch on the stored form of an attribute of this
|
|
450
|
+
* schema even when it is not configured packed: its encoded side is
|
|
451
|
+
* `Structured`, so a native value is never `S` or `B`.
|
|
452
|
+
*/
|
|
453
|
+
export const isDispatchEligible = (schema) => packedShape(schema)._tag === "Structured";
|
|
454
|
+
//# sourceMappingURL=PackedCodec.js.map
|