@reallyme/codec 0.1.20 → 0.1.22

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/README.md CHANGED
@@ -47,8 +47,22 @@ const decoded = ReallyMeCodec.base64urlDecode(encoded);
47
47
  | Multiformats | `base58btcEncode`, `base58btcDecode`, `multibase*`, `multicodec*`, `multikey*`, binding validation |
48
48
  | DAG-CBOR and CID | `dagCborEncode`, `dagCborDecode`, `dagCborComputeCid`, `dagCborVerifyCid`, content hash and multihash helpers |
49
49
  | JCS | `canonicalizeJson`, `canonicalizeJsonText` |
50
- | PEM | `encodePem`, `decodePem` with strict label and size policy |
51
- | Protobuf | `@reallyme/codec/proto` exports `reallyme.codec.v1` generated types |
50
+ | PEM | wipeable `Uint8Array` armor through `encodePem`, `decodePem`, with strict label and size policy |
51
+ | Protobuf | `processProto`, `processProtoJson`, and `@reallyme/codec/proto` generated types |
52
+
53
+ `processProto` accepts binary generated `CodecOperationRequest` bytes.
54
+ `processProtoJson` accepts UTF-8 bytes containing the generated ProtoJSON view
55
+ of that same message. Both return binary `CodecProtoResultEnvelope` bytes;
56
+ expected validation failures are represented in the envelope and are not
57
+ thrown as backend exceptions.
58
+
59
+ The operation-specific `*Proto` helpers remain ergonomic request builders. They
60
+ encode generated messages and enter the same single WASM boundary rather than
61
+ calling operation-specific WASM exports.
62
+
63
+ Swift and Kotlin/Java expose the same generic `processProto` and
64
+ `processProtoJson` methods through the Rust C/JNI boundary. The method names
65
+ and envelope semantics are intentionally identical across SDKs.
52
66
 
53
67
  Errors are typed as `ReallyMeCodecError`; they do not include raw input bytes
54
68
  or backend exception text.
