@effect-dynamodb/schema 1.25.0 → 1.27.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityPacked.js","sourceRoot":"","sources":["../../src/internal/EntityPacked.ts"],"names":[],"mappings":"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"}
@@ -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