@kici-dev/shared 0.0.0 → 0.1.1
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/README.md +1 -6
- package/dist/chunk-gOLHoazu.js +4 -0
- package/dist/cold-store/bucket.d.ts +26 -0
- package/dist/cold-store/bucket.js +40 -0
- package/dist/cold-store/bucket.test.d.ts +2 -0
- package/dist/cold-store/chunk-encoder.d.ts +63 -0
- package/dist/cold-store/chunk-encoder.js +94 -0
- package/dist/cold-store/chunk-encoder.test.d.ts +2 -0
- package/dist/cold-store/chunk-id.d.ts +10 -0
- package/dist/cold-store/chunk-id.js +29 -0
- package/dist/cold-store/chunk-id.test.d.ts +2 -0
- package/dist/cold-store/cold-store.d.ts +345 -0
- package/dist/cold-store/cold-store.js +1078 -0
- package/dist/cold-store/cold-store.test.d.ts +2 -0
- package/dist/cold-store/config.d.ts +62 -0
- package/dist/cold-store/config.js +28 -0
- package/dist/cold-store/index.d.ts +20 -0
- package/dist/cold-store/index.js +11 -0
- package/dist/cold-store/key.d.ts +89 -0
- package/dist/cold-store/key.js +88 -0
- package/dist/cold-store/key.test.d.ts +2 -0
- package/dist/cold-store/lru.d.ts +33 -0
- package/dist/cold-store/lru.js +59 -0
- package/dist/cold-store/lru.test.d.ts +2 -0
- package/dist/cold-store/manifest.d.ts +11 -0
- package/dist/cold-store/manifest.js +57 -0
- package/dist/cold-store/manifest.test.d.ts +2 -0
- package/dist/cold-store/metrics.d.ts +103 -0
- package/dist/cold-store/metrics.js +179 -0
- package/dist/cold-store/table-adapter.d.ts +229 -0
- package/dist/cold-store/table-adapter.js +2 -0
- package/dist/cold-store/types.d.ts +111 -0
- package/dist/cold-store/types.js +2 -0
- package/dist/crypto.d.ts +33 -0
- package/dist/crypto.js +67 -0
- package/dist/db-admin.d.ts +1422 -0
- package/dist/db-admin.js +2525 -0
- package/dist/db-admin.test.d.ts +2 -0
- package/dist/db.d.ts +14 -0
- package/dist/db.js +23 -0
- package/dist/env/allowlist.d.ts +79 -0
- package/dist/env/allowlist.js +86 -0
- package/dist/env/define-env.d.ts +172 -0
- package/dist/env/define-env.js +295 -0
- package/dist/env/define-env.test.d.ts +2 -0
- package/dist/env/env-rule-allowlist.test.d.ts +2 -0
- package/dist/env/index.d.ts +11 -0
- package/dist/env/index.js +4 -0
- package/dist/env/logger-env.d.ts +30 -0
- package/dist/env/logger-env.js +95 -0
- package/dist/error.d.ts +16 -0
- package/dist/error.js +58 -0
- package/dist/error.test.d.ts +2 -0
- package/dist/format-bytes.d.ts +5 -0
- package/dist/format-bytes.js +15 -0
- package/dist/format-bytes.test.d.ts +2 -0
- package/dist/format-duration.d.ts +11 -0
- package/dist/format-duration.js +32 -0
- package/dist/format-duration.test.d.ts +2 -0
- package/dist/graceful-shutdown.d.ts +58 -0
- package/dist/graceful-shutdown.js +71 -0
- package/dist/graceful-shutdown.test.d.ts +2 -0
- package/dist/idempotency-files.d.ts +113 -0
- package/dist/idempotency-files.js +190 -0
- package/dist/idempotency-files.test.d.ts +2 -0
- package/dist/idempotency.d.ts +68 -0
- package/dist/idempotency.js +49 -0
- package/dist/idempotency.test.d.ts +2 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +31 -0
- package/dist/logger.d.ts +57 -0
- package/dist/logger.js +175 -0
- package/dist/logger.test.d.ts +2 -0
- package/dist/reconnect-delay.d.ts +11 -0
- package/dist/reconnect-delay.js +22 -0
- package/dist/request-context.d.ts +42 -0
- package/dist/request-context.js +37 -0
- package/dist/ring-buffer.d.ts +35 -0
- package/dist/ring-buffer.js +62 -0
- package/dist/ring-buffer.test.d.ts +2 -0
- package/dist/routes/health.d.ts +26 -0
- package/dist/routes/health.js +45 -0
- package/dist/routes/health.test.d.ts +2 -0
- package/dist/routes/metrics.d.ts +20 -0
- package/dist/routes/metrics.js +38 -0
- package/dist/s3-client.d.ts +42 -0
- package/dist/s3-client.js +25 -0
- package/dist/telemetry/index.d.ts +3 -0
- package/dist/telemetry/index.js +4 -0
- package/dist/telemetry/init.d.ts +20 -0
- package/dist/telemetry/init.js +42 -0
- package/dist/telemetry/init.test.d.ts +2 -0
- package/dist/telemetry/metrics.d.ts +8 -0
- package/dist/telemetry/metrics.js +16 -0
- package/dist/tool-check.d.ts +33 -0
- package/dist/tool-check.js +60 -0
- package/dist/tool-check.test.d.ts +2 -0
- package/dist/ts-loader-hook.d.ts +26 -0
- package/dist/ts-loader-hook.js +48 -0
- package/dist/zx.d.ts +8 -0
- package/dist/zx.js +78 -0
- package/package.json +69 -5
- package/sbom.spdx.json +8775 -0
- package/index.js +0 -3
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseColdStore — the DB-agnostic framework class.
|
|
3
|
+
*
|
|
4
|
+
* Multiple concrete subclasses extend this one (e.g., `PlatformColdStore`,
|
|
5
|
+
* `OrchestratorColdStore`). Each wires the adapter map with its
|
|
6
|
+
* package-specific adapters.
|
|
7
|
+
*
|
|
8
|
+
* Phase B: `runArchiveCycle()` now drives the per-adapter archive loop
|
|
9
|
+
* (design §4). `fetchRange` / `hasRange` / `countRange` are real — they
|
|
10
|
+
* resolve manifests via `ListObjectsV2` and GET overlapping chunks
|
|
11
|
+
* through an in-process LRU.
|
|
12
|
+
*
|
|
13
|
+
* ## Advisory-lock namespace
|
|
14
|
+
*
|
|
15
|
+
* All Postgres advisory locks acquired by the cold-store framework use
|
|
16
|
+
* keys derived from `hashtext('cold-store|...')`. The `cold-store|`
|
|
17
|
+
* prefix is reserved — do NOT reuse it in pipeline or engine code
|
|
18
|
+
* without coordinating to avoid collisions.
|
|
19
|
+
*
|
|
20
|
+
* ## S3 bucket versioning caveat
|
|
21
|
+
*
|
|
22
|
+
* When the cold-store bucket has versioning ON (recommended default),
|
|
23
|
+
* retried chunk PUTs produce new object versions at the same key.
|
|
24
|
+
* Reconciliation (the `reconcile` CLI) MUST use `ListObjectVersions`,
|
|
25
|
+
* not `ListObjects`, to detect orphaned objects correctly. The live
|
|
26
|
+
* read-through uses `ListObjectsV2` because it only cares about the
|
|
27
|
+
* current version of each manifest.
|
|
28
|
+
*/
|
|
29
|
+
import { type S3Client } from '@aws-sdk/client-s3';
|
|
30
|
+
import { type DbKind } from './key.js';
|
|
31
|
+
import type { ChunkLru } from './lru.js';
|
|
32
|
+
import type { ColdStoreConfig } from './config.js';
|
|
33
|
+
import type { TableAdapter } from './table-adapter.js';
|
|
34
|
+
import type { ArchiveCycleSummary, ChunkManifest } from './types.js';
|
|
35
|
+
export interface ColdStoreFetchRangeArgs<TRow> {
|
|
36
|
+
db: DbKind;
|
|
37
|
+
table: string;
|
|
38
|
+
tenantId: string;
|
|
39
|
+
fromTs: Date;
|
|
40
|
+
toTs: Date;
|
|
41
|
+
decode?: (line: string) => TRow;
|
|
42
|
+
}
|
|
43
|
+
export interface ColdStoreReplayChunkArgs {
|
|
44
|
+
db: DbKind;
|
|
45
|
+
table: string;
|
|
46
|
+
tenantId: string;
|
|
47
|
+
partitionDate: string;
|
|
48
|
+
chunkId: string;
|
|
49
|
+
}
|
|
50
|
+
export interface ColdStoreReplayRowArgs {
|
|
51
|
+
db: DbKind;
|
|
52
|
+
table: string;
|
|
53
|
+
tenantId: string;
|
|
54
|
+
rowId: string | number;
|
|
55
|
+
}
|
|
56
|
+
export interface ColdStoreReplayResult {
|
|
57
|
+
/** Number of rows newly inserted into PG. */
|
|
58
|
+
inserted: number;
|
|
59
|
+
/** Rows whose unique key was already in PG (idempotent re-replay). */
|
|
60
|
+
skipped: number;
|
|
61
|
+
/** The chunk that was replayed (`null` when replayRow finds no match). */
|
|
62
|
+
chunkId: string | null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Phase 2 — purge-sweep options.
|
|
66
|
+
*
|
|
67
|
+
* `tableFilter` restricts to a single table (used by
|
|
68
|
+
* `cold-store purge-now <table>`). `bucketFilter` further restricts to a
|
|
69
|
+
* single bucket subprefix. `limit` caps the number of chunks processed
|
|
70
|
+
* per call so a single sweep can't OOM the orchestrator on a tenant
|
|
71
|
+
* with millions of expired chunks. `dryRun=true` lists candidates
|
|
72
|
+
* without actually deleting anything (the CLI default — operators must
|
|
73
|
+
* pass `--apply`).
|
|
74
|
+
*/
|
|
75
|
+
export interface PurgeExpiredChunksOpts {
|
|
76
|
+
tableFilter?: string;
|
|
77
|
+
bucketFilter?: string;
|
|
78
|
+
limit?: number;
|
|
79
|
+
dryRun?: boolean;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Phase 2 — one row per (chunk attempt, outcome). Returned from
|
|
83
|
+
* `purgeExpiredChunks` so callers (scheduled job, CLI) can log / report
|
|
84
|
+
* what happened without re-querying the chunk index.
|
|
85
|
+
*/
|
|
86
|
+
export interface PurgeChunkResult {
|
|
87
|
+
table: string;
|
|
88
|
+
tenantId: string;
|
|
89
|
+
chunkId: string;
|
|
90
|
+
bucket: string;
|
|
91
|
+
gzipBytes: number;
|
|
92
|
+
rowCount: number;
|
|
93
|
+
outcome: 'purged' | 'dry_run' | 'skipped_locked' | 'failure';
|
|
94
|
+
/** Populated only when `outcome === 'failure'`. */
|
|
95
|
+
error?: string;
|
|
96
|
+
}
|
|
97
|
+
export interface PurgeExpiredChunksSummary {
|
|
98
|
+
results: PurgeChunkResult[];
|
|
99
|
+
/** Total bytes (gzipped) actually purged from S3. */
|
|
100
|
+
bytesPurged: number;
|
|
101
|
+
/** Total chunks actually purged (i.e., outcome === 'purged'). */
|
|
102
|
+
chunksPurged: number;
|
|
103
|
+
/** Wall-clock duration of the whole sweep. */
|
|
104
|
+
durationMs: number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Phase 2 — chunk-index row shape returned by
|
|
108
|
+
* `BaseColdStore.listPurgeableChunks`. Mirrors `cold_store_chunks` row
|
|
109
|
+
* with a normalized `maxColdDays` field.
|
|
110
|
+
*/
|
|
111
|
+
export interface PurgeableChunk {
|
|
112
|
+
table: string;
|
|
113
|
+
tenantId: string;
|
|
114
|
+
chunkId: string;
|
|
115
|
+
bucket: string;
|
|
116
|
+
archivedAt: Date;
|
|
117
|
+
gzipBytes: number;
|
|
118
|
+
rowCount: number;
|
|
119
|
+
maxColdDays: number;
|
|
120
|
+
objectKey: string;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Public cold-store API. Phase B implements all four core methods.
|
|
124
|
+
* Phase F adds `replayChunk` / `replayRow` for the rerun-from-archive
|
|
125
|
+
* flow.
|
|
126
|
+
*/
|
|
127
|
+
export interface ColdStore {
|
|
128
|
+
/** Stream archived rows that overlap [fromTs, toTs). */
|
|
129
|
+
fetchRange<TRow>(args: ColdStoreFetchRangeArgs<TRow>): AsyncIterable<TRow>;
|
|
130
|
+
hasRange(args: Omit<ColdStoreFetchRangeArgs<unknown>, 'decode'>): Promise<boolean>;
|
|
131
|
+
countRange(args: Omit<ColdStoreFetchRangeArgs<unknown>, 'decode'>): Promise<number>;
|
|
132
|
+
/**
|
|
133
|
+
* Run one archive cycle. By default iterates every registered adapter
|
|
134
|
+
* in registration order; pass `tableFilter` to restrict to a single
|
|
135
|
+
* adapter (used by `archive-now <table>` CLI commands so an operator
|
|
136
|
+
* can flush one table on demand without waiting for the next cron tick).
|
|
137
|
+
*/
|
|
138
|
+
runArchiveCycle(opts?: {
|
|
139
|
+
tableFilter?: string;
|
|
140
|
+
}): Promise<ArchiveCycleSummary>;
|
|
141
|
+
/**
|
|
142
|
+
* Phase F — promote every row in a chunk back into PG transactionally.
|
|
143
|
+
* Idempotent on re-run via the adapter's `ON CONFLICT DO NOTHING`.
|
|
144
|
+
* Throws if the adapter doesn't implement `replayInsert`, the chunk
|
|
145
|
+
* is missing, or the contentHash check fails.
|
|
146
|
+
*/
|
|
147
|
+
replayChunk(args: ColdStoreReplayChunkArgs): Promise<ColdStoreReplayResult>;
|
|
148
|
+
/**
|
|
149
|
+
* Phase F — locate the chunk containing `rowId` (via manifest
|
|
150
|
+
* `minRowId`/`maxRowId` bounds) and replay it. Returns
|
|
151
|
+
* `chunkId: null` when no manifest matches — caller handles as
|
|
152
|
+
* "row truly does not exist anywhere".
|
|
153
|
+
*/
|
|
154
|
+
replayRow(args: ColdStoreReplayRowArgs): Promise<ColdStoreReplayResult>;
|
|
155
|
+
/**
|
|
156
|
+
* Phase 2 — purge expired chunks from S3.
|
|
157
|
+
*
|
|
158
|
+
* Looks up `cold_store_chunks` rows where
|
|
159
|
+
* `now() > archived_at + max_cold_days * INTERVAL '1 day'` AND
|
|
160
|
+
* `max_cold_days != 'forever'`. For each:
|
|
161
|
+
* 1. Acquire a per-chunk advisory lock so concurrent sweeps can't
|
|
162
|
+
* double-delete.
|
|
163
|
+
* 2. Issue `DeleteObject` for both the data and manifest keys.
|
|
164
|
+
* 3. Call `adapter.purgeChunkRecord(...)` to transactionally delete
|
|
165
|
+
* the row from `cold_store_chunks`, decrement the rollup, and
|
|
166
|
+
* write a `purge_chunk` audit row.
|
|
167
|
+
*
|
|
168
|
+
* `dryRun=true` (CLI default) skips steps 2–3 and returns the
|
|
169
|
+
* candidate list without mutating anything. Production scheduled
|
|
170
|
+
* sweeps pass `dryRun=false`.
|
|
171
|
+
*/
|
|
172
|
+
purgeExpiredChunks(opts?: PurgeExpiredChunksOpts): Promise<PurgeExpiredChunksSummary>;
|
|
173
|
+
}
|
|
174
|
+
export interface BaseColdStoreDeps {
|
|
175
|
+
db: DbKind;
|
|
176
|
+
config: ColdStoreConfig;
|
|
177
|
+
instanceId: string;
|
|
178
|
+
/** Caller-supplied logger-like callable; omits pull on shared logger. */
|
|
179
|
+
log: (level: 'info' | 'warn' | 'error', msg: string, extra?: Record<string, unknown>) => void;
|
|
180
|
+
/** Caller-supplied LRU; shared across consumers if desired. */
|
|
181
|
+
chunkCache?: ChunkLru<string, Buffer>;
|
|
182
|
+
/**
|
|
183
|
+
* Pre-built S3 client. If omitted, the BaseColdStore constructs one
|
|
184
|
+
* from `config.storage` via `createS3Client`. Tests pass a mock.
|
|
185
|
+
*/
|
|
186
|
+
s3Client?: S3Client;
|
|
187
|
+
}
|
|
188
|
+
export declare abstract class BaseColdStore implements ColdStore {
|
|
189
|
+
protected readonly db: DbKind;
|
|
190
|
+
protected readonly config: ColdStoreConfig;
|
|
191
|
+
protected readonly instanceId: string;
|
|
192
|
+
protected readonly log: BaseColdStoreDeps['log'];
|
|
193
|
+
protected readonly chunkCache: ChunkLru<string, Buffer> | undefined;
|
|
194
|
+
protected readonly s3: S3Client;
|
|
195
|
+
/** Registered table adapters by `table` name. */
|
|
196
|
+
protected readonly adapters: Map<string, TableAdapter<unknown>>;
|
|
197
|
+
protected constructor(deps: BaseColdStoreDeps);
|
|
198
|
+
/**
|
|
199
|
+
* Register a concrete table adapter. Subclasses typically call this
|
|
200
|
+
* from their constructor.
|
|
201
|
+
*/
|
|
202
|
+
protected registerAdapter(adapter: TableAdapter<unknown>): void;
|
|
203
|
+
/** Test / CLI helper — adapter-map read access. */
|
|
204
|
+
getAdapter(table: string): TableAdapter<unknown> | undefined;
|
|
205
|
+
listAdapters(): ReadonlyArray<TableAdapter<unknown>>;
|
|
206
|
+
/**
|
|
207
|
+
* CLI-accessible S3 primitives. Exposed so admin CLI commands can run
|
|
208
|
+
* inspection / reconciliation operations without a direct dependency
|
|
209
|
+
* on `@aws-sdk/client-s3`. Not intended for hot-path use.
|
|
210
|
+
*/
|
|
211
|
+
get bucket(): string;
|
|
212
|
+
get storagePrefix(): string;
|
|
213
|
+
/** List all object keys under a prefix, paging internally. */
|
|
214
|
+
listObjectKeys(prefix: string): Promise<string[]>;
|
|
215
|
+
/** Fetch an object's raw body as a Buffer. */
|
|
216
|
+
getObjectBody(key: string): Promise<Buffer>;
|
|
217
|
+
/** Upload a manifest JSON object at `key`. */
|
|
218
|
+
putManifestObject(key: string, manifest: ChunkManifest): Promise<void>;
|
|
219
|
+
/**
|
|
220
|
+
* Delete a single object by key. CLI-accessible companion to
|
|
221
|
+
* `listObjectKeys` / `getObjectBody`, used by tenant-scoped sweep
|
|
222
|
+
* paths (e.g. the post-`deleteOrgCascade` S3 cleanup) that operate
|
|
223
|
+
* on chunks the index already forgot about. Does NOT touch the
|
|
224
|
+
* `cold_store_chunks` index — callers that need both must follow
|
|
225
|
+
* the `purgeExpiredChunks` pattern with `withPurgeLock` +
|
|
226
|
+
* `adapter.purgeChunkRecord`.
|
|
227
|
+
*/
|
|
228
|
+
deleteObject(key: string): Promise<void>;
|
|
229
|
+
runArchiveCycle(opts?: {
|
|
230
|
+
tableFilter?: string;
|
|
231
|
+
}): Promise<ArchiveCycleSummary>;
|
|
232
|
+
private processAdapter;
|
|
233
|
+
/**
|
|
234
|
+
* Archive one (tenant, partitionDate).
|
|
235
|
+
*
|
|
236
|
+
* - Adapters WITHOUT `coldTtlDays`: legacy single-chunk path. Writes one
|
|
237
|
+
* chunk per partition at the day-prefix root with a v1 manifest. The
|
|
238
|
+
* GC sweep treats v1 chunks as `'forever'`.
|
|
239
|
+
* - Adapters WITH `coldTtlDays`: Phase 2 per-bucket path. Buffers all
|
|
240
|
+
* eligible rows, groups by `coldDaysToBucket(coldTtlDays(row))`,
|
|
241
|
+
* then emits one chunk per non-empty bucket under the bucket
|
|
242
|
+
* subprefix with a v2 manifest carrying `bucket` + `maxColdDays`.
|
|
243
|
+
*
|
|
244
|
+
* In both paths the caller holds the advisory lock for the
|
|
245
|
+
* partition. Returns the total row count archived across all chunks
|
|
246
|
+
* so the outer loop can honor `maxRowsPerCycle`.
|
|
247
|
+
*/
|
|
248
|
+
private archivePartition;
|
|
249
|
+
/**
|
|
250
|
+
* Legacy single-chunk-per-partition flow. Adapters that do not
|
|
251
|
+
* implement `coldTtlDays` get a v1 manifest at the day-prefix root.
|
|
252
|
+
*/
|
|
253
|
+
private archivePartitionLegacy;
|
|
254
|
+
/**
|
|
255
|
+
* Phase 2 per-bucket flow. Buffers all eligible rows, groups by
|
|
256
|
+
* `coldDaysToBucket(coldTtlDays(row))`, then emits one chunk per
|
|
257
|
+
* non-empty bucket.
|
|
258
|
+
*/
|
|
259
|
+
private archivePartitionBucketed;
|
|
260
|
+
/**
|
|
261
|
+
* Common chunk-write flow: PUT data, verify, PUT manifest, transactional
|
|
262
|
+
* mark-archived-and-delete on the adapter, increment metrics.
|
|
263
|
+
*
|
|
264
|
+
* Shared by `archivePartitionLegacy` (v1 manifests) and
|
|
265
|
+
* `archivePartitionBucketed` (v2 manifests with bucket + maxColdDays).
|
|
266
|
+
*/
|
|
267
|
+
private writeChunk;
|
|
268
|
+
private putChunkData;
|
|
269
|
+
private verifyChunkData;
|
|
270
|
+
private putManifest;
|
|
271
|
+
fetchRange<TRow>(args: ColdStoreFetchRangeArgs<TRow>): AsyncIterable<TRow>;
|
|
272
|
+
hasRange(args: Omit<ColdStoreFetchRangeArgs<unknown>, 'decode'>): Promise<boolean>;
|
|
273
|
+
countRange(args: Omit<ColdStoreFetchRangeArgs<unknown>, 'decode'>): Promise<number>;
|
|
274
|
+
/**
|
|
275
|
+
* Resolve all manifest sidecars whose time-range overlaps
|
|
276
|
+
* [fromTs, toTs). Uses one LIST walk under the tenant prefix, then
|
|
277
|
+
* GETs each `.manifest.json`.
|
|
278
|
+
*/
|
|
279
|
+
private listRelevantManifests;
|
|
280
|
+
private getManifest;
|
|
281
|
+
private getChunkData;
|
|
282
|
+
/**
|
|
283
|
+
* Promote every row in a chunk back into PG. The adapter owns the
|
|
284
|
+
* actual INSERT + audit + chunk-counter decrement (transactional).
|
|
285
|
+
*
|
|
286
|
+
* Throws when:
|
|
287
|
+
* - the cold store is disabled
|
|
288
|
+
* - the adapter is unregistered or doesn't implement `replayInsert`
|
|
289
|
+
* - the manifest is missing
|
|
290
|
+
* - the data file's sha256 doesn't match the manifest's
|
|
291
|
+
* `contentHash` (refuse to replay a tampered chunk)
|
|
292
|
+
*
|
|
293
|
+
* Idempotent on re-run: the adapter's `ON CONFLICT DO NOTHING`
|
|
294
|
+
* collapses the second pass to `inserted=0, skipped=rowCount`.
|
|
295
|
+
*/
|
|
296
|
+
replayChunk(args: ColdStoreReplayChunkArgs): Promise<ColdStoreReplayResult>;
|
|
297
|
+
/**
|
|
298
|
+
* Locate the chunk whose `[minRowId, maxRowId]` window covers
|
|
299
|
+
* `rowId` and replay it. Scans manifests under the tenant prefix
|
|
300
|
+
* (LIST is one round-trip; manifests are tiny). When several manifests
|
|
301
|
+
* match (overlapping ranges from re-archives), the one with the
|
|
302
|
+
* smallest range is preferred — it's the most recently re-archived
|
|
303
|
+
* shard and therefore the one whose row contents are freshest.
|
|
304
|
+
*
|
|
305
|
+
* Returns `chunkId: null` when no manifest matches — the caller
|
|
306
|
+
* decides whether that means "row never existed" (rerun → 404) or
|
|
307
|
+
* "S3 outage" (rerun → 410 replayFailed). This method only fails
|
|
308
|
+
* loudly on infrastructure errors (missing chunk after manifest
|
|
309
|
+
* found, hash mismatch).
|
|
310
|
+
*/
|
|
311
|
+
replayRow(args: ColdStoreReplayRowArgs): Promise<ColdStoreReplayResult>;
|
|
312
|
+
private toDate;
|
|
313
|
+
/**
|
|
314
|
+
* Subclass hook — query the per-DB `cold_store_chunks` index for
|
|
315
|
+
* purge candidates. Concrete subclasses (PlatformColdStore /
|
|
316
|
+
* OrchestratorColdStore) implement this with one Kysely SQL query
|
|
317
|
+
* keyed off `archived_at + max_cold_days * INTERVAL '1 day' < now()`
|
|
318
|
+
* and `max_cold_days != 'forever'`.
|
|
319
|
+
*
|
|
320
|
+
* Returning an empty array makes `purgeExpiredChunks` a no-op.
|
|
321
|
+
* Default implementation returns `[]` so non-purge subclasses (or
|
|
322
|
+
* test fixtures) can opt out trivially.
|
|
323
|
+
*/
|
|
324
|
+
protected listPurgeableChunks(_opts: {
|
|
325
|
+
tableFilter?: string;
|
|
326
|
+
bucketFilter?: string;
|
|
327
|
+
limit: number;
|
|
328
|
+
}): Promise<PurgeableChunk[]>;
|
|
329
|
+
/**
|
|
330
|
+
* Acquire a per-chunk advisory lock so concurrent sweeps can't
|
|
331
|
+
* race on the same `DeleteObject` + `cold_store_chunks` row.
|
|
332
|
+
*
|
|
333
|
+
* Default implementation is a no-op (always succeeds). Subclasses
|
|
334
|
+
* with a Kysely instance override to use `pg_try_advisory_lock` on
|
|
335
|
+
* `hashtext('cold-store-purge|<db>|<table>|<chunkId>')` and release
|
|
336
|
+
* inside `fn`'s `finally`. Returns `null` when another worker holds
|
|
337
|
+
* the lock; returns the `fn` result on success.
|
|
338
|
+
*/
|
|
339
|
+
protected withPurgeLock<T>(_args: {
|
|
340
|
+
table: string;
|
|
341
|
+
chunkId: string;
|
|
342
|
+
}, fn: () => Promise<T>): Promise<T | null>;
|
|
343
|
+
purgeExpiredChunks(opts?: PurgeExpiredChunksOpts): Promise<PurgeExpiredChunksSummary>;
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=cold-store.d.ts.map
|