@@ -0,0 +1,11 @@
1
+ export declare const MAX_CODEC_FFI_INPUT_BYTES = 1048576;
2
+ export declare const MAX_CODEC_FFI_OUTPUT_BYTES = 67108864;
3
+ export declare const MAX_CODEC_PROTO_MESSAGE_BYTES = 1048576;
4
+ export declare const MAX_CODEC_PROTO_JSON_BYTES = 1572864;
5
+ export declare const MAX_CODEC_BOUNDARY_NODES = 1048576;
6
+ export declare const requireBoundaryAggregate: (lengths: ReadonlyArray<number>, maximum?: number) => void;
7
+ /** Returns UTF-8 length without allocating an encoded copy. */
8
+ export declare const utf8ByteLength: (value: string) => number;
9
+ export declare const requireBoundaryUtf8String: (value: string, allowEmpty?: boolean, maximum?: number) => number;
10
+ /** Validates, snapshots, and serializes untrusted JSON within a fixed budget. */
11
+ export declare const stringifyBoundaryJson: (value: unknown) => string;
@@ -0,0 +1,250 @@
1
+ // SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ import { ReallyMeCodecError } from "./errors.js";
5
+ export const MAX_CODEC_FFI_INPUT_BYTES = 1_048_576;
6
+ export const MAX_CODEC_FFI_OUTPUT_BYTES = 67_108_864;
7
+ export const MAX_CODEC_PROTO_MESSAGE_BYTES = 1_048_576;
8
+ export const MAX_CODEC_PROTO_JSON_BYTES = 1_572_864;
9
+ // Every JSON node needs at least one serialized byte. Deriving this traversal
10
+ // guard from the wire limit prevents hostile in-memory graphs from causing
11
+ // unbounded work without rejecting a document the byte-bounded Rust lane accepts.
12
+ export const MAX_CODEC_BOUNDARY_NODES = MAX_CODEC_FFI_INPUT_BYTES;
13
+ const MAX_JSON_CONTAINER_DEPTH = 128;
14
+ const invalidInput = () => {
15
+ throw new ReallyMeCodecError("invalid-input");
16
+ };
17
+ const consumeBudget = (budget, field, amount, maximum) => {
18
+ if (!Number.isSafeInteger(amount) || amount < 0 || budget[field] > maximum - amount) {
19
+ invalidInput();
20
+ }
21
+ budget[field] += amount;
22
+ };
23
+ export const requireBoundaryAggregate = (lengths, maximum = MAX_CODEC_FFI_INPUT_BYTES) => {
24
+ let aggregate = 0;
25
+ for (const length of lengths) {
26
+ if (!Number.isSafeInteger(length) ||
27
+ length < 0 ||
28
+ aggregate > maximum - length) {
29
+ invalidInput();
30
+ }
31
+ aggregate += length;
32
+ }
33
+ };
34
+ /** Returns UTF-8 length without allocating an encoded copy. */
35
+ export const utf8ByteLength = (value) => {
36
+ if (typeof value !== "string") {
37
+ invalidInput();
38
+ }
39
+ let length = 0;
40
+ for (let index = 0; index < value.length; index += 1) {
41
+ const codeUnit = value.charCodeAt(index);
42
+ let increment;
43
+ if (codeUnit <= 0x7f) {
44
+ increment = 1;
45
+ }
46
+ else if (codeUnit <= 0x7ff) {
47
+ increment = 2;
48
+ }
49
+ else if (codeUnit >= 0xd800 &&
50
+ codeUnit <= 0xdbff &&
51
+ index + 1 < value.length) {
52
+ const next = value.charCodeAt(index + 1);
53
+ if (next >= 0xdc00 && next <= 0xdfff) {
54
+ increment = 4;
55
+ index += 1;
56
+ }
57
+ else {
58
+ increment = 3;
59
+ }
60
+ }
61
+ else {
62
+ increment = 3;
63
+ }
64
+ if (length > Number.MAX_SAFE_INTEGER - increment) {
65
+ invalidInput();
66
+ }
67
+ length += increment;
68
+ }
69
+ return length;
70
+ };
71
+ export const requireBoundaryUtf8String = (value, allowEmpty = true, maximum = MAX_CODEC_FFI_INPUT_BYTES) => {
72
+ if (typeof value !== "string" || (!allowEmpty && value.length === 0)) {
73
+ invalidInput();
74
+ }
75
+ // Every UTF-16 code unit contributes at least one UTF-8 byte. This rejects
76
+ // obviously oversized strings before walking their full contents.
77
+ if (value.length > maximum) {
78
+ invalidInput();
79
+ }
80
+ const length = utf8ByteLength(value);
81
+ requireBoundaryAggregate([length], maximum);
82
+ return length;
83
+ };
84
+ const jsonStringByteLength = (value) => {
85
+ let length = 2;
86
+ for (let index = 0; index < value.length; index += 1) {
87
+ const codeUnit = value.charCodeAt(index);
88
+ let increment;
89
+ if (codeUnit === 0x22 || codeUnit === 0x5c) {
90
+ increment = 2;
91
+ }
92
+ else if (codeUnit <= 0x1f) {
93
+ increment =
94
+ codeUnit === 0x08 ||
95
+ codeUnit === 0x09 ||
96
+ codeUnit === 0x0a ||
97
+ codeUnit === 0x0c ||
98
+ codeUnit === 0x0d
99
+ ? 2
100
+ : 6;
101
+ }
102
+ else if (codeUnit <= 0x7f) {
103
+ increment = 1;
104
+ }
105
+ else if (codeUnit <= 0x7ff) {
106
+ increment = 2;
107
+ }
108
+ else if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
109
+ if (index + 1 < value.length) {
110
+ const next = value.charCodeAt(index + 1);
111
+ if (next >= 0xdc00 && next <= 0xdfff) {
112
+ increment = 4;
113
+ index += 1;
114
+ }
115
+ else {
116
+ increment = 6;
117
+ }
118
+ }
119
+ else {
120
+ increment = 6;
121
+ }
122
+ }
123
+ else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
124
+ increment = 6;
125
+ }
126
+ else {
127
+ increment = 3;
128
+ }
129
+ if (length > Number.MAX_SAFE_INTEGER - increment) {
130
+ invalidInput();
131
+ }
132
+ length += increment;
133
+ }
134
+ return length;
135
+ };
136
+ const readOwnDataProperty = (value, key) => {
137
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
138
+ if (descriptor === undefined || !("value" in descriptor)) {
139
+ return invalidInput();
140
+ }
141
+ return descriptor.value;
142
+ };
143
+ const normalizeJson = (value, depth, seen, budget) => {
144
+ consumeBudget(budget, "nodes", 1, MAX_CODEC_BOUNDARY_NODES);
145
+ if (value === null) {
146
+ consumeBudget(budget, "bytes", 4, MAX_CODEC_FFI_INPUT_BYTES);
147
+ return null;
148
+ }
149
+ switch (typeof value) {
150
+ case "boolean":
151
+ consumeBudget(budget, "bytes", value ? 4 : 5, MAX_CODEC_FFI_INPUT_BYTES);
152
+ return value;
153
+ case "string":
154
+ consumeBudget(budget, "bytes", jsonStringByteLength(value), MAX_CODEC_FFI_INPUT_BYTES);
155
+ return value;
156
+ case "number": {
157
+ if (!Number.isFinite(value)) {
158
+ invalidInput();
159
+ }
160
+ const encoded = JSON.stringify(value);
161
+ if (typeof encoded !== "string") {
162
+ invalidInput();
163
+ }
164
+ consumeBudget(budget, "bytes", encoded.length, MAX_CODEC_FFI_INPUT_BYTES);
165
+ return value;
166
+ }
167
+ case "bigint":
168
+ case "function":
169
+ case "symbol":
170
+ case "undefined":
171
+ return invalidInput();
172
+ case "object":
173
+ break;
174
+ }
175
+ if (seen.has(value) || depth >= MAX_JSON_CONTAINER_DEPTH) {
176
+ invalidInput();
177
+ }
178
+ const prototype = Object.getPrototypeOf(value);
179
+ if (prototype !== Object.prototype && prototype !== null && !Array.isArray(value)) {
180
+ invalidInput();
181
+ }
182
+ seen.add(value);
183
+ try {
184
+ if (Array.isArray(value)) {
185
+ const rawLength = readOwnDataProperty(value, "length");
186
+ if (typeof rawLength !== "number") {
187
+ return invalidInput();
188
+ }
189
+ const length = rawLength;
190
+ if (!Number.isSafeInteger(length) ||
191
+ length < 0 ||
192
+ length > MAX_CODEC_BOUNDARY_NODES - budget.nodes) {
193
+ invalidInput();
194
+ }
195
+ consumeBudget(budget, "bytes", length === 0 ? 2 : length + 1, MAX_CODEC_FFI_INPUT_BYTES);
196
+ const normalized = [];
197
+ for (let index = 0; index < length; index += 1) {
198
+ // Indexed access and iteration both invoke array accessors. Snapshot
199
+ // only own data properties so validation cannot execute untrusted code.
200
+ const entry = readOwnDataProperty(value, String(index));
201
+ normalized.push(normalizeJson(entry, depth + 1, seen, budget));
202
+ }
203
+ return normalized;
204
+ }
205
+ const keys = Object.keys(value);
206
+ if (keys.length > MAX_CODEC_BOUNDARY_NODES - budget.nodes) {
207
+ invalidInput();
208
+ }
209
+ consumeBudget(budget, "bytes", keys.length === 0 ? 2 : keys.length + 1, MAX_CODEC_FFI_INPUT_BYTES);
210
+ const entries = [];
211
+ for (const key of keys) {
212
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
213
+ if (descriptor === undefined) {
214
+ return invalidInput();
215
+ }
216
+ if (!("value" in descriptor)) {
217
+ return invalidInput();
218
+ }
219
+ consumeBudget(budget, "bytes", jsonStringByteLength(key), MAX_CODEC_FFI_INPUT_BYTES);
220
+ consumeBudget(budget, "bytes", 1, MAX_CODEC_FFI_INPUT_BYTES);
221
+ const propertyValue = descriptor.value;
222
+ entries.push([key, normalizeJson(propertyValue, depth + 1, seen, budget)]);
223
+ }
224
+ return Object.fromEntries(entries);
225
+ }
226
+ finally {
227
+ seen.delete(value);
228
+ }
229
+ };
230
+ /** Validates, snapshots, and serializes untrusted JSON within a fixed budget. */
231
+ export const stringifyBoundaryJson = (value) => {
232
+ try {
233
+ const normalized = normalizeJson(value, 0, new WeakSet(), {
234
+ bytes: 0,
235
+ nodes: 0,
236
+ });
237
+ const encoded = JSON.stringify(normalized);
238
+ if (typeof encoded !== "string") {
239
+ invalidInput();
240
+ }
241
+ requireBoundaryAggregate([utf8ByteLength(encoded)]);
242
+ return encoded;
243
+ }
244
+ catch (error) {
245
+ if (error instanceof ReallyMeCodecError) {
246
+ throw error;
247
+ }
248
+ return invalidInput();
249
+ }
250
+ };
package/dist/cbor.js CHANGED
@@ -1,10 +1,54 @@
1
1
  // SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
