@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.
- package/LICENSE +202 -0
- package/NOTICE +12 -0
- package/README.md +22 -0
- package/dist/azure/index.cjs +368 -0
- package/dist/azure/index.cjs.map +1 -0
- package/dist/azure/index.d.cts +48 -0
- package/dist/azure/index.d.ts +48 -0
- package/dist/azure/index.js +304 -0
- package/dist/azure/index.js.map +1 -0
- package/dist/cassandra/index.cjs +265 -0
- package/dist/cassandra/index.cjs.map +1 -0
- package/dist/cassandra/index.d.cts +52 -0
- package/dist/cassandra/index.d.ts +52 -0
- package/dist/cassandra/index.js +204 -0
- package/dist/cassandra/index.js.map +1 -0
- package/dist/chunk-2YDULGXS.js +203 -0
- package/dist/chunk-2YDULGXS.js.map +1 -0
- package/dist/chunk-7LMLYSVJ.js +43 -0
- package/dist/chunk-7LMLYSVJ.js.map +1 -0
- package/dist/chunk-AS6ODRLT.js +6 -0
- package/dist/chunk-AS6ODRLT.js.map +1 -0
- package/dist/chunk-NUIDEEFZ.js +91 -0
- package/dist/chunk-NUIDEEFZ.js.map +1 -0
- package/dist/chunk-SNJVZ227.js +35 -0
- package/dist/chunk-SNJVZ227.js.map +1 -0
- package/dist/dynamodb/index.cjs +731 -0
- package/dist/dynamodb/index.cjs.map +1 -0
- package/dist/dynamodb/index.d.cts +106 -0
- package/dist/dynamodb/index.d.ts +106 -0
- package/dist/dynamodb/index.js +460 -0
- package/dist/dynamodb/index.js.map +1 -0
- package/dist/gcs/index.cjs +343 -0
- package/dist/gcs/index.cjs.map +1 -0
- package/dist/gcs/index.d.cts +46 -0
- package/dist/gcs/index.d.ts +46 -0
- package/dist/gcs/index.js +279 -0
- package/dist/gcs/index.js.map +1 -0
- package/dist/index.cjs +4031 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1945 -0
- package/dist/index.d.ts +1945 -0
- package/dist/index.js +3642 -0
- package/dist/index.js.map +1 -0
- package/dist/mongodb/index.cjs +260 -0
- package/dist/mongodb/index.cjs.map +1 -0
- package/dist/mongodb/index.d.cts +45 -0
- package/dist/mongodb/index.d.ts +45 -0
- package/dist/mongodb/index.js +199 -0
- package/dist/mongodb/index.js.map +1 -0
- package/dist/mysql/index.cjs +281 -0
- package/dist/mysql/index.cjs.map +1 -0
- package/dist/mysql/index.d.cts +56 -0
- package/dist/mysql/index.d.ts +56 -0
- package/dist/mysql/index.js +211 -0
- package/dist/mysql/index.js.map +1 -0
- package/dist/ports-D3BrJ6ax.d.cts +357 -0
- package/dist/ports-D3BrJ6ax.d.ts +357 -0
- package/dist/postgres/index.cjs +273 -0
- package/dist/postgres/index.cjs.map +1 -0
- package/dist/postgres/index.d.cts +54 -0
- package/dist/postgres/index.d.ts +54 -0
- package/dist/postgres/index.js +203 -0
- package/dist/postgres/index.js.map +1 -0
- package/dist/redis/index.cjs +257 -0
- package/dist/redis/index.cjs.map +1 -0
- package/dist/redis/index.d.cts +42 -0
- package/dist/redis/index.d.ts +42 -0
- package/dist/redis/index.js +197 -0
- package/dist/redis/index.js.map +1 -0
- package/dist/s3/index.cjs +891 -0
- package/dist/s3/index.cjs.map +1 -0
- package/dist/s3/index.d.cts +112 -0
- package/dist/s3/index.d.ts +112 -0
- package/dist/s3/index.js +555 -0
- package/dist/s3/index.js.map +1 -0
- package/package.json +172 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
var events = require('events');
|
|
5
|
+
|
|
6
|
+
// src/drivers/gcs/cold.ts
|
|
7
|
+
|
|
8
|
+
// src/core/errors.ts
|
|
9
|
+
var ERROR_BRAND = /* @__PURE__ */ Symbol.for("cloud-roaring.error");
|
|
10
|
+
var TRANSIENT_BRAND = /* @__PURE__ */ Symbol.for("cloud-roaring.error.transient");
|
|
11
|
+
var CloudRoaringError = class extends Error {
|
|
12
|
+
/** Cross-bundle brand — see the predicates ({@link isCloudRoaringError}, …). Non-enumerable-ish (symbol key ⇒ not in JSON). */
|
|
13
|
+
[ERROR_BRAND] = true;
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = new.target.name;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var ValidationError = class extends CloudRoaringError {
|
|
20
|
+
};
|
|
21
|
+
var WriteConflictError = class extends CloudRoaringError {
|
|
22
|
+
};
|
|
23
|
+
var NotFoundError = class extends CloudRoaringError {
|
|
24
|
+
};
|
|
25
|
+
var TransientError = class extends CloudRoaringError {
|
|
26
|
+
/** A second brand so the whole transient subtree (incl. {@link TimeoutError}) is classifiable cross-bundle. */
|
|
27
|
+
[TRANSIENT_BRAND] = true;
|
|
28
|
+
constructor(message, options) {
|
|
29
|
+
super(message);
|
|
30
|
+
if (options && "cause" in options) this.cause = options.cause;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
function hasBrand(err, brand) {
|
|
34
|
+
return typeof err === "object" && err !== null && err[brand] === true;
|
|
35
|
+
}
|
|
36
|
+
function isCloudRoaringError(err) {
|
|
37
|
+
return hasBrand(err, ERROR_BRAND);
|
|
38
|
+
}
|
|
39
|
+
function isWriteConflictError(err) {
|
|
40
|
+
return isCloudRoaringError(err) && err.name === "WriteConflictError";
|
|
41
|
+
}
|
|
42
|
+
function isNotFoundError(err) {
|
|
43
|
+
return isCloudRoaringError(err) && err.name === "NotFoundError";
|
|
44
|
+
}
|
|
45
|
+
function isValidationError(err) {
|
|
46
|
+
return isCloudRoaringError(err) && err.name === "ValidationError";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/core/validate.ts
|
|
50
|
+
var NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;
|
|
51
|
+
function validatePart(value, field) {
|
|
52
|
+
if (typeof value !== "string" || !NAME.test(value) || value.includes("..")) {
|
|
53
|
+
throw new ValidationError(
|
|
54
|
+
`${field} must match ${String(NAME)} and contain no "..": got ${JSON.stringify(value)}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function validateSegmentRef(ref) {
|
|
59
|
+
validatePart(ref.segment, "segment");
|
|
60
|
+
if (ref.namespace !== void 0) validatePart(ref.namespace, "namespace");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/drivers/_shared/keys.ts
|
|
64
|
+
var DEFAULT_NAMESPACE = "_default";
|
|
65
|
+
function namespacePart(namespace) {
|
|
66
|
+
return namespace ?? DEFAULT_NAMESPACE;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/drivers/gcs/keys.ts
|
|
70
|
+
var SUFFIX = ".crbm";
|
|
71
|
+
function prefixPart(prefix) {
|
|
72
|
+
if (prefix === void 0) return "";
|
|
73
|
+
const trimmed = prefix.replace(/^\/+|\/+$/g, "");
|
|
74
|
+
return trimmed === "" ? "" : `${trimmed}/`;
|
|
75
|
+
}
|
|
76
|
+
function normalizeGcsPrefix(prefix) {
|
|
77
|
+
if (prefix === void 0) return void 0;
|
|
78
|
+
for (const ch of prefix) {
|
|
79
|
+
if (ch.charCodeAt(0) < 32) {
|
|
80
|
+
throw new ValidationError("prefix must not contain control characters");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const segment of prefix.split("/")) {
|
|
84
|
+
if (segment === "." || segment === "..") {
|
|
85
|
+
throw new ValidationError(
|
|
86
|
+
`prefix must not contain "." or ".." path segments: ${JSON.stringify(prefix)}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return prefix;
|
|
91
|
+
}
|
|
92
|
+
function segmentObjectPrefix(prefix, ref) {
|
|
93
|
+
validateSegmentRef(ref);
|
|
94
|
+
return `${prefixPart(prefix)}${namespacePart(ref.namespace)}/segments/${ref.segment}.`;
|
|
95
|
+
}
|
|
96
|
+
function coldObjectName(prefix, key) {
|
|
97
|
+
if (!Number.isInteger(key.generation) || key.generation < 0) {
|
|
98
|
+
throw new ValidationError(`generation must be a non-negative integer; got ${key.generation}`);
|
|
99
|
+
}
|
|
100
|
+
return `${segmentObjectPrefix(prefix, key)}${key.generation}${SUFFIX}`;
|
|
101
|
+
}
|
|
102
|
+
function parseGenerationFromName(segmentPrefix, objectName) {
|
|
103
|
+
if (!objectName.startsWith(segmentPrefix) || !objectName.endsWith(SUFFIX)) return null;
|
|
104
|
+
const middle = objectName.slice(segmentPrefix.length, objectName.length - SUFFIX.length);
|
|
105
|
+
if (!/^(0|[1-9]\d*)$/.test(middle)) return null;
|
|
106
|
+
const generation = Number(middle);
|
|
107
|
+
return Number.isSafeInteger(generation) ? generation : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/drivers/gcs/gcs-errors.ts
|
|
111
|
+
function httpStatus(err) {
|
|
112
|
+
const e = err;
|
|
113
|
+
if (typeof e?.code === "number") return e.code;
|
|
114
|
+
if (typeof e?.response?.status === "number") return e.response.status;
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
117
|
+
function networkCode(err) {
|
|
118
|
+
const code = err?.code;
|
|
119
|
+
return typeof code === "string" ? code : void 0;
|
|
120
|
+
}
|
|
121
|
+
function isPreconditionFailed(err) {
|
|
122
|
+
return httpStatus(err) === 412;
|
|
123
|
+
}
|
|
124
|
+
function isNotFound(err) {
|
|
125
|
+
return httpStatus(err) === 404;
|
|
126
|
+
}
|
|
127
|
+
function isInvalidRange(err) {
|
|
128
|
+
return httpStatus(err) === 416;
|
|
129
|
+
}
|
|
130
|
+
function isTransient(err) {
|
|
131
|
+
if (isPreconditionFailed(err) || isNotFound(err) || isInvalidRange(err)) return false;
|
|
132
|
+
const status = httpStatus(err);
|
|
133
|
+
if (status === 429 || status !== void 0 && status >= 500 && status < 600) return true;
|
|
134
|
+
const net = networkCode(err);
|
|
135
|
+
return net === "ECONNRESET" || net === "ETIMEDOUT" || net === "ECONNREFUSED" || net === "EPIPE" || net === "EAI_AGAIN" || net === "ENOTFOUND";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/drivers/gcs/cold.ts
|
|
139
|
+
var DEFAULT_MAX_OBJECT_BYTES = 5 * 1024 * 1024 * 1024 * 1024;
|
|
140
|
+
var DEFAULT_UPLOAD_THRESHOLD_BYTES = 8 * 1024 * 1024;
|
|
141
|
+
var GcsColdDriver = class {
|
|
142
|
+
storage;
|
|
143
|
+
bucket;
|
|
144
|
+
prefix;
|
|
145
|
+
maxObjectBytes;
|
|
146
|
+
threshold;
|
|
147
|
+
constructor(options) {
|
|
148
|
+
this.storage = options.storage;
|
|
149
|
+
this.bucket = options.bucket;
|
|
150
|
+
this.prefix = normalizeGcsPrefix(options.prefix);
|
|
151
|
+
this.maxObjectBytes = options.maxObjectBytes ?? DEFAULT_MAX_OBJECT_BYTES;
|
|
152
|
+
this.threshold = options.simpleUploadThresholdBytes ?? DEFAULT_UPLOAD_THRESHOLD_BYTES;
|
|
153
|
+
}
|
|
154
|
+
capabilities() {
|
|
155
|
+
return { rangeRead: true, maxObjectBytes: this.maxObjectBytes, conditionalPut: true };
|
|
156
|
+
}
|
|
157
|
+
file(name) {
|
|
158
|
+
return this.storage.bucket(this.bucket).file(name);
|
|
159
|
+
}
|
|
160
|
+
async putImmutable(key, write) {
|
|
161
|
+
const objectName = coldObjectName(this.prefix, key);
|
|
162
|
+
const sink = new GcsUploadSink(this.file(objectName), this.maxObjectBytes, this.threshold);
|
|
163
|
+
try {
|
|
164
|
+
await write(sink);
|
|
165
|
+
return await sink.finish();
|
|
166
|
+
} catch (err) {
|
|
167
|
+
await sink.abort();
|
|
168
|
+
if (isPreconditionFailed(err)) {
|
|
169
|
+
throw new WriteConflictError(
|
|
170
|
+
`generation already exists (write-once): ${key.segment}.${key.generation}`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (isValidationError(err) || isWriteConflictError(err) || isNotFoundError(err)) throw err;
|
|
174
|
+
throw this.mapError(err);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async getRange(key, offset, length) {
|
|
178
|
+
if (!Number.isInteger(offset) || !Number.isInteger(length) || offset < 0 || length < 0) {
|
|
179
|
+
throw new ValidationError(`invalid range offset=${offset} length=${length}`);
|
|
180
|
+
}
|
|
181
|
+
if (length === 0) return new Uint8Array(0);
|
|
182
|
+
const objectName = coldObjectName(this.prefix, key);
|
|
183
|
+
try {
|
|
184
|
+
const [buf] = await this.file(objectName).download({
|
|
185
|
+
start: offset,
|
|
186
|
+
end: offset + length - 1
|
|
187
|
+
});
|
|
188
|
+
if (buf.length !== length) {
|
|
189
|
+
throw new ValidationError(
|
|
190
|
+
`range [${offset}, ${offset + length}) out of bounds (got ${buf.length}B)`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
throw this.mapReadError(err, key);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async getTail(key, maxBytes) {
|
|
199
|
+
const objectName = coldObjectName(this.prefix, key);
|
|
200
|
+
try {
|
|
201
|
+
const [meta] = await this.file(objectName).getMetadata();
|
|
202
|
+
const size = Number(meta.size ?? 0);
|
|
203
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
204
|
+
throw new ValidationError(`GCS returned an invalid object size: ${String(meta.size)}`);
|
|
205
|
+
}
|
|
206
|
+
if (maxBytes <= 0 || size === 0) return { bytes: new Uint8Array(0), size };
|
|
207
|
+
const start = Math.max(0, size - maxBytes);
|
|
208
|
+
const [buf] = await this.file(objectName).download({ start, end: size - 1 });
|
|
209
|
+
return { bytes: new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength), size };
|
|
210
|
+
} catch (err) {
|
|
211
|
+
throw this.mapReadError(err, key);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async delete(key) {
|
|
215
|
+
try {
|
|
216
|
+
await this.file(coldObjectName(this.prefix, key)).delete({ ignoreNotFound: true });
|
|
217
|
+
} catch (err) {
|
|
218
|
+
throw this.mapError(err);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async *list(ref) {
|
|
222
|
+
const prefix = segmentObjectPrefix(this.prefix, ref);
|
|
223
|
+
let files;
|
|
224
|
+
try {
|
|
225
|
+
[files] = await this.storage.bucket(this.bucket).getFiles({ prefix });
|
|
226
|
+
} catch (err) {
|
|
227
|
+
throw this.mapError(err);
|
|
228
|
+
}
|
|
229
|
+
for (const f of files) {
|
|
230
|
+
const generation = parseGenerationFromName(prefix, f.name);
|
|
231
|
+
if (generation !== null) {
|
|
232
|
+
yield { namespace: ref.namespace, segment: ref.segment, generation };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/** Map GCS read errors to the driver vocabulary; pass everything else through {@link mapError}. */
|
|
237
|
+
mapReadError(err, key) {
|
|
238
|
+
if (isValidationError(err)) return err;
|
|
239
|
+
if (isNotFound(err))
|
|
240
|
+
return new NotFoundError(`no such generation: ${key.segment}.${key.generation}`);
|
|
241
|
+
if (isInvalidRange(err)) {
|
|
242
|
+
return new ValidationError(`range out of bounds for ${key.segment}.${key.generation}`);
|
|
243
|
+
}
|
|
244
|
+
return this.mapError(err);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Reclassify a transient GCS fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}
|
|
248
|
+
* so the retry decorator can ride it out; everything else propagates unchanged. The final fallback at every
|
|
249
|
+
* client-call site, so callers + the decorator only ever see typed errors.
|
|
250
|
+
*/
|
|
251
|
+
mapError(err) {
|
|
252
|
+
if (isTransient(err)) {
|
|
253
|
+
return new TransientError(
|
|
254
|
+
`transient GCS fault: ${err?.code ?? "unknown"}`,
|
|
255
|
+
{ cause: err }
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
return err;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
function concatBytes(parts, total) {
|
|
262
|
+
const out = new Uint8Array(total);
|
|
263
|
+
let offset = 0;
|
|
264
|
+
for (const p of parts) {
|
|
265
|
+
out.set(p, offset);
|
|
266
|
+
offset += p.length;
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
var GcsUploadSink = class _GcsUploadSink {
|
|
271
|
+
constructor(file, maxObjectBytes, threshold) {
|
|
272
|
+
this.file = file;
|
|
273
|
+
this.maxObjectBytes = maxObjectBytes;
|
|
274
|
+
this.threshold = threshold;
|
|
275
|
+
}
|
|
276
|
+
file;
|
|
277
|
+
maxObjectBytes;
|
|
278
|
+
threshold;
|
|
279
|
+
hash = crypto.createHash("sha256");
|
|
280
|
+
total = 0;
|
|
281
|
+
buffered = [];
|
|
282
|
+
bufferedLen = 0;
|
|
283
|
+
stream;
|
|
284
|
+
// set once we cross the threshold → resumable mode
|
|
285
|
+
done;
|
|
286
|
+
failure;
|
|
287
|
+
static WRITE_OPTS = {
|
|
288
|
+
preconditionOpts: { ifGenerationMatch: 0 },
|
|
289
|
+
// write-once: create only if absent
|
|
290
|
+
metadata: { contentType: "application/octet-stream" }
|
|
291
|
+
};
|
|
292
|
+
async write(bytes) {
|
|
293
|
+
if (this.failure !== void 0) throw this.failure;
|
|
294
|
+
if (bytes.length === 0) return;
|
|
295
|
+
this.total += bytes.length;
|
|
296
|
+
if (this.total > this.maxObjectBytes) {
|
|
297
|
+
throw new ValidationError(`object exceeds maxObjectBytes ${this.maxObjectBytes}`);
|
|
298
|
+
}
|
|
299
|
+
this.hash.update(bytes);
|
|
300
|
+
if (this.stream !== void 0) {
|
|
301
|
+
if (!this.stream.write(bytes)) await events.once(this.stream, "drain");
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
this.buffered.push(bytes);
|
|
305
|
+
this.bufferedLen += bytes.length;
|
|
306
|
+
if (this.bufferedLen > this.threshold) this.startResumable();
|
|
307
|
+
}
|
|
308
|
+
/** Cross into resumable streaming: open the stream, flush the buffered bytes, keep only ~one threshold resident. */
|
|
309
|
+
startResumable() {
|
|
310
|
+
const stream = this.file.createWriteStream({ resumable: true, ..._GcsUploadSink.WRITE_OPTS });
|
|
311
|
+
this.stream = stream;
|
|
312
|
+
this.done = events.once(stream, "finish");
|
|
313
|
+
stream.on("error", (e) => {
|
|
314
|
+
this.failure ??= e;
|
|
315
|
+
});
|
|
316
|
+
this.done.catch(() => void 0);
|
|
317
|
+
const buf = concatBytes(this.buffered, this.bufferedLen);
|
|
318
|
+
this.buffered.length = 0;
|
|
319
|
+
this.bufferedLen = 0;
|
|
320
|
+
stream.write(buf);
|
|
321
|
+
}
|
|
322
|
+
async finish() {
|
|
323
|
+
if (this.failure !== void 0) throw this.failure;
|
|
324
|
+
const sha256 = this.hash.digest("hex");
|
|
325
|
+
if (this.stream === void 0) {
|
|
326
|
+
await this.file.save(concatBytes(this.buffered, this.bufferedLen), {
|
|
327
|
+
resumable: false,
|
|
328
|
+
..._GcsUploadSink.WRITE_OPTS
|
|
329
|
+
});
|
|
330
|
+
return { size: this.total, sha256 };
|
|
331
|
+
}
|
|
332
|
+
this.stream.end();
|
|
333
|
+
await this.done;
|
|
334
|
+
return { size: this.total, sha256 };
|
|
335
|
+
}
|
|
336
|
+
async abort() {
|
|
337
|
+
if (this.stream !== void 0 && !this.stream.destroyed) this.stream.destroy();
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
exports.GcsColdDriver = GcsColdDriver;
|
|
342
|
+
//# sourceMappingURL=index.cjs.map
|
|
343
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/errors.ts","../../src/core/validate.ts","../../src/drivers/_shared/keys.ts","../../src/drivers/gcs/keys.ts","../../src/drivers/gcs/gcs-errors.ts","../../src/drivers/gcs/cold.ts"],"names":["createHash","once"],"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 * 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 * Logical-ref → GCS object-name mapping for {@link GcsColdDriver} (Phase 7).\n *\n * Pure string logic, no SDK dependency — unit-testable without GCS or an emulator. Uses the **same\n * backend-agnostic `.crbm` object-name scheme** as the S3 + LocalFs cold drivers\n * (`<prefix><ns>/segments/<segment>.<gen>.crbm`), so a segment reads identically whichever cold backend holds\n * it. The default (absent) namespace maps to `_default` (the grammar forbids a leading underscore, so it can't\n * collide with a real namespace).\n *\n * NOTE (DRY): this mirrors the pure cold-key builders in `drivers/s3/keys.ts`. They are deliberately **not**\n * shared across driver folders today — a driver must stay self-contained so it lifts cleanly into its own\n * package at the [Phase 9 split]. At that split the shared cold-key\n * scheme is promoted into `@cloudbitmaps/core`'s driver-kit (imported by every driver package), which is the\n * right home for it; until then a self-contained copy beats a cross-driver import.\n */\nimport { ValidationError } from '@/core/errors';\nimport { validateSegmentRef } from '@/core/validate';\nimport type { GenKey, SegmentRef } from '@/core/ports';\nimport { namespacePart } from '../_shared/keys';\n\nconst SUFFIX = '.crbm';\n\n/** Normalize an optional caller prefix to either `''` or `trimmed/` (no leading/trailing slashes). */\nfunction prefixPart(prefix: string | undefined): string {\n if (prefix === undefined) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed === '' ? '' : `${trimmed}/`;\n}\n\n/**\n * Validate the caller-supplied key prefix (trusted config, but a real containment boundary): reject control\n * characters and `.`/`..` path segments so a prefix can't traverse out of its intended space. Returns it\n * unchanged (the key builders normalize slashes).\n */\nexport function normalizeGcsPrefix(prefix: string | undefined): string | undefined {\n if (prefix === undefined) return undefined;\n for (const ch of prefix) {\n if (ch.charCodeAt(0) < 0x20) {\n throw new ValidationError('prefix must not contain control characters');\n }\n }\n for (const segment of prefix.split('/')) {\n if (segment === '.' || segment === '..') {\n throw new ValidationError(\n `prefix must not contain \".\" or \"..\" path segments: ${JSON.stringify(prefix)}`,\n );\n }\n }\n return prefix;\n}\n\n/**\n * The GCS object-name prefix shared by all of a segment's generations: `<prefix><ns>/segments/<segment>.`.\n * Used both as the `getFiles` prefix and as the string stripped by {@link parseGenerationFromName}.\n */\nexport function segmentObjectPrefix(prefix: string | undefined, ref: SegmentRef): string {\n validateSegmentRef(ref);\n return `${prefixPart(prefix)}${namespacePart(ref.namespace)}/segments/${ref.segment}.`;\n}\n\n/** The full GCS object name of one `.crbm` generation: `<segmentPrefix><gen>.crbm`. */\nexport function coldObjectName(prefix: string | undefined, key: GenKey): string {\n if (!Number.isInteger(key.generation) || key.generation < 0) {\n throw new ValidationError(`generation must be a non-negative integer; got ${key.generation}`);\n }\n return `${segmentObjectPrefix(prefix, key)}${key.generation}${SUFFIX}`;\n}\n\n/**\n * Parse a generation number out of a full object name, given its segment prefix, or `null` if it doesn't\n * match. Canonical decimal only — no leading zeros (so `…s.07.crbm` can't alias `…s.7.crbm`) and within\n * safe-integer range. Also rejects a *different* segment whose name merely shares the prefix (its middle\n * isn't all digits).\n */\nexport function parseGenerationFromName(segmentPrefix: string, objectName: string): number | null {\n if (!objectName.startsWith(segmentPrefix) || !objectName.endsWith(SUFFIX)) return null;\n const middle = objectName.slice(segmentPrefix.length, objectName.length - SUFFIX.length);\n if (!/^(0|[1-9]\\d*)$/.test(middle)) return null;\n const generation = Number(middle);\n return Number.isSafeInteger(generation) ? generation : null;\n}\n","/**\n * Pure helpers for classifying Google Cloud Storage SDK errors (Phase 7; transient class mirrors S3's).\n *\n * SDK-free + side-effect-free — they only read structural shapes off the thrown value (`err.code`,\n * `err.response.status`, `err.name`), so the GCS-specific translation is unit-testable without a live GCS or\n * an emulator, and without importing `@google-cloud/storage`. GCS's `ApiError` carries the HTTP status on\n * `.code` (a number); dropped/timed-out sockets surface as a Node error with a string `.code` (`ECONNRESET`,\n * `ETIMEDOUT`, …).\n */\n\n/** The HTTP status of a GCS `ApiError`, if present (`err.code` as a number, or `err.response.status`). */\nfunction httpStatus(err: unknown): number | undefined {\n const e = err as { code?: unknown; response?: { status?: unknown } } | null;\n if (typeof e?.code === 'number') return e.code;\n if (typeof e?.response?.status === 'number') return e.response.status;\n return undefined;\n}\n\n/** A network-level error `code` string (e.g. `ECONNRESET`, `ETIMEDOUT`, `EAI_AGAIN`, `EPIPE`). */\nfunction networkCode(err: unknown): string | undefined {\n const code = (err as { code?: unknown } | null)?.code;\n return typeof code === 'string' ? code : undefined;\n}\n\n/**\n * A conditional `ifGenerationMatch: 0` write lost the write-once race — the object already existed, so GCS\n * returns **412 Precondition Failed**. Maps to `WriteConflictError` (caller OCC), never a blind retry.\n */\nexport function isPreconditionFailed(err: unknown): boolean {\n return httpStatus(err) === 412;\n}\n\n/** The object / generation does not exist (GCS returns 404). */\nexport function isNotFound(err: unknown): boolean {\n return httpStatus(err) === 404;\n}\n\n/** A range request started past EOF (HTTP 416 Requested Range Not Satisfiable). */\nexport function isInvalidRange(err: unknown): boolean {\n return httpStatus(err) === 416;\n}\n\n/**\n * A transient GCS fault that is safe to retry: throttling (429), any 5xx, or a dropped/timed-out socket.\n * Excludes the deterministic, caller-meaningful outcomes (412/404/416) — those must never be reclassified as\n * a blind transient (a retried doomed conditional write would just fail again, and mask an OCC conflict).\n */\nexport function isTransient(err: unknown): boolean {\n if (isPreconditionFailed(err) || isNotFound(err) || isInvalidRange(err)) return false;\n const status = httpStatus(err);\n if (status === 429 || (status !== undefined && status >= 500 && status < 600)) return true;\n const net = networkCode(err);\n return (\n net === 'ECONNRESET' ||\n net === 'ETIMEDOUT' ||\n net === 'ECONNREFUSED' ||\n net === 'EPIPE' ||\n net === 'EAI_AGAIN' ||\n net === 'ENOTFOUND'\n );\n}\n","/**\n * `GcsColdDriver` — an {@link IColdDriver} over Google Cloud Storage (Phase 7).\n *\n * Uses the official `@google-cloud/storage`, an **optional peer dependency** — only consumers of\n * `cloud-roaring/gcs` install it. The `Storage` client is **injected** (dependency injection): the driver owns\n * no credential/project/endpoint logic, so it's thin, reuses the caller's client, and is testable against the\n * `fake-gcs-server` emulator (point a `Storage` at its `apiEndpoint`).\n *\n * Generations are write-once immutable objects: a resumable upload with the **`ifGenerationMatch: 0`**\n * precondition (\"create only if it does not exist\") makes the publish atomic — a second write to the same\n * object fails with 412 → {@link WriteConflictError}, never a silent overwrite (C13), the GCS analogue of S3's\n * `If-None-Match: *` and LocalFs's atomic `link`. **Writes stream in constant memory:** the codec's bytes are\n * piped into a GCS resumable-upload `Writable` (chunked/freed by the SDK as they go), so the daemon's write\n * footprint stays bounded regardless of segment size, up to the advertised `maxObjectBytes`. Drivers may use\n * `node:crypto`; only `core/` is bound by the determinism lint.\n */\nimport { createHash, type Hash } from 'node:crypto';\nimport type { Writable } from 'node:stream';\nimport { once } from 'node:events';\nimport type { Storage } from '@google-cloud/storage';\nimport type { BlobSink } from '@/core/blob';\nimport {\n NotFoundError,\n TransientError,\n ValidationError,\n WriteConflictError,\n isNotFoundError,\n isValidationError,\n isWriteConflictError,\n} from '@/core/errors';\nimport type { ColdCaps, GenKey, IColdDriver, SegmentRef } from '@/core/ports';\nimport {\n coldObjectName,\n normalizeGcsPrefix,\n parseGenerationFromName,\n segmentObjectPrefix,\n} from './keys';\nimport { isInvalidRange, isNotFound, isPreconditionFailed, isTransient } from './gcs-errors';\n\n/** Default object ceiling: GCS's 5 TiB per-object hard max. Set lower to fail fast on a runaway write. */\nconst DEFAULT_MAX_OBJECT_BYTES = 5 * 1024 * 1024 * 1024 * 1024;\n\n/**\n * Objects at/under this size are uploaded in a **single simple (non-resumable) request**; larger ones switch to\n * a **resumable stream** (constant memory). Mirrors S3's single-PUT-vs-multipart split — the simple path buffers\n * only ≤ this many bytes, the resumable path streams. Default 8 MiB (≈ S3's part size), so peak write memory is\n * ~one threshold's worth regardless of object size.\n */\nconst DEFAULT_UPLOAD_THRESHOLD_BYTES = 8 * 1024 * 1024;\n\nexport interface GcsColdDriverOptions {\n /** A constructed `@google-cloud/storage` `Storage` client (point `apiEndpoint` at fake-gcs-server locally). */\n readonly storage: Storage;\n /** Target bucket (must already exist). */\n readonly bucket: string;\n /** Optional object-name prefix under which all objects live (e.g. `cloudroaring/`). */\n readonly prefix?: string;\n /** Largest object this driver will write/advertise (default = GCS's 5 TiB max). */\n readonly maxObjectBytes?: number;\n /** Bytes at/under which a single non-resumable upload is used instead of a resumable stream (default 8 MiB). */\n readonly simpleUploadThresholdBytes?: number;\n}\n\nexport class GcsColdDriver implements IColdDriver {\n private readonly storage: Storage;\n private readonly bucket: string;\n private readonly prefix: string | undefined;\n private readonly maxObjectBytes: number;\n private readonly threshold: number;\n\n constructor(options: GcsColdDriverOptions) {\n this.storage = options.storage;\n this.bucket = options.bucket;\n this.prefix = normalizeGcsPrefix(options.prefix);\n this.maxObjectBytes = options.maxObjectBytes ?? DEFAULT_MAX_OBJECT_BYTES;\n this.threshold = options.simpleUploadThresholdBytes ?? DEFAULT_UPLOAD_THRESHOLD_BYTES;\n }\n\n capabilities(): ColdCaps {\n return { rangeRead: true, maxObjectBytes: this.maxObjectBytes, conditionalPut: true };\n }\n\n private file(name: string) {\n return this.storage.bucket(this.bucket).file(name);\n }\n\n async putImmutable(\n key: GenKey,\n write: (sink: BlobSink) => Promise<void>,\n ): Promise<{ size: number; sha256: string }> {\n const objectName = coldObjectName(this.prefix, key); // validates ref + generation\n const sink = new GcsUploadSink(this.file(objectName), this.maxObjectBytes, this.threshold);\n try {\n await write(sink);\n return await sink.finish();\n } catch (err) {\n await sink.abort(); // best-effort teardown of the in-flight upload\n // A lost write-once race — the object already existed, so `ifGenerationMatch: 0` failed with 412.\n if (isPreconditionFailed(err)) {\n throw new WriteConflictError(\n `generation already exists (write-once): ${key.segment}.${key.generation}`,\n );\n }\n if (isValidationError(err) || isWriteConflictError(err) || isNotFoundError(err)) throw err;\n throw this.mapError(err);\n }\n }\n\n async getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array> {\n if (!Number.isInteger(offset) || !Number.isInteger(length) || offset < 0 || length < 0) {\n throw new ValidationError(`invalid range offset=${offset} length=${length}`);\n }\n if (length === 0) return new Uint8Array(0);\n const objectName = coldObjectName(this.prefix, key);\n try {\n // GCS `end` is inclusive.\n const [buf] = await this.file(objectName).download({\n start: offset,\n end: offset + length - 1,\n });\n // A short read means the range ran past EOF — treat as out-of-bounds, never a partial result.\n if (buf.length !== length) {\n throw new ValidationError(\n `range [${offset}, ${offset + length}) out of bounds (got ${buf.length}B)`,\n );\n }\n return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n } catch (err) {\n throw this.mapReadError(err, key);\n }\n }\n\n async getTail(key: GenKey, maxBytes: number): Promise<{ bytes: Uint8Array; size: number }> {\n const objectName = coldObjectName(this.prefix, key);\n try {\n // Two round-trips (metadata for the size, then a ranged download) vs S3's one (suffix-range +\n // Content-Range). This is on the per-*generation* open path, which the reader caches — NOT the per-op hot\n // path (add/has/count/intersect) — so it's amortized; collapsing to one RT is a tracked follow-up.\n const [meta] = await this.file(objectName).getMetadata();\n const size = Number(meta.size ?? 0);\n if (!Number.isSafeInteger(size) || size < 0) {\n throw new ValidationError(`GCS returned an invalid object size: ${String(meta.size)}`);\n }\n if (maxBytes <= 0 || size === 0) return { bytes: new Uint8Array(0), size };\n const start = Math.max(0, size - maxBytes);\n const [buf] = await this.file(objectName).download({ start, end: size - 1 });\n return { bytes: new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength), size };\n } catch (err) {\n throw this.mapReadError(err, key);\n }\n }\n\n async delete(key: GenKey): Promise<void> {\n // Idempotent: `ignoreNotFound` so a racing/retried GC sweep of an absent object is a no-op.\n try {\n await this.file(coldObjectName(this.prefix, key)).delete({ ignoreNotFound: true });\n } catch (err) {\n throw this.mapError(err);\n }\n }\n\n async *list(ref: SegmentRef): AsyncIterable<GenKey> {\n const prefix = segmentObjectPrefix(this.prefix, ref); // validates ref\n let files;\n try {\n // autoPaginate (default) drains every page; a segment has few generations, so the set is small.\n [files] = await this.storage.bucket(this.bucket).getFiles({ prefix });\n } catch (err) {\n throw this.mapError(err);\n }\n for (const f of files) {\n const generation = parseGenerationFromName(prefix, f.name);\n if (generation !== null) {\n yield { namespace: ref.namespace, segment: ref.segment, generation };\n }\n }\n }\n\n /** Map GCS read errors to the driver vocabulary; pass everything else through {@link mapError}. */\n private mapReadError(err: unknown, key: GenKey): unknown {\n if (isValidationError(err)) return err;\n if (isNotFound(err))\n return new NotFoundError(`no such generation: ${key.segment}.${key.generation}`);\n // A fully out-of-range request (start past EOF) — the BlobReader contract treats range errors as\n // ValidationError, never a short/empty read.\n if (isInvalidRange(err)) {\n return new ValidationError(`range out of bounds for ${key.segment}.${key.generation}`);\n }\n return this.mapError(err);\n }\n\n /**\n * Reclassify a transient GCS fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}\n * so the retry decorator can ride it out; everything else propagates unchanged. The final fallback at every\n * client-call site, so callers + the decorator only ever see typed errors.\n */\n private mapError(err: unknown): unknown {\n if (isTransient(err)) {\n return new TransientError(\n `transient GCS fault: ${(err as { code?: unknown } | null)?.code ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n }\n}\n\ntype GcsFile = ReturnType<ReturnType<Storage['bucket']>['file']>;\n\n/** Concatenate byte chunks of known total length into one buffer. */\nfunction concatBytes(parts: readonly Uint8Array[], total: number): Uint8Array {\n const out = new Uint8Array(total);\n let offset = 0;\n for (const p of parts) {\n out.set(p, offset);\n offset += p.length;\n }\n return out;\n}\n\n/**\n * {@link BlobSink} that uploads one GCS object write-once. It **buffers up to a threshold**: an object that\n * finishes at/under it is committed in a **single simple (non-resumable) request** (`file.save`) — the write-once\n * path both real GCS and `fake-gcs-server` enforce; a larger one switches to a **resumable stream**, flushing the\n * buffer then piping the rest in constant memory (peak ≈ one threshold). SHA-256 is hashed incrementally. Both\n * paths carry `ifGenerationMatch: 0` (create-only-if-absent) → a conflict is a 412, mapped by the driver to\n * {@link WriteConflictError}. On error the caller invokes {@link abort}.\n */\nclass GcsUploadSink implements BlobSink {\n private readonly hash: Hash = createHash('sha256');\n private total = 0;\n private readonly buffered: Uint8Array[] = [];\n private bufferedLen = 0;\n private stream: Writable | undefined; // set once we cross the threshold → resumable mode\n private done: Promise<unknown[]> | undefined;\n private failure: unknown;\n\n constructor(\n private readonly file: GcsFile,\n private readonly maxObjectBytes: number,\n private readonly threshold: number,\n ) {}\n\n private static readonly WRITE_OPTS = {\n preconditionOpts: { ifGenerationMatch: 0 as const }, // write-once: create only if absent\n metadata: { contentType: 'application/octet-stream' },\n };\n\n async write(bytes: Uint8Array): Promise<void> {\n if (this.failure !== undefined) throw this.failure;\n if (bytes.length === 0) return;\n this.total += bytes.length;\n if (this.total > this.maxObjectBytes) {\n throw new ValidationError(`object exceeds maxObjectBytes ${this.maxObjectBytes}`);\n }\n this.hash.update(bytes);\n if (this.stream !== undefined) {\n // Resumable mode: honor backpressure. `events.once(stream,'drain')` REJECTS if the stream emits 'error'\n // while we wait (that's its documented behavior for any awaited event except 'error'), and auto-removes\n // both listeners on settle — so there's no listener accumulation across drain cycles.\n if (!this.stream.write(bytes)) await once(this.stream, 'drain');\n return;\n }\n this.buffered.push(bytes);\n this.bufferedLen += bytes.length;\n if (this.bufferedLen > this.threshold) this.startResumable();\n }\n\n /** Cross into resumable streaming: open the stream, flush the buffered bytes, keep only ~one threshold resident. */\n private startResumable(): void {\n const stream = this.file.createWriteStream({ resumable: true, ...GcsUploadSink.WRITE_OPTS });\n this.stream = stream;\n this.done = once(stream, 'finish'); // resolves on a clean commit; rejects on 'error' (e.g. 412)\n stream.on('error', (e: unknown) => {\n this.failure ??= e; // a persistent listener so a stray 'error' is never an unhandled 'error' event\n });\n this.done.catch(() => undefined); // finish() awaits this; guard against unhandled rejection until then\n const buf = concatBytes(this.buffered, this.bufferedLen);\n this.buffered.length = 0;\n this.bufferedLen = 0;\n stream.write(buf); // ≤ threshold+one-chunk; backpressure is absorbed by the next write()'s drain-await / end()\n }\n\n async finish(): Promise<{ size: number; sha256: string }> {\n if (this.failure !== undefined) throw this.failure;\n const sha256 = this.hash.digest('hex');\n if (this.stream === undefined) {\n // Small object: a single simple (non-resumable) upload — write-once enforced everywhere (incl. the emulator).\n await this.file.save(concatBytes(this.buffered, this.bufferedLen), {\n resumable: false,\n ...GcsUploadSink.WRITE_OPTS,\n });\n return { size: this.total, sha256 };\n }\n this.stream.end();\n await this.done; // rejects if the commit fails (e.g. 412 write-once conflict)\n return { size: this.total, sha256 };\n }\n\n async abort(): Promise<void> {\n if (this.stream !== undefined && !this.stream.destroyed) this.stream.destroy();\n }\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Storage } from '@google-cloud/storage';
|
|
2
|
+
import { I as IColdDriver, C as ColdCaps, G as GenKey, B as BlobSink, S as SegmentRef } from '../ports-D3BrJ6ax.cjs';
|
|
3
|
+
|
|
4
|
+
interface GcsColdDriverOptions {
|
|
5
|
+
/** A constructed `@google-cloud/storage` `Storage` client (point `apiEndpoint` at fake-gcs-server locally). */
|
|
6
|
+
readonly storage: Storage;
|
|
7
|
+
/** Target bucket (must already exist). */
|
|
8
|
+
readonly bucket: string;
|
|
9
|
+
/** Optional object-name prefix under which all objects live (e.g. `cloudroaring/`). */
|
|
10
|
+
readonly prefix?: string;
|
|
11
|
+
/** Largest object this driver will write/advertise (default = GCS's 5 TiB max). */
|
|
12
|
+
readonly maxObjectBytes?: number;
|
|
13
|
+
/** Bytes at/under which a single non-resumable upload is used instead of a resumable stream (default 8 MiB). */
|
|
14
|
+
readonly simpleUploadThresholdBytes?: number;
|
|
15
|
+
}
|
|
16
|
+
declare class GcsColdDriver implements IColdDriver {
|
|
17
|
+
private readonly storage;
|
|
18
|
+
private readonly bucket;
|
|
19
|
+
private readonly prefix;
|
|
20
|
+
private readonly maxObjectBytes;
|
|
21
|
+
private readonly threshold;
|
|
22
|
+
constructor(options: GcsColdDriverOptions);
|
|
23
|
+
capabilities(): ColdCaps;
|
|
24
|
+
private file;
|
|
25
|
+
putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
|
|
26
|
+
size: number;
|
|
27
|
+
sha256: string;
|
|
28
|
+
}>;
|
|
29
|
+
getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
|
|
30
|
+
getTail(key: GenKey, maxBytes: number): Promise<{
|
|
31
|
+
bytes: Uint8Array;
|
|
32
|
+
size: number;
|
|
33
|
+
}>;
|
|
34
|
+
delete(key: GenKey): Promise<void>;
|
|
35
|
+
list(ref: SegmentRef): AsyncIterable<GenKey>;
|
|
36
|
+
/** Map GCS read errors to the driver vocabulary; pass everything else through {@link mapError}. */
|
|
37
|
+
private mapReadError;
|
|
38
|
+
/**
|
|
39
|
+
* Reclassify a transient GCS fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}
|
|
40
|
+
* so the retry decorator can ride it out; everything else propagates unchanged. The final fallback at every
|
|
41
|
+
* client-call site, so callers + the decorator only ever see typed errors.
|
|
42
|
+
*/
|
|
43
|
+
private mapError;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { GcsColdDriver, type GcsColdDriverOptions };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Storage } from '@google-cloud/storage';
|
|
2
|
+
import { I as IColdDriver, C as ColdCaps, G as GenKey, B as BlobSink, S as SegmentRef } from '../ports-D3BrJ6ax.js';
|
|
3
|
+
|
|
4
|
+
interface GcsColdDriverOptions {
|
|
5
|
+
/** A constructed `@google-cloud/storage` `Storage` client (point `apiEndpoint` at fake-gcs-server locally). */
|
|
6
|
+
readonly storage: Storage;
|
|
7
|
+
/** Target bucket (must already exist). */
|
|
8
|
+
readonly bucket: string;
|
|
9
|
+
/** Optional object-name prefix under which all objects live (e.g. `cloudroaring/`). */
|
|
10
|
+
readonly prefix?: string;
|
|
11
|
+
/** Largest object this driver will write/advertise (default = GCS's 5 TiB max). */
|
|
12
|
+
readonly maxObjectBytes?: number;
|
|
13
|
+
/** Bytes at/under which a single non-resumable upload is used instead of a resumable stream (default 8 MiB). */
|
|
14
|
+
readonly simpleUploadThresholdBytes?: number;
|
|
15
|
+
}
|
|
16
|
+
declare class GcsColdDriver implements IColdDriver {
|
|
17
|
+
private readonly storage;
|
|
18
|
+
private readonly bucket;
|
|
19
|
+
private readonly prefix;
|
|
20
|
+
private readonly maxObjectBytes;
|
|
21
|
+
private readonly threshold;
|
|
22
|
+
constructor(options: GcsColdDriverOptions);
|
|
23
|
+
capabilities(): ColdCaps;
|
|
24
|
+
private file;
|
|
25
|
+
putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
|
|
26
|
+
size: number;
|
|
27
|
+
sha256: string;
|
|
28
|
+
}>;
|
|
29
|
+
getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
|
|
30
|
+
getTail(key: GenKey, maxBytes: number): Promise<{
|
|
31
|
+
bytes: Uint8Array;
|
|
32
|
+
size: number;
|
|
33
|
+
}>;
|
|
34
|
+
delete(key: GenKey): Promise<void>;
|
|
35
|
+
list(ref: SegmentRef): AsyncIterable<GenKey>;
|
|
36
|
+
/** Map GCS read errors to the driver vocabulary; pass everything else through {@link mapError}. */
|
|
37
|
+
private mapReadError;
|
|
38
|
+
/**
|
|
39
|
+
* Reclassify a transient GCS fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}
|
|
40
|
+
* so the retry decorator can ride it out; everything else propagates unchanged. The final fallback at every
|
|
41
|
+
* client-call site, so callers + the decorator only ever see typed errors.
|
|
42
|
+
*/
|
|
43
|
+
private mapError;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { GcsColdDriver, type GcsColdDriverOptions };
|