@platformatic/sqlite-js-stream-s3 0.2.1 → 0.3.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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/leaser.d.ts.map +1 -1
- package/dist/leaser.js +1 -55
- package/dist/replica-client.d.ts +118 -0
- package/dist/replica-client.d.ts.map +1 -1
- package/dist/replica-client.js +404 -30
- package/dist/util.d.ts +14 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +68 -0
- package/package.json +3 -3
- package/src/index.ts +5 -0
- package/src/leaser.ts +1 -54
- package/src/replica-client.ts +451 -31
- package/src/util.ts +68 -0
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
* factory, so `newReplicaClientFromURL('s3://bucket/prefix')` resolves after
|
|
6
6
|
* this addon is loaded.
|
|
7
7
|
*/
|
|
8
|
-
export { S3ReplicaClient, newReplicaClientFromURL, ReplicaClientType, type S3ClientLike, type S3ReplicaClientOptions, } from './replica-client.ts';
|
|
8
|
+
export { S3ReplicaClient, newReplicaClientFromURL, isFenced, GenerationMetadataKey, GenerationObjectName, ReplicaClientType, type GenerationFence, type GenerationState, type S3ClientLike, type S3ReplicaClientOptions, } from './replica-client.ts';
|
|
9
9
|
export { S3Leaser } from './leaser.ts';
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,sBAAsB,GAC5B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,QAAQ,EACR,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,sBAAsB,GAC5B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* factory, so `newReplicaClientFromURL('s3://bucket/prefix')` resolves after
|
|
6
6
|
* this addon is loaded.
|
|
7
7
|
*/
|
|
8
|
-
export { S3ReplicaClient, newReplicaClientFromURL, ReplicaClientType, } from "./replica-client.js";
|
|
8
|
+
export { S3ReplicaClient, newReplicaClientFromURL, isFenced, GenerationMetadataKey, GenerationObjectName, ReplicaClientType, } from "./replica-client.js";
|
|
9
9
|
export { S3Leaser } from "./leaser.js";
|
|
10
10
|
import { registerReplicaClientFactory } from '@platformatic/sqlite-js-stream';
|
|
11
11
|
import { newReplicaClientFromURL } from "./replica-client.js";
|
package/dist/leaser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaser.d.ts","sourceRoot":"","sources":["../src/leaser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,EAIL,KAAK,EAEL,KAAK,MAAM,EACZ,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"leaser.d.ts","sourceRoot":"","sources":["../src/leaser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,EAIL,KAAK,EAEL,KAAK,MAAM,EACZ,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,wEAAwE;AACxE,qBAAa,QAAS,YAAW,MAAM;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,KAAK,CAAS;gBAEV,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAUrG,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC;IAuC9B,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IA2BxC,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;YAiBjC,SAAS;CAoBxB"}
|
package/dist/leaser.js
CHANGED
|
@@ -7,61 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, } from '@aws-sdk/client-s3';
|
|
9
9
|
import { DefaultLeasePath, DefaultLeaseTTL, ErrLeaseNotHeld, Lease, LeaseExistsError, } from '@platformatic/sqlite-js-stream';
|
|
10
|
-
|
|
11
|
-
async function readBody(body) {
|
|
12
|
-
// AWS SDK v3 returns a mix; normalize to a Uint8Array.
|
|
13
|
-
if (body instanceof Uint8Array)
|
|
14
|
-
return body;
|
|
15
|
-
if (typeof body?.on === 'function') {
|
|
16
|
-
// Node stream.
|
|
17
|
-
const chunks = [];
|
|
18
|
-
for await (const chunk of body) {
|
|
19
|
-
chunks.push(typeof chunk === 'string' ? new TextEncoder().encode(chunk) : chunk);
|
|
20
|
-
}
|
|
21
|
-
let total = 0;
|
|
22
|
-
for (const c of chunks)
|
|
23
|
-
total += c.length;
|
|
24
|
-
const out = new Uint8Array(total);
|
|
25
|
-
let o = 0;
|
|
26
|
-
for (const c of chunks) {
|
|
27
|
-
out.set(c, o);
|
|
28
|
-
o += c.length;
|
|
29
|
-
}
|
|
30
|
-
return out;
|
|
31
|
-
}
|
|
32
|
-
// Web ReadableStream or Buffer.
|
|
33
|
-
if (typeof body?.getReader === 'function') {
|
|
34
|
-
const reader = body.getReader();
|
|
35
|
-
const chunks = [];
|
|
36
|
-
for (;;) {
|
|
37
|
-
const { done, value } = await reader.read();
|
|
38
|
-
if (done)
|
|
39
|
-
break;
|
|
40
|
-
chunks.push(value);
|
|
41
|
-
}
|
|
42
|
-
let total = 0;
|
|
43
|
-
for (const c of chunks)
|
|
44
|
-
total += c.length;
|
|
45
|
-
const out = new Uint8Array(total);
|
|
46
|
-
let o = 0;
|
|
47
|
-
for (const c of chunks) {
|
|
48
|
-
out.set(c, o);
|
|
49
|
-
o += c.length;
|
|
50
|
-
}
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
|
-
return new Uint8Array(body);
|
|
54
|
-
}
|
|
55
|
-
function isPreconditionFailed(err) {
|
|
56
|
-
const code = err?.$metadata?.httpStatusCode;
|
|
57
|
-
const name = err?.name ?? '';
|
|
58
|
-
return code === 412 || name === 'PreconditionFailed';
|
|
59
|
-
}
|
|
60
|
-
function isNotFound(err) {
|
|
61
|
-
const code = err?.$metadata?.httpStatusCode;
|
|
62
|
-
const name = err?.name ?? '';
|
|
63
|
-
return code === 404 || name === 'NoSuchKey' || name === 'NotFound';
|
|
64
|
-
}
|
|
10
|
+
import { isNotFound, isPreconditionFailed, readBody } from "./util.js";
|
|
65
11
|
/** S3Leaser acquires/renews/releases a lease object in an S3 bucket. */
|
|
66
12
|
export class S3Leaser {
|
|
67
13
|
bucket;
|
package/dist/replica-client.d.ts
CHANGED
|
@@ -10,6 +10,26 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Creation timestamps are preserved on upload as the `ltx-timestamp` object
|
|
12
12
|
* metadata; listing falls back to the object's `LastModified`.
|
|
13
|
+
*
|
|
14
|
+
* The client also supports *fenced writes* (see {@link S3ReplicaClient.registerGeneration}).
|
|
15
|
+
* S3 cannot check one object and write another in a single request, so fencing
|
|
16
|
+
* is a protocol built from conditional writes:
|
|
17
|
+
*
|
|
18
|
+
* - `{prefix}/generation.json` holds the current generation and a list of
|
|
19
|
+
* *fences*. A fence `(generation, txid)` records the latest TXID of the
|
|
20
|
+
* replica right after `generation` was registered. Registering is a
|
|
21
|
+
* conditional write (`If-Match` / `If-None-Match: *`) that only raises the
|
|
22
|
+
* generation; the fence is then added with another conditional write.
|
|
23
|
+
* - LTX files of a fenced client are written with `If-None-Match: *`, so a TXID
|
|
24
|
+
* range is written once per level, and carry their generation in the
|
|
25
|
+
* `ltx-generation` metadata. A file with no `ltx-generation` is generation 0.
|
|
26
|
+
* - Listings ignore a file whose generation is lower than a fence's generation
|
|
27
|
+
* and whose `maxTXID` is above that fence's TXID: it was written by a
|
|
28
|
+
* superseded writer after the new one read the replica.
|
|
29
|
+
* - After each upload, a fenced client re-reads the generation object and fails
|
|
30
|
+
* with {@link ErrGenerationSuperseded} if a higher generation is registered.
|
|
31
|
+
* An upload that resolves was therefore written while its generation was the
|
|
32
|
+
* latest, and every later generation sees it.
|
|
13
33
|
*/
|
|
14
34
|
import { Readable } from 'node:stream';
|
|
15
35
|
import { type FileInfo, type Logger, type LTXFilesOptions, type OpenLTXFileOptions, type ReplicaClient, type WriteLTXFileOptions } from '@platformatic/sqlite-js-stream';
|
|
@@ -37,13 +57,49 @@ export interface S3ReplicaClientOptions {
|
|
|
37
57
|
sessionToken?: string;
|
|
38
58
|
/** Inject a preconfigured client. Mostly for tests. */
|
|
39
59
|
client?: S3ClientLike;
|
|
60
|
+
/**
|
|
61
|
+
* Fenced-write generation, or `null` (the default) to disable fencing. Set
|
|
62
|
+
* by {@link S3ReplicaClient.registerGeneration}; when set, uploads and
|
|
63
|
+
* deletions fail with {@link ErrGenerationSuperseded} once a higher
|
|
64
|
+
* generation is registered.
|
|
65
|
+
*/
|
|
66
|
+
generation?: bigint | null;
|
|
67
|
+
}
|
|
68
|
+
/** Name of the generation object, relative to the replica prefix. */
|
|
69
|
+
export declare const GenerationObjectName = "generation.json";
|
|
70
|
+
/** Object metadata key carrying the generation of a fenced LTX upload. */
|
|
71
|
+
export declare const GenerationMetadataKey = "ltx-generation";
|
|
72
|
+
/**
|
|
73
|
+
* A fence: `generation` was registered when the latest TXID of the replica was
|
|
74
|
+
* `txid`. Files of lower generations with a `maxTXID` above `txid` are ignored.
|
|
75
|
+
*/
|
|
76
|
+
export interface GenerationFence {
|
|
77
|
+
generation: bigint;
|
|
78
|
+
txid: bigint;
|
|
79
|
+
}
|
|
80
|
+
/** Contents of the generation object. */
|
|
81
|
+
export interface GenerationState {
|
|
82
|
+
/** The highest registered generation. */
|
|
83
|
+
generation: bigint;
|
|
84
|
+
/** Fences, in registration order (one per generation that completed registration). */
|
|
85
|
+
fences: GenerationFence[];
|
|
40
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns true if a file of `generation` ending at `maxTXID` is part of the
|
|
89
|
+
* replica: no fence of a higher generation was recorded below `maxTXID`.
|
|
90
|
+
*/
|
|
91
|
+
export declare function isFenced(fences: readonly GenerationFence[], generation: bigint, maxTXID: bigint): boolean;
|
|
41
92
|
/** S3ReplicaClient writes LTX files to an Amazon S3 bucket. */
|
|
42
93
|
export declare class S3ReplicaClient implements ReplicaClient {
|
|
43
94
|
readonly bucket: string;
|
|
44
95
|
readonly prefix: string;
|
|
45
96
|
private client;
|
|
46
97
|
private logger;
|
|
98
|
+
private generation;
|
|
99
|
+
/** The higher generation that superseded this client, once observed. */
|
|
100
|
+
private supersededBy;
|
|
101
|
+
/** Generation of listed objects, keyed by `key` + ETag (objects are immutable). */
|
|
102
|
+
private readonly objectGenerations;
|
|
47
103
|
constructor(opts: S3ReplicaClientOptions);
|
|
48
104
|
type(): string;
|
|
49
105
|
/** Best-effort connectivity check; real I/O errors surface on first use. */
|
|
@@ -55,10 +111,72 @@ export declare class S3ReplicaClient implements ReplicaClient {
|
|
|
55
111
|
private levelPrefix;
|
|
56
112
|
/** Returns the object key for a level and TXID range. */
|
|
57
113
|
private fileKey;
|
|
114
|
+
/** Returns the key of the generation object. */
|
|
115
|
+
private generationKey;
|
|
116
|
+
/** Current fenced-write generation, or `null` when fencing is disabled. */
|
|
117
|
+
getGeneration(): bigint | null;
|
|
118
|
+
/** Reads the generation object, or returns `null` if fencing was never used. */
|
|
119
|
+
getGenerationState(): Promise<GenerationState | null>;
|
|
120
|
+
private readGenerationObject;
|
|
121
|
+
/**
|
|
122
|
+
* Writes the generation object if it was not changed since it was read with
|
|
123
|
+
* `etag` (`null`: if it does not exist). Returns false on a lost race.
|
|
124
|
+
*/
|
|
125
|
+
private writeGenerationObject;
|
|
126
|
+
/** Records that this client was superseded by `generation` and returns the error. */
|
|
127
|
+
private superseded;
|
|
128
|
+
/**
|
|
129
|
+
* Fails with {@link ErrGenerationSuperseded} if a generation higher than this
|
|
130
|
+
* client's is registered. A no-op when fencing is disabled.
|
|
131
|
+
*/
|
|
132
|
+
private assertGeneration;
|
|
133
|
+
/**
|
|
134
|
+
* Raises the replica's generation to `generation`, failing with
|
|
135
|
+
* {@link ErrGenerationSuperseded} if a higher one is already registered, and
|
|
136
|
+
* records a fence at the replica's latest TXID as read after the
|
|
137
|
+
* registration. From then on, files of lower generations above that TXID
|
|
138
|
+
* are ignored by every listing, and uploads and deletions of lower
|
|
139
|
+
* generations fail once they observe the new generation.
|
|
140
|
+
*
|
|
141
|
+
* Call it before restoring: the restore then sees every file that a former
|
|
142
|
+
* writer can still make part of the replica. Registering the current
|
|
143
|
+
* generation again is a no-op that keeps its fence.
|
|
144
|
+
*/
|
|
145
|
+
registerGeneration(generation: bigint): Promise<void>;
|
|
146
|
+
/** Returns the generation of an object (from `ltx-generation`), or null if it is gone. */
|
|
147
|
+
private objectGeneration;
|
|
148
|
+
private rememberGeneration;
|
|
149
|
+
/**
|
|
150
|
+
* Lists the LTX files of a level. When the replica has fences, files ending
|
|
151
|
+
* above the lowest fence need their generation (a HeadObject request, cached
|
|
152
|
+
* per key and ETag), and those written by a superseded writer are skipped.
|
|
153
|
+
*/
|
|
58
154
|
ltxFiles(opts: LTXFilesOptions): AsyncIterable<FileInfo>;
|
|
59
155
|
openLtxFile(opts: OpenLTXFileOptions): Promise<Readable>;
|
|
60
156
|
writeLtxFile(opts: WriteLTXFileOptions, body: AsyncIterable<Uint8Array | string>): Promise<FileInfo>;
|
|
157
|
+
/**
|
|
158
|
+
* Creates a fenced LTX object with `If-None-Match: *`. If the key already
|
|
159
|
+
* exists:
|
|
160
|
+
* - written by a higher generation: this client is superseded;
|
|
161
|
+
* - written by this generation (a retried upload), or by a lower one but
|
|
162
|
+
* still part of the replica: it holds the same transactions, so it is
|
|
163
|
+
* kept and its metadata returned;
|
|
164
|
+
* - otherwise it is a superseded writer's file, replaced with `If-Match`.
|
|
165
|
+
* With `probe`, the key is checked with HeadObject before the first write.
|
|
166
|
+
* Returns null when this call created the object.
|
|
167
|
+
*/
|
|
168
|
+
private commitFenced;
|
|
169
|
+
/**
|
|
170
|
+
* Deletes LTX files. A fenced client first checks that its generation is
|
|
171
|
+
* still the latest and fails with {@link ErrGenerationSuperseded} otherwise
|
|
172
|
+
* (a deletion sent just before a new generation registers still applies).
|
|
173
|
+
*/
|
|
61
174
|
deleteLtxFiles(infos: FileInfo[]): Promise<void>;
|
|
175
|
+
/**
|
|
176
|
+
* Deletes every object under the prefix except the generation object, so a
|
|
177
|
+
* superseded writer stays fenced. A fenced client checks its generation
|
|
178
|
+
* first, as {@link deleteLtxFiles} does.
|
|
179
|
+
*/
|
|
62
180
|
deleteAll(): Promise<void>;
|
|
63
181
|
}
|
|
64
182
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replica-client.d.ts","sourceRoot":"","sources":["../src/replica-client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"replica-client.d.ts","sourceRoot":"","sources":["../src/replica-client.ts"],"names":[],"mappings":"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"}
|