@bjornpagen/bumbledb-log 0.20.0 → 0.20.2
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/dist/braids.d.ts +28 -0
- package/dist/braids.d.ts.map +1 -0
- package/{src/braids.ts → dist/braids.js} +22 -29
- package/dist/braids.js.map +1 -0
- package/dist/bytes.d.ts +26 -0
- package/dist/bytes.d.ts.map +1 -0
- package/dist/bytes.js +86 -0
- package/dist/bytes.js.map +1 -0
- package/dist/chain.d.ts +56 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +137 -0
- package/dist/chain.js.map +1 -0
- package/dist/codec.d.ts +71 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +179 -0
- package/dist/codec.js.map +1 -0
- package/dist/descriptor.d.ts +61 -0
- package/dist/descriptor.d.ts.map +1 -0
- package/dist/descriptor.js +160 -0
- package/dist/descriptor.js.map +1 -0
- package/dist/errors.d.ts +202 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +122 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +52 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +166 -0
- package/dist/keys.js.map +1 -0
- package/dist/manifest.d.ts +42 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +116 -0
- package/dist/manifest.js.map +1 -0
- package/dist/replica.d.ts +154 -0
- package/dist/replica.d.ts.map +1 -0
- package/dist/replica.js +820 -0
- package/dist/replica.js.map +1 -0
- package/dist/store-s3.d.ts +31 -0
- package/dist/store-s3.d.ts.map +1 -0
- package/dist/store-s3.js +299 -0
- package/dist/store-s3.js.map +1 -0
- package/dist/store.d.ts +129 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +577 -0
- package/dist/store.js.map +1 -0
- package/dist/tenants.d.ts +56 -0
- package/dist/tenants.d.ts.map +1 -0
- package/dist/tenants.js +333 -0
- package/dist/tenants.js.map +1 -0
- package/dist/tilde-family.json +21 -0
- package/dist/vector.d.ts +31 -0
- package/dist/vector.d.ts.map +1 -0
- package/dist/vector.js +87 -0
- package/dist/vector.js.map +1 -0
- package/dist/writer.d.ts +106 -0
- package/dist/writer.d.ts.map +1 -0
- package/dist/writer.js +677 -0
- package/dist/writer.js.map +1 -0
- package/package.json +12 -7
- package/src/bytes.ts +0 -115
- package/src/chain.ts +0 -181
- package/src/codec.ts +0 -252
- package/src/descriptor.ts +0 -240
- package/src/errors.ts +0 -309
- package/src/index.ts +0 -57
- package/src/keys.ts +0 -218
- package/src/manifest.ts +0 -161
- package/src/replica.ts +0 -1061
- package/src/store-s3.ts +0 -377
- package/src/store.ts +0 -727
- package/src/tenants.ts +0 -414
- package/src/vector.ts +0 -103
- package/src/writer.ts +0 -977
package/src/store-s3.ts
DELETED
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `s3Store`: the five verbs over one S3-compatible target. The official
|
|
3
|
-
* `@aws-sdk/client-s3` client signs and talks; this module maps HTTP
|
|
4
|
-
* outcomes onto the sums. One storage class for every key. The vendor
|
|
5
|
-
* ETag header is the opaque token, carried verbatim. R2 rides region
|
|
6
|
-
* `auto` and a required endpoint. A 409, a timeout, or any other
|
|
7
|
-
* unproved conditional-write result is Ambiguous; the GET-verify law
|
|
8
|
-
* resolves it. Credentials are consulted per request. Conditional
|
|
9
|
-
* writes are not retried by the client: a re-sent PUT is an unproved
|
|
10
|
-
* outcome. Body-stream failures wrap ErrStore.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3"
|
|
14
|
-
import * as errors from "@superbuilders/errors"
|
|
15
|
-
import { bytesEqual } from "#bytes.ts"
|
|
16
|
-
import { wrapStore } from "#errors.ts"
|
|
17
|
-
import type { StoreKey } from "#keys.ts"
|
|
18
|
-
import { parsePrefix } from "#keys.ts"
|
|
19
|
-
import type { Create, Etag, Fetched, ObjectStore, Swap } from "#store.ts"
|
|
20
|
-
|
|
21
|
-
interface StaticKeys {
|
|
22
|
-
readonly accessKeyId: string
|
|
23
|
-
readonly secretAccessKey: string
|
|
24
|
-
readonly sessionToken?: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Static keys, or a caller-owned refresh the store invokes before each signed request. */
|
|
28
|
-
type S3Credentials = StaticKeys | (() => StaticKeys | Promise<StaticKeys>)
|
|
29
|
-
|
|
30
|
-
interface S3Config {
|
|
31
|
-
readonly endpoint?: string
|
|
32
|
-
readonly region: string
|
|
33
|
-
readonly bucket: string
|
|
34
|
-
readonly credentials: S3Credentials
|
|
35
|
-
readonly prefix?: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const READ_ATTEMPTS = 6
|
|
39
|
-
const READ_BASE_MS = 50
|
|
40
|
-
const READ_CAP_MS = 2_000
|
|
41
|
-
|
|
42
|
-
function asEtag(raw: string): Etag {
|
|
43
|
-
return raw as Etag
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function joinPrefix(prefix: string, key: string): string {
|
|
47
|
-
return prefix.length === 0 ? key : `${prefix}/${key}`
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function resolveKeys(credentials: S3Credentials): Promise<StaticKeys> {
|
|
51
|
-
if (typeof credentials === "function") {
|
|
52
|
-
return await credentials()
|
|
53
|
-
}
|
|
54
|
-
return credentials
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function httpStatus(error: unknown): number | undefined {
|
|
58
|
-
if (error === null || typeof error !== "object" || !("$metadata" in error)) {
|
|
59
|
-
return undefined
|
|
60
|
-
}
|
|
61
|
-
const metadata = error.$metadata
|
|
62
|
-
if (metadata === null || typeof metadata !== "object" || !("httpStatusCode" in metadata)) {
|
|
63
|
-
return undefined
|
|
64
|
-
}
|
|
65
|
-
const status = metadata.httpStatusCode
|
|
66
|
-
return typeof status === "number" ? status : undefined
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function asError(error: unknown): Error {
|
|
70
|
-
if (error instanceof Error) {
|
|
71
|
-
return error
|
|
72
|
-
}
|
|
73
|
-
return errors.new(String(error))
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function etagOf(raw: string | undefined, op: string, key: StoreKey): Etag {
|
|
77
|
-
if (raw === undefined || raw.length === 0) {
|
|
78
|
-
throw wrapStore(errors.new("vendor omitted ETag"), `${op} ${key}`)
|
|
79
|
-
}
|
|
80
|
-
return asEtag(raw)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function isTransientName(error: unknown): boolean {
|
|
84
|
-
if (error === null || typeof error !== "object" || !("name" in error)) {
|
|
85
|
-
return true
|
|
86
|
-
}
|
|
87
|
-
const name = error.name
|
|
88
|
-
return (
|
|
89
|
-
name === "TimeoutError" ||
|
|
90
|
-
name === "RequestTimeout" ||
|
|
91
|
-
name === "TimeoutErrorException" ||
|
|
92
|
-
name === "NetworkingError" ||
|
|
93
|
-
name === "Conflict" ||
|
|
94
|
-
name === "ConflictException" ||
|
|
95
|
-
name === undefined
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** 409 Conflict is Ambiguous, never a proved Exists or Moved. */
|
|
100
|
-
function isUnprovedWrite(error: unknown): boolean {
|
|
101
|
-
const status = httpStatus(error)
|
|
102
|
-
if (status === 409) {
|
|
103
|
-
return true
|
|
104
|
-
}
|
|
105
|
-
if (status !== undefined && status >= 500 && status < 600) {
|
|
106
|
-
return true
|
|
107
|
-
}
|
|
108
|
-
if (status !== undefined) {
|
|
109
|
-
return false
|
|
110
|
-
}
|
|
111
|
-
return isTransientName(error)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function isRetryableRead(error: unknown): boolean {
|
|
115
|
-
const status = httpStatus(error)
|
|
116
|
-
if (status !== undefined) {
|
|
117
|
-
return status >= 500 && status < 600
|
|
118
|
-
}
|
|
119
|
-
return isTransientName(error)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function sleep(ms: number): Promise<void> {
|
|
123
|
-
await new Promise(function later(resolve) {
|
|
124
|
-
setTimeout(resolve, ms)
|
|
125
|
-
})
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function jitteredMs(ceiling: number): number {
|
|
129
|
-
return Math.floor(Math.random() * (ceiling + 1))
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async function bodyBytes(body: unknown, op: string, key: StoreKey): Promise<Uint8Array> {
|
|
133
|
-
const ran = await errors.try(
|
|
134
|
-
(async function consume() {
|
|
135
|
-
if (body === null || typeof body !== "object") {
|
|
136
|
-
throw errors.new("vendor omitted body stream")
|
|
137
|
-
}
|
|
138
|
-
const stream = body as { transformToByteArray?: () => Promise<Uint8Array> }
|
|
139
|
-
if (typeof stream.transformToByteArray !== "function") {
|
|
140
|
-
throw errors.new("vendor omitted body stream")
|
|
141
|
-
}
|
|
142
|
-
return await stream.transformToByteArray()
|
|
143
|
-
})()
|
|
144
|
-
)
|
|
145
|
-
if (ran.error) {
|
|
146
|
-
throw wrapStore(ran.error, `${op} ${key}`)
|
|
147
|
-
}
|
|
148
|
-
return ran.data
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function s3Store(config: S3Config): ObjectStore {
|
|
152
|
-
if (config.region === "auto" && config.endpoint === undefined) {
|
|
153
|
-
throw errors.new("region auto needs an endpoint")
|
|
154
|
-
}
|
|
155
|
-
const prefix = parsePrefix(config.prefix ?? "")
|
|
156
|
-
const client = new S3Client({
|
|
157
|
-
region: config.region,
|
|
158
|
-
maxAttempts: 1,
|
|
159
|
-
credentials: async function credentials() {
|
|
160
|
-
const keys = await resolveKeys(config.credentials)
|
|
161
|
-
return {
|
|
162
|
-
accessKeyId: keys.accessKeyId,
|
|
163
|
-
secretAccessKey: keys.secretAccessKey,
|
|
164
|
-
...(keys.sessionToken === undefined ? {} : { sessionToken: keys.sessionToken })
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
requestChecksumCalculation: "WHEN_REQUIRED",
|
|
168
|
-
responseChecksumValidation: "WHEN_REQUIRED",
|
|
169
|
-
...(config.endpoint === undefined ? {} : { endpoint: config.endpoint, forcePathStyle: true })
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
function objectKey(key: StoreKey): string {
|
|
173
|
-
return joinPrefix(prefix, key)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
type Load =
|
|
177
|
-
| { readonly tag: "hit"; readonly fetched: Fetched }
|
|
178
|
-
| { readonly tag: "miss" }
|
|
179
|
-
| { readonly tag: "retry"; readonly error: Error }
|
|
180
|
-
| { readonly tag: "fail"; readonly error: Error }
|
|
181
|
-
|
|
182
|
-
async function load(key: StoreKey): Promise<Load> {
|
|
183
|
-
const ran = await errors.try(
|
|
184
|
-
client.send(
|
|
185
|
-
new GetObjectCommand({
|
|
186
|
-
Bucket: config.bucket,
|
|
187
|
-
Key: objectKey(key)
|
|
188
|
-
})
|
|
189
|
-
)
|
|
190
|
-
)
|
|
191
|
-
if (ran.error) {
|
|
192
|
-
if (httpStatus(ran.error) === 404) {
|
|
193
|
-
return { tag: "miss" }
|
|
194
|
-
}
|
|
195
|
-
const error = wrapStore(asError(ran.error), `get ${key}`)
|
|
196
|
-
return isRetryableRead(ran.error) ? { tag: "retry", error } : { tag: "fail", error }
|
|
197
|
-
}
|
|
198
|
-
if (ran.data.Body === undefined) {
|
|
199
|
-
return { tag: "fail", error: wrapStore(errors.new("vendor omitted body"), `get ${key}`) }
|
|
200
|
-
}
|
|
201
|
-
const body = await errors.try(bodyBytes(ran.data.Body, "get", key))
|
|
202
|
-
if (body.error) {
|
|
203
|
-
return { tag: "retry", error: body.error }
|
|
204
|
-
}
|
|
205
|
-
const tag = await errors.try(
|
|
206
|
-
(async function etag() {
|
|
207
|
-
return etagOf(ran.data.ETag, "get", key)
|
|
208
|
-
})()
|
|
209
|
-
)
|
|
210
|
-
if (tag.error) {
|
|
211
|
-
return { tag: "fail", error: tag.error }
|
|
212
|
-
}
|
|
213
|
-
return { tag: "hit", fetched: { bytes: body.data, etag: tag.data } }
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async function getOnce(key: StoreKey): Promise<Fetched | null> {
|
|
217
|
-
const step = await load(key)
|
|
218
|
-
if (step.tag === "hit") {
|
|
219
|
-
return step.fetched
|
|
220
|
-
}
|
|
221
|
-
if (step.tag === "miss") {
|
|
222
|
-
return null
|
|
223
|
-
}
|
|
224
|
-
throw step.error
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async function retryGet(key: StoreKey): Promise<Fetched | null> {
|
|
228
|
-
let attempt = 0
|
|
229
|
-
for (;;) {
|
|
230
|
-
const step = await load(key)
|
|
231
|
-
if (step.tag === "hit") {
|
|
232
|
-
return step.fetched
|
|
233
|
-
}
|
|
234
|
-
if (step.tag === "miss") {
|
|
235
|
-
return null
|
|
236
|
-
}
|
|
237
|
-
if (step.tag === "fail") {
|
|
238
|
-
throw step.error
|
|
239
|
-
}
|
|
240
|
-
attempt += 1
|
|
241
|
-
if (attempt === READ_ATTEMPTS) {
|
|
242
|
-
throw step.error
|
|
243
|
-
}
|
|
244
|
-
const ceiling = Math.min(READ_CAP_MS, READ_BASE_MS * 2 ** (attempt - 1))
|
|
245
|
-
await sleep(jitteredMs(ceiling))
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async function proveCreate(key: StoreKey, attempted: Uint8Array): Promise<Create> {
|
|
250
|
-
const fetched = await retryGet(key)
|
|
251
|
-
if (fetched === null) {
|
|
252
|
-
return { tag: "ambiguous" }
|
|
253
|
-
}
|
|
254
|
-
if (bytesEqual(fetched.bytes, attempted)) {
|
|
255
|
-
return { tag: "created", etag: fetched.etag }
|
|
256
|
-
}
|
|
257
|
-
return { tag: "exists" }
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
async function proveSwap(key: StoreKey, attempted: Uint8Array): Promise<Swap> {
|
|
261
|
-
const fetched = await retryGet(key)
|
|
262
|
-
if (fetched === null) {
|
|
263
|
-
return { tag: "moved" }
|
|
264
|
-
}
|
|
265
|
-
if (bytesEqual(fetched.bytes, attempted)) {
|
|
266
|
-
return { tag: "swapped", etag: fetched.etag }
|
|
267
|
-
}
|
|
268
|
-
return { tag: "moved" }
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return {
|
|
272
|
-
async get(key) {
|
|
273
|
-
return await getOnce(key)
|
|
274
|
-
},
|
|
275
|
-
|
|
276
|
-
async getIfChanged(key, etag) {
|
|
277
|
-
const ran = await errors.try(
|
|
278
|
-
client.send(
|
|
279
|
-
new GetObjectCommand({
|
|
280
|
-
Bucket: config.bucket,
|
|
281
|
-
Key: objectKey(key),
|
|
282
|
-
IfNoneMatch: etag
|
|
283
|
-
})
|
|
284
|
-
)
|
|
285
|
-
)
|
|
286
|
-
if (ran.error) {
|
|
287
|
-
if (httpStatus(ran.error) === 304) {
|
|
288
|
-
return { tag: "unchanged" }
|
|
289
|
-
}
|
|
290
|
-
throw wrapStore(asError(ran.error), `getIfChanged ${key}`)
|
|
291
|
-
}
|
|
292
|
-
if (ran.data.Body === undefined) {
|
|
293
|
-
throw wrapStore(errors.new("vendor omitted body"), `getIfChanged ${key}`)
|
|
294
|
-
}
|
|
295
|
-
return {
|
|
296
|
-
tag: "changed",
|
|
297
|
-
fetched: {
|
|
298
|
-
bytes: await bodyBytes(ran.data.Body, "getIfChanged", key),
|
|
299
|
-
etag: etagOf(ran.data.ETag, "getIfChanged", key)
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
},
|
|
303
|
-
|
|
304
|
-
async putCreate(key, bytes) {
|
|
305
|
-
const ran = await errors.try(
|
|
306
|
-
client.send(
|
|
307
|
-
new PutObjectCommand({
|
|
308
|
-
Bucket: config.bucket,
|
|
309
|
-
Key: objectKey(key),
|
|
310
|
-
Body: bytes,
|
|
311
|
-
IfNoneMatch: "*"
|
|
312
|
-
})
|
|
313
|
-
)
|
|
314
|
-
)
|
|
315
|
-
if (ran.error) {
|
|
316
|
-
if (httpStatus(ran.error) === 412) {
|
|
317
|
-
return { tag: "exists" }
|
|
318
|
-
}
|
|
319
|
-
if (isUnprovedWrite(ran.error)) {
|
|
320
|
-
return await proveCreate(key, bytes)
|
|
321
|
-
}
|
|
322
|
-
throw wrapStore(asError(ran.error), `putCreate ${key}`)
|
|
323
|
-
}
|
|
324
|
-
if (ran.data.ETag === undefined || ran.data.ETag.length === 0) {
|
|
325
|
-
return await proveCreate(key, bytes)
|
|
326
|
-
}
|
|
327
|
-
return { tag: "created", etag: asEtag(ran.data.ETag) }
|
|
328
|
-
},
|
|
329
|
-
|
|
330
|
-
async putSwap(key, bytes, etag) {
|
|
331
|
-
const ran = await errors.try(
|
|
332
|
-
client.send(
|
|
333
|
-
new PutObjectCommand({
|
|
334
|
-
Bucket: config.bucket,
|
|
335
|
-
Key: objectKey(key),
|
|
336
|
-
Body: bytes,
|
|
337
|
-
IfMatch: etag
|
|
338
|
-
})
|
|
339
|
-
)
|
|
340
|
-
)
|
|
341
|
-
if (ran.error) {
|
|
342
|
-
const status = httpStatus(ran.error)
|
|
343
|
-
if (status === 412 || status === 404) {
|
|
344
|
-
return { tag: "moved" }
|
|
345
|
-
}
|
|
346
|
-
if (isUnprovedWrite(ran.error)) {
|
|
347
|
-
return await proveSwap(key, bytes)
|
|
348
|
-
}
|
|
349
|
-
throw wrapStore(asError(ran.error), `putSwap ${key}`)
|
|
350
|
-
}
|
|
351
|
-
if (ran.data.ETag === undefined || ran.data.ETag.length === 0) {
|
|
352
|
-
return await proveSwap(key, bytes)
|
|
353
|
-
}
|
|
354
|
-
return { tag: "swapped", etag: asEtag(ran.data.ETag) }
|
|
355
|
-
},
|
|
356
|
-
|
|
357
|
-
async delete(key) {
|
|
358
|
-
const ran = await errors.try(
|
|
359
|
-
client.send(
|
|
360
|
-
new DeleteObjectCommand({
|
|
361
|
-
Bucket: config.bucket,
|
|
362
|
-
Key: objectKey(key)
|
|
363
|
-
})
|
|
364
|
-
)
|
|
365
|
-
)
|
|
366
|
-
if (ran.error) {
|
|
367
|
-
if (httpStatus(ran.error) === 404) {
|
|
368
|
-
return
|
|
369
|
-
}
|
|
370
|
-
throw wrapStore(asError(ran.error), `delete ${key}`)
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
export type { S3Config, S3Credentials }
|
|
377
|
-
export { joinPrefix, s3Store }
|