@smonn/ids 0.3.5 → 0.5.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 +53 -33
- package/dist/bytes-lhzKVaBV.mjs +53 -0
- package/dist/bytes-lhzKVaBV.mjs.map +1 -0
- package/dist/cli.mjs +6 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/{codec-shell-C0arqqX3.mjs → codec-shell-dWpxoFmy.mjs} +2 -23
- package/dist/codec-shell-dWpxoFmy.mjs.map +1 -0
- package/dist/index.d.mts +9 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{opaque-2WH8PojZ.mjs → opaque-B4ps7Pqk.mjs} +9 -57
- package/dist/opaque-B4ps7Pqk.mjs.map +1 -0
- package/dist/opaque.d.mts +11 -13
- package/dist/opaque.d.mts.map +1 -1
- package/dist/opaque.mjs +2 -2
- package/dist/{id-CcoPE2EX.mjs → timestamp-Bgzxx8bE.mjs} +9 -8
- package/dist/timestamp-Bgzxx8bE.mjs.map +1 -0
- package/dist/timestamp-bytes-B57RM7Ho.mjs +26 -0
- package/dist/timestamp-bytes-B57RM7Ho.mjs.map +1 -0
- package/dist/{types-Bv-63YK4.d.mts → types-g7CiQDyE.d.mts} +2 -2
- package/dist/{types-Bv-63YK4.d.mts.map → types-g7CiQDyE.d.mts.map} +1 -1
- package/dist/wrapped.d.mts +55 -0
- package/dist/wrapped.d.mts.map +1 -0
- package/dist/wrapped.mjs +336 -0
- package/dist/wrapped.mjs.map +1 -0
- package/package.json +6 -5
- package/dist/codec-shell-C0arqqX3.mjs.map +0 -1
- package/dist/id-CcoPE2EX.mjs.map +0 -1
- package/dist/opaque-2WH8PojZ.mjs.map +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as toWireId, i as payloadBytesFromId, n as registerBrand, r as payloadBase32Length, s as validateBrand, t as wireMethods } from "./codec-shell-dWpxoFmy.mjs";
|
|
2
|
+
import { r as writeTimestamp, t as readTimestampMs } from "./timestamp-bytes-B57RM7Ho.mjs";
|
|
3
|
+
import { i as encodeHex, n as decodeHex, r as encodeBase64Url, t as decodeBase64Url } from "./bytes-lhzKVaBV.mjs";
|
|
2
4
|
//#region src/layouts/opaque.ts
|
|
3
5
|
const zeroIv = new Uint8Array(16);
|
|
4
6
|
const pkcsPad = 16;
|
|
@@ -42,7 +44,7 @@ async function generateWireId(prefix, key, rng, ms) {
|
|
|
42
44
|
function schemaExample(prefix) {
|
|
43
45
|
return prefix + "0".repeat(payloadBase32Length);
|
|
44
46
|
}
|
|
45
|
-
/** Layout ops binder for the Opaque variant. `extractTimestampFromId` is module-private; the binder exposes `extractTimestamp` for the codec constructor. */
|
|
47
|
+
/** Layout ops binder for the Opaque Timestamp variant. `extractTimestampFromId` is module-private; the binder exposes `extractTimestamp` for the codec constructor. */
|
|
46
48
|
function createOpaqueLayoutOps(prefix, key, rng) {
|
|
47
49
|
return {
|
|
48
50
|
generateAt: (ms) => generateWireId(prefix, key, rng, ms),
|
|
@@ -51,56 +53,6 @@ function createOpaqueLayoutOps(prefix, key, rng) {
|
|
|
51
53
|
};
|
|
52
54
|
}
|
|
53
55
|
//#endregion
|
|
54
|
-
//#region src/bytes.ts
|
|
55
|
-
const hexDigits = "0123456789abcdef";
|
|
56
|
-
const invalidNibble = 255;
|
|
57
|
-
const hexCharCodeToNibble = new Uint8Array(128).fill(invalidNibble);
|
|
58
|
-
for (let i = 0; i < 10; i++) hexCharCodeToNibble[48 + i] = i;
|
|
59
|
-
for (let i = 0; i < 6; i++) {
|
|
60
|
-
hexCharCodeToNibble[97 + i] = 10 + i;
|
|
61
|
-
hexCharCodeToNibble[65 + i] = 10 + i;
|
|
62
|
-
}
|
|
63
|
-
/** Lowercase hex encoding of raw bytes. */
|
|
64
|
-
function encodeHex(bytes) {
|
|
65
|
-
const codes = new Array(bytes.length * 2);
|
|
66
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
67
|
-
const b = bytes[i];
|
|
68
|
-
codes[i * 2] = hexDigits.charCodeAt(b >>> 4);
|
|
69
|
-
codes[i * 2 + 1] = hexDigits.charCodeAt(b & 15);
|
|
70
|
-
}
|
|
71
|
-
return String.fromCharCode(...codes);
|
|
72
|
-
}
|
|
73
|
-
/** Decodes a hex string to raw bytes. Throws on non-hex input. */
|
|
74
|
-
function decodeHex(encoded) {
|
|
75
|
-
if (encoded.length % 2 !== 0) throw new Error("invalid hex");
|
|
76
|
-
const out = new Uint8Array(encoded.length / 2);
|
|
77
|
-
for (let i = 0; i < out.length; i++) {
|
|
78
|
-
const hiCode = encoded.charCodeAt(i * 2);
|
|
79
|
-
const loCode = encoded.charCodeAt(i * 2 + 1);
|
|
80
|
-
if (hiCode >= hexCharCodeToNibble.length || loCode >= hexCharCodeToNibble.length) throw new Error("invalid hex");
|
|
81
|
-
const hi = hexCharCodeToNibble[hiCode];
|
|
82
|
-
const lo = hexCharCodeToNibble[loCode];
|
|
83
|
-
if (hi === invalidNibble || lo === invalidNibble) throw new Error("invalid hex");
|
|
84
|
-
out[i] = hi << 4 | lo;
|
|
85
|
-
}
|
|
86
|
-
return out;
|
|
87
|
-
}
|
|
88
|
-
/** Base64url encoding without padding. */
|
|
89
|
-
function encodeBase64Url(bytes) {
|
|
90
|
-
let binary = "";
|
|
91
|
-
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
92
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
93
|
-
}
|
|
94
|
-
/** Decodes a base64url string to raw bytes. Throws on invalid input. */
|
|
95
|
-
function decodeBase64Url(encoded) {
|
|
96
|
-
const base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
97
|
-
const pad = (4 - base64.length % 4) % 4;
|
|
98
|
-
const binary = atob(base64 + "=".repeat(pad));
|
|
99
|
-
const out = new Uint8Array(binary.length);
|
|
100
|
-
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
101
|
-
return out;
|
|
102
|
-
}
|
|
103
|
-
//#endregion
|
|
104
56
|
//#region src/opaque-key.ts
|
|
105
57
|
const validAesKeyByteLengths = new Set([
|
|
106
58
|
16,
|
|
@@ -159,7 +111,7 @@ function defaultRng(target) {
|
|
|
159
111
|
crypto.getRandomValues(target);
|
|
160
112
|
}
|
|
161
113
|
/**
|
|
162
|
-
* Imports a raw AES key for use with the Opaque codec.
|
|
114
|
+
* Imports a raw AES key for use with the Opaque Timestamp codec.
|
|
163
115
|
*
|
|
164
116
|
* @param bytes - Raw key bytes (16, 24, or 32 bytes for AES-128/192/256).
|
|
165
117
|
*/
|
|
@@ -167,12 +119,12 @@ function importOpaqueKey(bytes) {
|
|
|
167
119
|
return crypto.subtle.importKey("raw", bytes, "AES-CBC", false, ["encrypt", "decrypt"]);
|
|
168
120
|
}
|
|
169
121
|
/**
|
|
170
|
-
* Creates an Opaque codec for `brand` (three lowercase a–z characters).
|
|
122
|
+
* Creates an Opaque Timestamp codec for `brand` (three lowercase a–z characters).
|
|
171
123
|
*
|
|
172
124
|
* @param brand - Entity type brand validated once at construction.
|
|
173
125
|
* @param opts - Required `key` plus optional `now`, `rng`, and `allowDuplicateBrand` overrides.
|
|
174
126
|
*/
|
|
175
|
-
function
|
|
127
|
+
function createOpaqueTimestampId(brand, opts) {
|
|
176
128
|
validateBrand(brand);
|
|
177
129
|
registerBrand(brand, opts.allowDuplicateBrand);
|
|
178
130
|
const key = opts.key;
|
|
@@ -193,6 +145,6 @@ function createOpaqueId(brand, opts) {
|
|
|
193
145
|
};
|
|
194
146
|
}
|
|
195
147
|
//#endregion
|
|
196
|
-
export { encodeOpaqueKey as i, importOpaqueKey as n, decodeOpaqueKey as r,
|
|
148
|
+
export { encodeOpaqueKey as i, importOpaqueKey as n, decodeOpaqueKey as r, createOpaqueTimestampId as t };
|
|
197
149
|
|
|
198
|
-
//# sourceMappingURL=opaque-
|
|
150
|
+
//# sourceMappingURL=opaque-B4ps7Pqk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opaque-B4ps7Pqk.mjs","names":[],"sources":["../src/layouts/opaque.ts","../src/opaque-key.ts","../src/opaque.ts"],"sourcesContent":["import type { Id, Prefix } from \"../types.js\";\nimport { payloadBytesFromId, toWireId } from \"../wire/envelope.js\";\nimport { payloadBase32Length, payloadByteLength } from \"../wire/invariants.js\";\nimport { readTimestampMs, timestampByteLength, writeTimestamp } 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(key: CryptoKey, plaintext: Uint8Array): 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: 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: 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: 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: 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 { decodeBase64Url, decodeHex, encodeBase64Url, encodeHex } from \"./bytes.js\";\n\n/** Wire encoding for opaque AES key material (not Crockford base32). */\nexport type OpaqueKeyFormat = \"hex\" | \"base64url\";\n\nconst validAesKeyByteLengths = new Set([16, 24, 32]);\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 assertOpaqueKeyFormat(format);\n assertValidAesKeyByteLength(bytes.length);\n if (format === \"hex\") return encodeHex(bytes);\n return encodeBase64Url(bytes);\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 assertOpaqueKeyFormat(format);\n let bytes: Uint8Array;\n if (format === \"hex\") {\n if (encoded.length === 0 || encoded.length % 2 !== 0) {\n throw new Error(\"invalid hex key: length must be a positive even number of characters\");\n }\n if (!/^[0-9a-fA-F]+$/.test(encoded)) {\n throw new Error(\"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 Error(\"invalid base64url key\");\n }\n }\n assertValidAesKeyByteLength(bytes.length);\n return bytes;\n}\n\nfunction assertValidAesKeyByteLength(byteLength: number): void {\n if (!validAesKeyByteLengths.has(byteLength)) {\n throw new Error(`invalid AES key length: expected 16, 24, or 32 bytes, got ${byteLength}`);\n }\n}\n\nfunction assertOpaqueKeyFormat(format: unknown): asserts format is OpaqueKeyFormat {\n if (format !== \"hex\" && format !== \"base64url\") {\n throw new Error(\n `invalid opaque key format: expected hex or base64url, got '${formatForError(format)}'`,\n );\n }\n}\n\nfunction formatForError(value: unknown): string {\n try {\n return String(value);\n } catch {\n return \"[unprintable]\";\n }\n}\n","import { validateBrand } from \"./brand.js\";\nimport { createOpaqueLayoutOps } from \"./layouts/opaque.js\";\nimport { registerBrand } from \"./registry.js\";\nimport type { Id, JsonSchema, ParseResult, Prefix, StandardSchemaProps } from \"./types.js\";\nimport { wireMethods } from \"./wire/codec-shell.js\";\n\nexport { decodeOpaqueKey, encodeOpaqueKey, type OpaqueKeyFormat } from \"./opaque-key.js\";\n\n/**\n * Configuration options for an Opaque Timestamp codec instance.\n */\nexport type OpaqueTimestampOptions = {\n /** AES-CBC key used for encryption and decryption. */\n key: CryptoKey;\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 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\nfunction defaultRng(target: Uint8Array): void {\n crypto.getRandomValues(target as Uint8Array<ArrayBuffer>);\n}\n\n/**\n * Imports a raw AES key for use with the Opaque Timestamp codec.\n *\n * @param bytes - Raw key bytes (16, 24, or 32 bytes for AES-128/192/256).\n */\nexport function importOpaqueKey(bytes: Uint8Array): Promise<CryptoKey> {\n return crypto.subtle.importKey(\"raw\", bytes as Uint8Array<ArrayBuffer>, \"AES-CBC\", false, [\n \"encrypt\",\n \"decrypt\",\n ]);\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` plus 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 key = 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, key, 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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"}
|
package/dist/opaque.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as StandardSchemaProps, i as ParseResult, n as JsonSchema, t as Id } from "./types-
|
|
1
|
+
import { a as StandardSchemaProps, i as ParseResult, n as JsonSchema, t as Id } from "./types-g7CiQDyE.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/opaque-key.d.ts
|
|
4
4
|
/** Wire encoding for opaque AES key material (not Crockford base32). */
|
|
@@ -20,23 +20,23 @@ declare function decodeOpaqueKey(encoded: string, format: OpaqueKeyFormat): Uint
|
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/opaque.d.ts
|
|
22
22
|
/**
|
|
23
|
-
* Configuration options for an Opaque codec instance.
|
|
23
|
+
* Configuration options for an Opaque Timestamp codec instance.
|
|
24
24
|
*/
|
|
25
|
-
type
|
|
25
|
+
type OpaqueTimestampOptions = {
|
|
26
26
|
/** AES-CBC key used for encryption and decryption. */key: CryptoKey; /** Returns the current timestamp in milliseconds. Defaults to `Date.now`. */
|
|
27
|
-
now
|
|
28
|
-
rng
|
|
27
|
+
now?: () => number; /** Writes random bytes into `target` for ID generation. Defaults to `crypto.getRandomValues`. */
|
|
28
|
+
rng?: (target: Uint8Array) => void; /** If true, silences the duplicate-brand warning in non-production environments. */
|
|
29
29
|
allowDuplicateBrand?: boolean;
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
32
|
-
* A brand-scoped codec for generating and validating
|
|
32
|
+
* A brand-scoped codec for generating and validating Opaque Timestamp IDs.
|
|
33
33
|
*
|
|
34
34
|
* Same wire shape as the Timestamp codec (`{brand}_` + 26 base32 chars) but the
|
|
35
35
|
* payload is AES-CBC encrypted. `generate`, `generateAt`, and `extractTimestamp`
|
|
36
36
|
* are async; parsing methods are sync. No `minIdForTime` / `maxIdForTime` —
|
|
37
37
|
* encrypted payloads do not sort by creation time.
|
|
38
38
|
*/
|
|
39
|
-
type
|
|
39
|
+
type OpaqueTimestampCodec<Brand extends string> = {
|
|
40
40
|
/** Produces a new canonical encrypted ID using the codec's `now` and `rng`. */generate(): Promise<Id<Brand>>; /** Produces a new canonical encrypted ID with timestamp bytes from `date`. Throws on invalid dates. */
|
|
41
41
|
generateAt(date: Date): Promise<Id<Brand>>;
|
|
42
42
|
/**
|
|
@@ -60,20 +60,18 @@ type OpaqueCodec<Brand extends string> = {
|
|
|
60
60
|
readonly "~standard": StandardSchemaProps<Brand>;
|
|
61
61
|
};
|
|
62
62
|
/**
|
|
63
|
-
* Imports a raw AES key for use with the Opaque codec.
|
|
63
|
+
* Imports a raw AES key for use with the Opaque Timestamp codec.
|
|
64
64
|
*
|
|
65
65
|
* @param bytes - Raw key bytes (16, 24, or 32 bytes for AES-128/192/256).
|
|
66
66
|
*/
|
|
67
67
|
declare function importOpaqueKey(bytes: Uint8Array): Promise<CryptoKey>;
|
|
68
68
|
/**
|
|
69
|
-
* Creates an Opaque codec for `brand` (three lowercase a–z characters).
|
|
69
|
+
* Creates an Opaque Timestamp codec for `brand` (three lowercase a–z characters).
|
|
70
70
|
*
|
|
71
71
|
* @param brand - Entity type brand validated once at construction.
|
|
72
72
|
* @param opts - Required `key` plus optional `now`, `rng`, and `allowDuplicateBrand` overrides.
|
|
73
73
|
*/
|
|
74
|
-
declare function
|
|
75
|
-
key: CryptoKey;
|
|
76
|
-
} & Partial<Omit<OpaqueOptions, "key">>): OpaqueCodec<Brand>;
|
|
74
|
+
declare function createOpaqueTimestampId<Brand extends string>(brand: Brand, opts: OpaqueTimestampOptions): OpaqueTimestampCodec<Brand>;
|
|
77
75
|
//#endregion
|
|
78
|
-
export {
|
|
76
|
+
export { type OpaqueKeyFormat, OpaqueTimestampCodec, OpaqueTimestampOptions, createOpaqueTimestampId, decodeOpaqueKey, encodeOpaqueKey, importOpaqueKey };
|
|
79
77
|
//# sourceMappingURL=opaque.d.mts.map
|
package/dist/opaque.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opaque.d.mts","names":[],"sources":["../src/opaque-key.ts","../src/opaque.ts"],"mappings":";;;;KAGY,eAAA;;AAAZ;;;;AAAY;iBAUI,eAAA,CAAgB,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,eAAA;;;;;;;iBAa3C,eAAA,CAAgB,OAAA,UAAiB,MAAA,EAAQ,eAAA,GAAkB,UAAA;;;;AAvB3E;;KCQY,
|
|
1
|
+
{"version":3,"file":"opaque.d.mts","names":[],"sources":["../src/opaque-key.ts","../src/opaque.ts"],"mappings":";;;;KAGY,eAAA;;AAAZ;;;;AAAY;iBAUI,eAAA,CAAgB,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,eAAA;;;;;;;iBAa3C,eAAA,CAAgB,OAAA,UAAiB,MAAA,EAAQ,eAAA,GAAkB,UAAA;;;;AAvB3E;;KCQY,sBAAA;EDRA,sDCUV,GAAA,EAAK,SAAA,EDAP;ECEE,GAAA;EAEA,GAAA,IAAO,MAAA,EAAQ,UAAA;EAEf,mBAAA;AAAA;;;ADNyD;AAa3D;;;;;KCIY,oBAAA;iFAEV,QAAA,IAAY,OAAA,CAAQ,EAAA,CAAG,KAAA,IDNkD;ECQzE,UAAA,CAAW,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,EAAA,CAAG,KAAA;;;;AAvBrC;EA4BE,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;;;;EAIhC,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;;;;EAI1B,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;;;AA5BvC;EAgCA,gBAAA,CAAiB,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,OAAA,CAAQ,IAAA,GArB/B;EAuBV,YAAA,IAAgB,UAAA;WAEP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;;;;;;iBAY5B,eAAA,CAAgB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,SAAA;;;;;;;iBAa5C,uBAAA,uBACd,KAAA,EAAO,KAAA,EACP,IAAA,EAAM,sBAAA,GACL,oBAAA,CAAqB,KAAA"}
|
package/dist/opaque.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as encodeOpaqueKey, n as importOpaqueKey, r as decodeOpaqueKey, t as
|
|
2
|
-
export {
|
|
1
|
+
import { i as encodeOpaqueKey, n as importOpaqueKey, r as decodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-B4ps7Pqk.mjs";
|
|
2
|
+
export { createOpaqueTimestampId, decodeOpaqueKey, encodeOpaqueKey, importOpaqueKey };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as toWireId, n as registerBrand, s as validateBrand, t as wireMethods } from "./codec-shell-dWpxoFmy.mjs";
|
|
2
|
+
import { n as readTimestampMsFromBase32Suffix, r as writeTimestamp } from "./timestamp-bytes-B57RM7Ho.mjs";
|
|
2
3
|
//#region src/layouts/timestamp.ts
|
|
3
4
|
const randomByteLength = 10;
|
|
4
5
|
/** Writes a 16-byte timestamp-layout payload into codec-owned scratch. */
|
|
@@ -40,11 +41,11 @@ function createTimestampLayoutOps(prefix, rng) {
|
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
//#endregion
|
|
43
|
-
//#region src/
|
|
44
|
+
//#region src/timestamp.ts
|
|
44
45
|
const hexCharCodeToNibble = new Uint8Array(128);
|
|
45
46
|
for (let i = 0; i < 10; i++) hexCharCodeToNibble[48 + i] = i;
|
|
46
47
|
for (let i = 0; i < 6; i++) hexCharCodeToNibble[97 + i] = 10 + i;
|
|
47
|
-
const
|
|
48
|
+
const defaultTimestampOptions = {
|
|
48
49
|
now: Date.now,
|
|
49
50
|
rng: (target) => {
|
|
50
51
|
const s = crypto.randomUUID();
|
|
@@ -66,12 +67,12 @@ const defaultOptions = {
|
|
|
66
67
|
* @param brand - Entity type brand validated once at construction.
|
|
67
68
|
* @param opts - Optional `now`, `rng`, and `allowDuplicateBrand` overrides.
|
|
68
69
|
*/
|
|
69
|
-
function
|
|
70
|
+
function createTimestampId(brand, opts = {}) {
|
|
70
71
|
validateBrand(brand);
|
|
71
72
|
registerBrand(brand, opts.allowDuplicateBrand);
|
|
72
73
|
const options = {
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
now: opts.now ?? defaultTimestampOptions.now,
|
|
75
|
+
rng: opts.rng ?? defaultTimestampOptions.rng
|
|
75
76
|
};
|
|
76
77
|
const prefix = `${brand}_`;
|
|
77
78
|
const wire = wireMethods(prefix);
|
|
@@ -90,6 +91,6 @@ function createId(brand, opts = {}) {
|
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
93
|
//#endregion
|
|
93
|
-
export {
|
|
94
|
+
export { createTimestampId as t };
|
|
94
95
|
|
|
95
|
-
//# sourceMappingURL=
|
|
96
|
+
//# sourceMappingURL=timestamp-Bgzxx8bE.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timestamp-Bgzxx8bE.mjs","names":[],"sources":["../src/layouts/timestamp.ts","../src/timestamp.ts"],"sourcesContent":["import type { Id, Prefix } from \"../types.js\";\nimport { toWireId } from \"../wire/envelope.js\";\nimport { payloadByteLength } from \"../wire/invariants.js\";\nimport {\n readTimestampMsFromBase32Suffix,\n timestampByteLength,\n writeTimestamp,\n} from \"../wire/timestamp-bytes.js\";\n\nconst randomByteLength: number = payloadByteLength - timestampByteLength;\n\n/** Writes a 16-byte timestamp-layout payload into codec-owned scratch. */\nfunction buildPayload(\n ms: number,\n rng: (target: Uint8Array) => void,\n buffer: Uint8Array,\n randomView: Uint8Array,\n): void {\n writeTimestamp(ms, buffer);\n rng(randomView);\n}\n\n/** Writes sentinel min/max random bytes into codec-owned scratch. */\nfunction buildSentinelPayload(\n ms: number,\n fill: number,\n buffer: Uint8Array,\n randomView: Uint8Array,\n): void {\n writeTimestamp(ms, buffer);\n randomView.fill(fill);\n}\n\n/** Decodes the creation timestamp from a trusted wire ID. */\nfunction extractTimestampFromId<Brand extends string>(prefix: Prefix<Brand>, id: Id<Brand>): Date {\n return new Date(readTimestampMsFromBase32Suffix(id.slice(prefix.length)));\n}\n\n/** Layout ops binder for the Timestamp variant. `extractTimestampFromId` is module-private; the binder exposes `extractTimestamp` for the codec constructor. */\nexport function createTimestampLayoutOps<Brand extends string>(\n prefix: Prefix<Brand>,\n rng: (target: Uint8Array) => void,\n) {\n // Per-codec scratch buffer. Shared across generateAt(), minIdForTime(),\n // maxIdForTime(), and exampleWireId() — all are synchronous and overwrite both\n // the timestamp and random slices before encoding, so successive callers see\n // their own freshly-written bytes. toWireId reads the buffer and returns an\n // independent string, so the caller never sees the buffer itself.\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 buildPayload(ms, rng, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n extractTimestamp: (id: Id<Brand>): Date => extractTimestampFromId(prefix, id),\n minIdForTime: (ms: number): Id<Brand> => {\n buildSentinelPayload(ms, 0x00, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n maxIdForTime: (ms: number): Id<Brand> => {\n buildSentinelPayload(ms, 0xff, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n exampleWireId: (ms: number): Id<Brand> => {\n buildPayload(ms, rng, buffer, randomView);\n return toWireId(prefix, buffer);\n },\n };\n}\n","import { validateBrand } from \"./brand.js\";\nimport { createTimestampLayoutOps } from \"./layouts/timestamp.js\";\nimport { registerBrand } from \"./registry.js\";\nimport type { Id, JsonSchema, ParseResult, Prefix, StandardSchemaProps } from \"./types.js\";\nimport { wireMethods } from \"./wire/codec-shell.js\";\n\n/**\n * Configuration options for a codec instance.\n */\nexport type TimestampOptions = {\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 a `crypto.randomUUID` fast path. */\n rng?: (target: Uint8Array) => void;\n /** If true, silences the duplicate-brand warning in non-production environments. */\n allowDuplicateBrand?: boolean;\n};\n\ntype ResolvedTimestampOptions = Required<Pick<TimestampOptions, \"now\" | \"rng\">> &\n Pick<TimestampOptions, \"allowDuplicateBrand\">;\n\n/**\n * A brand-scoped codec for generating and validating public-facing IDs.\n *\n * Wire format: `{brand}_` plus 26 lowercase Crockford base32 characters encoding a\n * 16-byte payload (6-byte ms timestamp + 10 random bytes). IDs sort by creation\n * time in ascending order.\n *\n * For encrypted IDs, use `createOpaqueTimestampId` from `@smonn/ids/opaque`.\n */\nexport type TimestampCodec<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>`. Trusts the type — use `safeParse()` at boundaries first. See ADR-0002.\n */\n extractTimestamp(id: Id<Brand>): Date;\n /** Tight lower bound for any ID generated at `date` (random portion `0x00`). Throws on invalid dates. */\n minIdForTime(date: Date): Id<Brand>;\n /** Tight upper bound for any ID generated at `date` (random portion `0xff`). Throws on invalid dates. */\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// hex charCode → 0–15 nibble, for decoding UUIDv4 strings into bytes.\n// Covers ['0'-'9' = 48–57] and ['a'-'f' = 97–102]; UUIDs are lowercase per spec.\nconst hexCharCodeToNibble = new Uint8Array(128);\nfor (let i = 0; i < 10; i++) hexCharCodeToNibble[48 + i] = i;\nfor (let i = 0; i < 6; i++) hexCharCodeToNibble[97 + i] = 10 + i;\n\nconst defaultTimestampOptions: ResolvedTimestampOptions = {\n now: Date.now,\n // crypto.randomUUID is ~7× faster than crypto.getRandomValues in Node 24\n // (~84 ns vs ~610 ns for a 16-byte fill — likely because the UUID path has\n // a tight fixed-format fast path). We use the 122 random bits of a UUIDv4\n // string as our entropy source, harvesting 10 fully-random bytes from\n // positions where no version (hex 12) or variant (hex 16) bits sit.\n // String layout: \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\" — bytes 0–5 are\n // string[0..7]+string[9..12], bytes 6–9 are string[24..31].\n rng: (target) => {\n const s = crypto.randomUUID();\n target[0] =\n (hexCharCodeToNibble[s.charCodeAt(0)]! << 4) | hexCharCodeToNibble[s.charCodeAt(1)]!;\n target[1] =\n (hexCharCodeToNibble[s.charCodeAt(2)]! << 4) | hexCharCodeToNibble[s.charCodeAt(3)]!;\n target[2] =\n (hexCharCodeToNibble[s.charCodeAt(4)]! << 4) | hexCharCodeToNibble[s.charCodeAt(5)]!;\n target[3] =\n (hexCharCodeToNibble[s.charCodeAt(6)]! << 4) | hexCharCodeToNibble[s.charCodeAt(7)]!;\n target[4] =\n (hexCharCodeToNibble[s.charCodeAt(9)]! << 4) | hexCharCodeToNibble[s.charCodeAt(10)]!;\n target[5] =\n (hexCharCodeToNibble[s.charCodeAt(11)]! << 4) | hexCharCodeToNibble[s.charCodeAt(12)]!;\n target[6] =\n (hexCharCodeToNibble[s.charCodeAt(24)]! << 4) | hexCharCodeToNibble[s.charCodeAt(25)]!;\n target[7] =\n (hexCharCodeToNibble[s.charCodeAt(26)]! << 4) | hexCharCodeToNibble[s.charCodeAt(27)]!;\n target[8] =\n (hexCharCodeToNibble[s.charCodeAt(28)]! << 4) | hexCharCodeToNibble[s.charCodeAt(29)]!;\n target[9] =\n (hexCharCodeToNibble[s.charCodeAt(30)]! << 4) | hexCharCodeToNibble[s.charCodeAt(31)]!;\n },\n};\n\n/**\n * Creates a codec for `brand` (three lowercase a–z characters).\n *\n * @param brand - Entity type brand validated once at construction.\n * @param opts - Optional `now`, `rng`, and `allowDuplicateBrand` overrides.\n */\nexport function createTimestampId<Brand extends string>(\n brand: Brand,\n opts: TimestampOptions = {},\n): TimestampCodec<Brand> {\n validateBrand(brand);\n registerBrand(brand, opts.allowDuplicateBrand);\n\n const options = {\n now: opts.now ?? defaultTimestampOptions.now,\n rng: opts.rng ?? defaultTimestampOptions.rng,\n } satisfies ResolvedTimestampOptions;\n\n const prefix: Prefix<Brand> = `${brand}_`;\n const wire = wireMethods(prefix);\n const layout = createTimestampLayoutOps(prefix, options.rng);\n\n return {\n generate: () => layout.generateAt(options.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(options.now())),\n \"~standard\": wire[\"~standard\"],\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { o as decodeBase32 } from "./codec-shell-dWpxoFmy.mjs";
|
|
2
|
+
const timestampBase32Length = Math.ceil(48 / 5);
|
|
3
|
+
/** Write the timestamp in big-endian; encoded via mod-256 to avoid 32-bit bitwise coercion. */
|
|
4
|
+
function writeTimestamp(ms, buffer) {
|
|
5
|
+
if (Number.isNaN(ms)) throw new Error("timestamp is not a number");
|
|
6
|
+
if (ms < 0) throw new Error("timestamp is negative");
|
|
7
|
+
if (ms >= 2 ** 48) throw new Error("timestamp exceeds 48-bit range");
|
|
8
|
+
for (let i = 5; i >= 0; i--) {
|
|
9
|
+
buffer[i] = ms % 256;
|
|
10
|
+
ms = Math.floor(ms / 256);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Decode the first `timestampByteLength` bytes of a buffer as a big-endian unsigned millisecond timestamp. */
|
|
14
|
+
function readTimestampMs(buffer) {
|
|
15
|
+
let ms = 0;
|
|
16
|
+
for (let i = 0; i < 6; i++) ms = ms * 256 + buffer[i];
|
|
17
|
+
return ms;
|
|
18
|
+
}
|
|
19
|
+
/** Decodes ms from the first 10 base32 chars of a payload suffix (partial decode). */
|
|
20
|
+
function readTimestampMsFromBase32Suffix(base32Suffix) {
|
|
21
|
+
return readTimestampMs(decodeBase32(base32Suffix.slice(0, timestampBase32Length)));
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { readTimestampMsFromBase32Suffix as n, writeTimestamp as r, readTimestampMs as t };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=timestamp-bytes-B57RM7Ho.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timestamp-bytes-B57RM7Ho.mjs","names":[],"sources":["../src/wire/timestamp-bytes.ts"],"sourcesContent":["import { decodeBase32 } from \"../base32.js\";\n\n// Timestamp byte layout: first N bytes of the plaintext payload encode a\n// big-endian Unix-ms timestamp. Shared by timestamp-family layouts.\nexport const timestampByteLength: number = 6;\n\nconst timestampBase32Length: number = Math.ceil((timestampByteLength * 8) / 5);\n\n/** Write the timestamp in big-endian; encoded via mod-256 to avoid 32-bit bitwise coercion. */\nexport function writeTimestamp(ms: number, buffer: Uint8Array): void {\n if (Number.isNaN(ms)) throw new Error(\"timestamp is not a number\");\n if (ms < 0) throw new Error(\"timestamp is negative\");\n if (ms >= 2 ** (timestampByteLength * 8)) {\n throw new Error(\"timestamp exceeds 48-bit range\");\n }\n for (let i = timestampByteLength - 1; i >= 0; i--) {\n buffer[i] = ms % 256;\n ms = Math.floor(ms / 256);\n }\n}\n\n/** Decode the first `timestampByteLength` bytes of a buffer as a big-endian unsigned millisecond timestamp. */\nexport function readTimestampMs(buffer: Uint8Array): number {\n let ms = 0;\n for (let i = 0; i < timestampByteLength; i++) ms = ms * 256 + buffer[i]!;\n return ms;\n}\n\n/** Decodes ms from the first 10 base32 chars of a payload suffix (partial decode). */\nexport function readTimestampMsFromBase32Suffix(base32Suffix: string): number {\n return readTimestampMs(decodeBase32(base32Suffix.slice(0, timestampBase32Length)));\n}\n"],"mappings":";AAMA,MAAM,wBAAgC,KAAK,KAAA,KAAiC,CAAC;;AAG7E,SAAgB,eAAe,IAAY,QAA0B;CACnE,IAAI,OAAO,MAAM,EAAE,GAAG,MAAM,IAAI,MAAM,2BAA2B;CACjE,IAAI,KAAK,GAAG,MAAM,IAAI,MAAM,uBAAuB;CACnD,IAAI,MAAM,KAAA,IACR,MAAM,IAAI,MAAM,gCAAgC;CAElD,KAAK,IAAI,IAAA,GAA6B,KAAK,GAAG,KAAK;EACjD,OAAO,KAAK,KAAK;EACjB,KAAK,KAAK,MAAM,KAAK,GAAG;CAC1B;AACF;;AAGA,SAAgB,gBAAgB,QAA4B;CAC1D,IAAI,KAAK;CACT,KAAK,IAAI,IAAI,GAAG,IAAA,GAAyB,KAAK,KAAK,KAAK,MAAM,OAAO;CACrE,OAAO;AACT;;AAGA,SAAgB,gCAAgC,cAA8B;CAC5E,OAAO,gBAAgB,aAAa,aAAa,MAAM,GAAG,qBAAqB,CAAC,CAAC;AACnF"}
|
|
@@ -22,7 +22,7 @@ type JsonSchema = {
|
|
|
22
22
|
readonly description: string;
|
|
23
23
|
readonly example: string;
|
|
24
24
|
};
|
|
25
|
-
/** Standard Schema validate entry point exposed on `
|
|
25
|
+
/** Standard Schema validate entry point exposed on a codec's `~standard` property. */
|
|
26
26
|
type StandardSchemaProps<Brand extends string> = {
|
|
27
27
|
readonly version: 1;
|
|
28
28
|
readonly vendor: "@smonn/ids";
|
|
@@ -43,4 +43,4 @@ type StandardSchemaProps<Brand extends string> = {
|
|
|
43
43
|
};
|
|
44
44
|
//#endregion
|
|
45
45
|
export { StandardSchemaProps as a, ParseResult as i, JsonSchema as n, ParseError as r, Id as t };
|
|
46
|
-
//# sourceMappingURL=types-
|
|
46
|
+
//# sourceMappingURL=types-g7CiQDyE.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-
|
|
1
|
+
{"version":3,"file":"types-g7CiQDyE.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;KACY,MAAA,4BAAkC,KAAA;;KAGlC,EAAA,4BAA8B,MAAA,CAAO,KAAA;EAAA,SACtC,OAAA,EAAS,KAAA;AAAA;AADpB;AAAA,KAKY,UAAA;;KAGA,WAAA;EACN,EAAA;EAAU,EAAA,EAAI,EAAA,CAAG,KAAA;AAAA;EACjB,EAAA;EAAW,KAAA,EAAO,UAAA;AAAA;;KAGZ,UAAA;EAAA,SACD,IAAA;EAAA,SACA,OAAA;EAAA,SACA,WAAA;EAAA,SACA,OAAA;AAAA;;KAIC,mBAAA;EAAA,SACD,OAAA;EAAA,SACA,MAAA;EAAA,SACA,QAAA,GACP,KAAA,WACA,OAAA;IAAA,SAAqB,cAAA,GAAiB,MAAA;EAAA;IAAA,SAEzB,KAAA,EAAO,EAAA,CAAG,KAAA;IAAA,SAAiB,MAAA;EAAA;IAAA,SAC3B,MAAA,EAAQ,aAAA;MAAA,SAAyB,OAAA;IAAA;EAAA;EAAA,SACvC,KAAA;IAAA,SAAmB,KAAA;IAAA,SAAyB,MAAA,EAAQ,EAAA,CAAG,KAAA;EAAA;AAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { a as StandardSchemaProps, i as ParseResult, n as JsonSchema, r as ParseError, t as Id } from "./types-g7CiQDyE.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/wrapping-key.d.ts
|
|
4
|
+
/** Wire encoding for wrapping operator secret bytes (not Crockford base32). */
|
|
5
|
+
type WrappingKeyFormat = "hex" | "base64url";
|
|
6
|
+
declare const wrappingKeyBrand: unique symbol;
|
|
7
|
+
/** Opaque imported handle for one operator wrapping secret (derived AES + HMAC subkeys). */
|
|
8
|
+
type WrappingKey = {
|
|
9
|
+
readonly [wrappingKeyBrand]: "WrappingKey";
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Imports raw operator secret bytes into a {@link WrappingKey} handle.
|
|
13
|
+
*
|
|
14
|
+
* @param bytes - 16, 24, or 32 raw key bytes (AES-128/192/256).
|
|
15
|
+
*/
|
|
16
|
+
declare function importWrappingKey(bytes: Uint8Array): Promise<WrappingKey>;
|
|
17
|
+
/**
|
|
18
|
+
* Encodes raw wrapping operator secret bytes for storage in env vars or secret managers.
|
|
19
|
+
*/
|
|
20
|
+
declare function encodeWrappingKey(bytes: Uint8Array, format: WrappingKeyFormat): string;
|
|
21
|
+
/**
|
|
22
|
+
* Decodes key material emitted by {@link encodeWrappingKey} back to raw bytes.
|
|
23
|
+
*/
|
|
24
|
+
declare function decodeWrappingKey(encoded: string, format: WrappingKeyFormat): Uint8Array;
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/wrapped.d.ts
|
|
27
|
+
type WrappedKind = "u32" | "i32" | "u64" | "i64";
|
|
28
|
+
type LookupKeyForKind<K extends WrappedKind> = K extends "u32" | "i32" ? number : bigint;
|
|
29
|
+
type UnwrapResult<Brand extends string, Kind extends WrappedKind> = {
|
|
30
|
+
ok: true;
|
|
31
|
+
id: Id<Brand>;
|
|
32
|
+
lookupKey: LookupKeyForKind<Kind>;
|
|
33
|
+
} | {
|
|
34
|
+
ok: false;
|
|
35
|
+
error: ParseError | "verification_failed";
|
|
36
|
+
};
|
|
37
|
+
type WrappedKeyCodec<Brand extends string, Kind extends WrappedKind> = {
|
|
38
|
+
wrap(lookupKey: LookupKeyForKind<Kind>): Promise<Id<Brand>>;
|
|
39
|
+
unwrap(id: Id<Brand>): Promise<LookupKeyForKind<Kind>>;
|
|
40
|
+
safeUnwrap(input: unknown): Promise<UnwrapResult<Brand, Kind>>;
|
|
41
|
+
is(value: unknown): value is Id<Brand>;
|
|
42
|
+
parse(value: unknown): Id<Brand>;
|
|
43
|
+
safeParse(value: unknown): ParseResult<Brand>;
|
|
44
|
+
toJsonSchema(): JsonSchema;
|
|
45
|
+
readonly "~standard": StandardSchemaProps<Brand>;
|
|
46
|
+
};
|
|
47
|
+
type WrappedKeyOptions<K extends WrappedKind> = {
|
|
48
|
+
kind: K;
|
|
49
|
+
keys: [WrappingKey, ...WrappingKey[]];
|
|
50
|
+
allowDuplicateBrand?: boolean;
|
|
51
|
+
};
|
|
52
|
+
declare function createWrappedKeyId<Brand extends string, Kind extends WrappedKind>(brand: Brand, opts: WrappedKeyOptions<Kind>): WrappedKeyCodec<Brand, Kind>;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { UnwrapResult, WrappedKeyCodec, WrappedKeyOptions, WrappedKind, type WrappingKey, type WrappingKeyFormat, createWrappedKeyId, decodeWrappingKey, encodeWrappingKey, importWrappingKey };
|
|
55
|
+
//# sourceMappingURL=wrapped.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrapped.d.mts","names":[],"sources":["../src/wrapping-key.ts","../src/wrapped.ts"],"mappings":";;;;KAGY,iBAAA;AAAA,cAOE,gBAAA;AAPd;AAAA,KAUY,WAAA;EAAA,UACA,gBAAA;AAAA;;AAJE;AAGd;;;iBAsBsB,iBAAA,CAAkB,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,WAAA;AArBxD;AAqBZ;;AArBY,iBAqCI,iBAAA,CAAkB,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,iBAAA;;;;iBAU7C,iBAAA,CAAkB,OAAA,UAAiB,MAAA,EAAQ,iBAAA,GAAoB,UAAA;;;KC/BnE,WAAA;AAAA,KAEP,gBAAA,WAA2B,WAAA,IAAe,CAAA;AAAA,KAEnC,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;AAAA,KAEZ,eAAA,oCAAmD,WAAA;EAC7D,IAAA,CAAK,SAAA,EAAW,gBAAA,CAAiB,IAAA,IAAQ,OAAA,CAAQ,EAAA,CAAG,KAAA;EACpD,MAAA,CAAO,EAAA,EAAI,EAAA,CAAG,KAAA,IAAS,OAAA,CAAQ,gBAAA,CAAiB,IAAA;EAChD,UAAA,CAAW,KAAA,YAAiB,OAAA,CAAQ,YAAA,CAAa,KAAA,EAAO,IAAA;EACxD,EAAA,CAAG,KAAA,YAAiB,KAAA,IAAS,EAAA,CAAG,KAAA;EAChC,KAAA,CAAM,KAAA,YAAiB,EAAA,CAAG,KAAA;EAC1B,SAAA,CAAU,KAAA,YAAiB,WAAA,CAAY,KAAA;EACvC,YAAA,IAAgB,UAAA;EAAA,SACP,WAAA,EAAa,mBAAA,CAAoB,KAAA;AAAA;AAAA,KAGhC,iBAAA,WAA4B,WAAA;EACtC,IAAA,EAAM,CAAA;EACN,IAAA,GAAO,WAAA,KAAgB,WAAA;EACvB,mBAAA;AAAA;AAAA,iBA2Fc,kBAAA,oCAAsD,WAAA,EACpE,KAAA,EAAO,KAAA,EACP,IAAA,EAAM,iBAAA,CAAkB,IAAA,IACvB,eAAA,CAAgB,KAAA,EAAO,IAAA"}
|