@cloudbitmaps/core 0.1.0-rc.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.
Files changed (76) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +12 -0
  3. package/README.md +22 -0
  4. package/dist/azure/index.cjs +368 -0
  5. package/dist/azure/index.cjs.map +1 -0
  6. package/dist/azure/index.d.cts +48 -0
  7. package/dist/azure/index.d.ts +48 -0
  8. package/dist/azure/index.js +304 -0
  9. package/dist/azure/index.js.map +1 -0
  10. package/dist/cassandra/index.cjs +265 -0
  11. package/dist/cassandra/index.cjs.map +1 -0
  12. package/dist/cassandra/index.d.cts +52 -0
  13. package/dist/cassandra/index.d.ts +52 -0
  14. package/dist/cassandra/index.js +204 -0
  15. package/dist/cassandra/index.js.map +1 -0
  16. package/dist/chunk-2YDULGXS.js +203 -0
  17. package/dist/chunk-2YDULGXS.js.map +1 -0
  18. package/dist/chunk-7LMLYSVJ.js +43 -0
  19. package/dist/chunk-7LMLYSVJ.js.map +1 -0
  20. package/dist/chunk-AS6ODRLT.js +6 -0
  21. package/dist/chunk-AS6ODRLT.js.map +1 -0
  22. package/dist/chunk-NUIDEEFZ.js +91 -0
  23. package/dist/chunk-NUIDEEFZ.js.map +1 -0
  24. package/dist/chunk-SNJVZ227.js +35 -0
  25. package/dist/chunk-SNJVZ227.js.map +1 -0
  26. package/dist/dynamodb/index.cjs +731 -0
  27. package/dist/dynamodb/index.cjs.map +1 -0
  28. package/dist/dynamodb/index.d.cts +106 -0
  29. package/dist/dynamodb/index.d.ts +106 -0
  30. package/dist/dynamodb/index.js +460 -0
  31. package/dist/dynamodb/index.js.map +1 -0
  32. package/dist/gcs/index.cjs +343 -0
  33. package/dist/gcs/index.cjs.map +1 -0
  34. package/dist/gcs/index.d.cts +46 -0
  35. package/dist/gcs/index.d.ts +46 -0
  36. package/dist/gcs/index.js +279 -0
  37. package/dist/gcs/index.js.map +1 -0
  38. package/dist/index.cjs +4031 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.cts +1945 -0
  41. package/dist/index.d.ts +1945 -0
  42. package/dist/index.js +3642 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/mongodb/index.cjs +260 -0
  45. package/dist/mongodb/index.cjs.map +1 -0
  46. package/dist/mongodb/index.d.cts +45 -0
  47. package/dist/mongodb/index.d.ts +45 -0
  48. package/dist/mongodb/index.js +199 -0
  49. package/dist/mongodb/index.js.map +1 -0
  50. package/dist/mysql/index.cjs +281 -0
  51. package/dist/mysql/index.cjs.map +1 -0
  52. package/dist/mysql/index.d.cts +56 -0
  53. package/dist/mysql/index.d.ts +56 -0
  54. package/dist/mysql/index.js +211 -0
  55. package/dist/mysql/index.js.map +1 -0
  56. package/dist/ports-D3BrJ6ax.d.cts +357 -0
  57. package/dist/ports-D3BrJ6ax.d.ts +357 -0
  58. package/dist/postgres/index.cjs +273 -0
  59. package/dist/postgres/index.cjs.map +1 -0
  60. package/dist/postgres/index.d.cts +54 -0
  61. package/dist/postgres/index.d.ts +54 -0
  62. package/dist/postgres/index.js +203 -0
  63. package/dist/postgres/index.js.map +1 -0
  64. package/dist/redis/index.cjs +257 -0
  65. package/dist/redis/index.cjs.map +1 -0
  66. package/dist/redis/index.d.cts +42 -0
  67. package/dist/redis/index.d.ts +42 -0
  68. package/dist/redis/index.js +197 -0
  69. package/dist/redis/index.js.map +1 -0
  70. package/dist/s3/index.cjs +891 -0
  71. package/dist/s3/index.cjs.map +1 -0
  72. package/dist/s3/index.d.cts +112 -0
  73. package/dist/s3/index.d.ts +112 -0
  74. package/dist/s3/index.js +555 -0
  75. package/dist/s3/index.js.map +1 -0
  76. package/package.json +172 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/drivers/mongodb/keys.ts","../../src/drivers/mongodb/mongodb-errors.ts","../../src/drivers/mongodb/warm.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Logical-ref → MongoDB document-id mapping for {@link MongoWarmDriver} (Phase 7).\n *\n * Pure, SDK-free string logic — unit-testable without a live MongoDB. Each chunk is one document with a\n * **deterministic composite `_id`** `<prefix>|<ns>|<seg>|<chunkKey>`, so create-if-absent is a plain\n * `insertOne` (a duplicate `_id` → the unique-index conflict, no extra index needed) and `get` is a direct\n * `_id` lookup. The ref fields are ALSO stored as separate typed fields (`kp`/`ns`/`seg`/`ck`) so `listChunks`\n * can query + **numerically** sort by `ck` (an `_id`-string sort would order `10` before `9`). The name grammar\n * (validateSegmentRef) forbids `|` in ns/seg and {@link normalizeKeyPrefix} forbids it in the prefix, so the\n * `|`-joined id is unambiguous.\n */\nimport { ValidationError } from '@/core/errors';\nimport { validateChunkRef, validateSegmentRef } from '@/core/validate';\nimport type { ChunkRef, SegmentRef } from '@/core/ports';\nimport { namespacePart } from '../_shared/keys';\n\n/** The scope fields shared by every doc of a segment — the `listChunks` filter. */\nexport interface SegmentScope {\n kp: string;\n ns: string;\n seg: string;\n}\n\n/**\n * Validate a caller-supplied key prefix: no control characters and no `|` (the composite-`_id` delimiter), so\n * it can neither corrupt an id nor alias another prefix. Empty/undefined ⇒ no prefix (stored as `''`).\n */\nexport function normalizeKeyPrefix(prefix: string | undefined): string {\n if (prefix === undefined || prefix === '') return '';\n for (const ch of prefix) {\n if (ch.charCodeAt(0) < 0x20 || ch === '|') {\n throw new ValidationError('keyPrefix must not contain \"|\" or control characters');\n }\n }\n return prefix;\n}\n\n/** The scope filter for all of a segment's chunk docs. Validates the ref. */\nexport function segmentScope(prefix: string, ref: SegmentRef): SegmentScope {\n validateSegmentRef(ref);\n return { kp: prefix, ns: namespacePart(ref.namespace), seg: ref.segment };\n}\n\n/** The deterministic `_id` of one chunk's doc: `<prefix>|<ns>|<seg>|<chunkKey>`. Validates the full ref. */\nexport function chunkDocId(prefix: string, ref: ChunkRef): string {\n validateChunkRef(ref);\n return `${prefix}|${namespacePart(ref.namespace)}|${ref.segment}|${ref.chunkKey}`;\n}\n","/**\n * Pure helpers for classifying MongoDB (`mongodb`) errors (Phase 7; transient class mirrors the other drivers).\n *\n * SDK-free + side-effect-free — they read only structural shapes off the thrown value (`err.code`, `err.name`,\n * the driver's `hasErrorLabel`, or a Node socket code). The driver signals a create-if-absent conflict itself\n * (a duplicate-key error, {@link isDuplicateKey}) and a token-fenced conflict via a 0 matched/deleted count,\n * so there's no general OCC classifier here — only duplicate-key + transient-vs-not.\n */\n\n/** A MongoDB numeric server error code, if present (`err.code`). */\nfunction serverCode(err: unknown): number | undefined {\n const code = (err as { code?: unknown } | null)?.code;\n return typeof code === 'number' ? code : undefined;\n}\n\n/** A Node system/socket error `code` string (starts with `E`, uppercase letters + underscore, no digits). */\nfunction networkCode(err: unknown): string | undefined {\n const code = (err as { code?: unknown } | null)?.code;\n return typeof code === 'string' && /^E[A-Z_]+$/.test(code) ? code : undefined;\n}\n\n/** A create-if-absent lost the race — the `_id` already existed (duplicate key: 11000 / 11001). */\nexport function isDuplicateKey(err: unknown): boolean {\n const code = serverCode(err);\n return code === 11000 || code === 11001;\n}\n\n/**\n * Retryable MongoDB server error codes: failover / primary-stepdown / node-unreachable / shutdown / network\n * timeouts. (Names from the server error catalogue.) These are safe to retry; deterministic errors\n * (BadValue, duplicate key, etc.) are not listed, so they surface.\n */\nconst RETRYABLE_CODES = new Set<number>([\n 6, // HostUnreachable\n 7, // HostNotFound\n 89, // NetworkTimeout\n 91, // ShutdownInProgress\n 189, // PrimarySteppedDown\n 262, // ExceededTimeLimit\n 9001, // SocketException\n 10107, // NotWritablePrimary\n 11600, // InterruptedAtShutdown\n 11602, // InterruptedDueToReplStateChange\n 13435, // NotPrimaryNoSecondaryOk\n 13436, // NotPrimaryOrSecondary\n 63, // StaleShardVersion\n 150, // StaleEpoch\n]);\n\n/**\n * A transient MongoDB fault safe to retry: a network error, a server-selection timeout, a driver retryable\n * label (`RetryableWriteError` / `TransientTransactionError`), a retryable server code, or a dropped socket.\n * A duplicate-key error is NOT transient (it's the deterministic create-if-absent conflict).\n */\nexport function isTransient(err: unknown): boolean {\n if (isDuplicateKey(err)) return false;\n const name = (err as { name?: unknown } | null)?.name;\n if (name === 'MongoNetworkError' || name === 'MongoServerSelectionError') return true;\n const hasLabel = (err as { hasErrorLabel?: (l: string) => boolean } | null)?.hasErrorLabel;\n if (typeof hasLabel === 'function') {\n if (\n hasLabel.call(err, 'RetryableWriteError') ||\n hasLabel.call(err, 'TransientTransactionError')\n ) {\n return true;\n }\n }\n const code = serverCode(err);\n if (code !== undefined && RETRYABLE_CODES.has(code)) return true;\n const net = networkCode(err);\n return (\n net === 'ECONNRESET' ||\n net === 'ECONNABORTED' ||\n net === 'ETIMEDOUT' ||\n net === 'ESOCKETTIMEDOUT' ||\n net === 'ECONNREFUSED' ||\n net === 'EHOSTUNREACH' ||\n net === 'ENETUNREACH' ||\n net === 'EPIPE' ||\n net === 'EAI_AGAIN' ||\n net === 'ENOTFOUND'\n );\n}\n","/**\n * `MongoWarmDriver` — an {@link IWarmDriver} over MongoDB / DocumentDB (Phase 7).\n *\n * Uses the official `mongodb` driver, an **optional peer dependency** — only consumers of\n * `cloud-roaring/mongodb` install it. A `Db` is **injected** (dependency injection): the driver owns no\n * connection/credential logic, so it's thin and reuses the caller's client.\n *\n * Each chunk is one document keyed by a deterministic composite `_id` (`<prefix>|<ns>|<seg>|<chunkKey>`) with\n * an opaque OCC **token** (a random UUID per write) and the delta `payload` (BSON binary). Optimistic\n * concurrency is per-document and server-side:\n * - **create-if-absent** = `insertOne` with that `_id`; a duplicate-key error ⇒ {@link WriteConflictError}.\n * - **token-fenced update / delete** = `updateOne` / `deleteOne` filtered on `{ _id, token: expected }`; a 0\n * matched/deleted count ⇒ {@link WriteConflictError}.\n *\n * Each op is a single-document atomic operation, so no transaction is needed. Tokens are not reused across\n * delete→recreate (ABA-safe, D3 — probabilistic random UUID, like the Postgres/Redis drivers). Reads are\n * strongly consistent when the `Db` targets the primary (majority/primary read preference), which the OCC\n * read-modify-write requires — see the `db` option note. Drivers may use `node:crypto`.\n */\nimport { randomUUID } from 'node:crypto';\nimport type { Collection, Db } from 'mongodb';\nimport { IntegrityError, TransientError, ValidationError, WriteConflictError } from '@/core/errors';\nimport { NO_ROW } from '@/core/ports';\nimport type { ChunkRef, IWarmDriver, NoRow, SegmentRef, Token, WarmRow } from '@/core/ports';\nimport { chunkDocId, normalizeKeyPrefix, segmentScope } from './keys';\nimport { isDuplicateKey, isTransient } from './mongodb-errors';\n\nconst DEFAULT_COLLECTION = 'cloud_roaring_warm';\nconst INDEX_NAME = 'cr_warm_scope_ck';\n/** find() cursor batch — bounds resident docs on a very wide segment. */\nconst DEFAULT_BATCH = 500;\n/**\n * Force **binary (byte-exact) comparison** on every op + the index, overriding any case-/diacritic-insensitive\n * *default* collation the collection may have been created with. Grammar-legal segment/namespace names differ\n * only by case (`\"A\"` vs `\"a\"`) are distinct stores; under a case-insensitive default collation an unpinned\n * `findOne({_id})` / `updateOne` / `deleteOne` would match the *wrong* document → cross-segment read/leak and\n * wrong-doc OCC updates. This is the Mongo analogue of the MySQL `utf8mb4_bin` requirement. Operation-level\n * collation is binary in the common (no-default-collation) case, so this is a no-op there and a fix otherwise.\n * NOTE: `insertOne` uniqueness is governed by the `_id` index's collation, which cannot be overridden per-op —\n * so a collection created with a case-insensitive **default** collation can still collapse `\"a\"`/`\"A\"` on\n * *insert* (a spurious create-if-absent conflict, not a data leak). The operator guide states the warm\n * collection must use the simple default collation; this pin closes the get/update/delete/list matching hole.\n */\nconst SIMPLE_COLLATION = { locale: 'simple' } as const;\n\n/** One warm chunk document. */\ninterface WarmDoc {\n _id: string;\n kp: string;\n ns: string;\n seg: string;\n ck: number;\n token: string;\n payload: unknown; // written as a Node Buffer; read back as a BSON Binary (or Buffer if promoteBuffers)\n}\n\nexport interface MongoWarmDriverOptions {\n /**\n * A `mongodb` `Db` (the driver calls `db.collection(...)`). The driver never connects/closes the client —\n * the caller owns its lifecycle. Reads must be strongly consistent for OCC (read-your-writes): use a\n * primary read preference (the default), not `secondary`/`secondaryPreferred`.\n */\n readonly db: Db;\n /** Warm collection name (default `cloud_roaring_warm`). */\n readonly collection?: string;\n /** Optional key-prefix component of the composite `_id` so several logical stores can share one collection. */\n readonly keyPrefix?: string;\n /** Advanced: `listChunks` cursor batch size (default 500). Tunes peak memory on wide segments. */\n readonly listPageSize?: number;\n}\n\n/**\n * Create the index `listChunks` needs — `{ kp, ns, seg, ck }` (scope filter + numeric ascending sort) —\n * idempotently. **Run this once at deploy time — it is effectively required, not merely an optimization:**\n * without it a `listChunks` over a segment whose dirty backlog exceeds MongoDB's 32 MB in-memory-sort ceiling\n * fails (server error 292, `QueryExceededMemoryLimitNoDiskUseAllowedException`) rather than merely running\n * slow. (create-if-absent needs no extra index — the composite `_id` is inherently unique.)\n */\nexport async function ensureMongoWarmIndexes(\n db: Db,\n collection: string = DEFAULT_COLLECTION,\n): Promise<void> {\n await db\n .collection(collection)\n .createIndex(\n { kp: 1, ns: 1, seg: 1, ck: 1 },\n { name: INDEX_NAME, collation: SIMPLE_COLLATION },\n );\n}\n\nexport class MongoWarmDriver implements IWarmDriver {\n private readonly col: Collection<WarmDoc>;\n private readonly keyPrefix: string;\n private readonly batch: number;\n\n constructor(options: MongoWarmDriverOptions) {\n this.keyPrefix = normalizeKeyPrefix(options.keyPrefix);\n const batch = options.listPageSize ?? DEFAULT_BATCH;\n if (!Number.isSafeInteger(batch) || batch < 1) {\n throw new ValidationError(`listPageSize must be a positive safe integer; got ${batch}`);\n }\n this.batch = batch;\n this.col = options.db.collection<WarmDoc>(options.collection ?? DEFAULT_COLLECTION);\n }\n\n // Reads are strongly consistent when the Db targets the primary (see the `db` option), so the optional\n // `WarmReadOptions` hint would be a no-op — the structurally-optional param is simply omitted, as in the\n // LocalFs / Postgres / Redis drivers.\n async get(ref: ChunkRef): Promise<WarmRow | null> {\n const _id = chunkDocId(this.keyPrefix, ref); // validates ref\n let doc: WarmDoc | null;\n try {\n doc = await this.col.findOne({ _id }, { collation: SIMPLE_COLLATION });\n } catch (err) {\n throw this.mapError(err);\n }\n return doc === null ? null : this.rowFrom(doc, ref.chunkKey);\n }\n\n async putConditional(\n ref: ChunkRef,\n bytes: Uint8Array,\n expected: Token | NoRow,\n ): Promise<{ token: Token }> {\n const _id = chunkDocId(this.keyPrefix, ref); // validates ref\n const token = randomUUID();\n const payload = Buffer.from(bytes); // copy: severs any caller-owned/reused input buffer; stored as Binary\n try {\n if (expected === NO_ROW) {\n const { kp, ns, seg } = segmentScope(this.keyPrefix, ref);\n await this.col.insertOne({ _id, kp, ns, seg, ck: ref.chunkKey, token, payload });\n } else {\n const res = await this.col.updateOne(\n { _id, token: expected },\n { $set: { token, payload } },\n { collation: SIMPLE_COLLATION },\n );\n if (res.matchedCount !== 1) {\n throw new WriteConflictError(`OCC conflict on chunk ${ref.chunkKey}`);\n }\n }\n return { token };\n } catch (err) {\n // A duplicate `_id` is the deterministic create-if-absent conflict — never a transient.\n if (isDuplicateKey(err)) {\n throw new WriteConflictError(`chunk ${ref.chunkKey} already exists (create-if-absent)`);\n }\n throw this.mapError(err); // WriteConflictError (from the update branch) passes straight through\n }\n }\n\n async deleteConditional(ref: ChunkRef, expected: Token): Promise<void> {\n const _id = chunkDocId(this.keyPrefix, ref); // validates ref\n let deletedCount: number;\n try {\n deletedCount = (\n await this.col.deleteOne({ _id, token: expected }, { collation: SIMPLE_COLLATION })\n ).deletedCount;\n } catch (err) {\n throw this.mapError(err);\n }\n if (deletedCount !== 1) {\n throw new WriteConflictError(`fenced-delete conflict on chunk ${ref.chunkKey}`);\n }\n }\n\n async *listChunks(ref: SegmentRef): AsyncIterable<{ chunkKey: number } & WarmRow> {\n const scope = segmentScope(this.keyPrefix, ref); // validates ref\n // The driver's cursor streams in `batchSize` batches, so peak memory is bounded on a wide segment; the\n // `{ kp, ns, seg, ck }` index (ensureMongoWarmIndexes) serves the filter + numeric ascending sort — and is\n // effectively required: without it a wide segment's sort spills past MongoDB's 32 MB in-memory-sort limit\n // and errors (292), not just runs slow.\n const cursor = this.col\n .find(scope, { collation: SIMPLE_COLLATION })\n .sort({ ck: 1 })\n .batchSize(this.batch);\n try {\n for await (const doc of cursor) {\n // Validate the yielded chunkKey (untrusted-data posture, like the sibling drivers) — a foreign/corrupt\n // doc matching the scope but lacking a numeric `ck` must not surface as `{ chunkKey: undefined }`.\n if (typeof doc.ck !== 'number') {\n throw new IntegrityError('warm doc is missing its numeric chunk key (ck)');\n }\n yield { chunkKey: doc.ck, ...this.rowFrom(doc, doc.ck) };\n }\n } catch (err) {\n throw this.mapError(err);\n }\n }\n\n /** Build a {@link WarmRow} from a doc; reject one missing/!typed fields (corrupt/foreign). */\n private rowFrom(doc: WarmDoc, chunkKey: number): WarmRow {\n if (typeof doc.token !== 'string' || doc.token === '') {\n throw new IntegrityError(`warm doc for chunk ${chunkKey} is missing its token`);\n }\n return { token: doc.token, bytes: decodePayload(doc.payload, chunkKey) };\n }\n\n /** Reclassify a transient Mongo fault as a retryable {@link TransientError}; else propagate unchanged. */\n private mapError(err: unknown): unknown {\n if (isTransient(err)) {\n return new TransientError(\n `transient MongoDB fault: ${(err as { message?: unknown } | null)?.message ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n }\n}\n\n/**\n * Decode a stored payload to owned bytes, duck-typed so the driver needs no runtime `mongodb`/`bson` import\n * (kept `import type`, so `cloud-roaring/mongodb`'s built bundle has no runtime SDK dependency). A Node\n * `Buffer` is a `Uint8Array` (the `promoteBuffers` case); a BSON `Binary` exposes its bytes on `.buffer`.\n */\nfunction decodePayload(p: unknown, chunkKey: number): Uint8Array {\n if (p instanceof Uint8Array) return new Uint8Array(p); // Buffer / Uint8Array — copy\n const inner = (p as { buffer?: unknown } | null)?.buffer; // BSON Binary → .buffer is a Buffer\n if (inner instanceof Uint8Array) return new Uint8Array(inner);\n throw new IntegrityError(`warm doc for chunk ${chunkKey} has no readable payload`);\n}\n"]}
@@ -0,0 +1,281 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ // src/drivers/mysql/warm.ts
6
+
7
+ // src/core/errors.ts
8
+ var ERROR_BRAND = /* @__PURE__ */ Symbol.for("cloud-roaring.error");
9
+ var TRANSIENT_BRAND = /* @__PURE__ */ Symbol.for("cloud-roaring.error.transient");
10
+ var CloudRoaringError = class extends Error {
11
+ /** Cross-bundle brand — see the predicates ({@link isCloudRoaringError}, …). Non-enumerable-ish (symbol key ⇒ not in JSON). */
12
+ [ERROR_BRAND] = true;
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = new.target.name;
16
+ }
17
+ };
18
+ var ValidationError = class extends CloudRoaringError {
19
+ };
20
+ var WriteConflictError = class extends CloudRoaringError {
21
+ };
22
+ var IntegrityError = class extends CloudRoaringError {
23
+ };
24
+ var TransientError = class extends CloudRoaringError {
25
+ /** A second brand so the whole transient subtree (incl. {@link TimeoutError}) is classifiable cross-bundle. */
26
+ [TRANSIENT_BRAND] = true;
27
+ constructor(message, options) {
28
+ super(message);
29
+ if (options && "cause" in options) this.cause = options.cause;
30
+ }
31
+ };
32
+ function hasBrand(err, brand) {
33
+ return typeof err === "object" && err !== null && err[brand] === true;
34
+ }
35
+ function isCloudRoaringError(err) {
36
+ return hasBrand(err, ERROR_BRAND);
37
+ }
38
+ function isWriteConflictError(err) {
39
+ return isCloudRoaringError(err) && err.name === "WriteConflictError";
40
+ }
41
+
42
+ // src/core/ports.ts
43
+ var NO_ROW = /* @__PURE__ */ Symbol.for("cloud-roaring.no-row");
44
+
45
+ // src/core/validate.ts
46
+ var CHUNK_KEY_MAX = 65535;
47
+ var NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;
48
+ function validatePart(value, field) {
49
+ if (typeof value !== "string" || !NAME.test(value) || value.includes("..")) {
50
+ throw new ValidationError(
51
+ `${field} must match ${String(NAME)} and contain no "..": got ${JSON.stringify(value)}`
52
+ );
53
+ }
54
+ }
55
+ function validateSegmentRef(ref) {
56
+ validatePart(ref.segment, "segment");
57
+ if (ref.namespace !== void 0) validatePart(ref.namespace, "namespace");
58
+ }
59
+ function validateChunkRef(ref) {
60
+ validateSegmentRef(ref);
61
+ if (!Number.isInteger(ref.chunkKey) || ref.chunkKey < 0 || ref.chunkKey > CHUNK_KEY_MAX) {
62
+ throw new ValidationError(
63
+ `chunkKey must be an integer in [0, ${CHUNK_KEY_MAX}]; got ${ref.chunkKey}`
64
+ );
65
+ }
66
+ }
67
+
68
+ // src/drivers/_shared/keys.ts
69
+ var DEFAULT_NAMESPACE = "_default";
70
+ function namespacePart(namespace) {
71
+ return namespace ?? DEFAULT_NAMESPACE;
72
+ }
73
+
74
+ // src/drivers/mysql/keys.ts
75
+ var IDENT = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
76
+ function validateAndQuoteTable(table) {
77
+ const parts = table.split(".");
78
+ if (parts.length > 2 || parts.some((p) => !IDENT.test(p))) {
79
+ throw new ValidationError(
80
+ `invalid table name ${JSON.stringify(table)} \u2014 expected an identifier or "db.table" (letters, digits, underscore; \u226464 chars; leading letter/underscore)`
81
+ );
82
+ }
83
+ return parts.map((p) => `\`${p}\``).join(".");
84
+ }
85
+ var MAX_KEY_PREFIX_CHARS = 191;
86
+ function normalizeKeyPrefix(prefix) {
87
+ if (prefix === void 0 || prefix === "") return "";
88
+ let chars = 0;
89
+ for (const ch of prefix) {
90
+ chars++;
91
+ if (ch.charCodeAt(0) < 32) {
92
+ throw new ValidationError("keyPrefix must not contain control characters");
93
+ }
94
+ }
95
+ if (chars > MAX_KEY_PREFIX_CHARS) {
96
+ throw new ValidationError(
97
+ `keyPrefix must be at most ${MAX_KEY_PREFIX_CHARS} characters (the key_prefix column width); got ${chars}`
98
+ );
99
+ }
100
+ return prefix;
101
+ }
102
+
103
+ // src/drivers/mysql/mysql-errors.ts
104
+ function errno(err) {
105
+ const n = err?.errno;
106
+ return typeof n === "number" ? n : void 0;
107
+ }
108
+ function code(err) {
109
+ const c = err?.code;
110
+ return typeof c === "string" ? c : void 0;
111
+ }
112
+ function isDuplicateKey(err) {
113
+ return errno(err) === 1062 || code(err) === "ER_DUP_ENTRY";
114
+ }
115
+ function isTransient(err) {
116
+ const n = errno(err);
117
+ if (n === 1205 || // ER_LOCK_WAIT_TIMEOUT
118
+ n === 1213 || // ER_LOCK_DEADLOCK
119
+ n === 1040 || // ER_CON_COUNT_ERROR (too many connections)
120
+ n === 1203 || // ER_TOO_MANY_USER_CONNECTIONS
121
+ n === 1053 || // ER_SERVER_SHUTDOWN
122
+ n === 1927 || // ER_CONNECTION_KILLED
123
+ n === 2006 || // CR_SERVER_GONE_ERROR
124
+ n === 2013) {
125
+ return true;
126
+ }
127
+ const c = code(err);
128
+ return c === "PROTOCOL_CONNECTION_LOST" || c === "PROTOCOL_SEQUENCE_TIMEOUT" || c === "CR_SERVER_GONE_ERROR" || c === "CR_SERVER_LOST" || c === "ECONNRESET" || c === "ECONNABORTED" || c === "ETIMEDOUT" || c === "ESOCKETTIMEDOUT" || c === "ECONNREFUSED" || c === "EHOSTUNREACH" || c === "ENETUNREACH" || c === "EPIPE" || c === "EAI_AGAIN" || c === "ENOTFOUND";
129
+ }
130
+
131
+ // src/drivers/mysql/warm.ts
132
+ var DEFAULT_TABLE = "cloud_roaring_warm";
133
+ var DEFAULT_LIST_BATCH = 1e3;
134
+ function mysqlWarmTableDDL(table = DEFAULT_TABLE) {
135
+ const t = validateAndQuoteTable(table);
136
+ return `CREATE TABLE IF NOT EXISTS ${t} (
137
+ key_prefix VARCHAR(191) NOT NULL,
138
+ namespace VARCHAR(256) NOT NULL,
139
+ segment VARCHAR(256) NOT NULL,
140
+ chunk_key INT NOT NULL,
141
+ token CHAR(36) NOT NULL,
142
+ payload LONGBLOB NOT NULL,
143
+ PRIMARY KEY (key_prefix, namespace, segment, chunk_key)
144
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;`;
145
+ }
146
+ var MysqlWarmDriver = class {
147
+ pool;
148
+ table;
149
+ // validated + backtick-quoted for interpolation
150
+ keyPrefix;
151
+ listBatch;
152
+ constructor(options) {
153
+ this.pool = options.pool;
154
+ this.table = validateAndQuoteTable(options.table ?? DEFAULT_TABLE);
155
+ this.keyPrefix = normalizeKeyPrefix(options.keyPrefix);
156
+ const batch = options.listPageSize ?? DEFAULT_LIST_BATCH;
157
+ if (!Number.isSafeInteger(batch) || batch < 1) {
158
+ throw new ValidationError(`listPageSize must be a positive safe integer; got ${batch}`);
159
+ }
160
+ this.listBatch = batch;
161
+ }
162
+ // Reads are always strongly consistent (single primary), so the optional `WarmReadOptions` hint would be a
163
+ // no-op — the structurally-optional param is simply omitted (still satisfies IWarmDriver), as in LocalFs/pg.
164
+ async get(ref) {
165
+ validateChunkRef(ref);
166
+ try {
167
+ const [rows] = await this.pool.query(
168
+ `SELECT token, payload FROM ${this.table} WHERE key_prefix = ? AND namespace = ? AND segment = ? AND chunk_key = ?`,
169
+ [this.keyPrefix, namespacePart(ref.namespace), ref.segment, ref.chunkKey]
170
+ );
171
+ const row = rows[0];
172
+ return row === void 0 ? null : this.rowFrom(row, ref.chunkKey);
173
+ } catch (err) {
174
+ throw this.mapError(err);
175
+ }
176
+ }
177
+ async putConditional(ref, bytes, expected) {
178
+ validateChunkRef(ref);
179
+ const token = crypto.randomUUID();
180
+ const payload = Buffer.from(bytes);
181
+ const ns = namespacePart(ref.namespace);
182
+ try {
183
+ if (expected === NO_ROW) {
184
+ await this.pool.query(
185
+ `INSERT INTO ${this.table} (key_prefix, namespace, segment, chunk_key, token, payload) VALUES (?, ?, ?, ?, ?, ?)`,
186
+ [this.keyPrefix, ns, ref.segment, ref.chunkKey, token, payload]
187
+ );
188
+ } else {
189
+ const [res] = await this.pool.query(
190
+ `UPDATE ${this.table} SET token = ?, payload = ? WHERE key_prefix = ? AND namespace = ? AND segment = ? AND chunk_key = ? AND token = ?`,
191
+ [token, payload, this.keyPrefix, ns, ref.segment, ref.chunkKey, expected]
192
+ );
193
+ if (res.affectedRows !== 1) {
194
+ throw new WriteConflictError(`OCC conflict on chunk ${ref.chunkKey}`);
195
+ }
196
+ }
197
+ return { token };
198
+ } catch (err) {
199
+ if (isWriteConflictError(err)) throw err;
200
+ if (expected === NO_ROW && isDuplicateKey(err)) {
201
+ throw new WriteConflictError(`chunk ${ref.chunkKey} already exists (create-if-absent)`);
202
+ }
203
+ throw this.mapError(err);
204
+ }
205
+ }
206
+ async deleteConditional(ref, expected) {
207
+ validateChunkRef(ref);
208
+ try {
209
+ const [res] = await this.pool.query(
210
+ `DELETE FROM ${this.table} WHERE key_prefix = ? AND namespace = ? AND segment = ? AND chunk_key = ? AND token = ?`,
211
+ [this.keyPrefix, namespacePart(ref.namespace), ref.segment, ref.chunkKey, expected]
212
+ );
213
+ if (res.affectedRows !== 1) {
214
+ throw new WriteConflictError(`fenced-delete conflict on chunk ${ref.chunkKey}`);
215
+ }
216
+ } catch (err) {
217
+ if (isWriteConflictError(err)) throw err;
218
+ throw this.mapError(err);
219
+ }
220
+ }
221
+ async *listChunks(ref) {
222
+ validateSegmentRef(ref);
223
+ const ns = namespacePart(ref.namespace);
224
+ let after = -1;
225
+ for (; ; ) {
226
+ let rows;
227
+ try {
228
+ const [res] = await this.pool.query(
229
+ `SELECT chunk_key, token, payload FROM ${this.table} WHERE key_prefix = ? AND namespace = ? AND segment = ? AND chunk_key > ? ORDER BY chunk_key ASC LIMIT ?`,
230
+ [this.keyPrefix, ns, ref.segment, after, this.listBatch]
231
+ );
232
+ rows = res;
233
+ } catch (err) {
234
+ throw this.mapError(err);
235
+ }
236
+ for (const row of rows) {
237
+ const chunkKey = row.chunk_key;
238
+ if (typeof chunkKey !== "number") {
239
+ throw new IntegrityError("warm row is missing its chunk_key");
240
+ }
241
+ yield { chunkKey, ...this.rowFrom(row, chunkKey) };
242
+ after = chunkKey;
243
+ }
244
+ if (rows.length < this.listBatch) return;
245
+ }
246
+ }
247
+ /**
248
+ * Build a {@link WarmRow} from a raw row. `token` must be a non-empty string and `payload` a Buffer — a
249
+ * missing/typo'd column means a corrupt or foreign row, which we reject (untrusted-data posture) rather
250
+ * than paper over. The returned bytes are a fresh copy (driver-owned, read-only per the contract).
251
+ */
252
+ rowFrom(row, chunkKey) {
253
+ if (typeof row.token !== "string" || row.token === "") {
254
+ throw new IntegrityError(`warm row for chunk ${chunkKey} is missing its token`);
255
+ }
256
+ if (!(row.payload instanceof Uint8Array)) {
257
+ throw new IntegrityError(`warm row for chunk ${chunkKey} is missing its payload`);
258
+ }
259
+ return { token: row.token, bytes: new Uint8Array(row.payload) };
260
+ }
261
+ /**
262
+ * Reclassify a transient MySQL fault (lock-wait/deadlock, connection lost, too-many-connections, dropped
263
+ * socket) as a retryable {@link TransientError}; everything else propagates unchanged. Applied at every
264
+ * query site so callers + the retry decorator only ever see typed errors. A deterministic OCC/duplicate-key
265
+ * conflict is signalled by the driver itself — never reclassified here.
266
+ */
267
+ mapError(err) {
268
+ if (isTransient(err)) {
269
+ return new TransientError(
270
+ `transient MySQL fault: ${err?.code ?? err?.errno ?? "unknown"}`,
271
+ { cause: err }
272
+ );
273
+ }
274
+ return err;
275
+ }
276
+ };
277
+
278
+ exports.MysqlWarmDriver = MysqlWarmDriver;
279
+ exports.mysqlWarmTableDDL = mysqlWarmTableDDL;
280
+ //# sourceMappingURL=index.cjs.map
281
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/errors.ts","../../src/core/ports.ts","../../src/core/validate.ts","../../src/drivers/_shared/keys.ts","../../src/drivers/mysql/keys.ts","../../src/drivers/mysql/mysql-errors.ts","../../src/drivers/mysql/warm.ts"],"names":["randomUUID"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Typed errors — callers learn *why* something failed, never by parsing strings. Retry is the driver\n * decorators' job, not the engine's: a driver classifies its backend's failures into this vocabulary, and\n * `withRetry` decides what is transient.\n */\n\n/**\n * Registry-symbol brands. The package ships as multiple bundles — the core entry and the `./s3` / `./dynamodb`\n * subpaths — and the builder inlines `core/errors` into each. A driver in a subpath bundle therefore throws a\n * *different* class object than the one the core engine/retry code would `instanceof`-check, so `instanceof`\n * silently returns false across that boundary in the published CJS package (defeating OCC/transient retry and\n * compaction race-handling). These `Symbol.for` brands are identity-stable across bundles/realms; classify\n * errors with the exported predicates below (never `instanceof`) anywhere an error may cross the boundary.\n */\nconst ERROR_BRAND: unique symbol = Symbol.for('cloud-roaring.error');\nconst TRANSIENT_BRAND: unique symbol = Symbol.for('cloud-roaring.error.transient');\n\n/** Base class for every error CloudRoaring throws. */\nexport class CloudRoaringError extends Error {\n /** Cross-bundle brand — see the predicates ({@link isCloudRoaringError}, …). Non-enumerable-ish (symbol key ⇒ not in JSON). */\n readonly [ERROR_BRAND] = true as const;\n constructor(message: string) {\n super(message);\n // Subclass name (works under transpilation since we set it explicitly). Also the discriminator the\n // predicates match on — a runtime string, so it survives bundling where the class identity does not.\n this.name = new.target.name;\n }\n}\n\n/** Invalid caller input (bad id, segment name, options). Raised before any storage call. */\nexport class ValidationError extends CloudRoaringError {}\n\n/** An OCC conditional write/delete lost the race — the row changed since it was read. */\nexport class WriteConflictError extends CloudRoaringError {}\n\n/** Bytes from a tier are corrupt, oversized, or fail a checksum/format check. */\nexport class IntegrityError extends CloudRoaringError {}\n\n/**\n * A requested object/row does not exist. Part of the driver error vocabulary; thrown by\n * persistent drivers from Phase 2 — the Phase-1 engine + in-memory drivers return `null` instead.\n */\nexport class NotFoundError extends CloudRoaringError {}\n\n/**\n * This build/configuration cannot perform the requested operation, though nothing is malformed. Two uses:\n * (1) **format** — the bytes are well-formed but unreadable here (an unknown `.crbm` major version, an\n * encrypted file before the crypto path exists) — distinct from `IntegrityError` (corruption); and (2)\n * **store configuration** — an operation this store's wiring doesn't support (e.g. a lifecycle helper like\n * `compact`/`eraseSubject` called on a store built without a raw cold driver + registry). Raised at\n * operation time, before any mutation.\n */\nexport class UnsupportedError extends CloudRoaringError {}\n\n/**\n * A driver cannot meet a capability the chosen topology requires (e.g. a Cold driver without range\n * reads). Raised fail-fast at wiring time, never mid-operation.\n */\nexport class CapabilityError extends CloudRoaringError {}\n\n/**\n * An operation would exceed its per-op **denial-of-wallet budget** — too many backend requests for a single\n * `count`/`iterate`/`intersect`/`subjectReport`/`eraseSubject` call — so it is refused **before** fanning out\n * (Decision #3 / invariant T3). Default-on but generous (normal ops never hit it); tune it\n * per store (`budget`) or per op, or disable with `budget: false`. Deterministic (never retried): the op is too\n * big by policy, not by luck. Each request's bytes are separately capped (the safe-deserialize ceiling), so\n * bounding the request count transitively bounds bytes. Carries the projected count + the limit, never data.\n */\nexport class BudgetExceededError extends CloudRoaringError {}\n\n/**\n * An encrypted segment's data key (DEK) cannot be unwrapped because the keystore holds none of the\n * key-encryption-keys (KEKs) its wrappings reference — the KEK was never configured, rotated away without\n * keeping the old key, or lost. Deterministic (never retried): without a KEK the ciphertext is unreadable by\n * design. The flip side of crypto-shred — when this is *intended* (a destroyed segment) the registry row is\n * already a `destroyed` tombstone; when it's *not*, restore the missing KEK (or its recovery KEK). Carries no\n * key material.\n */\nexport class KeyUnavailableError extends CloudRoaringError {}\n\n/**\n * A **transient** infrastructure fault that is safe to retry — throttling, a 5xx, a dropped connection,\n * a client-side request timeout. Drivers classify their backend's retryable faults and raise this (the\n * SDK-specific knowledge stays in the SDK-specific driver); the retry layer (`core/retry`) retries **only**\n * this class, never a deterministic error like {@link ValidationError}, {@link IntegrityError},\n * {@link NotFoundError}, or {@link WriteConflictError} (retrying those is pointless or wrong). The original\n * error is preserved in `cause` so callers can still inspect it.\n *\n * Note for logging hygiene (threat-model S12): `cause` is the **raw SDK error**, which may carry operational\n * metadata (endpoint host, request IDs, `$metadata`). The library's own `message` is identifier-only and safe\n * to log; if you serialize the whole error *chain*, be aware you're including that metadata.\n */\nexport class TransientError extends CloudRoaringError {\n /** A second brand so the whole transient subtree (incl. {@link TimeoutError}) is classifiable cross-bundle. */\n readonly [TRANSIENT_BRAND] = true as const;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message);\n if (options && 'cause' in options) this.cause = options.cause;\n }\n}\n\n/**\n * A single attempt exceeded its time budget. Subclass of {@link TransientError} so the retry layer treats a\n * timeout as retryable by default — a stalled request often succeeds on a fresh connection. Raised by a\n * driver whose injected client reports a request timeout — setting one on your injected client is the\n * recommended way to bound a hang.\n */\nexport class TimeoutError extends TransientError {}\n\n/**\n * Bundle-safe error predicates — use these, not `instanceof`, wherever an error may cross the core↔driver\n * (`./s3` / `./dynamodb`) boundary (and prefer them in consumer `catch` blocks too, for the same reason). They\n * match the {@link ERROR_BRAND} registry brand + the runtime `name`, both of which survive separate bundling.\n */\nfunction hasBrand(err: unknown, brand: symbol): boolean {\n return (\n typeof err === 'object' && err !== null && (err as Record<symbol, unknown>)[brand] === true\n );\n}\n\n/** Any error thrown by CloudRoaring (any tier, any bundle). */\nexport function isCloudRoaringError(err: unknown): err is CloudRoaringError {\n return hasBrand(err, ERROR_BRAND);\n}\n\n/** An OCC conditional write/delete lost the race — retry the read-modify-write, don't fail. */\nexport function isWriteConflictError(err: unknown): err is WriteConflictError {\n return isCloudRoaringError(err) && err.name === 'WriteConflictError';\n}\n\n/** A retryable transient infrastructure fault (incl. {@link TimeoutError}). The retry layer keys on this. */\nexport function isTransientError(err: unknown): err is TransientError {\n return hasBrand(err, TRANSIENT_BRAND);\n}\n\n/** A requested object/row does not exist. */\nexport function isNotFoundError(err: unknown): err is NotFoundError {\n return isCloudRoaringError(err) && err.name === 'NotFoundError';\n}\n\n/** Corrupt/oversized/failed-checksum bytes from a tier. */\nexport function isIntegrityError(err: unknown): err is IntegrityError {\n return isCloudRoaringError(err) && err.name === 'IntegrityError';\n}\n\n/** Invalid caller input. */\nexport function isValidationError(err: unknown): err is ValidationError {\n return isCloudRoaringError(err) && err.name === 'ValidationError';\n}\n","/**\n * Storage-driver contracts the engine depends on.\n *\n * Phase 1 uses the subset needed by the in-memory engine: the per-chunk Warm store (under OCC) and a\n * per-chunk read view of Cold. Drivers move opaque bytes + an OCC token; they never understand roaring,\n * `adds`/`removes`, or the `.crbm` layout. The full `IColdDriver` (blob/range) + `IRegistryDriver` arrive\n * with generations in Phase 2/3 — the `.crbm` reader will implement `ColdChunkSource`.\n */\n\nimport type { BlobSink } from './blob';\nimport type { WrappedDek } from './crypto';\n\n/** Opaque optimistic-concurrency token — unique per write, compared by equality only. */\nexport type Token = string;\n\n/** Sentinel for `putConditional` meaning \"the row must not exist yet\" (create). */\n// `Symbol.for` (the global registry), NOT `Symbol()`: the package ships multiple bundles (the core entry and\n// the `./dynamodb` / `./s3` subpaths), and tsup inlines `core/ports` into each. A plain `Symbol('no-row')`\n// would be a DISTINCT instance per bundle, so the engine's `NO_ROW` wouldn't `===` the one the DynamoDb warm\n// driver compares against — every create would misroute to the token-fenced path and fail. A registry symbol\n// is identity-stable across bundles/realms. (Invisible in tests, which share one source module graph.)\nexport const NO_ROW: unique symbol = Symbol.for('cloud-roaring.no-row');\nexport type NoRow = typeof NO_ROW;\n\nexport interface SegmentRef {\n readonly namespace?: string;\n readonly segment: string;\n}\n\nexport interface ChunkRef extends SegmentRef {\n readonly chunkKey: number;\n}\n\n/** Identifies one immutable `.crbm` object — a single generation of a segment. */\nexport interface GenKey extends SegmentRef {\n readonly generation: number;\n}\n\nexport interface WarmRow {\n readonly token: Token;\n /** Read-only: owned by the driver. Callers must NOT mutate the buffer (re-encode to write). */\n readonly bytes: Uint8Array;\n}\n\n/**\n * Read consistency for a Warm fetch (gap #9). `consistent: true` (the default — omitted ⇒ true) is a\n * strong, read-your-writes read, as the OCC read-modify-write path requires. `consistent: false` requests an\n * **eventually-consistent** read — on DynamoDB that is ~½ the RCU cost; a driver that is always strongly\n * consistent (in-memory, LocalFs) ignores it. The engine uses it only on read paths (`has`/`count`/`iterate`/\n * `intersect`) when the store opts into `warmReadConsistency: 'eventual'`, trading read-after-write for cost.\n */\nexport interface WarmReadOptions {\n readonly consistent?: boolean;\n}\n\n/** Per-chunk Warm store under optimistic concurrency. Returned byte buffers are read-only. */\nexport interface IWarmDriver {\n get(ref: ChunkRef, opts?: WarmReadOptions): Promise<WarmRow | null>;\n /** OCC write. `expected` = `NO_ROW` to create, else the token previously read. Throws `WriteConflictError` on mismatch. */\n putConditional(\n ref: ChunkRef,\n bytes: Uint8Array,\n expected: Token | NoRow,\n ): Promise<{ token: Token }>;\n /**\n * Fenced delete — only if the stored token still equals `expected`; throws `WriteConflictError`\n * otherwise. **Used by compaction (Phase 4); the Phase-1 engine never calls it** — drivers still\n * implement it to satisfy the conformance suite.\n */\n deleteConditional(ref: ChunkRef, expected: Token): Promise<void>;\n /** All dirty chunks of a segment, ascending `chunkKey`. Yielded `bytes` are read-only. */\n listChunks(\n ref: SegmentRef,\n opts?: WarmReadOptions,\n ): AsyncIterable<{ chunkKey: number } & WarmRow>;\n}\n\n/**\n * A segment's grounded on-disk footprint (Phase 5b) — the current generation's Cold object bytes, read\n * cheaply from the `.crbm` footer/index (no payload reads). Powers the grounded `costReport()`.\n */\nexport interface SegmentSize {\n readonly sizeBytes: number;\n}\n\n/** Per-chunk read view of the immutable Cold tier (implemented by the `.crbm` reader from Phase 2). */\nexport interface ColdChunkSource {\n /** Read-only bytes for the chunk, or `null` if absent. Callers must not mutate the buffer. */\n getChunk(ref: ChunkRef): Promise<Uint8Array | null>;\n listChunkKeys(ref: SegmentRef): Promise<number[]>;\n /**\n * Optional (Phase 5b): the current generation's grounded size, cheaply (from the already-parsed\n * `.crbm` index — no payload reads), or `null` if the segment has no Cold generation. Powers the\n * grounded `costReport()`.\n */\n sizeOf?(ref: SegmentRef): Promise<SegmentSize | null>;\n /**\n * Optional (Phase 5c): the current generation's **per-chunk cardinality** (`chunkKey → count`), read\n * from the already-parsed `.crbm` index with **no payload reads**, or `null` if the segment has no Cold\n * generation. Powers the cheap `count()` — a warm-delta-free chunk is counted straight from the index\n * instead of fetching + deserializing it. A source with no index (e.g. the in-memory source) omits this,\n * and `count()` falls back to fetching + merging every chunk.\n */\n cardinalities?(ref: SegmentRef): Promise<ReadonlyMap<number, number> | null>;\n /**\n * Optional (Phase B, gap #4): the segment's **current generation number** as this source resolves it right now\n * (registry `currentGen`, or the highest cold generation), or `null` if the segment has no Cold generation. The\n * engine keys its HOT chunk cache by this so a generation bump (a compaction commit) is observed — a new\n * generation misses the cache instead of serving a stale decoded chunk, and an erased id can't resurrect from a\n * cached pre-compaction chunk. Cheap: served from the source's own (short-TTL-refreshed) snapshot, **not** a\n * fresh backend read per call. A source that pins one immutable generation for its whole lifetime and never\n * refreshes may omit this — the engine then keys the cache without a generation, exactly as before.\n */\n currentGeneration?(ref: SegmentRef): Promise<number | null>;\n}\n\n/** Capabilities a Cold driver advertises; validated at wiring time, fail-fast. */\nexport interface ColdCaps {\n /** REQUIRED — the format relies on byte-range reads. */\n readonly rangeRead: true;\n /** Largest single object the backend accepts (informs single-object-vs-shard, B7 — future). */\n readonly maxObjectBytes: number;\n /** Optional: enables the pure-object `LATEST`-pointer registry variant (S3 conditional put). */\n readonly conditionalPut?: boolean;\n}\n\n/**\n * Immutable object storage for `.crbm` generations. A \"dumb byte mover\": it\n * understands neither roaring nor the `.crbm` layout, only opaque bytes addressed by a {@link GenKey}.\n * The core never reuses a key, so puts are write-once.\n */\nexport interface IColdDriver {\n capabilities(): ColdCaps;\n /**\n * Stream a new immutable generation. The driver opens a destination, hands `write` a {@link BlobSink},\n * then atomically commits (and computes the content hash). Throws if the key already exists (write-once).\n */\n putImmutable(\n key: GenKey,\n write: (sink: BlobSink) => Promise<void>,\n ): Promise<{ size: number; sha256: string }>;\n /** Range read; the caller bounds-checks. Out-of-range is rejected, never a short/adjacent read. */\n getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;\n /** Speculative tail read: the last `min(maxBytes, size)` bytes + the total object size. */\n getTail(key: GenKey, maxBytes: number): Promise<{ bytes: Uint8Array; size: number }>;\n delete(key: GenKey): Promise<void>;\n /** Enumerate the generations present for a segment (orphan sweep / latest-gen resolution). */\n list(ref: SegmentRef): AsyncIterable<GenKey>;\n}\n\n/**\n * Lifecycle status of a segment. `active` is the steady state;\n * `compacting`/`erasing` are transient flags a daemon sets while it works (Phase 4d/4e); `destroyed` is the\n * post-crypto-shred tombstone (the row is kept for audit but the segment is logically gone). The 4c registry\n * stores and round-trips the field; the *transitions* are driven by their owning features.\n */\nexport type RegistryStatus = 'active' | 'compacting' | 'erasing' | 'destroyed';\n\n/** Free-form, JSON-serializable governance metadata (retention/residency policy); shape lands in Phase 6. */\nexport type GovernanceMeta = Record<string, unknown>;\n\n/**\n * One registry row — the authoritative per-segment record. Exactly one per segment.\n */\nexport interface RegistryRecord extends SegmentRef {\n /** **The** authoritative LATEST pointer: which immutable Cold generation is current. */\n readonly currentGen: number;\n /**\n * Per-segment data-key (DEK) wrappings for encryption-at-rest (Phase 4e): the DEK envelope-wrapped under one\n * or more KEKs (active + optional recovery). Reading unwraps with any held KEK; **crypto-shred deletes this\n * whole list**, making the segment's at-rest bytes permanently unrecoverable. Absent ⇒ the segment is\n * cleartext. See [DECISIONS #19].\n */\n readonly wrappedDeks?: readonly WrappedDek[];\n /**\n * Optional **external** key reference (reserved) — e.g. a KMS key ARN for a future KMS keystore adapter that\n * keeps wrapped material in the KMS rather than in-band {@link wrappedDeks}. Unused by the in-process BYOK\n * keystore. Clearable on crypto-shred.\n */\n readonly keyId?: string;\n /**\n * Dirty-row hint written by `findCompactable`. Discovery still recomputes the live dirty count by scanning\n * Warm each cycle (the count that decides candidacy), but it now **reads** this hint to gate a change-guarded\n * CAS — the hint is only rewritten when the count actually moved, so an all-idle fleet issues no per-segment\n * registry writes. It does not yet drive candidacy — turning the Warm scan itself into O(dirty) is a\n * deferred cheap-enumeration fix.\n */\n readonly dirtyChunkCount: number;\n readonly status: RegistryStatus;\n /**\n * Daemon health (Phase D, gap #2): epoch-ms of the last **successfully committed** compaction, and the\n * count of **consecutive** compaction failures (reset to 0 by a successful commit). `lastCompactedAt`\n * powers a dead-man's-switch / staleness alarm; `consecutiveFailures` drives **poison-segment quarantine** —\n * discovery skips a segment past a failure threshold so one corrupt Warm row can't freeze the compaction of a\n * segment **that has a committed generation** forever (with no alarm) while its Warm backlog grows unbounded.\n * (A segment whose *very first* compaction keeps failing has no registry row to count against yet —\n * bootstrap faults are surfaced as `error` results but not yet quarantined; gap #2.) Both\n * **optional** (absent on rows written before Phase D, and on a segment never compacted) — read them as\n * `lastCompactedAt ?? undefined`, `consecutiveFailures ?? 0`.\n */\n readonly lastCompactedAt?: number;\n readonly consecutiveFailures?: number;\n /**\n * Compaction lease (Phase 4d): the opaque id of the worker currently compacting this segment, and the\n * epoch-ms at which its lease expires. `status === 'compacting'` with `leaseExpiresAt` in the future means\n * a live daemon owns it; once `leaseExpiresAt` is past, another worker may **steal** the lease (the prior\n * holder crashed). Both absent in the steady (`active`) state. The lease is an efficiency guard only — the\n * 2-phase commit is correct without it (OCC-fenced swap + write-once generations + version-fenced purge).\n */\n readonly leaseOwner?: string;\n readonly leaseExpiresAt?: number;\n /** Governance policy (Phase 6); stored + round-tripped now, semantics later. */\n readonly retention?: GovernanceMeta;\n readonly residency?: GovernanceMeta;\n /** Epoch-ms of creation / last mutation (from the driver's injected clock). */\n readonly createdAt: number;\n readonly updatedAt: number;\n /** Opaque OCC token — compare-by-equality, never reused (ABA-safe), exactly like {@link WarmRow.token}. */\n readonly token: Token;\n}\n\n/** The caller-settable fields at {@link IRegistryDriver.create} (audit + token are driver-managed). */\nexport interface NewRegistryRecord {\n readonly currentGen: number;\n readonly wrappedDeks?: readonly WrappedDek[];\n readonly keyId?: string;\n /** Defaults to 0. */\n readonly dirtyChunkCount?: number;\n /** Defaults to `'active'`. */\n readonly status?: RegistryStatus;\n readonly retention?: GovernanceMeta;\n readonly residency?: GovernanceMeta;\n}\n\n/** Fields a {@link IRegistryDriver.compareAndSwap} may mutate (identity + audit + token are off-limits). */\nexport type RegistryPatch = Partial<\n Pick<\n RegistryRecord,\n | 'currentGen'\n | 'wrappedDeks'\n | 'keyId'\n | 'dirtyChunkCount'\n | 'status'\n | 'leaseOwner'\n | 'leaseExpiresAt'\n | 'lastCompactedAt'\n | 'consecutiveFailures'\n | 'retention'\n | 'residency'\n >\n>;\n\n/** Capabilities a registry driver advertises; validated fail-fast at wiring time. */\nexport interface RegCaps {\n /** REQUIRED — `currentGen` feeds read correctness + compaction CAS, so reads must be strongly consistent. */\n readonly strongRead: true;\n}\n\n/**\n * Per-segment registry — the authoritative source of `currentGen`, the discovery index, and (Phase 4e) the\n * wrapped-DEK holder. One row per segment under OCC: a\n * never-reused, equality-compared {@link Token} (ABA-safe across delete→recreate, like the Warm tier).\n */\nexport interface IRegistryDriver {\n capabilities(): RegCaps;\n /** The segment's record, or `null` if it doesn't exist (or was deleted). */\n get(ref: SegmentRef): Promise<RegistryRecord | null>;\n /** Create the row; throws {@link WriteConflictError} if it already exists (use CAS to mutate). */\n create(ref: SegmentRef, record: NewRegistryRecord): Promise<{ token: Token }>;\n /** Server-side compare-and-set: apply `patch` iff the stored token equals `expected`, else `WriteConflictError`. */\n compareAndSwap(ref: SegmentRef, expected: Token, patch: RegistryPatch): Promise<{ token: Token }>;\n /** Discovery: every live record, optionally scoped to one namespace. Order is unspecified. */\n list(namespace?: string): AsyncIterable<RegistryRecord>;\n /** Remove the row (tombstoned for ABA-safety — a later `create` still gets a fresh, greater token). */\n delete(ref: SegmentRef): Promise<void>;\n}\n","/**\n * Boundary validation for segment / namespace names (finding S2).\n * Names become object keys, partition keys, and file paths — so they're validated before they\n * ever reach storage: strict charset, bounded length, no path traversal.\n */\nimport { ValidationError } from './errors';\nimport type { ChunkRef, SegmentRef } from './ports';\n\nconst CHUNK_KEY_MAX = 0xffff;\n\n// The locked name grammar: 1 leading alphanumeric + up to 255 more (max 256 chars).\nconst NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;\n\nfunction validatePart(value: string, field: string): void {\n if (typeof value !== 'string' || !NAME.test(value) || value.includes('..')) {\n throw new ValidationError(\n `${field} must match ${String(NAME)} and contain no \"..\": got ${JSON.stringify(value)}`,\n );\n }\n}\n\nexport function validateSegmentRef(ref: SegmentRef): void {\n validatePart(ref.segment, 'segment');\n if (ref.namespace !== undefined) validatePart(ref.namespace, 'namespace');\n}\n\n/** Validate a chunk ref: the segment/namespace grammar plus `chunkKey` ∈ `[0, 65535]` (a u16). */\nexport function validateChunkRef(ref: ChunkRef): void {\n validateSegmentRef(ref);\n if (!Number.isInteger(ref.chunkKey) || ref.chunkKey < 0 || ref.chunkKey > CHUNK_KEY_MAX) {\n throw new ValidationError(\n `chunkKey must be an integer in [0, ${CHUNK_KEY_MAX}]; got ${ref.chunkKey}`,\n );\n }\n}\n","/**\n * Shared key-grammar fragments used by every driver's logical-ref → physical-key mapping (Phase 4c).\n *\n * Extracted once the registry drivers became the 4th/5th consumer of the same `_default` namespace sentinel\n * (it lived copy-pasted in `s3/keys`, `dynamodb/keys`, `localfs/paths`). Pure string logic, no SDK, no I/O —\n * lives in the SDK-free `_shared` bundle so any driver may import it.\n */\n\n/**\n * The physical stand-in for an **absent** namespace. The name grammar forbids a leading underscore\n * so `_default` can never collide with a real\n * namespace — `segment(\"s\")` and `segment(\"s\", { namespace: \"_default\" })` would be a grammar error, not an\n * aliasing hazard.\n */\nexport const DEFAULT_NAMESPACE = '_default';\n\n/** Map an optional namespace to its physical part: the namespace itself, or {@link DEFAULT_NAMESPACE}. */\nexport function namespacePart(namespace: string | undefined): string {\n return namespace ?? DEFAULT_NAMESPACE;\n}\n","/**\n * Identifier + key validation for {@link MysqlWarmDriver} (Phase 7).\n *\n * Pure, SDK-free string logic — unit-testable without a live MySQL. Like the Postgres driver, the logical ref\n * is stored across **parameterized columns** (`namespace`, `segment`, `chunk_key`, `key_prefix`), so ref/prefix\n * values are never concatenated into SQL — `mysql2`'s `?` placeholders bind them as data (no injection surface).\n * The one value that CANNOT be a bind parameter is the **table name** (an identifier, not a value), so it is\n * validated against a strict grammar and **backtick-quoted** before interpolation — that is why this exists.\n */\nimport { ValidationError } from '@/core/errors';\n\n/** A SQL identifier segment: leading letter/underscore, then letters/digits/underscores (ASCII, ≤64 = MySQL max). */\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;\n\n/**\n * Validate a table name — optionally database-qualified (`db.table`) — and return it **safely quoted** for\n * interpolation (each part wrapped in backticks, MySQL's identifier quote). A table name is an identifier, so\n * it can't be a bind parameter; validating against {@link IDENT} (which excludes backticks, whitespace, `;`,\n * `-`, etc.) and quoting closes the only SQL-injection vector the driver has. Rejects anything else, fail-fast.\n */\nexport function validateAndQuoteTable(table: string): string {\n const parts = table.split('.');\n if (parts.length > 2 || parts.some((p) => !IDENT.test(p))) {\n throw new ValidationError(\n `invalid table name ${JSON.stringify(table)} — expected an identifier or \"db.table\" ` +\n `(letters, digits, underscore; ≤64 chars; leading letter/underscore)`,\n );\n }\n return parts.map((p) => `\\`${p}\\``).join('.');\n}\n\n/**\n * The `key_prefix` column is `VARCHAR(191)` (sized so the composite primary key fits InnoDB's 3072-byte index\n * limit under utf8mb4). A longer prefix must be rejected **fail-fast**: under a non-strict `sql_mode` MySQL\n * would otherwise *silently truncate* it, and two logical stores whose prefixes share the same first 191\n * characters would then collide on the same rows — defeating the isolation `keyPrefix` exists to provide.\n */\nconst MAX_KEY_PREFIX_CHARS = 191;\n\n/**\n * Validate a caller-supplied `keyPrefix` (lets several logical stores share one table). It's bound as a `?`\n * value, not concatenated, so the character check is a light sanity guard (no control characters), not an\n * anti-injection boundary; the length check, by contrast, is a real correctness guard (see\n * {@link MAX_KEY_PREFIX_CHARS}). Empty/undefined means no prefix (stored as the empty string).\n */\nexport function normalizeKeyPrefix(prefix: string | undefined): string {\n if (prefix === undefined || prefix === '') return '';\n let chars = 0;\n for (const ch of prefix) {\n chars++; // `for…of` iterates Unicode code points — matches MySQL's VARCHAR character count\n if (ch.charCodeAt(0) < 0x20) {\n throw new ValidationError('keyPrefix must not contain control characters');\n }\n }\n if (chars > MAX_KEY_PREFIX_CHARS) {\n throw new ValidationError(\n `keyPrefix must be at most ${MAX_KEY_PREFIX_CHARS} characters (the key_prefix column width); got ${chars}`,\n );\n }\n return prefix;\n}\n","/**\n * Pure helpers for classifying MySQL (`mysql2`) errors (Phase 7; transient class mirrors S3/GCS/Azure/Postgres).\n *\n * SDK-free + side-effect-free — they read only structural shapes off the thrown value (`err.errno`, a MySQL\n * error number; and `err.code`, a `mysql2`/Node string code), so the translation is unit-testable without a\n * live MySQL. Two classifiers: {@link isDuplicateKey} (the create-if-absent conflict signal, MySQL errno 1062)\n * and {@link isTransient} (retryable). Everything else propagates unchanged so a real bug is never blind-retried.\n */\n\n/** The MySQL error number off a thrown `mysql2` error, if present. */\nfunction errno(err: unknown): number | undefined {\n const n = (err as { errno?: unknown } | null)?.errno;\n return typeof n === 'number' ? n : undefined;\n}\n\n/** The string `code` off a thrown `mysql2`/Node error (e.g. `ER_DUP_ENTRY`, `PROTOCOL_CONNECTION_LOST`, `ECONNRESET`). */\nfunction code(err: unknown): string | undefined {\n const c = (err as { code?: unknown } | null)?.code;\n return typeof c === 'string' ? c : undefined;\n}\n\n/**\n * A duplicate-key violation (MySQL errno **1062** / `ER_DUP_ENTRY`). The driver's create-if-absent path is a\n * plain `INSERT`; a pre-existing row surfaces as this, which the driver maps to a `WriteConflictError`. Kept\n * separate from {@link isTransient} — a duplicate key is a **deterministic** conflict, never retried.\n */\nexport function isDuplicateKey(err: unknown): boolean {\n return errno(err) === 1062 || code(err) === 'ER_DUP_ENTRY';\n}\n\n/**\n * A transient MySQL fault that is safe to retry: a lock-wait timeout / deadlock (1205 / 1213), too-many-\n * connections (1040 / 1203), a server shutdown / killed connection (1053 / 1927), the client's\n * server-gone / connection-lost codes (2006 / 2013, `PROTOCOL_CONNECTION_LOST`, `PROTOCOL_SEQUENCE_TIMEOUT`),\n * or a dropped/timed-out socket. Deterministic, caller-meaningful outcomes (duplicate key, unknown table,\n * syntax errors, etc.) are NOT transient — they must surface, never be blind-retried.\n */\nexport function isTransient(err: unknown): boolean {\n const n = errno(err);\n if (\n n === 1205 || // ER_LOCK_WAIT_TIMEOUT\n n === 1213 || // ER_LOCK_DEADLOCK\n n === 1040 || // ER_CON_COUNT_ERROR (too many connections)\n n === 1203 || // ER_TOO_MANY_USER_CONNECTIONS\n n === 1053 || // ER_SERVER_SHUTDOWN\n n === 1927 || // ER_CONNECTION_KILLED\n n === 2006 || // CR_SERVER_GONE_ERROR\n n === 2013 // CR_SERVER_LOST\n ) {\n return true;\n }\n const c = code(err);\n return (\n c === 'PROTOCOL_CONNECTION_LOST' ||\n c === 'PROTOCOL_SEQUENCE_TIMEOUT' ||\n c === 'CR_SERVER_GONE_ERROR' ||\n c === 'CR_SERVER_LOST' ||\n c === 'ECONNRESET' ||\n c === 'ECONNABORTED' ||\n c === 'ETIMEDOUT' ||\n c === 'ESOCKETTIMEDOUT' ||\n c === 'ECONNREFUSED' ||\n c === 'EHOSTUNREACH' ||\n c === 'ENETUNREACH' ||\n c === 'EPIPE' ||\n c === 'EAI_AGAIN' ||\n c === 'ENOTFOUND'\n );\n}\n","/**\n * `MysqlWarmDriver` — an {@link IWarmDriver} over MySQL / MariaDB (Phase 7).\n *\n * \"No DynamoDB — use the MySQL you already run.\" Uses the official `mysql2` (its promise API), an **optional\n * peer dependency** — only consumers of `cloud-roaring/mysql` install it. A `mysql2` `Pool` is **injected**\n * (dependency injection): the driver owns no connection/credential logic, so it's thin, reuses the caller's\n * pool, and is testable against a MySQL container.\n *\n * Each chunk is one row in a single table (`PRIMARY KEY (key_prefix, namespace, segment, chunk_key)`), with an\n * opaque OCC **token** (a random UUID minted per write) and the delta `payload` (LONGBLOB). Optimistic\n * concurrency is real, cross-process, and server-side:\n * - **create-if-absent** = a plain `INSERT`; a pre-existing row raises `ER_DUP_ENTRY` (errno 1062) ⇒\n * {@link WriteConflictError}.\n * - **token-fenced update / delete** = `UPDATE … / DELETE … WHERE … AND token = ?`; `affectedRows !== 1` ⇒\n * the stored token moved (or the row is gone) ⇒ {@link WriteConflictError}.\n *\n * **Why `affectedRows` is reliable here despite MySQL counting *changed* (not matched) rows by default:** the\n * new token is a fresh random UUID on every write, so a matched fenced `UPDATE` **always** changes the `token`\n * column ⇒ `affectedRows === 1` on a match and `0` on a miss, regardless of the connection's\n * `CLIENT_FOUND_ROWS` flag. (The driver never sets a value equal to what's stored, so the \"unchanged row ⇒ 0\n * affected\" MySQL quirk cannot mask a real match.)\n *\n * **Collation matters for correctness:** the table is `utf8mb4_bin`, so `key_prefix` / `namespace` / `segment`\n * / `token` compare **byte-exact and case-sensitive**. MySQL's *default* collation (`utf8mb4_0900_ai_ci`) is\n * case- and accent-**insensitive**, which would make `segment = 'A'` wrongly match a row keyed `'a'` — a\n * correctness hole. {@link mysqlWarmTableDDL} pins `utf8mb4_bin` to close it; a caller supplying their own\n * table must do the same.\n *\n * Tokens are not reused across delete→recreate (ABA-safe, D3): a delete removes the row and a recreate mints a\n * fresh random UUID, so a token from before the delete won't match. (Probabilistic — a random 122-bit UUIDv4,\n * collision odds negligible — vs the DynamoDB driver's structural monotonic counter; the hard-delete model\n * can't offer the latter, and conformance D3 pins the behavior. See DECISIONS #57.) Reads are strongly\n * consistent (single primary), so the optional `WarmReadOptions` consistency hint is a no-op, like the\n * in-memory / LocalFs / Postgres drivers. Drivers may use `node:crypto`; only `core/` is determinism-bound.\n */\nimport { randomUUID } from 'node:crypto';\nimport type { Pool, ResultSetHeader, RowDataPacket } from 'mysql2/promise';\nimport {\n IntegrityError,\n TransientError,\n ValidationError,\n WriteConflictError,\n isWriteConflictError,\n} from '@/core/errors';\nimport { NO_ROW } from '@/core/ports';\nimport type { ChunkRef, IWarmDriver, NoRow, SegmentRef, Token, WarmRow } from '@/core/ports';\nimport { validateChunkRef, validateSegmentRef } from '@/core/validate';\nimport { namespacePart } from '../_shared/keys';\nimport { normalizeKeyPrefix, validateAndQuoteTable } from './keys';\nimport { isDuplicateKey, isTransient } from './mysql-errors';\n\nconst DEFAULT_TABLE = 'cloud_roaring_warm';\n/** Default listChunks keyset-pagination batch — bounds peak memory to ~this many rows regardless of width. */\nconst DEFAULT_LIST_BATCH = 1000;\n\nexport interface MysqlWarmDriverOptions {\n /**\n * A constructed `mysql2` promise `Pool` (or a pool-compatible client exposing `query`). The driver never\n * opens/closes connections — the caller owns the pool's lifecycle. It **must target the primary**: reads are\n * treated as strongly consistent (the OCC read-modify-write needs read-your-writes), so a pool pointed at a\n * read replica could serve stale rows and silently break OCC.\n */\n readonly pool: Pool;\n /** Warm table name — identifier or `db.table` (default `cloud_roaring_warm`). Must already exist. */\n readonly table?: string;\n /** Optional key-prefix column value so several logical stores can share one table (bound as data). */\n readonly keyPrefix?: string;\n /** Advanced: `listChunks` keyset-pagination page size (default 1000). Tunes peak memory on wide segments. */\n readonly listPageSize?: number;\n}\n\n/** One raw warm row as selected. */\ninterface RawRow extends RowDataPacket {\n chunk_key?: number;\n token?: unknown;\n payload?: unknown;\n}\n\n/**\n * The idempotent DDL for the warm table (identifier-validated + backtick-quoted). The driver does **not**\n * create schema (it stays thin + needs no DDL privileges at runtime); run this once at deploy time, e.g.\n * `await pool.query(mysqlWarmTableDDL())`. The `utf8mb4_bin` collation is **required for correctness** (see the\n * class doc): it makes the key columns compare byte-exact and case-sensitive, unlike MySQL's default ci\n * collation. Column lengths keep the composite primary key within InnoDB's 3072-byte index limit under utf8mb4.\n */\nexport function mysqlWarmTableDDL(table: string = DEFAULT_TABLE): string {\n const t = validateAndQuoteTable(table);\n return (\n `CREATE TABLE IF NOT EXISTS ${t} (\\n` +\n ` key_prefix VARCHAR(191) NOT NULL,\\n` +\n ` namespace VARCHAR(256) NOT NULL,\\n` +\n ` segment VARCHAR(256) NOT NULL,\\n` +\n ` chunk_key INT NOT NULL,\\n` +\n ` token CHAR(36) NOT NULL,\\n` +\n ` payload LONGBLOB NOT NULL,\\n` +\n ` PRIMARY KEY (key_prefix, namespace, segment, chunk_key)\\n` +\n `) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;`\n );\n}\n\nexport class MysqlWarmDriver implements IWarmDriver {\n private readonly pool: Pool;\n private readonly table: string; // validated + backtick-quoted for interpolation\n private readonly keyPrefix: string;\n private readonly listBatch: number;\n\n constructor(options: MysqlWarmDriverOptions) {\n this.pool = options.pool;\n this.table = validateAndQuoteTable(options.table ?? DEFAULT_TABLE);\n this.keyPrefix = normalizeKeyPrefix(options.keyPrefix);\n const batch = options.listPageSize ?? DEFAULT_LIST_BATCH;\n if (!Number.isSafeInteger(batch) || batch < 1) {\n throw new ValidationError(`listPageSize must be a positive safe integer; got ${batch}`);\n }\n this.listBatch = batch;\n }\n\n // Reads are always strongly consistent (single primary), so the optional `WarmReadOptions` hint would be a\n // no-op — the structurally-optional param is simply omitted (still satisfies IWarmDriver), as in LocalFs/pg.\n async get(ref: ChunkRef): Promise<WarmRow | null> {\n validateChunkRef(ref);\n try {\n const [rows] = await this.pool.query<RawRow[]>(\n `SELECT token, payload FROM ${this.table} ` +\n `WHERE key_prefix = ? AND namespace = ? AND segment = ? AND chunk_key = ?`,\n [this.keyPrefix, namespacePart(ref.namespace), ref.segment, ref.chunkKey],\n );\n const row = rows[0];\n return row === undefined ? null : this.rowFrom(row, ref.chunkKey);\n } catch (err) {\n throw this.mapError(err);\n }\n }\n\n async putConditional(\n ref: ChunkRef,\n bytes: Uint8Array,\n expected: Token | NoRow,\n ): Promise<{ token: Token }> {\n validateChunkRef(ref);\n const token = randomUUID();\n // Copy the payload into a Buffer for the LONGBLOB binding — severs any caller-owned/reused input buffer.\n const payload = Buffer.from(bytes);\n const ns = namespacePart(ref.namespace);\n try {\n if (expected === NO_ROW) {\n // create-if-absent: a plain INSERT. A pre-existing PK raises ER_DUP_ENTRY (1062), caught below as a\n // deterministic conflict — never an INSERT IGNORE (which would also swallow unrelated errors).\n await this.pool.query<ResultSetHeader>(\n `INSERT INTO ${this.table} (key_prefix, namespace, segment, chunk_key, token, payload) ` +\n `VALUES (?, ?, ?, ?, ?, ?)`,\n [this.keyPrefix, ns, ref.segment, ref.chunkKey, token, payload],\n );\n } else {\n const [res] = await this.pool.query<ResultSetHeader>(\n `UPDATE ${this.table} SET token = ?, payload = ? ` +\n `WHERE key_prefix = ? AND namespace = ? AND segment = ? AND chunk_key = ? AND token = ?`,\n [token, payload, this.keyPrefix, ns, ref.segment, ref.chunkKey, expected],\n );\n // The fresh token always changes the row, so affectedRows reflects the MATCH: 1 = fenced write applied,\n // anything else = the stored token moved or the row is gone (a conflict), never a silent false-success.\n if (res.affectedRows !== 1) {\n throw new WriteConflictError(`OCC conflict on chunk ${ref.chunkKey}`);\n }\n }\n return { token };\n } catch (err) {\n if (isWriteConflictError(err)) throw err; // our own deterministic conflict — never a transient\n if (expected === NO_ROW && isDuplicateKey(err)) {\n throw new WriteConflictError(`chunk ${ref.chunkKey} already exists (create-if-absent)`);\n }\n throw this.mapError(err);\n }\n }\n\n async deleteConditional(ref: ChunkRef, expected: Token): Promise<void> {\n validateChunkRef(ref);\n try {\n const [res] = await this.pool.query<ResultSetHeader>(\n `DELETE FROM ${this.table} ` +\n `WHERE key_prefix = ? AND namespace = ? AND segment = ? AND chunk_key = ? AND token = ?`,\n [this.keyPrefix, namespacePart(ref.namespace), ref.segment, ref.chunkKey, expected],\n );\n if (res.affectedRows !== 1) {\n throw new WriteConflictError(`fenced-delete conflict on chunk ${ref.chunkKey}`);\n }\n } catch (err) {\n if (isWriteConflictError(err)) throw err;\n throw this.mapError(err);\n }\n }\n\n async *listChunks(ref: SegmentRef): AsyncIterable<{ chunkKey: number } & WarmRow> {\n validateSegmentRef(ref);\n const ns = namespacePart(ref.namespace);\n // Keyset pagination on chunk_key (ascending) bounds peak memory to one page WITHIN a single enumeration,\n // regardless of how wide the segment is. (It does NOT resume across a transient fault — the retry decorator\n // re-enumerates a streaming method from the start and buffers, see drivers/retry/retrying-drivers.ts.)\n let after = -1;\n for (;;) {\n let rows: RawRow[];\n try {\n const [res] = await this.pool.query<RawRow[]>(\n `SELECT chunk_key, token, payload FROM ${this.table} ` +\n `WHERE key_prefix = ? AND namespace = ? AND segment = ? AND chunk_key > ? ` +\n `ORDER BY chunk_key ASC LIMIT ?`,\n [this.keyPrefix, ns, ref.segment, after, this.listBatch],\n );\n rows = res;\n } catch (err) {\n throw this.mapError(err);\n }\n for (const row of rows) {\n const chunkKey = row.chunk_key;\n if (typeof chunkKey !== 'number') {\n throw new IntegrityError('warm row is missing its chunk_key');\n }\n yield { chunkKey, ...this.rowFrom(row, chunkKey) };\n after = chunkKey;\n }\n if (rows.length < this.listBatch) return;\n }\n }\n\n /**\n * Build a {@link WarmRow} from a raw row. `token` must be a non-empty string and `payload` a Buffer — a\n * missing/typo'd column means a corrupt or foreign row, which we reject (untrusted-data posture) rather\n * than paper over. The returned bytes are a fresh copy (driver-owned, read-only per the contract).\n */\n private rowFrom(row: RawRow, chunkKey: number): WarmRow {\n if (typeof row.token !== 'string' || row.token === '') {\n throw new IntegrityError(`warm row for chunk ${chunkKey} is missing its token`);\n }\n if (!(row.payload instanceof Uint8Array)) {\n throw new IntegrityError(`warm row for chunk ${chunkKey} is missing its payload`);\n }\n return { token: row.token, bytes: new Uint8Array(row.payload) };\n }\n\n /**\n * Reclassify a transient MySQL fault (lock-wait/deadlock, connection lost, too-many-connections, dropped\n * socket) as a retryable {@link TransientError}; everything else propagates unchanged. Applied at every\n * query site so callers + the retry decorator only ever see typed errors. A deterministic OCC/duplicate-key\n * conflict is signalled by the driver itself — never reclassified here.\n */\n private mapError(err: unknown): unknown {\n if (isTransient(err)) {\n return new TransientError(\n `transient MySQL fault: ${(err as { code?: unknown } | null)?.code ?? (err as { errno?: unknown } | null)?.errno ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n }\n}\n"]}
@@ -0,0 +1,56 @@
1
+ import { Pool } from 'mysql2/promise';
2
+ import { a as IWarmDriver, b as ChunkRef, c as WarmRow, T as Token, N as NoRow, S as SegmentRef } from '../ports-D3BrJ6ax.cjs';
3
+
4
+ interface MysqlWarmDriverOptions {
5
+ /**
6
+ * A constructed `mysql2` promise `Pool` (or a pool-compatible client exposing `query`). The driver never
7
+ * opens/closes connections — the caller owns the pool's lifecycle. It **must target the primary**: reads are
8
+ * treated as strongly consistent (the OCC read-modify-write needs read-your-writes), so a pool pointed at a
9
+ * read replica could serve stale rows and silently break OCC.
10
+ */
11
+ readonly pool: Pool;
12
+ /** Warm table name — identifier or `db.table` (default `cloud_roaring_warm`). Must already exist. */
13
+ readonly table?: string;
14
+ /** Optional key-prefix column value so several logical stores can share one table (bound as data). */
15
+ readonly keyPrefix?: string;
16
+ /** Advanced: `listChunks` keyset-pagination page size (default 1000). Tunes peak memory on wide segments. */
17
+ readonly listPageSize?: number;
18
+ }
19
+ /**
20
+ * The idempotent DDL for the warm table (identifier-validated + backtick-quoted). The driver does **not**
21
+ * create schema (it stays thin + needs no DDL privileges at runtime); run this once at deploy time, e.g.
22
+ * `await pool.query(mysqlWarmTableDDL())`. The `utf8mb4_bin` collation is **required for correctness** (see the
23
+ * class doc): it makes the key columns compare byte-exact and case-sensitive, unlike MySQL's default ci
24
+ * collation. Column lengths keep the composite primary key within InnoDB's 3072-byte index limit under utf8mb4.
25
+ */
26
+ declare function mysqlWarmTableDDL(table?: string): string;
27
+ declare class MysqlWarmDriver implements IWarmDriver {
28
+ private readonly pool;
29
+ private readonly table;
30
+ private readonly keyPrefix;
31
+ private readonly listBatch;
32
+ constructor(options: MysqlWarmDriverOptions);
33
+ get(ref: ChunkRef): Promise<WarmRow | null>;
34
+ putConditional(ref: ChunkRef, bytes: Uint8Array, expected: Token | NoRow): Promise<{
35
+ token: Token;
36
+ }>;
37
+ deleteConditional(ref: ChunkRef, expected: Token): Promise<void>;
38
+ listChunks(ref: SegmentRef): AsyncIterable<{
39
+ chunkKey: number;
40
+ } & WarmRow>;
41
+ /**
42
+ * Build a {@link WarmRow} from a raw row. `token` must be a non-empty string and `payload` a Buffer — a
43
+ * missing/typo'd column means a corrupt or foreign row, which we reject (untrusted-data posture) rather
44
+ * than paper over. The returned bytes are a fresh copy (driver-owned, read-only per the contract).
45
+ */
46
+ private rowFrom;
47
+ /**
48
+ * Reclassify a transient MySQL fault (lock-wait/deadlock, connection lost, too-many-connections, dropped
49
+ * socket) as a retryable {@link TransientError}; everything else propagates unchanged. Applied at every
50
+ * query site so callers + the retry decorator only ever see typed errors. A deterministic OCC/duplicate-key
51
+ * conflict is signalled by the driver itself — never reclassified here.
52
+ */
53
+ private mapError;
54
+ }
55
+
56
+ export { MysqlWarmDriver, type MysqlWarmDriverOptions, mysqlWarmTableDDL };
@@ -0,0 +1,56 @@
1
+ import { Pool } from 'mysql2/promise';
2
+ import { a as IWarmDriver, b as ChunkRef, c as WarmRow, T as Token, N as NoRow, S as SegmentRef } from '../ports-D3BrJ6ax.js';
3
+
4
+ interface MysqlWarmDriverOptions {
5
+ /**
6
+ * A constructed `mysql2` promise `Pool` (or a pool-compatible client exposing `query`). The driver never
7
+ * opens/closes connections — the caller owns the pool's lifecycle. It **must target the primary**: reads are
8
+ * treated as strongly consistent (the OCC read-modify-write needs read-your-writes), so a pool pointed at a
9
+ * read replica could serve stale rows and silently break OCC.
10
+ */
11
+ readonly pool: Pool;
12
+ /** Warm table name — identifier or `db.table` (default `cloud_roaring_warm`). Must already exist. */
13
+ readonly table?: string;
14
+ /** Optional key-prefix column value so several logical stores can share one table (bound as data). */
15
+ readonly keyPrefix?: string;
16
+ /** Advanced: `listChunks` keyset-pagination page size (default 1000). Tunes peak memory on wide segments. */
17
+ readonly listPageSize?: number;
18
+ }
19
+ /**
20
+ * The idempotent DDL for the warm table (identifier-validated + backtick-quoted). The driver does **not**
21
+ * create schema (it stays thin + needs no DDL privileges at runtime); run this once at deploy time, e.g.
22
+ * `await pool.query(mysqlWarmTableDDL())`. The `utf8mb4_bin` collation is **required for correctness** (see the
23
+ * class doc): it makes the key columns compare byte-exact and case-sensitive, unlike MySQL's default ci
24
+ * collation. Column lengths keep the composite primary key within InnoDB's 3072-byte index limit under utf8mb4.
25
+ */
26
+ declare function mysqlWarmTableDDL(table?: string): string;
27
+ declare class MysqlWarmDriver implements IWarmDriver {
28
+ private readonly pool;
29
+ private readonly table;
30
+ private readonly keyPrefix;
31
+ private readonly listBatch;
32
+ constructor(options: MysqlWarmDriverOptions);
33
+ get(ref: ChunkRef): Promise<WarmRow | null>;
34
+ putConditional(ref: ChunkRef, bytes: Uint8Array, expected: Token | NoRow): Promise<{
35
+ token: Token;
36
+ }>;
37
+ deleteConditional(ref: ChunkRef, expected: Token): Promise<void>;
38
+ listChunks(ref: SegmentRef): AsyncIterable<{
39
+ chunkKey: number;
40
+ } & WarmRow>;
41
+ /**
42
+ * Build a {@link WarmRow} from a raw row. `token` must be a non-empty string and `payload` a Buffer — a
43
+ * missing/typo'd column means a corrupt or foreign row, which we reject (untrusted-data posture) rather
44
+ * than paper over. The returned bytes are a fresh copy (driver-owned, read-only per the contract).
45
+ */
46
+ private rowFrom;
47
+ /**
48
+ * Reclassify a transient MySQL fault (lock-wait/deadlock, connection lost, too-many-connections, dropped
49
+ * socket) as a retryable {@link TransientError}; everything else propagates unchanged. Applied at every
50
+ * query site so callers + the retry decorator only ever see typed errors. A deterministic OCC/duplicate-key
51
+ * conflict is signalled by the driver itself — never reclassified here.
52
+ */
53
+ private mapError;
54
+ }
55
+
56
+ export { MysqlWarmDriver, type MysqlWarmDriverOptions, mysqlWarmTableDDL };