+ import { create } from "@bufbuild/protobuf";
5
+ import { MAX_CODEC_BOUNDARY_NODES, stringifyBoundaryJson, } from "./boundary.js";
4
6
  import { ReallyMeCodecError } from "./errors.js";
5
- import { ensureBytesInput, ensureStringValue, readBooleanProperty, readBytesOutput, readObjectOutput, readProtoResultOutput, readStringOutput, readStringProperty, readStringValueProperty, readNumberOutput, } from "./readOutput.js";
7
+ import { CodecDagCborVerifyCidRequestSchema, CodecOperationRequestSchema, } from "./proto/generated/reallyme/codec/v1/codec_pb.js";
8
+ import { processGeneratedProtoRequest, protoPayloadOrThrow, } from "./protoProcess.js";
9
+ import { ensureBytesInput, ensureStringValue, readBooleanProperty, readBytesOutput, readObjectOutput, readStringOutput, readStringProperty, readStringValueProperty, readNumberOutput, } from "./readOutput.js";
6
10
  import { requireReallyMeCodecWasmProvider } from "./wasmProvider.js";
7
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
11
+ const invalidCborInput = () => {
12
+ throw new ReallyMeCodecError("invalid-input");
13
+ };
14
+ const isRecord = (value) => {
15
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
16
+ return false;
17
+ }
18
+ const prototype = Object.getPrototypeOf(value);
19
+ return prototype === Object.prototype || prototype === null;
20
+ };
21
+ const readDataProperty = (value, name) => {
22
+ const descriptor = Object.getOwnPropertyDescriptor(value, name);
23
+ if (descriptor === undefined || !("value" in descriptor)) {
24
+ return invalidCborInput();
25
+ }
26
+ return descriptor.value;
27
+ };
28
+ const snapshotArray = (value) => {
29
+ if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
30
+ return invalidCborInput();
31
+ }
32
+ const rawLength = readDataProperty(value, "length");
33
+ if (typeof rawLength !== "number") {
34
+ return invalidCborInput();
35
+ }
36
+ const length = rawLength;
37
+ if (!Number.isSafeInteger(length) ||
38
+ length < 0 ||
39
+ length > MAX_CODEC_BOUNDARY_NODES) {
40
+ return invalidCborInput();
41
+ }
42
+ const entries = [];
43
+ for (let index = 0; index < length; index += 1) {
44
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
45
+ if (descriptor === undefined || !("value" in descriptor)) {
46
+ return invalidCborInput();
47
+ }
48
+ entries.push(descriptor.value);
49
+ }
50
+ return entries;
51
+ };
8
52
  const i64Min = -(1n << 63n);
