@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/redis/keys.ts","../../src/drivers/redis/redis-errors.ts","../../src/drivers/redis/warm.ts"],"names":[],"mappings":";;;;;AAoBO,SAAS,qBAAqB,MAAA,EAAoC;AACvE,EAAA,IAAI,MAAA,KAAW,MAAA,IAAa,MAAA,KAAW,EAAA,EAAI,OAAO,EAAA;AAClD,EAAA,KAAA,MAAW,MAAM,MAAA,EAAQ;AACvB,IAAA,MAAM,IAAA,GAAO,EAAA,CAAG,UAAA,CAAW,CAAC,CAAA;AAC5B,IAAA,IAAI,IAAA,GAAO,MAAQ,EAAA,KAAO,GAAA,IAAO,OAAO,GAAA,IAAO,EAAA,KAAO,GAAA,IAAO,EAAA,KAAO,GAAA,EAAK;AACvE,MAAA,MAAM,IAAI,eAAA;AAAA,QACR,CAAA,oEAAA;AAAA,OACF;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAGA,SAAS,GAAA,CAAI,QAAgB,GAAA,EAAyB;AACpD,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,aAAA,CAAc,IAAI,SAAS,CAAC,CAAA,CAAA,EAAI,GAAA,CAAI,OAAO,CAAA,CAAA;AACjE;AAGO,SAAS,eAAA,CAAgB,QAAgB,GAAA,EAAyB;AACvE,EAAA,kBAAA,CAAmB,GAAG,CAAA;AACtB,EAAA,OAAO,CAAA,CAAA,EAAI,GAAA,CAAI,MAAA,EAAQ,GAAG,CAAC,CAAA,IAAA,CAAA;AAC7B;AAGO,SAAS,YAAA,CAAa,QAAgB,GAAA,EAAuB;AAClE,EAAA,gBAAA,CAAiB,GAAG,CAAA;AACpB,EAAA,OAAO,IAAI,GAAA,CAAI,MAAA,EAAQ,GAAG,CAAC,CAAA,GAAA,EAAM,IAAI,QAAQ,CAAA,CAAA;AAC/C;AAGO,SAAS,iBAAiB,MAAA,EAA+B;AAC9D,EAAA,IAAI,CAAC,gBAAA,CAAiB,IAAA,CAAK,MAAM,GAAG,OAAO,IAAA;AAC3C,EAAA,MAAM,QAAA,GAAW,OAAO,MAAM,CAAA;AAC9B,EAAA,OAAO,MAAA,CAAO,UAAU,QAAQ,CAAA,IAAK,YAAY,CAAA,IAAK,QAAA,IAAY,QAAS,QAAA,GAAW,IAAA;AACxF;;;AC7CA,SAAS,YAAY,GAAA,EAAkC;AACrD,EAAA,MAAM,OAAQ,GAAA,EAAmC,IAAA;AACjD,EAAA,OAAO,OAAO,IAAA,KAAS,QAAA,IAAY,aAAa,IAAA,CAAK,IAAI,IAAI,IAAA,GAAO,MAAA;AACtE;AAQA,IAAM,iBAAA,GACJ,4NAAA;AAEK,SAAS,YAAY,GAAA,EAAuB;AACjD,EAAA,MAAM,GAAA,GAAM,YAAY,GAAG,CAAA;AAC3B,EAAA,IACE,QAAQ,YAAA,IACR,GAAA,KAAQ,kBACR,GAAA,KAAQ,WAAA,IACR,QAAQ,iBAAA,IACR,GAAA,KAAQ,kBACR,GAAA,KAAQ,cAAA,IACR,QAAQ,aAAA,IACR,GAAA,KAAQ,WACR,GAAA,KAAQ,WAAA,IACR,QAAQ,WAAA,EACR;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAA,YAAe,KAAA,IAAS,iBAAA,CAAkB,IAAA,CAAK,IAAI,OAAO,CAAA;AACnE;;;ACVA,IAAM,kBAAA,GAAqB,GAAA;AAO3B,IAAM,OAAA,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA,CAAA;AAYhB,IAAM,OAAA,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA,QAAA,CAAA;AAyCT,IAAM,kBAAN,MAA6C;AAAA,EACjC,MAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EAEjB,YAAY,OAAA,EAAiC;AAC3C,IAAA,IAAA,CAAK,SAAA,GAAY,oBAAA,CAAqB,OAAA,CAAQ,SAAS,CAAA;AACvD,IAAA,MAAM,KAAA,GAAQ,QAAQ,YAAA,IAAgB,kBAAA;AACtC,IAAA,IAAI,CAAC,MAAA,CAAO,aAAA,CAAc,KAAK,CAAA,IAAK,QAAQ,CAAA,EAAG;AAC7C,MAAA,MAAM,IAAI,eAAA,CAAgB,CAAA,kDAAA,EAAqD,KAAK,CAAA,CAAE,CAAA;AAAA,IACxF;AACA,IAAA,IAAA,CAAK,SAAA,GAAY,KAAA;AACjB,IAAA,MAAM,SAAS,OAAA,CAAQ,MAAA;AAGvB,IAAA,IAAI,OAAO,MAAA,CAAO,SAAA,KAAc,UAAA,EAAY;AAC1C,MAAA,MAAA,CAAO,cAAc,WAAA,EAAa,EAAE,cAAc,CAAA,EAAG,GAAA,EAAK,SAAS,CAAA;AAAA,IACrE;AACA,IAAA,IAAI,OAAO,MAAA,CAAO,SAAA,KAAc,UAAA,EAAY;AAC1C,MAAA,MAAA,CAAO,cAAc,WAAA,EAAa,EAAE,cAAc,CAAA,EAAG,GAAA,EAAK,SAAS,CAAA;AAAA,IACrE;AACA,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA,EAEA,MAAM,IAAI,GAAA,EAAwC;AAChD,IAAA,MAAM,OAAA,GAAU,YAAA,CAAa,IAAA,CAAK,SAAA,EAAW,GAAG,CAAA;AAChD,IAAA,IAAI,GAAA;AACJ,IAAA,IAAI;AACF,MAAA,GAAA,GAAM,MAAM,IAAA,CAAK,MAAA,CAAO,aAAA,CAAc,OAAO,CAAA;AAAA,IAC/C,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,IAAA,CAAK,SAAS,GAAG,CAAA;AAAA,IACzB;AACA,IAAA,IAAI,OAAO,IAAA,CAAK,GAAG,CAAA,CAAE,MAAA,KAAW,GAAG,OAAO,IAAA;AAC1C,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAK,GAAA,CAAI,QAAQ,CAAA;AAAA,EACvC;AAAA,EAEA,MAAM,cAAA,CACJ,GAAA,EACA,KAAA,EACA,QAAA,EAC2B;AAC3B,IAAA,MAAM,OAAA,GAAU,YAAA,CAAa,IAAA,CAAK,SAAA,EAAW,GAAG,CAAA;AAChD,IAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,IAAA,CAAK,SAAA,EAAW,GAAG,CAAA;AACpD,IAAA,MAAM,QAAQ,UAAA,EAAW;AACzB,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA;AACjC,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,GAAA,CAAI,QAAQ,CAAA;AAClC,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AACF,MAAA,OAAA,GACE,aAAa,MAAA,GACT,MAAM,IAAA,CAAK,MAAA,CAAO,UAAU,OAAA,EAAS,QAAA,EAAU,QAAA,EAAU,EAAA,EAAI,OAAO,MAAA,EAAQ,OAAO,CAAA,GACnF,MAAM,KAAK,MAAA,CAAO,SAAA;AAAA,QAChB,OAAA;AAAA,QACA,QAAA;AAAA,QACA,QAAA;AAAA,QACA,QAAA;AAAA,QACA,KAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACF;AAAA,IACR,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,IAAA,CAAK,SAAS,GAAG,CAAA;AAAA,IACzB;AACA,IAAA,IAAI,YAAY,CAAA,EAAG;AACjB,MAAA,MAAM,IAAI,kBAAA;AAAA,QACR,QAAA,KAAa,SACT,CAAA,MAAA,EAAS,GAAA,CAAI,QAAQ,CAAA,kCAAA,CAAA,GACrB,CAAA,sBAAA,EAAyB,IAAI,QAAQ,CAAA;AAAA,OAC3C;AAAA,IACF;AACA,IAAA,OAAO,EAAE,KAAA,EAAM;AAAA,EACjB;AAAA,EAEA,MAAM,iBAAA,CAAkB,GAAA,EAAe,QAAA,EAAgC;AACrE,IAAA,MAAM,OAAA,GAAU,YAAA,CAAa,IAAA,CAAK,SAAA,EAAW,GAAG,CAAA;AAChD,IAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,IAAA,CAAK,SAAA,EAAW,GAAG,CAAA;AACpD,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,OAAA,EAAS,UAAU,QAAA,EAAU,MAAA,CAAO,GAAA,CAAI,QAAQ,CAAC,CAAA;AAAA,IACzF,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,IAAA,CAAK,SAAS,GAAG,CAAA;AAAA,IACzB;AACA,IAAA,IAAI,YAAY,CAAA,EAAG;AACjB,MAAA,MAAM,IAAI,kBAAA,CAAmB,CAAA,gCAAA,EAAmC,GAAA,CAAI,QAAQ,CAAA,CAAE,CAAA;AAAA,IAChF;AAAA,EACF;AAAA,EAEA,OAAO,WAAW,GAAA,EAAgE;AAChF,IAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,IAAA,CAAK,SAAA,EAAW,GAAG,CAAA;AACpD,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AAGF,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,QAAA,EAAU,GAAG,CAAA,CAAE,CAAA;AAAA,IACpD,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,IAAA,CAAK,SAAS,GAAG,CAAA;AAAA,IACzB;AACA,IAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,QAAQ,MAAA,EAAQ,CAAA,IAAK,KAAK,SAAA,EAAW;AACvD,MAAA,MAAM,QAAQ,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,CAAA,GAAI,KAAK,SAAS,CAAA;AAGjD,MAAA,MAAM,OAAO,KAAA,CACV,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,QAAA,EAAU,gBAAA,CAAiB,CAAC,CAAA,GAAI,CAAA,CAC9C,MAAA,CAAO,CAAC,CAAA,KAAiC,CAAA,CAAE,aAAa,IAAI,CAAA;AAC/D,MAAA,IAAI,OAAA;AACJ,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,QAAA,EAAS;AACtC,QAAA,KAAA,MAAW,EAAE,QAAA,EAAS,IAAK,IAAA,EAAM;AAC/B,UAAA,QAAA,CAAS,aAAA,CAAc,aAAa,IAAA,CAAK,SAAA,EAAW,EAAE,GAAG,GAAA,EAAK,QAAA,EAAU,CAAC,CAAA;AAAA,QAC3E;AACA,QAAA,OAAA,GAAU,MAAM,SAAS,IAAA,EAAK;AAAA,MAChC,SAAS,GAAA,EAAK;AACZ,QAAA,MAAM,IAAA,CAAK,SAAS,GAAG,CAAA;AAAA,MACzB;AACA,MAAA,OAAA,KAAY,EAAC;AACb,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,QAAQ,CAAA,EAAA,EAAK;AACpC,QAAA,MAAM,CAAC,KAAK,GAAG,CAAA,GAAI,QAAQ,CAAC,CAAA,IAAK,CAAC,IAAA,EAAM,MAAS,CAAA;AACjD,QAAA,IAAI,GAAA,EAAK,MAAM,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA;AAChC,QAAA,MAAM,GAAA,GAAM,GAAA;AAGZ,QAAA,IAAI,GAAA,KAAQ,MAAA,IAAa,GAAA,CAAI,CAAA,KAAM,MAAA,EAAW;AAC9C,QAAA,MAAM,EAAE,QAAA,EAAU,IAAA,CAAK,CAAC,EAAG,QAAA,EAAU,GAAG,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAK,IAAA,CAAK,CAAC,CAAA,CAAG,QAAQ,CAAA,EAAE;AAAA,MAC/E;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,OAAA,CAAQ,KAA6B,QAAA,EAA2B;AACtE,IAAA,MAAM,QAAQ,GAAA,CAAI,CAAA;AAClB,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,MAAM,IAAI,cAAA,CAAe,CAAA,mBAAA,EAAsB,QAAQ,CAAA,qBAAA,CAAuB,CAAA;AAAA,IAChF;AACA,IAAA,MAAM,UAAU,GAAA,CAAI,CAAA;AACpB,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA,MAAM,IAAI,cAAA,CAAe,CAAA,mBAAA,EAAsB,QAAQ,CAAA,uBAAA,CAAyB,CAAA;AAAA,IAClF;AACA,IAAA,OAAO,EAAE,KAAA,EAAO,KAAA,CAAM,QAAA,CAAS,MAAM,GAAG,KAAA,EAAO,IAAI,UAAA,CAAW,OAAO,CAAA,EAAE;AAAA,EACzE;AAAA;AAAA,EAGQ,SAAS,GAAA,EAAuB;AACtC,IAAA,IAAI,WAAA,CAAY,GAAG,CAAA,EAAG;AACpB,MAAA,OAAO,IAAI,cAAA;AAAA,QACT,CAAA,uBAAA,EAA2B,GAAA,EAAsC,OAAA,IAAW,SAAS,CAAA,CAAA;AAAA,QACrF,EAAE,OAAO,GAAA;AAAI,OACf;AAAA,IACF;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AACF","file":"index.js","sourcesContent":["/**\n * Logical-ref → Redis key mapping for {@link RedisWarmDriver} (Phase 7).\n *\n * Pure, SDK-free string logic — unit-testable without a live Redis. Each chunk is one hash (`t` = token,\n * `b` = payload); each segment has one sorted-set index of its live chunk keys (Redis has no range scan, so\n * `listChunks` reads the index). Both keys for a segment share a **hash tag** `{<prefix>|<ns>|<seg>}` so they\n * always land in the same Redis Cluster slot — the OCC Lua script touches both atomically, which Cluster only\n * allows for same-slot keys. The name grammar (validateSegmentRef) forbids `|`/`{`/`}`/`:`/control chars in\n * ns/seg, and {@link normalizeRedisPrefix} forbids them in the prefix, so the tag can't be broken or aliased.\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/**\n * Validate the caller-supplied key prefix: no control characters and none of the reserved key/tag delimiters\n * (`{`, `}`, `|`, `:`), so it can neither break out of the hash tag nor alias another prefix's keyspace.\n * Empty/undefined ⇒ no prefix.\n */\nexport function normalizeRedisPrefix(prefix: string | undefined): string {\n if (prefix === undefined || prefix === '') return '';\n for (const ch of prefix) {\n const code = ch.charCodeAt(0);\n if (code < 0x20 || ch === '{' || ch === '}' || ch === '|' || ch === ':') {\n throw new ValidationError(\n `keyPrefix must not contain \"{\", \"}\", \"|\", \":\", or control characters`,\n );\n }\n }\n return prefix;\n}\n\n/** The shared hash-tag body for a segment: `<prefix>|<ns>|<seg>` (all delimiter-free by grammar). */\nfunction tag(prefix: string, ref: SegmentRef): string {\n return `${prefix}|${namespacePart(ref.namespace)}|${ref.segment}`;\n}\n\n/** The sorted-set index key holding a segment's live chunk keys (scored by chunkKey): `{tag}idx`. */\nexport function segmentIndexKey(prefix: string, ref: SegmentRef): string {\n validateSegmentRef(ref);\n return `{${tag(prefix, ref)}}idx`;\n}\n\n/** The hash key for one chunk's warm row (`t`/`b` fields): `{tag}c:<chunkKey>`. Validates the full ref. */\nexport function chunkHashKey(prefix: string, ref: ChunkRef): string {\n validateChunkRef(ref);\n return `{${tag(prefix, ref)}}c:${ref.chunkKey}`;\n}\n\n/** Parse a chunkKey out of a sorted-set index member (the canonical decimal the writer stored), or null. */\nexport function parseIndexMember(member: string): number | null {\n if (!/^(0|[1-9]\\d*)$/.test(member)) return null;\n const chunkKey = Number(member);\n return Number.isInteger(chunkKey) && chunkKey >= 0 && chunkKey <= 0xffff ? chunkKey : null;\n}\n","/**\n * Pure helpers for classifying Redis (`ioredis`) 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`, a Node\n * socket code; or the error message, which for a Redis server reply begins with the error-code word). The\n * driver signals an OCC conflict itself (its Lua CAS returns 0), so there's no \"conflict\" classifier — only\n * transient-vs-not. Everything not transient propagates unchanged so a real bug is never blind-retried.\n */\n\n/** A network-level 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/**\n * Transient Redis error-reply prefixes + ioredis connection-state messages. A Redis server error reply's\n * message begins with the code word (`LOADING`, `CLUSTERDOWN`, …); ioredis raises connection faults as an\n * `Error` whose message is one of the phrases below. All are safe to retry (the resilience layer rides them\n * out); a deterministic reply (`WRONGTYPE`, a Lua `ERR`, etc.) is NOT here, so it surfaces.\n */\nconst TRANSIENT_MESSAGE =\n /^(LOADING|CLUSTERDOWN|TRYAGAIN|MASTERDOWN|READONLY)\\b|Connection is closed|Command timed out|Stream isn't writeable|Reached the max retries per request limit|max number of clients reached|failed to refresh slots cache/i;\n\nexport function isTransient(err: unknown): boolean {\n const net = networkCode(err);\n if (\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 return true;\n }\n return err instanceof Error && TRANSIENT_MESSAGE.test(err.message);\n}\n","/**\n * `RedisWarmDriver` — an {@link IWarmDriver} over Redis (Phase 7).\n *\n * The \"sub-millisecond writes, accept always-on\" warm tier. Uses the official `ioredis`, an **optional peer\n * dependency** — only consumers of `cloud-roaring/redis` install it. An `ioredis` client is **injected**\n * (dependency injection): the driver owns no connection/credential logic, so it's thin and reuses the\n * caller's client.\n *\n * Each chunk is one Redis **hash** (`t` = opaque OCC token, a random UUID per write; `b` = delta payload).\n * Redis has no range scan, so each segment keeps a **sorted-set index** of its live chunk keys (scored by\n * chunkKey) that `listChunks` reads. Optimistic concurrency is a **server-side atomic compare-and-set**: a\n * Lua script (Redis runs it atomically, single-threaded — no `WATCH`/`MULTI` needed) checks the stored token\n * and only then writes, keeping the hash and the index consistent in one step:\n * - **create-if-absent** — fails if the hash exists ⇒ {@link WriteConflictError}.\n * - **token-fenced update / delete** — fails unless the stored `t` equals the expected token ⇒\n * {@link WriteConflictError}.\n *\n * The hash and index for a segment share a Redis-Cluster **hash tag** so the multi-key script is slot-safe.\n * Tokens are not reused across delete→recreate (ABA-safe, D3): a delete removes the hash and a recreate mints\n * a fresh random UUID (probabilistic, like the Postgres driver; conformance D3 pins it). Drivers may use\n * `node:crypto`; only `core/` is bound by the determinism lint.\n */\nimport { randomUUID } from 'node:crypto';\nimport type { Redis } from 'ioredis';\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 { chunkHashKey, normalizeRedisPrefix, parseIndexMember, segmentIndexKey } from './keys';\nimport { isTransient } from './redis-errors';\n\n/** listChunks value-fetch batch — bounds resident payloads to ~this many rows on a very wide segment. */\nconst DEFAULT_LIST_BATCH = 500;\n\n// Atomic OCC compare-and-set scripts. KEYS[1] = chunk hash, KEYS[2] = segment index (sorted set). Redis runs\n// each atomically, so the check-then-write can't race. Return 1 = applied, 0 = OCC conflict. Redis scripts\n// have no rollback, so the hash write is ordered before the index write (and DEL before ZREM): the only way\n// the second command could fail is a WRONGTYPE from external tampering on the shared-tag keyspace — outside\n// the driver's closed world, where hash and index are only ever these keys.\nconst PUT_LUA = `\nif ARGV[1] == 'create' then\n if redis.call('EXISTS', KEYS[1]) == 1 then return 0 end\n redis.call('HSET', KEYS[1], 't', ARGV[3], 'b', ARGV[5])\n redis.call('ZADD', KEYS[2], tonumber(ARGV[4]), ARGV[4])\n return 1\nelse\n local cur = redis.call('HGET', KEYS[1], 't')\n if not cur or cur ~= ARGV[2] then return 0 end\n redis.call('HSET', KEYS[1], 't', ARGV[3], 'b', ARGV[5])\n return 1\nend`;\nconst DEL_LUA = `\nlocal cur = redis.call('HGET', KEYS[1], 't')\nif not cur or cur ~= ARGV[1] then return 0 end\nredis.call('DEL', KEYS[1])\nredis.call('ZREM', KEYS[2], ARGV[2])\nreturn 1`;\n\n/** ioredis client augmented with the two custom OCC commands defined in the constructor. */\ntype WarmRedis = Redis & {\n crWarmPut(\n hashKey: string,\n indexKey: string,\n mode: string,\n expected: string,\n token: string,\n member: string,\n payload: Buffer,\n ): Promise<number>;\n crWarmDel(hashKey: string, indexKey: string, expected: string, member: string): Promise<number>;\n};\n\nexport interface RedisWarmDriverOptions {\n /**\n * A constructed `ioredis` client. The driver never connects/quits it — the caller owns its lifecycle.\n *\n * **The warm tier holds the only live copy of un-compacted deltas, so the instance must be configured for\n * durability + no silent loss:**\n * - `maxmemory-policy noeviction` (or no `maxmemory`). Under a key-evicting policy the chunk hash and its\n * segment-index entry are independent keys and can evict independently → `listChunks` silently drops or\n * ghosts a chunk (the hash-tag guarantees same-*slot*, not co-eviction). The driver sets no TTLs.\n * - Durable persistence (AOF, e.g. `appendfsync everysec`) if you rely on warm survival across a restart.\n * - **Reads must hit the master** — the OCC read-modify-write needs read-your-writes, so do NOT enable\n * replica reads (`scaleReads: 'slave'` / a replica `readFrom`), which could serve stale rows and break OCC.\n */\n readonly client: Redis;\n /** Optional key prefix so several logical stores can share one Redis (no `{`,`}`,`|`,`:`, or control chars). */\n readonly keyPrefix?: string;\n /** Advanced: `listChunks` value-fetch batch size (default 500). Tunes peak memory on wide segments. */\n readonly listPageSize?: number;\n}\n\nexport class RedisWarmDriver implements IWarmDriver {\n private readonly client: WarmRedis;\n private readonly keyPrefix: string;\n private readonly listBatch: number;\n\n constructor(options: RedisWarmDriverOptions) {\n this.keyPrefix = normalizeRedisPrefix(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 const client = options.client as WarmRedis;\n // Register the OCC scripts once (idempotent across driver instances sharing a client). ioredis caches\n // them and dispatches via EVALSHA with an automatic EVAL fallback.\n if (typeof client.crWarmPut !== 'function') {\n client.defineCommand('crWarmPut', { numberOfKeys: 2, lua: PUT_LUA });\n }\n if (typeof client.crWarmDel !== 'function') {\n client.defineCommand('crWarmDel', { numberOfKeys: 2, lua: DEL_LUA });\n }\n this.client = client;\n }\n\n async get(ref: ChunkRef): Promise<WarmRow | null> {\n const hashKey = chunkHashKey(this.keyPrefix, ref); // validates ref\n let raw: Record<string, Buffer>;\n try {\n raw = await this.client.hgetallBuffer(hashKey);\n } catch (err) {\n throw this.mapError(err);\n }\n if (Object.keys(raw).length === 0) return null; // absent hash → ioredis returns {}\n return this.rowFrom(raw, ref.chunkKey); // present-but-token-less ⇒ IntegrityError (untrusted-data posture)\n }\n\n async putConditional(\n ref: ChunkRef,\n bytes: Uint8Array,\n expected: Token | NoRow,\n ): Promise<{ token: Token }> {\n const hashKey = chunkHashKey(this.keyPrefix, ref); // validates ref\n const indexKey = segmentIndexKey(this.keyPrefix, ref);\n const token = randomUUID();\n const payload = Buffer.from(bytes); // copy: severs any caller-owned/reused input buffer\n const member = String(ref.chunkKey);\n let applied: number;\n try {\n applied =\n expected === NO_ROW\n ? await this.client.crWarmPut(hashKey, indexKey, 'create', '', token, member, payload)\n : await this.client.crWarmPut(\n hashKey,\n indexKey,\n 'update',\n expected,\n token,\n member,\n payload,\n );\n } catch (err) {\n throw this.mapError(err);\n }\n if (applied !== 1) {\n throw new WriteConflictError(\n expected === NO_ROW\n ? `chunk ${ref.chunkKey} already exists (create-if-absent)`\n : `OCC conflict on chunk ${ref.chunkKey}`,\n );\n }\n return { token };\n }\n\n async deleteConditional(ref: ChunkRef, expected: Token): Promise<void> {\n const hashKey = chunkHashKey(this.keyPrefix, ref); // validates ref\n const indexKey = segmentIndexKey(this.keyPrefix, ref);\n let applied: number;\n try {\n applied = await this.client.crWarmDel(hashKey, indexKey, expected, String(ref.chunkKey));\n } catch (err) {\n throw this.mapError(err);\n }\n if (applied !== 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 indexKey = segmentIndexKey(this.keyPrefix, ref); // validates ref\n let members: string[];\n try {\n // The index is a sorted set scored by chunkKey, so ZRANGE returns members ascending. It holds only small\n // integers, so reading it whole is cheap; the (larger) payloads are fetched in bounded batches below.\n members = await this.client.zrange(indexKey, 0, -1);\n } catch (err) {\n throw this.mapError(err);\n }\n for (let i = 0; i < members.length; i += this.listBatch) {\n const batch = members.slice(i, i + this.listBatch);\n // Resolve members → chunk keys first, dropping any foreign/corrupt member, then fetch that batch's\n // hashes in one pipeline (bounded resident payloads on a very wide segment).\n const keys = batch\n .map((m) => ({ chunkKey: parseIndexMember(m) }))\n .filter((x): x is { chunkKey: number } => x.chunkKey !== null);\n let results: [Error | null, unknown][] | null;\n try {\n const pipeline = this.client.pipeline();\n for (const { chunkKey } of keys) {\n pipeline.hgetallBuffer(chunkHashKey(this.keyPrefix, { ...ref, chunkKey }));\n }\n results = await pipeline.exec();\n } catch (err) {\n throw this.mapError(err);\n }\n results ??= [];\n for (let j = 0; j < keys.length; j++) {\n const [err, raw] = results[j] ?? [null, undefined];\n if (err) throw this.mapError(err);\n const row = raw as Record<string, Buffer> | undefined;\n // A member with no hash means a delete raced our snapshot — skip it (matches the moving-target\n // semantics of the DynamoDB/Postgres paginated lists).\n if (row === undefined || row.t === undefined) continue;\n yield { chunkKey: keys[j]!.chunkKey, ...this.rowFrom(row, keys[j]!.chunkKey) };\n }\n }\n }\n\n /** Build a {@link WarmRow} from a raw hash; reject a hash missing its fields (corrupt/foreign). */\n private rowFrom(raw: Record<string, Buffer>, chunkKey: number): WarmRow {\n const token = raw.t;\n if (token === undefined) {\n throw new IntegrityError(`warm row for chunk ${chunkKey} is missing its token`);\n }\n const payload = raw.b;\n if (payload === undefined) {\n throw new IntegrityError(`warm row for chunk ${chunkKey} is missing its payload`);\n }\n return { token: token.toString('utf8'), bytes: new Uint8Array(payload) };\n }\n\n /** Reclassify a transient Redis 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 Redis fault: ${(err as { message?: unknown } | null)?.message ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n }\n}\n"]}