@smonn/ids 0.12.3 → 0.13.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/README.md +32 -2
- package/dist/{adapter-types-CdYJM6Sf.d.mts → adapter-types-CIc-4O-P.d.mts} +2 -2
- package/dist/{adapter-types-CdYJM6Sf.d.mts.map → adapter-types-CIc-4O-P.d.mts.map} +1 -1
- package/dist/cli.mjs +235 -103
- package/dist/cli.mjs.map +1 -1
- package/dist/codec-shell-DvrTDa65.mjs.map +1 -1
- package/dist/{digest-CknNw2wa.mjs → digest-Drnof-l_.mjs} +8 -23
- package/dist/digest-Drnof-l_.mjs.map +1 -0
- package/dist/digest.d.mts +3 -3
- package/dist/digest.d.mts.map +1 -1
- package/dist/digest.mjs +1 -1
- package/dist/drizzle.d.mts +3 -3
- package/dist/drizzle.d.mts.map +1 -1
- package/dist/drizzle.mjs.map +1 -1
- package/dist/error-Cp5qYZcv.mjs.map +1 -1
- package/dist/{error-JIPylU_E.d.mts → error-Dqyho9vp.d.mts} +7 -2
- package/dist/error-Dqyho9vp.d.mts.map +1 -0
- package/dist/express.d.mts +2 -2
- package/dist/fastify.d.mts +2 -2
- package/dist/graphql.d.mts +3 -3
- package/dist/graphql.mjs +2 -2
- package/dist/graphql.mjs.map +1 -1
- package/dist/hono.d.mts +2 -2
- package/dist/hono.mjs +1 -2
- package/dist/hono.mjs.map +1 -1
- package/dist/index.d.mts +21 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{key-material-f29JIyrz.mjs → key-material-DsukgnR5.mjs} +53 -2
- package/dist/key-material-DsukgnR5.mjs.map +1 -0
- package/dist/kysely.d.mts +3 -3
- package/dist/kysely.d.mts.map +1 -1
- package/dist/kysely.mjs.map +1 -1
- package/dist/mikro-orm.d.mts +3 -3
- package/dist/mikro-orm.d.mts.map +1 -1
- package/dist/mikro-orm.mjs.map +1 -1
- package/dist/nestjs.d.mts +2 -2
- package/dist/nestjs.mjs +1 -1
- package/dist/nestjs.mjs.map +1 -1
- package/dist/{opaque-BQVNoIIh.mjs → opaque-D7y5cgzT.mjs} +3 -26
- package/dist/opaque-D7y5cgzT.mjs.map +1 -0
- package/dist/opaque.d.mts +22 -4
- package/dist/opaque.d.mts.map +1 -1
- package/dist/opaque.mjs +1 -1
- package/dist/prisma.d.mts +32 -27
- package/dist/prisma.d.mts.map +1 -1
- package/dist/prisma.mjs +11 -15
- package/dist/prisma.mjs.map +1 -1
- package/dist/{reverse-DsPd7Lco.mjs → reverse-DrAofYWV.mjs} +10 -3
- package/dist/reverse-DrAofYWV.mjs.map +1 -0
- package/dist/reverse.d.mts +20 -4
- package/dist/reverse.d.mts.map +1 -1
- package/dist/reverse.mjs +1 -1
- package/dist/{signed-4h2BnlWx.mjs → signed-B2Aa3zMg.mjs} +10 -31
- package/dist/signed-B2Aa3zMg.mjs.map +1 -0
- package/dist/signed.d.mts +13 -4
- package/dist/signed.d.mts.map +1 -1
- package/dist/signed.mjs +1 -1
- package/dist/{timestamp-Cg9nRfnK.mjs → timestamp-YPd58344.mjs} +10 -3
- package/dist/timestamp-YPd58344.mjs.map +1 -0
- package/dist/typeorm.d.mts +2 -2
- package/dist/typeorm.d.mts.map +1 -1
- package/dist/typeorm.mjs.map +1 -1
- package/dist/{types-g7CiQDyE.d.mts → types-wplmOgOK.d.mts} +20 -3
- package/dist/types-wplmOgOK.d.mts.map +1 -0
- package/dist/{wrapped-BQ-lNECo.mjs → wrapped-BjmVzuYc.mjs} +17 -75
- package/dist/wrapped-BjmVzuYc.mjs.map +1 -0
- package/dist/wrapped.d.mts +31 -5
- package/dist/wrapped.d.mts.map +1 -1
- package/dist/wrapped.mjs +1 -1
- package/package.json +80 -27
- package/dist/digest-CknNw2wa.mjs.map +0 -1
- package/dist/error-JIPylU_E.d.mts.map +0 -1
- package/dist/key-material-f29JIyrz.mjs.map +0 -1
- package/dist/opaque-BQVNoIIh.mjs.map +0 -1
- package/dist/reverse-DsPd7Lco.mjs.map +0 -1
- package/dist/signed-4h2BnlWx.mjs.map +0 -1
- package/dist/timestamp-Cg9nRfnK.mjs.map +0 -1
- package/dist/types-g7CiQDyE.d.mts.map +0 -1
- package/dist/wrapped-BQ-lNECo.mjs.map +0 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { t as IdsError } from "./error-Cp5qYZcv.mjs";
|
|
2
2
|
import { a as toWireId, i as payloadBytesFromId, n as registerBrand, r as payloadBase32Length, s as validateBrand, t as wireMethods } from "./codec-shell-DvrTDa65.mjs";
|
|
3
|
-
import { i as encodeKeyMaterial, n as assertValidKeyring, r as decodeKeyMaterial, t as assertValidKeyMaterialByteLength } from "./key-material-
|
|
3
|
+
import { a as decryptPayload, c as timingSafeEqual, i as encodeKeyMaterial, l as writeLen32, n as assertValidKeyring, o as deriveKey, r as decodeKeyMaterial, s as encryptPayload, t as assertValidKeyMaterialByteLength } from "./key-material-DsukgnR5.mjs";
|
|
4
4
|
//#region src/codecs/wrapped/layout.ts
|
|
5
|
-
const zeroIv = /* @__PURE__ */ new Uint8Array(16);
|
|
6
|
-
const pkcsPad = 16;
|
|
7
5
|
const laneByteLength = 8;
|
|
8
6
|
const tagByteLength = 8;
|
|
9
7
|
function writeU32Lane(value, lane) {
|
|
@@ -61,12 +59,6 @@ function readLane(kind, lane) {
|
|
|
61
59
|
if (kind === "i64") return readI64Lane(lane);
|
|
62
60
|
return kind === "i32" ? readI32Lane(lane) : readU32Lane(lane);
|
|
63
61
|
}
|
|
64
|
-
function writeLen32(value, target, offset) {
|
|
65
|
-
target[offset] = value >>> 24 & 255;
|
|
66
|
-
target[offset + 1] = value >>> 16 & 255;
|
|
67
|
-
target[offset + 2] = value >>> 8 & 255;
|
|
68
|
-
target[offset + 3] = value & 255;
|
|
69
|
-
}
|
|
70
62
|
function createHmacMessageTemplate(brand, kind) {
|
|
71
63
|
const encoder = new TextEncoder();
|
|
72
64
|
const brandBytes = encoder.encode(brand);
|
|
@@ -95,34 +87,6 @@ function hmacMessage(template, lane) {
|
|
|
95
87
|
async function computeTag(key, template, lane) {
|
|
96
88
|
return new Uint8Array(await crypto.subtle.sign("HMAC", key.hmacKey, hmacMessage(template, lane))).subarray(0, tagByteLength);
|
|
97
89
|
}
|
|
98
|
-
function tagsEqual(a, b) {
|
|
99
|
-
/* v8 ignore next -- defensive guard; both call sites always pass tagByteLength-byte arrays */
|
|
100
|
-
if (a.length !== b.length) return false;
|
|
101
|
-
let diff = 0;
|
|
102
|
-
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
103
|
-
return diff === 0;
|
|
104
|
-
}
|
|
105
|
-
async function encryptPayload(key, plaintext) {
|
|
106
|
-
return new Uint8Array(await crypto.subtle.encrypt({
|
|
107
|
-
name: "AES-CBC",
|
|
108
|
-
iv: zeroIv
|
|
109
|
-
}, key.aesKey, plaintext)).subarray(0, 16);
|
|
110
|
-
}
|
|
111
|
-
async function decryptPayload(key, c1) {
|
|
112
|
-
const c2Input = /* @__PURE__ */ new Uint8Array(16);
|
|
113
|
-
for (let i = 0; i < 16; i++) c2Input[i] = pkcsPad ^ c1[i];
|
|
114
|
-
const c2Encrypted = new Uint8Array(await crypto.subtle.encrypt({
|
|
115
|
-
name: "AES-CBC",
|
|
116
|
-
iv: zeroIv
|
|
117
|
-
}, key.aesKey, c2Input));
|
|
118
|
-
const ciphertext = /* @__PURE__ */ new Uint8Array(32);
|
|
119
|
-
ciphertext.set(c1, 0);
|
|
120
|
-
ciphertext.set(c2Encrypted.subarray(0, 16), 16);
|
|
121
|
-
return new Uint8Array(await crypto.subtle.decrypt({
|
|
122
|
-
name: "AES-CBC",
|
|
123
|
-
iv: zeroIv
|
|
124
|
-
}, key.aesKey, ciphertext));
|
|
125
|
-
}
|
|
126
90
|
function buildPlaintext(lane, tag) {
|
|
127
91
|
const plaintext = /* @__PURE__ */ new Uint8Array(16);
|
|
128
92
|
plaintext.set(lane, 0);
|
|
@@ -132,12 +96,13 @@ function buildPlaintext(lane, tag) {
|
|
|
132
96
|
async function wrapLookupKey(prefix, template, key, kind, lookupKey) {
|
|
133
97
|
const lane = new Uint8Array(laneByteLength);
|
|
134
98
|
writeLane(kind, lookupKey, lane);
|
|
135
|
-
|
|
99
|
+
const tag = await computeTag(key, template, lane);
|
|
100
|
+
return toWireId(prefix, await encryptPayload(key.aesKey, buildPlaintext(lane, tag)));
|
|
136
101
|
}
|
|
137
102
|
async function tryUnwrapLookupKey(prefix, template, key, kind, id) {
|
|
138
|
-
const plaintext = await decryptPayload(key, payloadBytesFromId(prefix, id));
|
|
103
|
+
const plaintext = await decryptPayload(key.aesKey, payloadBytesFromId(prefix, id));
|
|
139
104
|
const lane = plaintext.subarray(0, laneByteLength);
|
|
140
|
-
if (!
|
|
105
|
+
if (!timingSafeEqual(plaintext.subarray(laneByteLength, 16), await computeTag(key, template, lane))) return null;
|
|
141
106
|
return readLane(kind, lane);
|
|
142
107
|
}
|
|
143
108
|
function schemaExample(prefix) {
|
|
@@ -155,14 +120,13 @@ function createWrappedLayoutOps(prefix, brand, kind, keys) {
|
|
|
155
120
|
}
|
|
156
121
|
return null;
|
|
157
122
|
},
|
|
158
|
-
exampleWireId: () => schemaExample(prefix)
|
|
123
|
+
exampleWireId: (_ms) => schemaExample(prefix)
|
|
159
124
|
};
|
|
160
125
|
}
|
|
161
126
|
//#endregion
|
|
162
127
|
//#region src/codecs/wrapped/key.ts
|
|
163
128
|
const aesInfo = new TextEncoder().encode("@smonn/ids/wrapped/aes");
|
|
164
129
|
const hmacInfo = new TextEncoder().encode("@smonn/ids/wrapped/hmac");
|
|
165
|
-
const SHA256_DIGEST_BYTES = 32;
|
|
166
130
|
const internals = /* @__PURE__ */ new WeakMap();
|
|
167
131
|
/**
|
|
168
132
|
* Import raw operator secret bytes into a {@link WrappingKey} handle.
|
|
@@ -177,8 +141,15 @@ const internals = /* @__PURE__ */ new WeakMap();
|
|
|
177
141
|
async function importWrappingKey(bytes) {
|
|
178
142
|
assertValidKeyMaterialByteLength(bytes.length, "wrapping");
|
|
179
143
|
const [aesKey, hmacKey, digestBuffer] = await Promise.all([
|
|
180
|
-
|
|
181
|
-
|
|
144
|
+
deriveKey(bytes, aesInfo, {
|
|
145
|
+
name: "AES-CBC",
|
|
146
|
+
length: 256
|
|
147
|
+
}, ["encrypt", "decrypt"]),
|
|
148
|
+
deriveKey(bytes, hmacInfo, {
|
|
149
|
+
name: "HMAC",
|
|
150
|
+
hash: "SHA-256",
|
|
151
|
+
length: 256
|
|
152
|
+
}, ["sign", "verify"]),
|
|
182
153
|
crypto.subtle.digest("SHA-256", bytes)
|
|
183
154
|
]);
|
|
184
155
|
const key = Object.freeze({});
|
|
@@ -213,11 +184,7 @@ function decodeWrappingKey(encoded, format) {
|
|
|
213
184
|
* not leak the position of the first differing byte through a timing side channel.
|
|
214
185
|
*/
|
|
215
186
|
function wrappingKeysEqual(a, b) {
|
|
216
|
-
|
|
217
|
-
const bDigest = getWrappingKeyInternals(b).keyDigest;
|
|
218
|
-
let diff = 0;
|
|
219
|
-
for (let i = 0; i < SHA256_DIGEST_BYTES; i++) diff |= aDigest[i] ^ bDigest[i];
|
|
220
|
-
return diff === 0;
|
|
187
|
+
return timingSafeEqual(getWrappingKeyInternals(a).keyDigest, getWrappingKeyInternals(b).keyDigest);
|
|
221
188
|
}
|
|
222
189
|
function getWrappingKeyMaterial(key) {
|
|
223
190
|
const keyInternals = getWrappingKeyInternals(key);
|
|
@@ -231,31 +198,6 @@ function getWrappingKeyInternals(key) {
|
|
|
231
198
|
if (keyInternals === void 0) throw new Error("invalid wrapping key");
|
|
232
199
|
return keyInternals;
|
|
233
200
|
}
|
|
234
|
-
async function deriveAesKey(bytes) {
|
|
235
|
-
const base = await crypto.subtle.importKey("raw", bytes, "HKDF", false, ["deriveKey"]);
|
|
236
|
-
return crypto.subtle.deriveKey({
|
|
237
|
-
name: "HKDF",
|
|
238
|
-
hash: "SHA-256",
|
|
239
|
-
salt: /* @__PURE__ */ new Uint8Array(),
|
|
240
|
-
info: aesInfo
|
|
241
|
-
}, base, {
|
|
242
|
-
name: "AES-CBC",
|
|
243
|
-
length: 256
|
|
244
|
-
}, false, ["encrypt", "decrypt"]);
|
|
245
|
-
}
|
|
246
|
-
async function deriveHmacKey(bytes) {
|
|
247
|
-
const base = await crypto.subtle.importKey("raw", bytes, "HKDF", false, ["deriveKey"]);
|
|
248
|
-
return crypto.subtle.deriveKey({
|
|
249
|
-
name: "HKDF",
|
|
250
|
-
hash: "SHA-256",
|
|
251
|
-
salt: /* @__PURE__ */ new Uint8Array(),
|
|
252
|
-
info: hmacInfo
|
|
253
|
-
}, base, {
|
|
254
|
-
name: "HMAC",
|
|
255
|
-
hash: "SHA-256",
|
|
256
|
-
length: 256
|
|
257
|
-
}, false, ["sign", "verify"]);
|
|
258
|
-
}
|
|
259
201
|
//#endregion
|
|
260
202
|
//#region src/codecs/wrapped/index.ts
|
|
261
203
|
const u32Max = 4294967295;
|
|
@@ -354,4 +296,4 @@ function createWrappedKeyId(brand, opts) {
|
|
|
354
296
|
//#endregion
|
|
355
297
|
export { importWrappingKey as i, decodeWrappingKey as n, encodeWrappingKey as r, createWrappedKeyId as t };
|
|
356
298
|
|
|
357
|
-
//# sourceMappingURL=wrapped-
|
|
299
|
+
//# sourceMappingURL=wrapped-BjmVzuYc.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrapped-BjmVzuYc.mjs","names":[],"sources":["../src/codecs/wrapped/layout.ts","../src/codecs/wrapped/key.ts","../src/codecs/wrapped/index.ts"],"sourcesContent":["import type { webcrypto } from \"node:crypto\";\nimport type { Id, LayoutOps, Prefix } from \"../../types.js\";\nimport { decryptPayload, encryptPayload, timingSafeEqual } from \"../_kernel/crypto.js\";\nimport { writeLen32 } from \"../_kernel/bytes.js\";\nimport { payloadBytesFromId, toWireId } from \"../../wire/envelope.js\";\nimport { payloadBase32Length, payloadByteLength } from \"../../wire/invariants.js\";\n\nconst laneByteLength = 8;\nconst tagByteLength = 8;\n\ntype LayoutWrappingKey = {\n aesKey: webcrypto.CryptoKey;\n hmacKey: webcrypto.CryptoKey;\n};\n\ntype LayoutWrappedKind = \"u32\" | \"i32\" | \"u64\" | \"i64\";\ntype LayoutLookupKey<K extends LayoutWrappedKind> = K extends \"u32\" | \"i32\" ? number : bigint;\n\nfunction writeU32Lane(value: number, lane: Uint8Array): void {\n lane[0] = 0;\n lane[1] = 0;\n lane[2] = 0;\n lane[3] = 0;\n lane[4] = (value >>> 24) & 0xff;\n lane[5] = (value >>> 16) & 0xff;\n lane[6] = (value >>> 8) & 0xff;\n lane[7] = value & 0xff;\n}\n\nfunction readU32Lane(lane: Uint8Array): number | null {\n for (let i = 0; i < 4; i++) {\n if (lane[i] !== 0) return null;\n }\n return ((lane[4]! << 24) | (lane[5]! << 16) | (lane[6]! << 8) | lane[7]!) >>> 0;\n}\n\nfunction writeI32Lane(value: number, lane: Uint8Array): void {\n lane.fill(value < 0 ? 0xff : 0x00, 0, 4);\n new DataView(lane.buffer, lane.byteOffset, lane.byteLength).setInt32(4, value, false);\n}\n\nfunction readI32Lane(lane: Uint8Array): number | null {\n const signExtension = (lane[4]! & 0x80) === 0 ? 0x00 : 0xff;\n for (let i = 0; i < 4; i++) {\n if (lane[i] !== signExtension) return null;\n }\n return new DataView(lane.buffer, lane.byteOffset, lane.byteLength).getInt32(4, false);\n}\n\nfunction writeU64Lane(value: bigint, lane: Uint8Array): void {\n new DataView(lane.buffer, lane.byteOffset, lane.byteLength).setBigUint64(0, value, false);\n}\n\nfunction readU64Lane(lane: Uint8Array): bigint {\n return new DataView(lane.buffer, lane.byteOffset, lane.byteLength).getBigUint64(0, false);\n}\n\nfunction writeI64Lane(value: bigint, lane: Uint8Array): void {\n new DataView(lane.buffer, lane.byteOffset, lane.byteLength).setBigInt64(0, value, false);\n}\n\nfunction readI64Lane(lane: Uint8Array): bigint {\n return new DataView(lane.buffer, lane.byteOffset, lane.byteLength).getBigInt64(0, false);\n}\n\nfunction writeLane<K extends LayoutWrappedKind>(\n kind: K,\n value: LayoutLookupKey<K>,\n lane: Uint8Array,\n): void {\n if (kind === \"i32\") {\n writeI32Lane(value as number, lane);\n return;\n }\n if (kind === \"u64\") {\n writeU64Lane(value as bigint, lane);\n return;\n }\n if (kind === \"i64\") {\n writeI64Lane(value as bigint, lane);\n return;\n }\n writeU32Lane(value as number, lane);\n}\n\nfunction readLane<K extends LayoutWrappedKind>(\n kind: K,\n lane: Uint8Array,\n): LayoutLookupKey<K> | null {\n if (kind === \"u64\") return readU64Lane(lane) as LayoutLookupKey<K>;\n if (kind === \"i64\") return readI64Lane(lane) as LayoutLookupKey<K>;\n const value = kind === \"i32\" ? readI32Lane(lane) : readU32Lane(lane);\n return value as LayoutLookupKey<K> | null;\n}\n\n/**\n * Precomputed HMAC-message template for a fixed (brand, kind) pair.\n *\n * The message is `len32(brand) ‖ brand ‖ len32(kind) ‖ kind ‖ lane`. Everything\n * except the trailing 8-byte lane is constant for the life of the codec, so we\n * build it once at construction. `brand`/`kind` are never re-encoded and no\n * `TextEncoder` is allocated on the `wrap` / `unwrap` hot paths.\n */\ntype HmacMessageTemplate = {\n /** Full-length buffer with the constant prefix written and the lane region zeroed. */\n readonly buffer: Uint8Array;\n /** Byte offset where the lane is copied in on each call. */\n readonly laneOffset: number;\n};\n\nfunction createHmacMessageTemplate(brand: string, kind: LayoutWrappedKind): HmacMessageTemplate {\n const encoder = new TextEncoder();\n const brandBytes = encoder.encode(brand);\n const kindBytes = encoder.encode(kind);\n const laneOffset = 4 + brandBytes.length + 4 + kindBytes.length;\n const buffer = new Uint8Array(laneOffset + laneByteLength);\n let offset = 0;\n writeLen32(brandBytes.length, buffer, offset);\n offset += 4;\n buffer.set(brandBytes, offset);\n offset += brandBytes.length;\n writeLen32(kindBytes.length, buffer, offset);\n offset += 4;\n buffer.set(kindBytes, offset);\n return { buffer, laneOffset };\n}\n\n/** Materialise the HMAC message for `lane`. Fresh buffer per call → safe under concurrent async signs. */\nfunction hmacMessage(template: HmacMessageTemplate, lane: Uint8Array): Uint8Array {\n const message = template.buffer.slice();\n message.set(lane, template.laneOffset);\n return message;\n}\n\nasync function computeTag(\n key: LayoutWrappingKey,\n template: HmacMessageTemplate,\n lane: Uint8Array,\n): Promise<Uint8Array> {\n const signature = new Uint8Array(\n await crypto.subtle.sign(\n \"HMAC\",\n key.hmacKey,\n hmacMessage(template, lane) as Uint8Array<ArrayBuffer>,\n ),\n );\n return signature.subarray(0, tagByteLength);\n}\n\nfunction buildPlaintext(lane: Uint8Array, tag: Uint8Array): Uint8Array {\n const plaintext = new Uint8Array(payloadByteLength);\n plaintext.set(lane, 0);\n plaintext.set(tag, laneByteLength);\n return plaintext;\n}\n\nasync function wrapLookupKey<Brand extends string, Kind extends LayoutWrappedKind>(\n prefix: Prefix<Brand>,\n template: HmacMessageTemplate,\n key: LayoutWrappingKey,\n kind: Kind,\n lookupKey: LayoutLookupKey<Kind>,\n): Promise<Id<Brand>> {\n const lane = new Uint8Array(laneByteLength);\n writeLane(kind, lookupKey, lane);\n const tag = await computeTag(key, template, lane);\n const encrypted = await encryptPayload(key.aesKey, buildPlaintext(lane, tag));\n return toWireId(prefix, encrypted);\n}\n\nasync function tryUnwrapLookupKey<Brand extends string, Kind extends LayoutWrappedKind>(\n prefix: Prefix<Brand>,\n template: HmacMessageTemplate,\n key: LayoutWrappingKey,\n kind: Kind,\n id: Id<Brand>,\n): Promise<LayoutLookupKey<Kind> | null> {\n const plaintext = await decryptPayload(key.aesKey, payloadBytesFromId(prefix, id));\n const lane = plaintext.subarray(0, laneByteLength);\n const tag = plaintext.subarray(laneByteLength, payloadByteLength);\n const expected = await computeTag(key, template, lane);\n if (!timingSafeEqual(tag, expected)) return null;\n return readLane(kind, lane);\n}\n\nfunction schemaExample<Brand extends string>(prefix: Prefix<Brand>): string {\n return prefix + \"0\".repeat(payloadBase32Length);\n}\n\nexport function createWrappedLayoutOps<Brand extends string, Kind extends LayoutWrappedKind>(\n prefix: Prefix<Brand>,\n brand: Brand,\n kind: Kind,\n keys: readonly LayoutWrappingKey[],\n): LayoutOps<Brand> & {\n wrap(lookupKey: LayoutLookupKey<Kind>): Promise<Id<Brand>>;\n tryUnwrap(id: Id<Brand>): Promise<LayoutLookupKey<Kind> | null>;\n} {\n const wrapKey = keys[0]!;\n // brand + kind are fixed for the codec's lifetime; encode them and build the\n // HMAC-message prefix once instead of on every wrap / unwrap-trial.\n const template = createHmacMessageTemplate(brand, kind);\n return {\n wrap: (lookupKey: LayoutLookupKey<Kind>): Promise<Id<Brand>> =>\n wrapLookupKey(prefix, template, wrapKey, kind, lookupKey),\n tryUnwrap: async (id: Id<Brand>): Promise<LayoutLookupKey<Kind> | null> => {\n for (const key of keys) {\n const lookupKey = await tryUnwrapLookupKey(prefix, template, key, kind, id);\n if (lookupKey !== null) return lookupKey;\n }\n return null;\n },\n exampleWireId: (_ms?: number): Id<Brand> => schemaExample(prefix) as Id<Brand>,\n };\n}\n","import type { webcrypto } from \"node:crypto\";\nimport { deriveKey, timingSafeEqual } from \"../_kernel/crypto.js\";\nimport {\n assertValidKeyMaterialByteLength,\n assertValidKeyring,\n decodeKeyMaterial,\n encodeKeyMaterial,\n} from \"../_kernel/key-material.js\";\n\nexport { assertValidKeyring };\n\n/** Wire encoding for wrapping operator secret bytes (not Crockford base32). */\nexport type WrappingKeyFormat = \"hex\" | \"base64url\";\n\nconst aesInfo = new TextEncoder().encode(\"@smonn/ids/wrapped/aes\");\nconst hmacInfo = new TextEncoder().encode(\"@smonn/ids/wrapped/hmac\");\n\ndeclare const wrappingKeyBrand: unique symbol;\n\n/**\n * Opaque imported handle for one operator wrapping secret.\n *\n * Holds derived AES and HMAC subkeys internally; callers never access subkeys\n * or raw `webcrypto.CryptoKey` values directly. Obtain handles via {@link importWrappingKey}\n * and pass them to `createWrappedKeyId` as the `keys` wrapping keyring.\n *\n * Distinct from the **Opaque key** used by `@smonn/ids/opaque` — one raw\n * secret must not silently serve both codecs without an explicit import.\n */\nexport type WrappingKey = {\n readonly [wrappingKeyBrand]: \"WrappingKey\";\n};\n\ntype WrappingKeyInternals = {\n keyDigest: Uint8Array;\n aesKey: webcrypto.CryptoKey;\n hmacKey: webcrypto.CryptoKey;\n};\n\nexport type WrappingKeyMaterial = {\n aesKey: webcrypto.CryptoKey;\n hmacKey: webcrypto.CryptoKey;\n};\n\nconst internals = new WeakMap<WrappingKey, WrappingKeyInternals>();\n\n/**\n * Import raw operator secret bytes into a {@link WrappingKey} handle.\n *\n * One raw secret derives into AES and HMAC subkeys held inside the returned\n * handle. Accepts 16, 24, or 32 bytes (AES-128 / AES-192 / AES-256 strength).\n * To store or transport key material, use {@link encodeWrappingKey} /\n * {@link decodeWrappingKey} (`\"hex\"` or `\"base64url\"` — not Crockford base32).\n *\n * @param bytes - 16, 24, or 32 raw key bytes.\n */\nexport async function importWrappingKey(bytes: Uint8Array): Promise<WrappingKey> {\n assertValidKeyMaterialByteLength(bytes.length, \"wrapping\");\n const [aesKey, hmacKey, digestBuffer] = await Promise.all([\n deriveKey(bytes, aesInfo, { name: \"AES-CBC\", length: 256 }, [\"encrypt\", \"decrypt\"]),\n deriveKey(bytes, hmacInfo, { name: \"HMAC\", hash: \"SHA-256\", length: 256 }, [\"sign\", \"verify\"]),\n crypto.subtle.digest(\"SHA-256\", bytes as Uint8Array<ArrayBuffer>),\n ]);\n const key = Object.freeze({}) as WrappingKey;\n internals.set(key, {\n keyDigest: new Uint8Array(digestBuffer),\n aesKey,\n hmacKey,\n });\n return key;\n}\n\n/**\n * Encode raw wrapping operator secret bytes for storage in env vars or secret managers.\n *\n * Supports `\"hex\"` (lowercase) and `\"base64url\"`. Output round-trips through\n * {@link decodeWrappingKey} back to the original bytes.\n */\nexport function encodeWrappingKey(bytes: Uint8Array, format: WrappingKeyFormat): string {\n return encodeKeyMaterial(bytes, format, \"wrapping\", \"wrapping\");\n}\n\n/**\n * Decode key material emitted by {@link encodeWrappingKey} back to raw bytes.\n *\n * The result can be passed directly to {@link importWrappingKey}.\n */\nexport function decodeWrappingKey(encoded: string, format: WrappingKeyFormat): Uint8Array {\n return decodeKeyMaterial(encoded, format, \"wrapping\", \"wrapping\");\n}\n\n/**\n * Returns true when two handles were imported from the same raw operator secret.\n *\n * Uses a constant-time comparison so duplicate detection over key material does\n * not leak the position of the first differing byte through a timing side channel.\n */\nexport function wrappingKeysEqual(a: WrappingKey, b: WrappingKey): boolean {\n return timingSafeEqual(\n getWrappingKeyInternals(a).keyDigest,\n getWrappingKeyInternals(b).keyDigest,\n );\n}\n\nexport function getWrappingKeyMaterial(key: WrappingKey): WrappingKeyMaterial {\n const keyInternals = getWrappingKeyInternals(key);\n return {\n aesKey: keyInternals.aesKey,\n hmacKey: keyInternals.hmacKey,\n };\n}\n\nfunction getWrappingKeyInternals(key: WrappingKey): WrappingKeyInternals {\n const keyInternals = internals.get(key);\n if (keyInternals === undefined) {\n throw new Error(\"invalid wrapping key\");\n }\n return keyInternals;\n}\n","import { validateBrand } from \"../_kernel/brand.js\";\nimport { IdsError } from \"../../error.js\";\nimport { createWrappedLayoutOps } from \"./layout.js\";\nimport { registerBrand } from \"../_kernel/registry.js\";\nimport type {\n Id,\n JsonSchema,\n ParseError,\n ParseResult,\n Prefix,\n StandardSchemaProps,\n ValidBrand,\n} from \"../../types.js\";\nimport { wireMethods } from \"../../wire/codec-shell.js\";\nimport {\n assertValidKeyring,\n decodeWrappingKey,\n encodeWrappingKey,\n getWrappingKeyMaterial,\n importWrappingKey,\n type WrappingKey,\n type WrappingKeyFormat,\n wrappingKeysEqual,\n} from \"./key.js\";\n\n/** {@link IdsError} class, {@link isIdsError} type guard, and {@link IdsErrorCode} union — re-exported from `\"@smonn/ids\"` for convenience. */\nexport { IdsError, isIdsError, type IdsErrorCode } from \"../../error.js\";\nexport {\n decodeWrappingKey,\n encodeWrappingKey,\n importWrappingKey,\n type WrappingKey,\n type WrappingKeyFormat,\n};\n\n/**\n * Integer kind for a {@link WrappedKeyCodec}, fixed at construction time.\n *\n * - `\"u32\"` — unsigned 32-bit integer; JS type `number`, range `[0, 4 294 967 295]`.\n * - `\"i32\"` — signed 32-bit integer; JS type `number`, range `[-2 147 483 648, 2 147 483 647]`.\n * - `\"u64\"` — unsigned 64-bit integer; JS type `bigint`, range `[0n, 18 446 744 073 709 551 615n]`.\n * - `\"i64\"` — signed 64-bit integer; JS type `bigint`, range `[-9 223 372 036 854 775 808n, 9 223 372 036 854 775 807n]`.\n *\n * 32-bit kinds use safe JavaScript `number` values; 64-bit kinds always use `bigint`\n * even when the magnitude would fit in a `number`, preventing silent truncation or sign erasure.\n */\nexport type WrappedKind = \"u32\" | \"i32\" | \"u64\" | \"i64\";\n\ntype LookupKeyForKind<K extends WrappedKind> = K extends \"u32\" | \"i32\" ? number : bigint;\n\n/**\n * Result returned by {@link WrappedKeyCodec.safeUnwrap}.\n *\n * On success, `id` is the canonical {@link Id} and `lookupKey` is the recovered\n * integer (`number` for 32-bit kinds, `bigint` for 64-bit kinds).\n * On failure, `error` is a {@link ParseError} for structural problems or\n * `\"verification_failed\"` when the payload is structurally valid but the\n * verification tag does not match any entry in the wrapping keyring.\n */\nexport type UnwrapResult<Brand extends string, Kind extends WrappedKind> =\n | { ok: true; id: Id<Brand>; lookupKey: LookupKeyForKind<Kind> }\n | { ok: false; error: ParseError | \"verification_failed\" };\n\n/**\n * Codec returned by {@link createWrappedKeyId}.\n *\n * Wraps a caller-owned integer **lookup key** into a public {@link Id} and\n * recovers it on unwrap. The codec is deterministic under fixed key material:\n * the same lookup key always yields the same public ID (**equality leakage**).\n *\n * - `wrap` / `unwrap` / `safeUnwrap` are async (WebCrypto).\n * - `is`, `parse`, `safeParse`, and `toJsonSchema` are synchronous and require\n * no key material — they validate prefix and base32 shape only.\n * - The `Kind` type parameter drives value types at the TypeScript boundary:\n * `u32` / `i32` → `number`; `u64` / `i64` → `bigint`.\n *\n * @remarks\n * **Security properties (correctness-grade verification, not AEAD):**\n *\n * - The construction is deterministic — the same lookup key always yields the\n * same public ID (**equality leakage**).\n * - The verification tag is a fixed **64-bit (8-byte) truncation** of a\n * domain-separated HMAC over the brand, kind, and lookup key lane.\n * - False-accept rate is approximately `keyring_size / 2^64` per `unwrap`\n * trial — correctness-grade verification, not AEAD-strength origin\n * authentication.\n * - Consumers requiring full AEAD guarantees must use a different construction.\n */\nexport type WrappedKeyCodec<Brand extends string, Kind extends WrappedKind> = {\n /**\n * Wrap `lookupKey` into a public ID using the current (first) wrapping key.\n *\n * Throws if `lookupKey` is out of range or the wrong JS type for `Kind`.\n */\n wrap(lookupKey: LookupKeyForKind<Kind>): Promise<Id<Brand>>;\n /**\n * Verify the payload of a trusted `Id<Brand>` and return the lookup key.\n *\n * Throws `IdsError` with `code: \"verification_failed\"` if no entry in the\n * wrapping keyring matches the payload tag. Use {@link safeUnwrap} for\n * untrusted input.\n */\n unwrap(id: Id<Brand>): Promise<LookupKeyForKind<Kind>>;\n /**\n * Non-throwing path for untrusted input.\n *\n * Structurally parses `input` first (same rules as {@link safeParse}), then\n * verifies the payload. Returns `{ ok: false, error }` on any failure —\n * `ParseError` for structural problems or `\"verification_failed\"` for tag\n * mismatch — without throwing. Tamper, wrong keyring, and revoked-key cases\n * all surface as `\"verification_failed\"`.\n */\n safeUnwrap(input: unknown): Promise<UnwrapResult<Brand, Kind>>;\n /** Strict type guard: `true` only for already-canonical `Id<Brand>` strings. */\n is(value: unknown): value is Id<Brand>;\n /** Normalise to canonical form, or throw on parse failure. */\n parse(value: unknown): Id<Brand>;\n /** Normalise to canonical form, or return `{ ok: false, error }`. */\n safeParse(value: unknown): ParseResult<Brand>;\n toJsonSchema(): JsonSchema;\n readonly \"~standard\": StandardSchemaProps<Brand>;\n};\n\n/**\n * Construction options for {@link createWrappedKeyId}.\n */\nexport type WrappedKeyOptions<K extends WrappedKind> = {\n /** Integer kind for the codec — fixed for the lifetime of the codec. Drives the JS value type (`number` for 32-bit, `bigint` for 64-bit). */\n kind: K;\n /** Non-empty ordered wrapping keyring. The first entry is current (used by `wrap`); all entries are tried on `unwrap`. Duplicate operator secrets are rejected at construction. */\n keys: [WrappingKey, ...WrappingKey[]];\n /** If true, silences the duplicate-brand warning in non-production environments. */\n allowDuplicateBrand?: boolean;\n};\n\nconst u32Max = 0xffff_ffff;\nconst i32Min = -0x8000_0000;\nconst i32Max = 0x7fff_ffff;\nconst u64Max = 0xffff_ffff_ffff_ffffn;\nconst i64Min = -(1n << 63n);\nconst i64Max = (1n << 63n) - 1n;\n\nfunction assertSupportedKind(kind: WrappedKind): asserts kind is WrappedKind {\n if (kind !== \"u32\" && kind !== \"i32\" && kind !== \"u64\" && kind !== \"i64\") {\n throw new IdsError(\"invalid_kind\", \"invalid wrapped key kind: expected u32, i32, u64, or i64\");\n }\n}\n\nfunction assertU32LookupKey(lookupKey: unknown): asserts lookupKey is number {\n if (\n typeof lookupKey !== \"number\" ||\n !Number.isInteger(lookupKey) ||\n Object.is(lookupKey, -0) ||\n lookupKey < 0 ||\n lookupKey > u32Max\n ) {\n throw new IdsError(\n \"invalid_lookup_key\",\n `invalid u32 lookup key: expected integer in [0, ${u32Max}], got ${lookupKey}`,\n );\n }\n}\n\nfunction assertI32LookupKey(lookupKey: unknown): asserts lookupKey is number {\n if (\n typeof lookupKey !== \"number\" ||\n !Number.isInteger(lookupKey) ||\n Object.is(lookupKey, -0) ||\n lookupKey < i32Min ||\n lookupKey > i32Max\n ) {\n throw new IdsError(\n \"invalid_lookup_key\",\n `invalid i32 lookup key: expected integer in [${i32Min}, ${i32Max}], got ${lookupKey}`,\n );\n }\n}\n\nfunction assertU64LookupKey(lookupKey: unknown): asserts lookupKey is bigint {\n if (typeof lookupKey !== \"bigint\" || lookupKey < 0n || lookupKey > u64Max) {\n throw new IdsError(\n \"invalid_lookup_key\",\n `invalid u64 lookup key: expected bigint in [0, ${u64Max}], got ${lookupKey}`,\n );\n }\n}\n\nfunction assertI64LookupKey(lookupKey: unknown): asserts lookupKey is bigint {\n if (typeof lookupKey !== \"bigint\" || lookupKey < i64Min || lookupKey > i64Max) {\n throw new IdsError(\n \"invalid_lookup_key\",\n `invalid i64 lookup key: expected bigint in [${i64Min}, ${i64Max}], got ${lookupKey}`,\n );\n }\n}\n\nfunction assertLookupKey<Kind extends WrappedKind>(\n kind: Kind,\n lookupKey: unknown,\n): asserts lookupKey is LookupKeyForKind<Kind> {\n if (kind === \"i32\") {\n assertI32LookupKey(lookupKey);\n return;\n }\n if (kind === \"u64\") {\n assertU64LookupKey(lookupKey);\n return;\n }\n if (kind === \"i64\") {\n assertI64LookupKey(lookupKey);\n return;\n }\n assertU32LookupKey(lookupKey);\n}\n\n/**\n * Construct a {@link WrappedKeyCodec} for `brand` and the given `kind`.\n *\n * `opts.kind` fixes the integer type at construction time — one brand, one\n * kind. `opts.keys` is a non-empty ordered wrapping keyring: the first entry\n * is current (used by `wrap`); all entries are tried on `unwrap`; duplicate\n * operator secrets are rejected at construction.\n *\n * @example\n * ```ts\n * const key = await importWrappingKey(new Uint8Array(32));\n * const invoices = createWrappedKeyId(\"inv\", { kind: \"u32\", keys: [key] });\n *\n * const id = await invoices.wrap(42); // Id<\"inv\">\n * await invoices.unwrap(id); // 42\n * ```\n */\nexport function createWrappedKeyId<Brand extends string, Kind extends WrappedKind>(\n brand: Brand & ValidBrand<Brand>,\n opts: WrappedKeyOptions<Kind>,\n): WrappedKeyCodec<Brand, Kind> {\n validateBrand(brand);\n registerBrand(brand, opts.allowDuplicateBrand);\n assertSupportedKind(opts.kind);\n assertValidKeyring(opts.keys, wrappingKeysEqual, \"wrapping\");\n const layoutKeys = opts.keys.map(getWrappingKeyMaterial);\n\n const prefix: Prefix<Brand> = `${brand}_`;\n const wire = wireMethods(prefix);\n const layout = createWrappedLayoutOps(prefix, brand, opts.kind, layoutKeys);\n\n return {\n wrap: async (lookupKey) => {\n assertLookupKey(opts.kind, lookupKey);\n return layout.wrap(lookupKey);\n },\n unwrap: async (id) => {\n const lookupKey = await layout.tryUnwrap(id);\n if (lookupKey === null) {\n throw new IdsError(\"verification_failed\", \"verification failed\");\n }\n return lookupKey;\n },\n safeUnwrap: async (input) => {\n const parsed = wire.safeParse(input);\n if (!parsed.ok) return parsed;\n const lookupKey = await layout.tryUnwrap(parsed.id);\n if (lookupKey === null) return { ok: false, error: \"verification_failed\" };\n return { ok: true, id: parsed.id, lookupKey };\n },\n is: wire.is,\n parse: wire.parse,\n safeParse: wire.safeParse,\n toJsonSchema: () => wire.toJsonSchema(brand, layout.exampleWireId()),\n \"~standard\": wire[\"~standard\"],\n };\n}\n"],"mappings":"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"}
|
package/dist/wrapped.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as
|
|
2
|
-
import {
|
|
1
|
+
import { a as StandardSchemaProps, i as ParseResult, n as JsonSchema, o as ValidBrand, r as ParseError, t as Id } from "./types-wplmOgOK.mjs";
|
|
2
|
+
import { n as IdsErrorCode, r as isIdsError, t as IdsError } from "./error-Dqyho9vp.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/codecs/wrapped/key.d.ts
|
|
5
5
|
/** Wire encoding for wrapping operator secret bytes (not Crockford base32). */
|
|
@@ -44,6 +44,17 @@ declare function encodeWrappingKey(bytes: Uint8Array, format: WrappingKeyFormat)
|
|
|
44
44
|
declare function decodeWrappingKey(encoded: string, format: WrappingKeyFormat): Uint8Array;
|
|
45
45
|
//#endregion
|
|
46
46
|
//#region src/codecs/wrapped/index.d.ts
|
|
47
|
+
/**
|
|
48
|
+
* Integer kind for a {@link WrappedKeyCodec}, fixed at construction time.
|
|
49
|
+
*
|
|
50
|
+
* - `"u32"` — unsigned 32-bit integer; JS type `number`, range `[0, 4 294 967 295]`.
|
|
51
|
+
* - `"i32"` — signed 32-bit integer; JS type `number`, range `[-2 147 483 648, 2 147 483 647]`.
|
|
52
|
+
* - `"u64"` — unsigned 64-bit integer; JS type `bigint`, range `[0n, 18 446 744 073 709 551 615n]`.
|
|
53
|
+
* - `"i64"` — signed 64-bit integer; JS type `bigint`, range `[-9 223 372 036 854 775 808n, 9 223 372 036 854 775 807n]`.
|
|
54
|
+
*
|
|
55
|
+
* 32-bit kinds use safe JavaScript `number` values; 64-bit kinds always use `bigint`
|
|
56
|
+
* even when the magnitude would fit in a `number`, preventing silent truncation or sign erasure.
|
|
57
|
+
*/
|
|
47
58
|
type WrappedKind = "u32" | "i32" | "u64" | "i64";
|
|
48
59
|
type LookupKeyForKind<K extends WrappedKind> = K extends "u32" | "i32" ? number : bigint;
|
|
49
60
|
/**
|
|
@@ -75,6 +86,18 @@ type UnwrapResult<Brand extends string, Kind extends WrappedKind> = {
|
|
|
75
86
|
* no key material — they validate prefix and base32 shape only.
|
|
76
87
|
* - The `Kind` type parameter drives value types at the TypeScript boundary:
|
|
77
88
|
* `u32` / `i32` → `number`; `u64` / `i64` → `bigint`.
|
|
89
|
+
*
|
|
90
|
+
* @remarks
|
|
91
|
+
* **Security properties (correctness-grade verification, not AEAD):**
|
|
92
|
+
*
|
|
93
|
+
* - The construction is deterministic — the same lookup key always yields the
|
|
94
|
+
* same public ID (**equality leakage**).
|
|
95
|
+
* - The verification tag is a fixed **64-bit (8-byte) truncation** of a
|
|
96
|
+
* domain-separated HMAC over the brand, kind, and lookup key lane.
|
|
97
|
+
* - False-accept rate is approximately `keyring_size / 2^64` per `unwrap`
|
|
98
|
+
* trial — correctness-grade verification, not AEAD-strength origin
|
|
99
|
+
* authentication.
|
|
100
|
+
* - Consumers requiring full AEAD guarantees must use a different construction.
|
|
78
101
|
*/
|
|
79
102
|
type WrappedKeyCodec<Brand extends string, Kind extends WrappedKind> = {
|
|
80
103
|
/**
|
|
@@ -107,9 +130,12 @@ type WrappedKeyCodec<Brand extends string, Kind extends WrappedKind> = {
|
|
|
107
130
|
toJsonSchema(): JsonSchema;
|
|
108
131
|
readonly "~standard": StandardSchemaProps<Brand>;
|
|
109
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* Construction options for {@link createWrappedKeyId}.
|
|
135
|
+
*/
|
|
110
136
|
type WrappedKeyOptions<K extends WrappedKind> = {
|
|
111
|
-
kind: K;
|
|
112
|
-
keys: [WrappingKey, ...WrappingKey[]];
|
|
137
|
+
/** Integer kind for the codec — fixed for the lifetime of the codec. Drives the JS value type (`number` for 32-bit, `bigint` for 64-bit). */kind: K; /** Non-empty ordered wrapping keyring. The first entry is current (used by `wrap`); all entries are tried on `unwrap`. Duplicate operator secrets are rejected at construction. */
|
|
138
|
+
keys: [WrappingKey, ...WrappingKey[]]; /** If true, silences the duplicate-brand warning in non-production environments. */
|
|
113
139
|
allowDuplicateBrand?: boolean;
|
|
114
140
|
};
|
|
115
141
|
/**
|
|
@@ -129,7 +155,7 @@ type WrappedKeyOptions<K extends WrappedKind> = {
|
|
|
129
155
|
* await invoices.unwrap(id); // 42
|
|
130
156
|
* ```
|
|
131
157
|
*/
|
|
132
|
-
declare function createWrappedKeyId<Brand extends string, Kind extends WrappedKind>(brand: Brand
|
|
158
|
+
declare function createWrappedKeyId<Brand extends string, Kind extends WrappedKind>(brand: Brand & ValidBrand<Brand>, opts: WrappedKeyOptions<Kind>): WrappedKeyCodec<Brand, Kind>;
|
|
133
159
|
//#endregion
|
|
134
160
|
export { IdsError, type IdsErrorCode, UnwrapResult, WrappedKeyCodec, WrappedKeyOptions, WrappedKind, type WrappingKey, type WrappingKeyFormat, createWrappedKeyId, decodeWrappingKey, encodeWrappingKey, importWrappingKey, isIdsError };
|
|
135
161
|
//# sourceMappingURL=wrapped.d.mts.map
|
package/dist/wrapped.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrapped.d.mts","names":[],"sources":["../src/codecs/wrapped/key.ts","../src/codecs/wrapped/index.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"wrapped.d.mts","names":[],"sources":["../src/codecs/wrapped/key.ts","../src/codecs/wrapped/index.ts"],"mappings":";;;;;KAYY,iBAAA;AAAA,cAKE,gBAAA;;;AALF;AAA4B;;;;AAK1B;AAYd;;KAAY,WAAA;EAAA,UACA,gBAAA;AAAA;;;;;;;;;;AA0BwD;iBAA9C,iBAAA,CAAkB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,WAAA;;;;;;;iBAsBpD,iBAAA,CAAkB,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,iBAAA;;AAAA;AAS7D;;;iBAAgB,iBAAA,CAAkB,OAAA,UAAiB,MAAA,EAAQ,iBAAA,GAAoB,UAAA;;;;;;AA3EnE;AAA4B;;;;AAK1B;AAYd;;KCiBY,WAAA;AAAA,KAEP,gBAAA,WAA2B,WAAA,IAAe,CAAA;ADlBnC;AA0BZ;;;;;;;;AA1BY,KC6BA,YAAA,oCAAgD,WAAA;EACtD,EAAA;EAAU,EAAA,EAAI,EAAA,CAAG,KAAA;EAAQ,SAAA,EAAW,gBAAA,CAAiB,IAAA;AAAA;EACrD,EAAA;EAAW,KAAA,EAAO,UAAA;AAAA;;;;;ADiBqC;AAS7D;;;;;;;;;AAA+E;;;;ACzC/E;;;;AAAY;AAAsC;;KA0CtC,eAAA,oCAAmD,WAAA;;;AAxChB;AAW/C;;EAmCE,IAAA,CAAK,SAAA,EAAW,gBAAA,CAAiB,IAAA,IAAQ,OAAA,CAAQ,EAAA,CAAG,KAAA;;;;;;;;EAQpD,MAAA,CAAO,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,OAAA,CAAQ,gBAAA,CAAiB,IAAA;;;;;;;;;;EAUhD,UAAA,CAAW,KAAA,YAAiB,OAAA,CAAQ,YAAA,CAAa,KAAA,EAAO,IAAA;EAExD,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;EAEhC,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA,GAvDJ;EAyDtB,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;EACvC,YAAA,IAAgB,UAAA;EAAA,SACP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;;;;KAMhC,iBAAA,WAA4B,WAAA;+IAEtC,IAAA,EAAM,CAAA;EAEN,IAAA,GAAO,WAAA,KAAgB,WAAA;EAEvB,mBAAA;AAAA;;;;;;;;;;;;;;;;;;iBAoGc,kBAAA,oCAAsD,WAAA,EACpE,KAAA,EAAO,KAAA,GAAQ,UAAA,CAAW,KAAA,GAC1B,IAAA,EAAM,iBAAA,CAAkB,IAAA,IACvB,eAAA,CAAgB,KAAA,EAAO,IAAA"}
|
package/dist/wrapped.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as isIdsError, t as IdsError } from "./error-Cp5qYZcv.mjs";
|
|
2
|
-
import { i as importWrappingKey, n as decodeWrappingKey, r as encodeWrappingKey, t as createWrappedKeyId } from "./wrapped-
|
|
2
|
+
import { i as importWrappingKey, n as decodeWrappingKey, r as encodeWrappingKey, t as createWrappedKeyId } from "./wrapped-BjmVzuYc.mjs";
|
|
3
3
|
export { IdsError, createWrappedKeyId, decodeWrappingKey, encodeWrappingKey, importWrappingKey, isIdsError };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smonn/ids",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Simon Ingeson (https://github.com/smonn)",
|
|
6
6
|
"repository": {
|
|
@@ -14,33 +14,83 @@
|
|
|
14
14
|
"dist"
|
|
15
15
|
],
|
|
16
16
|
"type": "module",
|
|
17
|
+
"types": "./dist/index.d.mts",
|
|
17
18
|
"exports": {
|
|
18
|
-
".":
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"./
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"./
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"./
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.mts",
|
|
21
|
+
"default": "./dist/index.mjs"
|
|
22
|
+
},
|
|
23
|
+
"./opaque": {
|
|
24
|
+
"types": "./dist/opaque.d.mts",
|
|
25
|
+
"default": "./dist/opaque.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./reverse": {
|
|
28
|
+
"types": "./dist/reverse.d.mts",
|
|
29
|
+
"default": "./dist/reverse.mjs"
|
|
30
|
+
},
|
|
31
|
+
"./signed": {
|
|
32
|
+
"types": "./dist/signed.d.mts",
|
|
33
|
+
"default": "./dist/signed.mjs"
|
|
34
|
+
},
|
|
35
|
+
"./wrapped": {
|
|
36
|
+
"types": "./dist/wrapped.d.mts",
|
|
37
|
+
"default": "./dist/wrapped.mjs"
|
|
38
|
+
},
|
|
39
|
+
"./digest": {
|
|
40
|
+
"types": "./dist/digest.d.mts",
|
|
41
|
+
"default": "./dist/digest.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./drizzle": {
|
|
44
|
+
"types": "./dist/drizzle.d.mts",
|
|
45
|
+
"default": "./dist/drizzle.mjs"
|
|
46
|
+
},
|
|
47
|
+
"./hono": {
|
|
48
|
+
"types": "./dist/hono.d.mts",
|
|
49
|
+
"default": "./dist/hono.mjs"
|
|
50
|
+
},
|
|
51
|
+
"./kysely": {
|
|
52
|
+
"types": "./dist/kysely.d.mts",
|
|
53
|
+
"default": "./dist/kysely.mjs"
|
|
54
|
+
},
|
|
55
|
+
"./mikro-orm": {
|
|
56
|
+
"types": "./dist/mikro-orm.d.mts",
|
|
57
|
+
"default": "./dist/mikro-orm.mjs"
|
|
58
|
+
},
|
|
59
|
+
"./prisma": {
|
|
60
|
+
"types": "./dist/prisma.d.mts",
|
|
61
|
+
"default": "./dist/prisma.mjs"
|
|
62
|
+
},
|
|
63
|
+
"./express": {
|
|
64
|
+
"types": "./dist/express.d.mts",
|
|
65
|
+
"default": "./dist/express.mjs"
|
|
66
|
+
},
|
|
67
|
+
"./fastify": {
|
|
68
|
+
"types": "./dist/fastify.d.mts",
|
|
69
|
+
"default": "./dist/fastify.mjs"
|
|
70
|
+
},
|
|
71
|
+
"./typeorm": {
|
|
72
|
+
"types": "./dist/typeorm.d.mts",
|
|
73
|
+
"default": "./dist/typeorm.mjs"
|
|
74
|
+
},
|
|
75
|
+
"./graphql": {
|
|
76
|
+
"types": "./dist/graphql.d.mts",
|
|
77
|
+
"default": "./dist/graphql.mjs"
|
|
78
|
+
},
|
|
79
|
+
"./nestjs": {
|
|
80
|
+
"types": "./dist/nestjs.d.mts",
|
|
81
|
+
"default": "./dist/nestjs.mjs"
|
|
82
|
+
},
|
|
34
83
|
"./package.json": "./package.json"
|
|
35
84
|
},
|
|
36
85
|
"devDependencies": {
|
|
86
|
+
"@arethetypeswrong/cli": "0.18.4",
|
|
37
87
|
"@changesets/cli": "2.31.0",
|
|
38
88
|
"@mikro-orm/core": "^7.1.4",
|
|
39
89
|
"@nestjs/common": "^11.1.27",
|
|
40
90
|
"@prisma/client": ">=5.0.0",
|
|
41
91
|
"@total-typescript/shoehorn": "^0.1.2",
|
|
42
92
|
"@types/express": "^5.0.6",
|
|
43
|
-
"@types/node": "
|
|
93
|
+
"@types/node": "22.20.0",
|
|
44
94
|
"@vitest/coverage-v8": "4.1.8",
|
|
45
95
|
"dependency-cruiser": "17.4.3",
|
|
46
96
|
"drizzle-orm": "^0.45.2",
|
|
@@ -54,24 +104,26 @@
|
|
|
54
104
|
"mitata": "1.0.34",
|
|
55
105
|
"oxfmt": "0.55.0",
|
|
56
106
|
"oxlint": "1.70.0",
|
|
107
|
+
"publint": "0.3.21",
|
|
57
108
|
"reflect-metadata": "^0.2.2",
|
|
58
109
|
"tsdown": "0.22.3",
|
|
59
110
|
"tslib": "^2.8.1",
|
|
60
111
|
"typeorm": "^1.0.0",
|
|
61
112
|
"typescript": "6.0.3",
|
|
62
|
-
"vitest": "4.1.9"
|
|
113
|
+
"vitest": "4.1.9",
|
|
114
|
+
"yaml": "2.9.0"
|
|
63
115
|
},
|
|
64
116
|
"peerDependencies": {
|
|
65
|
-
"@mikro-orm/core": ">=
|
|
66
|
-
"@nestjs/common": ">=
|
|
67
|
-
"@prisma/client": ">=5.
|
|
68
|
-
"drizzle-orm": ">=0.
|
|
117
|
+
"@mikro-orm/core": ">=7.0.0",
|
|
118
|
+
"@nestjs/common": ">=11.0.0",
|
|
119
|
+
"@prisma/client": ">=5.9.1",
|
|
120
|
+
"drizzle-orm": ">=0.36.0",
|
|
69
121
|
"express": ">=4.0.0",
|
|
70
122
|
"fastify": ">=4.0.0",
|
|
71
123
|
"graphql": ">=16.0.0",
|
|
72
|
-
"hono": ">=4.
|
|
124
|
+
"hono": ">=4.6.15",
|
|
73
125
|
"kysely": ">=0.27.0",
|
|
74
|
-
"typeorm": ">=0.
|
|
126
|
+
"typeorm": ">=1.0.0"
|
|
75
127
|
},
|
|
76
128
|
"peerDependenciesMeta": {
|
|
77
129
|
"@mikro-orm/core": {
|
|
@@ -106,7 +158,7 @@
|
|
|
106
158
|
}
|
|
107
159
|
},
|
|
108
160
|
"engines": {
|
|
109
|
-
"node": ">=
|
|
161
|
+
"node": ">=22.0.0"
|
|
110
162
|
},
|
|
111
163
|
"scripts": {
|
|
112
164
|
"lint": "oxlint",
|
|
@@ -115,6 +167,7 @@
|
|
|
115
167
|
"fmt:check": "oxfmt --check",
|
|
116
168
|
"typecheck": "tsc --noEmit",
|
|
117
169
|
"build": "tsdown",
|
|
170
|
+
"pack:check": "attw --pack . --profile esm-only && publint",
|
|
118
171
|
"test": "vitest run",
|
|
119
172
|
"test:watch": "vitest",
|
|
120
173
|
"test:coverage": "vitest run --coverage",
|
|
@@ -123,6 +176,6 @@
|
|
|
123
176
|
"bench:build": "tsdown -c tsdown.bench.config.ts",
|
|
124
177
|
"bench": "pnpm -s bench:build 1>&2 && node bench/dist/index.mjs",
|
|
125
178
|
"bench:compare": "pnpm -s bench:build 1>&2 && node bench/dist/compare.mjs",
|
|
126
|
-
"version-packages": "changeset version
|
|
179
|
+
"version-packages": "changeset version"
|
|
127
180
|
}
|
|
128
181
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"digest-CknNw2wa.mjs","names":[],"sources":["../src/codecs/digest/layout.ts","../src/codecs/digest/key.ts","../src/codecs/digest/index.ts"],"sourcesContent":["import type { webcrypto } from \"node:crypto\";\nimport type { Id, Prefix } from \"../../types.js\";\nimport { toWireId } from \"../../wire/envelope.js\";\nimport { payloadBase32Length, payloadByteLength } from \"../../wire/invariants.js\";\n\nconst encoder = new TextEncoder();\n\nfunction writeLen32(value: number, target: Uint8Array, offset: number): void {\n target[offset] = (value >>> 24) & 0xff;\n target[offset + 1] = (value >>> 16) & 0xff;\n target[offset + 2] = (value >>> 8) & 0xff;\n target[offset + 3] = value & 0xff;\n}\n\nfunction buildMessage(\n brandBytes: Uint8Array,\n nsBytes: Uint8Array,\n material: Uint8Array,\n): Uint8Array {\n const msgLen = 4 + brandBytes.length + 4 + nsBytes.length + material.length;\n const message = new Uint8Array(msgLen);\n let offset = 0;\n writeLen32(brandBytes.length, message, offset);\n offset += 4;\n message.set(brandBytes, offset);\n offset += brandBytes.length;\n writeLen32(nsBytes.length, message, offset);\n offset += 4;\n message.set(nsBytes, offset);\n offset += nsBytes.length;\n message.set(material, offset);\n return message;\n}\n\nexport function createDigestLayoutOps<Brand extends string>(\n prefix: Prefix<Brand>,\n brand: Brand,\n ns: string,\n hmacKey: webcrypto.CryptoKey,\n) {\n const brandBytes = encoder.encode(brand);\n const nsBytes = encoder.encode(ns);\n\n return {\n digest: async (material: string | Uint8Array): Promise<Id<Brand>> => {\n const materialBytes = typeof material === \"string\" ? encoder.encode(material) : material;\n const message = buildMessage(brandBytes, nsBytes, materialBytes);\n const hmacOutput = new Uint8Array(\n await crypto.subtle.sign(\"HMAC\", hmacKey, message as Uint8Array<ArrayBuffer>),\n );\n const payload = hmacOutput.subarray(0, payloadByteLength);\n return toWireId(prefix, payload);\n },\n exampleWireId: (): Id<Brand> => (prefix + \"0\".repeat(payloadBase32Length)) as Id<Brand>,\n };\n}\n","import type { webcrypto } from \"node:crypto\";\nimport {\n assertValidKeyMaterialByteLength,\n decodeKeyMaterial,\n encodeKeyMaterial,\n} from \"../_kernel/key-material.js\";\n\n/** Wire encoding for digest operator key material (not Crockford base32). */\nexport type DigestKeyFormat = \"hex\" | \"base64url\";\n\nconst hmacInfo = new TextEncoder().encode(\"@smonn/ids/digest/hmac\");\n\ndeclare const digestKeyBrand: unique symbol;\n\n/**\n * Opaque imported handle for one operator Digest key.\n *\n * Holds a single HMAC-SHA-256 key derived via HKDF under the domain-separation\n * label `@smonn/ids/digest/hmac`. The underlying `webcrypto.CryptoKey` is held internally and\n * never exposed to callers. Obtain handles via {@link importDigestKey} and pass\n * them to `createDigestId` as the `key` option.\n *\n * Unlike the other keyed codecs, the Digest codec holds exactly one key — there\n * is no keyring. Re-keying is a deliberate, breaking operator action (every ID\n * changes), never an in-band rotation.\n *\n * Distinct from the **Opaque key**, **Wrapping key**, and **Signing key** — the\n * same raw bytes imported as a `DigestKey` are cryptographically independent of\n * any other codec's key.\n */\nexport type DigestKey = {\n readonly [digestKeyBrand]: \"DigestKey\";\n};\n\ntype DigestKeyInternals = {\n hmacKey: webcrypto.CryptoKey;\n};\n\nconst internals = new WeakMap<DigestKey, DigestKeyInternals>();\n\n/**\n * Import raw operator key material into a {@link DigestKey} handle.\n *\n * Derives a single HMAC-SHA-256 key via HKDF under the domain-separation label\n * `@smonn/ids/digest/hmac`. Accepts 16, 24, or 32 bytes. To store or transport key\n * material, use {@link encodeDigestKey} / {@link decodeDigestKey}\n * (`\"hex\"` or `\"base64url\"` — not Crockford base32).\n *\n * @param bytes - 16, 24, or 32 raw key bytes.\n * @throws {IdsError} `invalid_key_length` if `bytes.length` is not 16, 24, or 32.\n */\nexport async function importDigestKey(bytes: Uint8Array): Promise<DigestKey> {\n assertValidKeyMaterialByteLength(bytes.length, \"digest\");\n const hmacKey = await deriveHmacKey(bytes);\n const key = Object.freeze({}) as DigestKey;\n internals.set(key, { hmacKey });\n return key;\n}\n\n/**\n * Encode raw digest operator key material for storage in env vars or secret managers.\n *\n * Supports `\"hex\"` (lowercase) and `\"base64url\"`. Output round-trips through\n * {@link decodeDigestKey} back to the original bytes.\n *\n * @throws {IdsError} `invalid_key_format` if `format` is not `\"hex\"` or `\"base64url\"`.\n * @throws {IdsError} `invalid_key_length` if `bytes.length` is not 16, 24, or 32.\n */\nexport function encodeDigestKey(bytes: Uint8Array, format: DigestKeyFormat): string {\n return encodeKeyMaterial(bytes, format, \"digest\", \"digest\");\n}\n\n/**\n * Decode key material emitted by {@link encodeDigestKey} back to raw bytes.\n *\n * The result can be passed directly to {@link importDigestKey}.\n *\n * @throws {IdsError} `invalid_key_format` if `format` is not `\"hex\"` or `\"base64url\"`.\n * @throws {IdsError} `invalid_key_encoding` if the string is malformed for its format.\n * @throws {IdsError} `invalid_key_length` if the decoded bytes are not 16, 24, or 32 bytes.\n */\nexport function decodeDigestKey(encoded: string, format: DigestKeyFormat): Uint8Array {\n return decodeKeyMaterial(encoded, format, \"digest\", \"digest\");\n}\n\n/**\n * Returns the derived HMAC webcrypto.CryptoKey held inside the handle.\n *\n * Intentional module-internal escape hatch for codec implementations.\n * Not re-exported from `@smonn/ids/digest`; external callers cannot reach this.\n */\nexport function getDigestKeyHmacKey(key: DigestKey): webcrypto.CryptoKey {\n const keyInternals = internals.get(key);\n /* v8 ignore next -- defensive guard; only reachable with a forged DigestKey handle */\n if (keyInternals === undefined) throw new Error(\"invalid digest key\");\n return keyInternals.hmacKey;\n}\n\nasync function deriveHmacKey(bytes: Uint8Array): Promise<webcrypto.CryptoKey> {\n const base = await crypto.subtle.importKey(\n \"raw\",\n bytes as Uint8Array<ArrayBuffer>,\n \"HKDF\",\n false,\n [\"deriveKey\"],\n );\n return crypto.subtle.deriveKey(\n { name: \"HKDF\", hash: \"SHA-256\", salt: new Uint8Array(), info: hmacInfo },\n base,\n { name: \"HMAC\", hash: \"SHA-256\", length: 256 },\n false,\n [\"sign\"],\n );\n}\n","import { validateBrand } from \"../_kernel/brand.js\";\nimport { IdsError, isIdsError, type IdsErrorCode } from \"../../error.js\";\nimport { createDigestLayoutOps } from \"./layout.js\";\nimport { registerBrand } from \"../_kernel/registry.js\";\nimport type { Id, JsonSchema, ParseResult, Prefix, StandardSchemaProps } from \"../../types.js\";\nimport { wireMethods } from \"../../wire/codec-shell.js\";\nimport {\n decodeDigestKey,\n encodeDigestKey,\n getDigestKeyHmacKey,\n importDigestKey,\n type DigestKey,\n type DigestKeyFormat,\n} from \"./key.js\";\n\n/** {@link IdsError} class, {@link isIdsError} type guard, and {@link IdsErrorCode} union — re-exported from `\"@smonn/ids\"` for convenience. */\nexport { IdsError, isIdsError, type IdsErrorCode };\nexport { decodeDigestKey, encodeDigestKey, importDigestKey, type DigestKey, type DigestKeyFormat };\n\n/**\n * Configuration options for a Digest codec instance.\n */\nexport type DigestOptions = {\n /**\n * Non-secret, required namespace. The same material under a different\n * `ns` yields a different ID, so one key can serve multiple unlinkable namespaces.\n * Must be non-empty and not whitespace-only.\n */\n ns: string;\n /**\n * Single operator digest key. The Digest codec holds exactly one key — there\n * is no keyring. Re-keying is a deliberate, breaking operator action.\n */\n key: DigestKey;\n /** If true, silences the duplicate-brand warning in non-production environments. */\n allowDuplicateBrand?: boolean;\n};\n\n/**\n * Codec returned by {@link createDigestId}.\n *\n * Maps caller **material** to a stable public ID under one **Digest key**:\n * the same material always yields the same ID, and the material cannot be\n * recovered from the ID (**equality leakage** is the intended property).\n *\n * - `digest` is async (WebCrypto HMAC).\n * - `is`, `parse`, `safeParse`, `toJsonSchema`, and `~standard` are synchronous\n * and require no key material — they validate prefix and base32 shape only.\n * - There is no reverse method (`unwrap`, `verify`, `extractTimestamp`) — the\n * codec is one-way by definition.\n */\nexport type DigestCodec<Brand extends string> = {\n /**\n * Digest `material` into a stable canonical {@link Id}.\n *\n * The same `(brand, ns, key, material)` tuple always returns the same ID.\n * Strings are UTF-8 encoded; byte arrays are used as-is.\n */\n digest(material: string | Uint8Array): Promise<Id<Brand>>;\n /** Strict type guard: `true` only for already-canonical `Id<Brand>` strings. */\n is(value: unknown): value is Id<Brand>;\n /** Normalise to canonical form, or throw on parse failure. */\n parse(value: unknown): Id<Brand>;\n /** Normalise to canonical form, or return `{ ok: false, error }`. */\n safeParse(value: unknown): ParseResult<Brand>;\n /** JSON Schema for the canonical wire form (`pattern` is canonical-only). */\n toJsonSchema(): JsonSchema;\n /** Standard Schema validate entry point. */\n readonly \"~standard\": StandardSchemaProps<Brand>;\n};\n\n/**\n * Construct a {@link DigestCodec} for `brand`.\n *\n * `opts.ns` is the required namespace — the same material under a\n * different `ns` yields a different ID. `opts.key` is the single operator\n * Digest key; there is no keyring.\n *\n * @example\n * ```ts\n * const key = await importDigestKey(new Uint8Array(32));\n * const idk = createDigestId(\"idk\", { ns: \"checkout\", key });\n *\n * const id = await idk.digest(\"order-123\"); // Id<\"idk\">\n * idk.is(id); // true\n * ```\n */\nexport function createDigestId<Brand extends string>(\n brand: Brand,\n opts: DigestOptions,\n): DigestCodec<Brand> {\n validateBrand(brand);\n registerBrand(brand, opts.allowDuplicateBrand);\n\n if (typeof opts.ns !== \"string\" || opts.ns.trim() === \"\") {\n throw new IdsError(\n \"invalid_namespace\",\n \"invalid namespace: ns must be a non-empty, non-whitespace string\",\n );\n }\n\n const hmacKey = getDigestKeyHmacKey(opts.key);\n const prefix: Prefix<Brand> = `${brand}_`;\n const wire = wireMethods(prefix);\n const layout = createDigestLayoutOps(prefix, brand, opts.ns, hmacKey);\n\n return {\n digest: layout.digest,\n is: wire.is,\n parse: wire.parse,\n safeParse: wire.safeParse,\n toJsonSchema: () => wire.toJsonSchema(brand, layout.exampleWireId()),\n \"~standard\": wire[\"~standard\"],\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"error-JIPylU_E.d.mts","names":[],"sources":["../src/error.ts"],"mappings":";;AAOA;;;;KAAY,YAAA;AA4BZ;;;;;;;;;;;;;;;AAAA,cAAa,QAAA,SAAiB,KAAA;EAAA,SACnB,IAAA,EAAM,YAAA;EAEf,WAAA,CAAY,IAAA,EAAM,YAAA,EAAc,OAAA,UAAiB,OAAA,GAAU,YAAA;AAAA;AAAA;AA2B7D;;;;;;;;AAAqD;;;;;AA3BQ,iBA2B7C,UAAA,CAAW,KAAA,YAAiB,KAAA,IAAS,QAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"key-material-f29JIyrz.mjs","names":[],"sources":["../src/codecs/_kernel/bytes.ts","../src/codecs/_kernel/key-material.ts"],"sourcesContent":["const hexDigits = \"0123456789abcdef\";\n\nconst invalidNibble = 0xff;\nconst hexCharCodeToNibble = new Uint8Array(128).fill(invalidNibble);\nfor (let i = 0; i < 10; i++) hexCharCodeToNibble[48 + i] = i;\nfor (let i = 0; i < 6; i++) {\n hexCharCodeToNibble[97 + i] = 10 + i;\n hexCharCodeToNibble[65 + i] = 10 + i;\n}\n\n/** Lowercase hex encoding of raw bytes. */\nexport function encodeHex(bytes: Uint8Array): string {\n // oxlint-disable-next-line no-new-array\n const codes = new Array<number>(bytes.length * 2);\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i]!;\n codes[i * 2] = hexDigits.charCodeAt(b >>> 4);\n codes[i * 2 + 1] = hexDigits.charCodeAt(b & 0x0f);\n }\n return String.fromCharCode(...codes);\n}\n\n/** Decodes a hex string to raw bytes. Throws on non-hex input. */\nexport function decodeHex(encoded: string): Uint8Array {\n if (encoded.length % 2 !== 0) throw new Error(\"invalid hex\");\n const out = new Uint8Array(encoded.length / 2);\n for (let i = 0; i < out.length; i++) {\n const hiCode = encoded.charCodeAt(i * 2);\n const loCode = encoded.charCodeAt(i * 2 + 1);\n if (hiCode >= hexCharCodeToNibble.length || loCode >= hexCharCodeToNibble.length) {\n throw new Error(\"invalid hex\");\n }\n const hi = hexCharCodeToNibble[hiCode]!;\n const lo = hexCharCodeToNibble[loCode]!;\n if (hi === invalidNibble || lo === invalidNibble) {\n throw new Error(\"invalid hex\");\n }\n out[i] = (hi << 4) | lo;\n }\n return out;\n}\n\n/** Base64url encoding without padding. */\nexport function encodeBase64Url(bytes: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Decodes a base64url string to raw bytes. Throws on invalid input. */\nexport function decodeBase64Url(encoded: string): Uint8Array {\n const base64 = encoded.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const pad = (4 - (base64.length % 4)) % 4;\n const binary = atob(base64 + \"=\".repeat(pad));\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n","import { decodeBase64Url, decodeHex, encodeBase64Url, encodeHex } from \"./bytes.js\";\nimport { IdsError } from \"../../error.js\";\n\ntype KeyMaterialFormat = \"hex\" | \"base64url\";\n\nconst validByteLengths = new Set([16, 24, 32]);\n\nfunction formatForError(value: unknown): string {\n try {\n return String(value);\n } catch {\n return \"[unprintable]\";\n }\n}\n\nfunction assertKeyMaterialFormat(\n format: unknown,\n noun: string,\n): asserts format is KeyMaterialFormat {\n if (format !== \"hex\" && format !== \"base64url\") {\n throw new IdsError(\n \"invalid_key_format\",\n `invalid ${noun} key format: expected hex or base64url, got '${formatForError(format)}'`,\n );\n }\n}\n\n/**\n * Throws `empty_keyring` when `keys` is empty.\n * `noun` appears in the message (e.g. `\"signing\"` → \"signing keyring must contain at least one key\").\n */\nfunction assertNonEmptyKeyring<K = unknown>(keys: readonly K[], noun: string): void {\n if (keys.length === 0) {\n throw new IdsError(\"empty_keyring\", `${noun} keyring must contain at least one key`);\n }\n}\n\n/**\n * Throws `duplicate_keyring_entry` when any two entries in `keys` compare equal.\n * Uses the caller-supplied constant-time `keysEqual` comparator.\n */\nfunction assertNoDuplicateKeyringEntries<K>(\n keys: readonly K[],\n keysEqual: (a: K, b: K) => boolean,\n noun: string,\n): void {\n for (let i = 0; i < keys.length; i++) {\n for (let j = i + 1; j < keys.length; j++) {\n if (keysEqual(keys[i]!, keys[j]!)) {\n throw new IdsError(\"duplicate_keyring_entry\", `duplicate ${noun} key in keyring`);\n }\n }\n }\n}\n\n/**\n * Asserts that `keys` is non-empty and contains no pairwise duplicates.\n *\n * Combines {@link assertNonEmptyKeyring} and {@link assertNoDuplicateKeyringEntries}\n * into a single call for codec constructors that validate a keyring at construction.\n *\n * @param keys - The keyring to validate.\n * @param keysEqual - Constant-time comparator (e.g. `wrappingKeysEqual`, `signingKeysEqual`).\n * @param noun - Noun used in error messages (e.g. `\"wrapping\"`, `\"signing\"`).\n */\nexport function assertValidKeyring<K>(\n keys: readonly K[],\n keysEqual: (a: K, b: K) => boolean,\n noun: string,\n): void {\n assertNonEmptyKeyring(keys, noun);\n assertNoDuplicateKeyringEntries(keys, keysEqual, noun);\n}\n\n/** Throws `invalid_key_length` when `byteLength` is not 16, 24, or 32. */\nexport function assertValidKeyMaterialByteLength(byteLength: number, noun: string): void {\n if (!validByteLengths.has(byteLength)) {\n throw new IdsError(\n \"invalid_key_length\",\n `invalid ${noun} key length: expected 16, 24, or 32 bytes, got ${byteLength}`,\n );\n }\n}\n\n/**\n * Encodes raw key bytes as hex or base64url.\n *\n * `formatNoun` appears in format error messages; `lengthNoun` in length error messages.\n * For most key types both are the same (e.g. `\"wrapping\"`, `\"signing\"`). For the\n * Opaque key, they differ (`\"opaque\"` and `\"AES\"` respectively) to preserve the\n * original human-readable messages.\n */\nexport function encodeKeyMaterial(\n bytes: Uint8Array,\n format: KeyMaterialFormat,\n formatNoun: string,\n lengthNoun: string,\n): string {\n assertKeyMaterialFormat(format, formatNoun);\n assertValidKeyMaterialByteLength(bytes.length, lengthNoun);\n if (format === \"hex\") return encodeHex(bytes);\n return encodeBase64Url(bytes);\n}\n\n/**\n * Decodes a hex or base64url-encoded key string back to raw bytes.\n *\n * `formatNoun` appears in format error messages; `lengthNoun` in length error messages.\n */\nexport function decodeKeyMaterial(\n encoded: string,\n format: KeyMaterialFormat,\n formatNoun: string,\n lengthNoun: string,\n): Uint8Array {\n assertKeyMaterialFormat(format, formatNoun);\n let bytes: Uint8Array;\n if (format === \"hex\") {\n if (encoded.length === 0 || encoded.length % 2 !== 0) {\n throw new IdsError(\n \"invalid_key_encoding\",\n \"invalid hex key: length must be a positive even number of characters\",\n );\n }\n if (!/^[0-9a-fA-F]+$/.test(encoded)) {\n throw new IdsError(\"invalid_key_encoding\", \"invalid hex key: expected [0-9a-fA-F] only\");\n }\n bytes = decodeHex(encoded);\n } else {\n try {\n bytes = decodeBase64Url(encoded);\n } catch {\n throw new IdsError(\"invalid_key_encoding\", \"invalid base64url key\");\n }\n }\n assertValidKeyMaterialByteLength(bytes.length, lengthNoun);\n return bytes;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"opaque-BQVNoIIh.mjs","names":[],"sources":["../src/codecs/opaque/layout.ts","../src/codecs/opaque/key.ts","../src/codecs/opaque/index.ts"],"sourcesContent":["import type { webcrypto } from \"node:crypto\";\nimport type { Id, Prefix } from \"../../types.js\";\nimport { payloadBytesFromId, toWireId } from \"../../wire/envelope.js\";\nimport { payloadBase32Length, payloadByteLength } from \"../../wire/invariants.js\";\nimport {\n readTimestampMs,\n timestampByteLength,\n writeTimestamp,\n} from \"../../wire/timestamp-bytes.js\";\n\nconst zeroIv = new Uint8Array(payloadByteLength);\nconst pkcsPad = 0x10;\n\nfunction buildPlaintext(ms: number, rng: (target: Uint8Array) => void): Uint8Array {\n const plaintext = new Uint8Array(payloadByteLength);\n writeTimestamp(ms, plaintext);\n rng(plaintext.subarray(timestampByteLength, payloadByteLength));\n return plaintext;\n}\n\nasync function encryptPayload(\n key: webcrypto.CryptoKey,\n plaintext: Uint8Array,\n): Promise<Uint8Array> {\n const encrypted = new Uint8Array(\n await crypto.subtle.encrypt(\n { name: \"AES-CBC\", iv: zeroIv },\n key,\n plaintext as Uint8Array<ArrayBuffer>,\n ),\n );\n return encrypted.subarray(0, payloadByteLength);\n}\n\n// AES-CBC strip-and-reconstruct decrypt (ADR-0004). The wire carries only C1\n// (16 bytes); C2 = AES_K(P2 XOR C1) where P2 is the PKCS#7 pad block (0x10×16).\n// Recompute C2 via CBC encrypt of (P2 XOR C1) with IV=0, then decrypt C1‖C2.\nasync function decryptPayload(key: webcrypto.CryptoKey, c1: Uint8Array): Promise<Uint8Array> {\n const c2Input = new Uint8Array(payloadByteLength);\n for (let i = 0; i < payloadByteLength; i++) c2Input[i] = pkcsPad ^ c1[i]!;\n const c2Encrypted = new Uint8Array(\n await crypto.subtle.encrypt(\n { name: \"AES-CBC\", iv: zeroIv },\n key,\n c2Input as Uint8Array<ArrayBuffer>,\n ),\n );\n const ciphertext = new Uint8Array(payloadByteLength * 2);\n ciphertext.set(c1, 0);\n ciphertext.set(c2Encrypted.subarray(0, payloadByteLength), payloadByteLength);\n return new Uint8Array(\n await crypto.subtle.decrypt(\n { name: \"AES-CBC\", iv: zeroIv },\n key,\n ciphertext as Uint8Array<ArrayBuffer>,\n ),\n );\n}\n\nasync function extractTimestampFromId<Brand extends string>(\n prefix: Prefix<Brand>,\n key: webcrypto.CryptoKey,\n id: Id<Brand>,\n): Promise<Date> {\n const plaintext = await decryptPayload(key, payloadBytesFromId(prefix, id));\n return new Date(readTimestampMs(plaintext));\n}\n\n/** Produces a canonical encrypted wire ID. Per-call plaintext/ciphertext buffers —\n * subtle dominates this path; reuse would be safe but not worth pinning to spec detail. */\nasync function generateWireId<Brand extends string>(\n prefix: Prefix<Brand>,\n key: webcrypto.CryptoKey,\n rng: (target: Uint8Array) => void,\n ms: number,\n): Promise<Id<Brand>> {\n const plaintext = buildPlaintext(ms, rng);\n const encrypted = await encryptPayload(key, plaintext);\n return toWireId(prefix, encrypted);\n}\n\n/** Structural placeholder for JSON Schema (encrypt is async). */\nfunction schemaExample<Brand extends string>(prefix: Prefix<Brand>): string {\n return prefix + \"0\".repeat(payloadBase32Length);\n}\n\n/** Layout ops binder for the Opaque Timestamp variant. `extractTimestampFromId` is module-private; the binder exposes `extractTimestamp` for the codec constructor. */\nexport function createOpaqueLayoutOps<Brand extends string>(\n prefix: Prefix<Brand>,\n key: webcrypto.CryptoKey,\n rng: (target: Uint8Array) => void,\n) {\n return {\n generateAt: (ms: number): Promise<Id<Brand>> => generateWireId(prefix, key, rng, ms),\n extractTimestamp: (id: Id<Brand>): Promise<Date> => extractTimestampFromId(prefix, key, id),\n exampleWireId: (): Id<Brand> => schemaExample(prefix) as Id<Brand>,\n };\n}\n","import type { webcrypto } from \"node:crypto\";\nimport {\n assertValidKeyMaterialByteLength,\n decodeKeyMaterial,\n encodeKeyMaterial,\n} from \"../_kernel/key-material.js\";\n\n/** Wire encoding for opaque AES key material (not Crockford base32). */\nexport type OpaqueKeyFormat = \"hex\" | \"base64url\";\n\ndeclare const opaqueKeyBrand: unique symbol;\n\n/**\n * Opaque imported handle for one AES key used by the Opaque Timestamp codec.\n *\n * Holds the underlying `webcrypto.CryptoKey` internally; callers never access it directly.\n * Obtain handles via {@link importOpaqueKey} and pass them to\n * `createOpaqueTimestampId` as the `key` option.\n *\n * Distinct from the `WrappingKey` used by `@smonn/ids/wrapped` — one raw\n * secret must not silently serve both codecs without an explicit import.\n */\nexport type OpaqueKey = {\n readonly [opaqueKeyBrand]: \"OpaqueKey\";\n};\n\nconst opaqueKeyInternals = new WeakMap<OpaqueKey, webcrypto.CryptoKey>();\n\n/**\n * Imports raw AES key bytes into an {@link OpaqueKey} handle for the Opaque\n * Timestamp codec.\n *\n * Accepts 16, 24, or 32 bytes (AES-128 / AES-192 / AES-256 strength).\n * To store or transport key material, use {@link encodeOpaqueKey} /\n * {@link decodeOpaqueKey} (`\"hex\"` or `\"base64url\"` — not Crockford base32).\n *\n * @param bytes - 16, 24, or 32 raw key bytes.\n */\nexport async function importOpaqueKey(bytes: Uint8Array): Promise<OpaqueKey> {\n assertValidKeyMaterialByteLength(bytes.length, \"AES\");\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n bytes as Uint8Array<ArrayBuffer>,\n \"AES-CBC\",\n false,\n [\"encrypt\", \"decrypt\"],\n );\n const key = Object.freeze({}) as OpaqueKey;\n opaqueKeyInternals.set(key, cryptoKey);\n return key;\n}\n\nexport function getOpaqueKeyCryptoKey(key: OpaqueKey): webcrypto.CryptoKey {\n const cryptoKey = opaqueKeyInternals.get(key);\n if (cryptoKey === undefined) {\n throw new Error(\"invalid opaque key\");\n }\n return cryptoKey;\n}\n\n/**\n * Encodes raw AES key bytes for storage in env vars or secret managers.\n *\n * @param bytes - 16, 24, or 32 raw key bytes (AES-128/192/256).\n * @param format - `hex` (lowercase) or `base64url`.\n */\nexport function encodeOpaqueKey(bytes: Uint8Array, format: OpaqueKeyFormat): string {\n return encodeKeyMaterial(bytes, format, \"opaque\", \"AES\");\n}\n\n/**\n * Decodes key material emitted by `encodeOpaqueKey` (or `ids keygen`) back to raw bytes.\n *\n * @param encoded - Hex or base64url string.\n * @param format - Must match how the string was encoded.\n */\nexport function decodeOpaqueKey(encoded: string, format: OpaqueKeyFormat): Uint8Array {\n return decodeKeyMaterial(encoded, format, \"opaque\", \"AES\");\n}\n","import { validateBrand } from \"../_kernel/brand.js\";\nimport { createOpaqueLayoutOps } from \"./layout.js\";\nimport { getOpaqueKeyCryptoKey, type OpaqueKey } from \"./key.js\";\nimport { registerBrand } from \"../_kernel/registry.js\";\nimport { defaultRng } from \"../_kernel/rng.js\";\nimport type { Id, JsonSchema, ParseResult, Prefix, StandardSchemaProps } from \"../../types.js\";\nimport { wireMethods } from \"../../wire/codec-shell.js\";\n\n/** {@link IdsError} class, {@link isIdsError} type guard, and {@link IdsErrorCode} union — re-exported from `\"@smonn/ids\"` for convenience. */\nexport { IdsError, isIdsError, type IdsErrorCode } from \"../../error.js\";\nexport {\n decodeOpaqueKey,\n encodeOpaqueKey,\n importOpaqueKey,\n type OpaqueKey,\n type OpaqueKeyFormat,\n} from \"./key.js\";\n\n/**\n * Configuration options for an Opaque Timestamp codec instance.\n */\nexport type OpaqueTimestampOptions = {\n /**\n * {@link OpaqueKey} handle for AES-CBC encryption and decryption.\n * Obtain via {@link importOpaqueKey}.\n *\n * A single key, not a ring: rotation is forward-only and caller-tracked —\n * hold one codec per key epoch and select it from your own records. The\n * library cannot trial keys (the payload is unauthenticated). See ADR-0013.\n */\n key: OpaqueKey;\n /** Returns the current timestamp in milliseconds. Defaults to `Date.now`. */\n now?: () => number;\n /** Writes random bytes into `target` for ID generation. Defaults to `crypto.getRandomValues`. */\n rng?: (target: Uint8Array) => void;\n /** If true, silences the duplicate-brand warning in non-production environments. */\n allowDuplicateBrand?: boolean;\n};\n\n/**\n * A brand-scoped codec for generating and validating Opaque Timestamp IDs.\n *\n * Same wire shape as the Timestamp codec (`{brand}_` + 26 base32 chars) but the\n * payload is AES-CBC encrypted. `generate`, `generateAt`, and `extractTimestamp`\n * are async; parsing methods are sync. No `minIdForTime` / `maxIdForTime` —\n * encrypted payloads do not sort by creation time.\n */\nexport type OpaqueTimestampCodec<Brand extends string> = {\n /** Produces a new canonical encrypted ID using the codec's `now` and `rng`. */\n generate(): Promise<Id<Brand>>;\n /** Produces a new canonical encrypted ID with timestamp bytes from `date`. Throws on invalid dates. */\n generateAt(date: Date): Promise<Id<Brand>>;\n /**\n * Strict type guard: `true` only for already-canonical strings for this brand.\n * For untrusted input, use `safeParse()` or `parse()` instead. See ADR-0003.\n */\n is(value: unknown): value is Id<Brand>;\n /**\n * Lenient parse: normalises case and Crockford aliases, returns canonical `Id<Brand>`, or throws.\n */\n parse(value: unknown): Id<Brand>;\n /**\n * Lenient parse without throwing: normalises to canonical form, or returns `{ ok: false, error }`.\n */\n safeParse(value: unknown): ParseResult<Brand>;\n /**\n * Decrypts and decodes the creation `Date` from an `Id<Brand>`. Trusts the type — use `safeParse()` at boundaries first. See ADR-0002.\n *\n * Requires the same key used at generation; a wrong key returns a plausible\n * but wrong `Date`, never an error. With rotation, select the codec for the\n * ID's key epoch from your own records — the library cannot. See ADR-0013.\n */\n extractTimestamp(id: Id<Brand>): Promise<Date>;\n /** JSON Schema for the canonical wire form (`example` is a structural placeholder). */\n toJsonSchema(): JsonSchema;\n /** Standard Schema validate entry point. */\n readonly \"~standard\": StandardSchemaProps<Brand>;\n};\n\n/**\n * Creates an Opaque Timestamp codec for `brand` (three lowercase a–z characters).\n *\n * @param brand - Entity type brand validated once at construction.\n * @param opts - Required `key` (an {@link OpaqueKey} from {@link importOpaqueKey}) plus\n * optional `now`, `rng`, and `allowDuplicateBrand` overrides.\n */\nexport function createOpaqueTimestampId<Brand extends string>(\n brand: Brand,\n opts: OpaqueTimestampOptions,\n): OpaqueTimestampCodec<Brand> {\n validateBrand(brand);\n registerBrand(brand, opts.allowDuplicateBrand);\n\n const cryptoKey = getOpaqueKeyCryptoKey(opts.key);\n const now = opts.now ?? Date.now;\n const rng = opts.rng ?? defaultRng;\n const prefix: Prefix<Brand> = `${brand}_`;\n const wire = wireMethods(prefix);\n const layout = createOpaqueLayoutOps(prefix, cryptoKey, rng);\n\n return {\n generate: () => layout.generateAt(now()),\n generateAt: (date: Date) => layout.generateAt(date.getTime()),\n is: wire.is,\n parse: wire.parse,\n safeParse: wire.safeParse,\n extractTimestamp: layout.extractTimestamp,\n toJsonSchema: () => wire.toJsonSchema(brand, layout.exampleWireId()),\n \"~standard\": wire[\"~standard\"],\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"reverse-DsPd7Lco.mjs","names":[],"sources":["../src/codecs/reverse/layout.ts","../src/codecs/reverse/index.ts"],"sourcesContent":["import type { Id, Prefix } from \"../../types.js\";\nimport { payloadBytesFromId, toWireId } from \"../../wire/envelope.js\";\nimport { payloadByteLength } from \"../../wire/invariants.js\";\nimport { timestampByteLength, writeTimestamp } from \"../../wire/timestamp-bytes.js\";\n\nconst randomByteLength: number = payloadByteLength - timestampByteLength;\n\n/** Writes inverted timestamp bytes, then fills random portion. */\nfunction buildReversePayload(\n ms: number,\n rng: (target: Uint8Array) => void,\n buffer: Uint8Array,\n randomView: Uint8Array,\n): void {\n writeTimestamp(ms, buffer);\n for (let i = 0; i < timestampByteLength; i++) {\n buffer[i] = ~buffer[i]! & 0xff;\n }\n rng(randomView);\n}\n\n/** Writes inverted timestamp bytes, then fills random portion with a sentinel. */\nfunction buildReverseSentinelPayload(\n ms: number,\n fill: number,\n buffer: Uint8Array,\n randomView: Uint8Array,\n): void {\n writeTimestamp(ms, buffer);\n for (let i = 0; i < timestampByteLength; i++) {\n buffer[i] = ~buffer[i]! & 0xff;\n }\n randomView.fill(fill);\n}\n\n/** Decodes the original timestamp by inverting the first 6 payload bytes. */\nfunction extractReverseTimestampFromId<Brand extends string>(\n prefix: Prefix<Brand>,\n id: Id<Brand>,\n): Date {\n const bytes = payloadBytesFromId(prefix, id);\n let ms = 0;\n for (let i = 0; i < timestampByteLength; i++) {\n ms = ms * 256 + (~bytes[i]! & 0xff);\n }\n return new Date(ms);\n}\n\n/** Layout ops binder for the Reverse Timestamp variant. */\nexport function createReverseTimestampLayoutOps<Brand extends string>(\n prefix: Prefix<Brand>,\n rng: (target: Uint8Array) => void,\n) {\n const buffer = new Uint8Array(payloadByteLength);\n const randomView = new Uint8Array(buffer.buffer, timestampByteLength, randomByteLength);\n\n return {\n generateAt: (ms: number): Id<Brand> => {\n buildReversePayload(ms, rng, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n extractTimestamp: (id: Id<Brand>): Date => extractReverseTimestampFromId(prefix, id),\n minIdForTime: (ms: number): Id<Brand> => {\n buildReverseSentinelPayload(ms, 0x00, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n maxIdForTime: (ms: number): Id<Brand> => {\n buildReverseSentinelPayload(ms, 0xff, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n exampleWireId: (ms: number): Id<Brand> => {\n buildReversePayload(ms, rng, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n };\n}\n","import { validateBrand } from \"../_kernel/brand.js\";\nimport { IdsError, isIdsError, type IdsErrorCode } from \"../../error.js\";\nimport { createReverseTimestampLayoutOps } from \"./layout.js\";\nimport { registerBrand } from \"../_kernel/registry.js\";\nimport { fastTenByteRng } from \"../_kernel/rng.js\";\nimport type { Id, JsonSchema, ParseResult, Prefix, StandardSchemaProps } from \"../../types.js\";\nimport { wireMethods } from \"../../wire/codec-shell.js\";\n\n/** {@link IdsError} class, {@link isIdsError} type guard, and {@link IdsErrorCode} union — re-exported from `\"@smonn/ids\"` for convenience. */\nexport { IdsError, isIdsError, type IdsErrorCode };\n\n/**\n * Configuration options for a Reverse Timestamp codec instance.\n */\nexport type ReverseTimestampOptions = {\n /** Returns the current timestamp in milliseconds. Defaults to `Date.now`. */\n now?: () => number;\n /** Writes the 10-byte random tail into `target`. Defaults to a `crypto.randomUUID` harvest fast path (same as the Timestamp codec). */\n rng?: (target: Uint8Array) => void;\n /** If true, silences the duplicate-brand warning in non-production environments. */\n allowDuplicateBrand?: boolean;\n};\n\n/**\n * A brand-scoped codec for generating and validating Reverse Timestamp IDs.\n *\n * Wire format: `{brand}_` plus 26 lowercase Crockford base32 characters encoding a\n * 16-byte payload (6-byte bitwise-inverted ms timestamp + 10 random bytes). IDs sort\n * by creation time in **descending** (newest-first) order.\n *\n * Range queries across a time interval [t_old, t_new] should scan from\n * `minIdForTime(t_new)` to `maxIdForTime(t_old)` — the reversed sort order means\n * newer timestamps produce lexicographically smaller IDs.\n *\n * Constructed via `createReverseTimestampId(brand)` from `@smonn/ids/reverse`.\n */\nexport type ReverseTimestampCodec<Brand extends string> = {\n /** Produces a new canonical ID using the codec's `now` and `rng`. */\n generate(): Id<Brand>;\n /** Produces a new canonical ID with timestamp bytes from `date` and a fresh random tail. Throws on invalid dates. */\n generateAt(date: Date): Id<Brand>;\n /**\n * Strict type guard: `true` only for already-canonical strings for this brand.\n * For untrusted input, use `safeParse()` or `parse()` instead. See ADR-0003.\n */\n is(value: unknown): value is Id<Brand>;\n /**\n * Lenient parse: normalises case and Crockford aliases, returns canonical `Id<Brand>`, or throws.\n */\n parse(value: unknown): Id<Brand>;\n /**\n * Lenient parse without throwing: normalises to canonical form, or returns `{ ok: false, error }`.\n */\n safeParse(value: unknown): ParseResult<Brand>;\n /**\n * Decodes the creation `Date` from an `Id<Brand>` by inverting the timestamp bytes.\n * Trusts the type — use `safeParse()` at boundaries first.\n */\n extractTimestamp(id: Id<Brand>): Date;\n /**\n * Lexicographically smallest ID for any ID generated at `date` (random portion `0x00`).\n * Because timestamps are inverted, a newer `date` yields a lexicographically smaller result —\n * use `minIdForTime(t_new)` as the lower bound when scanning [t_old, t_new].\n * Throws on invalid dates.\n */\n minIdForTime(date: Date): Id<Brand>;\n /**\n * Lexicographically largest ID for any ID generated at `date` (random portion `0xff`).\n * Because timestamps are inverted, an older `date` yields a lexicographically larger result —\n * use `maxIdForTime(t_old)` as the upper bound when scanning [t_old, t_new].\n * Throws on invalid dates.\n */\n maxIdForTime(date: Date): Id<Brand>;\n /** JSON Schema for the canonical wire form (`pattern` is canonical-only). */\n toJsonSchema(): JsonSchema;\n /** Standard Schema validate entry point. */\n readonly \"~standard\": StandardSchemaProps<Brand>;\n};\n\n/**\n * Creates a Reverse Timestamp codec for `brand` (three lowercase a–z characters).\n *\n * IDs sort newest-first: the 48-bit timestamp field is bitwise-inverted before encoding,\n * so lexicographic ID order equals descending creation-time order. `extractTimestamp`\n * inverts back to recover the original millisecond.\n *\n * @param brand - Entity type brand validated once at construction.\n * @param opts - Optional `now`, `rng`, and `allowDuplicateBrand` overrides.\n */\nexport function createReverseTimestampId<Brand extends string>(\n brand: Brand,\n opts: ReverseTimestampOptions = {},\n): ReverseTimestampCodec<Brand> {\n validateBrand(brand);\n registerBrand(brand, opts.allowDuplicateBrand);\n\n const now = opts.now ?? Date.now;\n const rng = opts.rng ?? fastTenByteRng;\n const prefix: Prefix<Brand> = `${brand}_`;\n const wire = wireMethods(prefix);\n const layout = createReverseTimestampLayoutOps(prefix, rng);\n\n return {\n generate: () => layout.generateAt(now()),\n generateAt: (date: Date) => layout.generateAt(date.getTime()),\n is: wire.is,\n parse: wire.parse,\n safeParse: wire.safeParse,\n extractTimestamp: layout.extractTimestamp,\n minIdForTime: (date: Date) => layout.minIdForTime(date.getTime()),\n maxIdForTime: (date: Date) => layout.maxIdForTime(date.getTime()),\n toJsonSchema: () => wire.toJsonSchema(brand, layout.exampleWireId(now())),\n \"~standard\": wire[\"~standard\"],\n };\n}\n"],"mappings":";;;AAKA,MAAM,mBAAA;;AAGN,SAAS,oBACP,IACA,KACA,QACA,YACM;CACN,eAAe,IAAI,MAAM;CACzB,KAAK,IAAI,IAAI,GAAG,IAAA,GAAyB,KACvC,OAAO,KAAK,CAAC,OAAO,KAAM;CAE5B,IAAI,UAAU;AAChB;;AAGA,SAAS,4BACP,IACA,MACA,QACA,YACM;CACN,eAAe,IAAI,MAAM;CACzB,KAAK,IAAI,IAAI,GAAG,IAAA,GAAyB,KACvC,OAAO,KAAK,CAAC,OAAO,KAAM;CAE5B,WAAW,KAAK,IAAI;AACtB;;AAGA,SAAS,8BACP,QACA,IACM;CACN,MAAM,QAAQ,mBAAmB,QAAQ,EAAE;CAC3C,IAAI,KAAK;CACT,KAAK,IAAI,IAAI,GAAG,IAAA,GAAyB,KACvC,KAAK,KAAK,OAAO,CAAC,MAAM,KAAM;CAEhC,OAAO,IAAI,KAAK,EAAE;AACpB;;AAGA,SAAgB,gCACd,QACA,KACA;CACA,MAAM,yBAAS,IAAI,WAAA,EAA4B;CAC/C,MAAM,aAAa,IAAI,WAAW,OAAO,QAAA,GAA6B,gBAAgB;CAEtF,OAAO;EACL,aAAa,OAA0B;GACrC,oBAAoB,IAAI,KAAK,QAAQ,UAAU;GAC/C,OAAO,SAAS,QAAQ,MAAM;EAChC;EACA,mBAAmB,OAAwB,8BAA8B,QAAQ,EAAE;EACnF,eAAe,OAA0B;GACvC,4BAA4B,IAAI,GAAM,QAAQ,UAAU;GACxD,OAAO,SAAS,QAAQ,MAAM;EAChC;EACA,eAAe,OAA0B;GACvC,4BAA4B,IAAI,KAAM,QAAQ,UAAU;GACxD,OAAO,SAAS,QAAQ,MAAM;EAChC;EACA,gBAAgB,OAA0B;GACxC,oBAAoB,IAAI,KAAK,QAAQ,UAAU;GAC/C,OAAO,SAAS,QAAQ,MAAM;EAChC;CACF;AACF;;;;;;;;;;;;;ACcA,SAAgB,yBACd,OACA,OAAgC,CAAC,GACH;CAC9B,cAAc,KAAK;CACnB,cAAc,OAAO,KAAK,mBAAmB;CAE7C,MAAM,MAAM,KAAK,OAAO,KAAK;CAC7B,MAAM,MAAM,KAAK,OAAO;CACxB,MAAM,SAAwB,GAAG,MAAM;CACvC,MAAM,OAAO,YAAY,MAAM;CAC/B,MAAM,SAAS,gCAAgC,QAAQ,GAAG;CAE1D,OAAO;EACL,gBAAgB,OAAO,WAAW,IAAI,CAAC;EACvC,aAAa,SAAe,OAAO,WAAW,KAAK,QAAQ,CAAC;EAC5D,IAAI,KAAK;EACT,OAAO,KAAK;EACZ,WAAW,KAAK;EAChB,kBAAkB,OAAO;EACzB,eAAe,SAAe,OAAO,aAAa,KAAK,QAAQ,CAAC;EAChE,eAAe,SAAe,OAAO,aAAa,KAAK,QAAQ,CAAC;EAChE,oBAAoB,KAAK,aAAa,OAAO,OAAO,cAAc,IAAI,CAAC,CAAC;EACxE,aAAa,KAAK;CACpB;AACF"}
|