9
53
  const i64Max = (1n << 63n) - 1n;
10
54
  const decimalIntegerPattern = /^-?(0|[1-9][0-9]*)$/u;
@@ -41,8 +85,16 @@ const validateCborInteger = (value) => {
41
85
  }
42
86
  throw new ReallyMeCodecError("invalid-input");
43
87
  };
44
- const validateCborValue = (value, depth = 0, seen = new WeakSet()) => {
45
- if (!isRecord(value) || typeof value.type !== "string") {
88
+ const validateCborValue = (value, depth = 0, seen = new WeakSet(), state = { nodes: 0 }) => {
89
+ if (state.nodes >= MAX_CODEC_BOUNDARY_NODES) {
90
+ throw new ReallyMeCodecError("invalid-input");
91
+ }
92
+ state.nodes += 1;
93
+ if (!isRecord(value)) {
94
+ throw new ReallyMeCodecError("invalid-input");
95
+ }
96
+ const type = readDataProperty(value, "type");
97
+ if (typeof type !== "string") {
46
98
  throw new ReallyMeCodecError("invalid-input");
47
99
  }
48
100
  if (seen.has(value)) {
@@ -50,46 +102,64 @@ const validateCborValue = (value, depth = 0, seen = new WeakSet()) => {
50
102
  }
51
103
  seen.add(value);
52
104
  try {
53
- switch (value.type) {
105
+ switch (type) {
54
106
  case "null":
55
107
  return { type: "null" };
56
- case "bool":
57
- if (typeof value.value !== "boolean") {
108
+ case "bool": {
109
+ const child = readDataProperty(value, "value");
110
+ if (typeof child !== "boolean") {
58
111
  throw new ReallyMeCodecError("invalid-input");
59
112
  }
60
- return { type: "bool", value: value.value };
113
+ return { type: "bool", value: child };
114
+ }
61
115
  case "int":
62
- return { type: "int", value: validateCborInteger(value.value) };
116
+ return {
117
+ type: "int",
118
+ value: validateCborInteger(readDataProperty(value, "value")),
119
+ };
63
120
  case "string":
64
- case "bytes":
65
- if (typeof value.value !== "string") {
121
+ case "bytes": {
122
+ const child = readDataProperty(value, "value");
123
+ if (typeof child !== "string") {
66
124
  throw new ReallyMeCodecError("invalid-input");
67
125
  }
68
- return { type: value.type, value: value.value };
69
- case "array":
70
- if (!Array.isArray(value.value)) {
126
+ return { type, value: child };
127
+ }
128
+ case "array": {
129
+ const children = snapshotArray(readDataProperty(value, "value"));
130
+ if (children.length > MAX_CODEC_BOUNDARY_NODES - state.nodes) {
71
131
  throw new ReallyMeCodecError("invalid-input");
72
132
  }
73
133
  return {
74
134
  type: "array",
75
- value: value.value.map((entry) => validateCborValue(entry, childContainerDepth(depth), seen)),
135
+ value: children.map((entry) => validateCborValue(entry, childContainerDepth(depth), seen, state)),
76
136
  };
77
- case "map":
78
- if (!Array.isArray(value.value)) {
137
+ }
138
+ case "map": {
139
+ const entries = snapshotArray(readDataProperty(value, "value"));
140
+ if (entries.length > MAX_CODEC_BOUNDARY_NODES - state.nodes) {
79
141
  throw new ReallyMeCodecError("invalid-input");
80
142
  }
143
+ state.nodes += entries.length;
144
+ const keys = new Set();
81
145
  return {
82
146
  type: "map",
83
- value: value.value.map((entry) => {
84
- if (!isRecord(entry) || typeof entry.key !== "string") {
147
+ value: entries.map((entry) => {
148
+ if (!isRecord(entry)) {
149
+ throw new ReallyMeCodecError("invalid-input");
150
+ }
151
+ const key = readDataProperty(entry, "key");
152
+ if (typeof key !== "string" || keys.has(key)) {
85
153
  throw new ReallyMeCodecError("invalid-input");
86
154
  }
155
+ keys.add(key);
87
156
  return {
88
- key: entry.key,
89
- value: validateCborValue(entry.value, childContainerDepth(depth), seen),
157
+ key,
158
+ value: validateCborValue(readDataProperty(entry, "value"), childContainerDepth(depth), seen, state),
90
159
  };
91
160
  }),
92
161
  };
162
+ }
93
163
  default:
94
164
  throw new ReallyMeCodecError("invalid-input");
95
165
  }
@@ -124,8 +194,13 @@ const cborValueForJson = (value) => {
124
194
  }
125
195
  };
126
196
  const readCborValue = (json) => {
127
- const parsed = JSON.parse(json);
128
- return validateCborValue(parsed);
197
+ try {
198
+ const parsed = JSON.parse(json);
199
+ return validateCborValue(parsed);
200
+ }
201
+ catch (_error) {
202
+ throw new ReallyMeCodecError("provider-failure");
203
+ }
129
204
  };
130
205
  const readCidVerification = (value) => {
131
206
  const object = readObjectOutput(value);
@@ -136,8 +211,17 @@ const readCidVerification = (value) => {
136
211
  };
137
212
  };
138
213
  export const dagCborEncode = (value) => {
139
- const normalized = validateCborValue(value);
140
- return readBytesOutput(requireReallyMeCodecWasmProvider().dagCborEncode(JSON.stringify(cborValueForJson(normalized))));
214
+ let normalized;
215
+ try {
216
+ normalized = validateCborValue(value);
217
+ }
218
+ catch (error) {
219
+ if (error instanceof ReallyMeCodecError) {
220
+ throw error;
221
+ }
222
+ return invalidCborInput();
223
+ }
224
+ return readBytesOutput(requireReallyMeCodecWasmProvider().dagCborEncode(stringifyBoundaryJson(cborValueForJson(normalized))));
141
225
  };
142
226
  export const dagCborDecode = (bytes) => {
143
227
  ensureBytesInput(bytes);
@@ -153,14 +237,20 @@ export const dagCborVerifyCid = (cid, bytes) => {
153
237
  return readCidVerification(requireReallyMeCodecWasmProvider().dagCborVerifyCid(cid, bytes));
154
238
  };
155
239
  export const dagCborVerifyCidProto = (cid, bytes) => {
156
- ensureStringValue(cid);
157
- ensureBytesInput(bytes);
158
- return readBytesOutput(requireReallyMeCodecWasmProvider().dagCborVerifyCidProto(cid, bytes));
240
+ return protoPayloadOrThrow(dagCborVerifyCidProtoResult(cid, bytes));
159
241
  };
160
242
  export const dagCborVerifyCidProtoResult = (cid, bytes) => {
161
243
  ensureStringValue(cid);
162
244
  ensureBytesInput(bytes);
163
- return readProtoResultOutput(requireReallyMeCodecWasmProvider().dagCborVerifyCidProtoResult(cid, bytes));
245
+ return processGeneratedProtoRequest(create(CodecOperationRequestSchema, {
246
+ operation: {
247
+ case: "dagCborVerifyCid",
248
+ value: create(CodecDagCborVerifyCidRequestSchema, {
249
+ cid,
250
+ payload: bytes,
251
+ }),
252
+ },
253
+ }));
164
254
  };
165
255
  export const dagCborSha256ContentHash = (bytes) => {
166
256
  ensureBytesInput(bytes);
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export { dagCborCodecCode, dagCborComputeCid, dagCborDecode, dagCborEncode, dagC
7
7
  export type { ReallyMeCborMapEntry, ReallyMeCborValue, ReallyMeDagCborCidVerification, } from "./cbor.js";
8
8
  export { canonicalizeJson, canonicalizeJsonText } from "./jcs.js";
9
9
  export { decodePem, decodePemProto, decodePemProtoResult, encodePem } from "./pem.js";
10
+ export { processProto, processProtoJson } from "./protoProcess.js";
10
11
  export type { ReallyMeCodecProtoResult, ReallyMeCodecProtoStatus } from "./readOutput.js";
11
12
  export type { ReallyMePemDecodePolicy, ReallyMePemDocument, ReallyMePemEncodeOptions, ReallyMePemLabel, ReallyMePemLineEnding, } from "./pem.js";
12
13
  export { REALLYME_CODEC_WASM_EXPORTS, installReallyMeCodecWasmProvider, requireReallyMeCodecWasmProvider, } from "./wasmProvider.js";
@@ -54,8 +55,10 @@ export declare const ReallyMeCodec: {
54
55
  readonly tryParseCid: (cid: string) => string | undefined;
55
56
  readonly canonicalizeJson: (value: unknown) => string;
56
57
  readonly canonicalizeJsonText: (json: string) => string;
57
- readonly decodePem: (input: string, policy?: import("./pem.js").ReallyMePemDecodePolicy) => import("./pem.js").ReallyMePemDocument;
58
- readonly decodePemProto: (input: string, policy?: import("./pem.js").ReallyMePemDecodePolicy) => Uint8Array;
59
- readonly decodePemProtoResult: (input: string, policy?: import("./pem.js").ReallyMePemDecodePolicy) => import("./readOutput.js").ReallyMeCodecProtoResult;
60
- readonly encodePem: (label: import("./pem.js").ReallyMePemLabel, der: Uint8Array, options?: import("./pem.js").ReallyMePemEncodeOptions) => string;
58
+ readonly decodePem: (input: Uint8Array, policy?: import("./pem.js").ReallyMePemDecodePolicy) => import("./pem.js").ReallyMePemDocument;
59
+ readonly decodePemProto: (input: Uint8Array, policy?: import("./pem.js").ReallyMePemDecodePolicy) => Uint8Array;
60
+ readonly decodePemProtoResult: (input: Uint8Array, policy?: import("./pem.js").ReallyMePemDecodePolicy) => import("./readOutput.js").ReallyMeCodecProtoResult;
61
+ readonly encodePem: (label: import("./pem.js").ReallyMePemLabel, der: Uint8Array, options?: import("./pem.js").ReallyMePemEncodeOptions) => Uint8Array;
62
+ readonly processProto: (requestBytes: Uint8Array) => Uint8Array;
63
+ readonly processProtoJson: (requestJson: Uint8Array) => Uint8Array;
61
64
  };
package/dist/index.js CHANGED
@@ -7,12 +7,14 @@ export { base58btcDecode, base58btcEncode, bindingTypeMatchesCodec, multibaseBas
7
7
  export { dagCborCodecCode, dagCborComputeCid, dagCborDecode, dagCborEncode, dagCborMultihash, dagCborSha256ContentHash, dagCborVerifyCid, dagCborVerifyCidProto, dagCborVerifyCidProtoResult, isValidCidString, tryParseCid, } from "./cbor.js";
8
8
  export { canonicalizeJson, canonicalizeJsonText } from "./jcs.js";
9
9
  export { decodePem, decodePemProto, decodePemProtoResult, encodePem } from "./pem.js";
10
+ export { processProto, processProtoJson } from "./protoProcess.js";
10
11
  export { REALLYME_CODEC_WASM_EXPORTS, installReallyMeCodecWasmProvider, requireReallyMeCodecWasmProvider, } from "./wasmProvider.js";
11
12
  import { base64Decode, base64Encode, base64urlDecode, base64urlDecodeBytes, base64urlEncode, bytesToLowerHex, lowerHexToBytes, } from "./baseEncoding.js";
12
13
  import { dagCborCodecCode, dagCborComputeCid, dagCborDecode, dagCborEncode, dagCborMultihash, dagCborSha256ContentHash, dagCborVerifyCid, dagCborVerifyCidProto, dagCborVerifyCidProtoResult, isValidCidString, tryParseCid, } from "./cbor.js";
13
14
  import { canonicalizeJson, canonicalizeJsonText } from "./jcs.js";
14
15
  import { base58btcDecode, base58btcEncode, bindingTypeMatchesCodec, multibaseBase58btcEncode, multibaseBase64urlEncode, multibaseDecode, multicodecLookupPrefixProto, multicodecLookupPrefixProtoResult, multicodecLookupPrefix, multicodecPrefixForNameProto, multicodecPrefixForNameProtoResult, multicodecPrefixForName, multicodecStripPrefix, multicodecTable, multicodecTableProto, multicodecTableProtoResult, multikeyEncode, multikeyParse, multikeyParseProto, multikeyParseProtoResult, requireSupportedMulticodec, validateKeyBinding, } from "./multiformat.js";
15
16
  import { decodePem, decodePemProto, decodePemProtoResult, encodePem } from "./pem.js";
17
+ import { processProto, processProtoJson } from "./protoProcess.js";
16
18
  export const ReallyMeCodec = {
17
19
  base64Decode,
18
20
  base64Encode,
@@ -60,4 +62,6 @@ export const ReallyMeCodec = {
60
62
  decodePemProto,
61
63
  decodePemProtoResult,
62
64
  encodePem,
65
+ processProto,
66
+ processProtoJson,
63
67
  };
package/dist/jcs.js CHANGED
@@ -1,71 +1,14 @@
1
1
  // SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
- import { ReallyMeCodecError } from "./errors.js";
4
+ import { requireBoundaryUtf8String, stringifyBoundaryJson } from "./boundary.js";
5
5
  import { readStringOutput } from "./readOutput.js";
6
6
  import { requireReallyMeCodecWasmProvider } from "./wasmProvider.js";
7
- const maxJsonContainerDepth = 128;
8
- const validateJsonCompatible = (value, depth = 0, seen = new WeakSet()) => {
9
- if (value === null) {
10
- return;
11
- }
12
- switch (typeof value) {
13
- case "boolean":
14
- case "string":
15
- return;
16
- case "number":
17
- if (!Number.isFinite(value)) {
18
- throw new ReallyMeCodecError("invalid-input");
19
- }
20
- return;
21
- case "bigint":
22
- case "function":
23
- case "symbol":
24
- case "undefined":
25
- throw new ReallyMeCodecError("invalid-input");
26
- case "object":
27
- break;
28
- }
29
- if (seen.has(value)) {
30
- throw new ReallyMeCodecError("invalid-input");
31
- }
32
- if (depth >= maxJsonContainerDepth) {
33
- throw new ReallyMeCodecError("invalid-input");
34
- }
35
- if (typeof Reflect.get(value, "toJSON") === "function") {
36
- throw new ReallyMeCodecError("invalid-input");
37
- }
38
- const prototype = Object.getPrototypeOf(value);
39
- if (prototype !== Object.prototype && prototype !== null && !Array.isArray(value)) {
40
- throw new ReallyMeCodecError("invalid-input");
41
- }
42
- seen.add(value);
43
- try {
44
- if (Array.isArray(value)) {
45
- for (const entry of value) {
46
- validateJsonCompatible(entry, depth + 1, seen);
47
- }
48
- return;
49
- }
50
- for (const key of Object.keys(value)) {
51
- validateJsonCompatible(Reflect.get(value, key), depth + 1, seen);
52
- }
53
- }
54
- finally {
55
- seen.delete(value);
56
- }
57
- };
58
7
  export const canonicalizeJson = (value) => {
59
- validateJsonCompatible(value);
60
- const encoded = JSON.stringify(value);
61
- if (encoded === undefined) {
62
- throw new ReallyMeCodecError("invalid-input");
63
- }
8
+ const encoded = stringifyBoundaryJson(value);
64
9
  return readStringOutput(requireReallyMeCodecWasmProvider().canonicalizeJson(encoded));
65
10
  };
66
11
  export const canonicalizeJsonText = (json) => {
67
- if (typeof json !== "string" || json.length === 0) {
68
- throw new ReallyMeCodecError("invalid-input");
69
- }
12
+ requireBoundaryUtf8String(json, false);
70
13
  return readStringOutput(requireReallyMeCodecWasmProvider().canonicalizeJson(json));
71
14
  };