@workglow/storage 0.2.31 → 0.2.32
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/browser.js +977 -48
- package/dist/browser.js.map +23 -13
- package/dist/bun.js +1030 -53
- package/dist/bun.js.map +24 -14
- package/dist/common.d.ts +7 -0
- package/dist/common.d.ts.map +1 -1
- package/dist/migrations/IMigration.d.ts +57 -0
- package/dist/migrations/IMigration.d.ts.map +1 -0
- package/dist/migrations/MigrationRunner.d.ts +44 -0
- package/dist/migrations/MigrationRunner.d.ts.map +1 -0
- package/dist/migrations/TabularMigration.d.ts +85 -0
- package/dist/migrations/TabularMigration.d.ts.map +1 -0
- package/dist/migrations/TabularMigrationOrchestrator.d.ts +34 -0
- package/dist/migrations/TabularMigrationOrchestrator.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +11 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/runBackfill.d.ts +19 -0
- package/dist/migrations/runBackfill.d.ts.map +1 -0
- package/dist/node.js +1030 -53
- package/dist/node.js.map +24 -14
- package/dist/sql/Dialect.d.ts +26 -0
- package/dist/sql/Dialect.d.ts.map +1 -0
- package/dist/sql/PredicateBuilder.d.ts +30 -0
- package/dist/sql/PredicateBuilder.d.ts.map +1 -0
- package/dist/sql/PrefixDdl.d.ts +79 -0
- package/dist/sql/PrefixDdl.d.ts.map +1 -0
- package/dist/sql/index.d.ts +9 -0
- package/dist/sql/index.d.ts.map +1 -0
- package/dist/tabular/BaseSqlTabularStorage.d.ts +63 -2
- package/dist/tabular/BaseSqlTabularStorage.d.ts.map +1 -1
- package/dist/tabular/BaseTabularStorage.d.ts +111 -6
- package/dist/tabular/BaseTabularStorage.d.ts.map +1 -1
- package/dist/tabular/CachedTabularStorage.d.ts +38 -0
- package/dist/tabular/CachedTabularStorage.d.ts.map +1 -1
- package/dist/tabular/Cursor.d.ts +79 -0
- package/dist/tabular/Cursor.d.ts.map +1 -0
- package/dist/tabular/FsFolderTabularStorage.d.ts +5 -1
- package/dist/tabular/FsFolderTabularStorage.d.ts.map +1 -1
- package/dist/tabular/HuggingFaceTabularStorage.d.ts +26 -2
- package/dist/tabular/HuggingFaceTabularStorage.d.ts.map +1 -1
- package/dist/tabular/ITabularStorage.d.ts +203 -3
- package/dist/tabular/ITabularStorage.d.ts.map +1 -1
- package/dist/tabular/InMemoryTabularMigrationApplier.d.ts +39 -0
- package/dist/tabular/InMemoryTabularMigrationApplier.d.ts.map +1 -0
- package/dist/tabular/InMemoryTabularStorage.d.ts +6 -2
- package/dist/tabular/InMemoryTabularStorage.d.ts.map +1 -1
- package/dist/tabular/SharedInMemoryTabularStorage.d.ts +4 -1
- package/dist/tabular/SharedInMemoryTabularStorage.d.ts.map +1 -1
- package/dist/tabular/SqlTabularMigrationApplier.d.ts +53 -0
- package/dist/tabular/SqlTabularMigrationApplier.d.ts.map +1 -0
- package/dist/tabular/StorageError.d.ts.map +1 -1
- package/dist/tabular/TelemetryTabularStorage.d.ts +11 -1
- package/dist/tabular/TelemetryTabularStorage.d.ts.map +1 -1
- package/dist/tabular/sqlMigrationDdl.d.ts +11 -0
- package/dist/tabular/sqlMigrationDdl.d.ts.map +1 -0
- package/dist/vector/IVectorStorage.d.ts +61 -1
- package/dist/vector/IVectorStorage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/tabular/README.md +73 -0
- package/src/vector/README.md +79 -0
package/dist/browser.js
CHANGED
|
@@ -27,7 +27,7 @@ class StorageEmptyCriteriaError extends StorageValidationError {
|
|
|
27
27
|
class StorageInvalidLimitError extends StorageValidationError {
|
|
28
28
|
static type = "StorageInvalidLimitError";
|
|
29
29
|
constructor(limit) {
|
|
30
|
-
super(`Query limit must be
|
|
30
|
+
super(`Query limit must be a positive integer, got ${limit}`);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -60,8 +60,214 @@ class CoveringIndexMissingError extends StorageError {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// src/tabular/Cursor.ts
|
|
64
|
+
var CURSOR_VERSION = 1;
|
|
65
|
+
var MAX_CURSOR_LENGTH = 8 * 1024;
|
|
66
|
+
function encodeCursor(payload) {
|
|
67
|
+
const json = JSON.stringify(payload);
|
|
68
|
+
let base64;
|
|
69
|
+
if (typeof Buffer !== "undefined") {
|
|
70
|
+
base64 = Buffer.from(json, "utf8").toString("base64");
|
|
71
|
+
} else {
|
|
72
|
+
const bytes = new TextEncoder().encode(json);
|
|
73
|
+
let binary = "";
|
|
74
|
+
const CHUNK = 32768;
|
|
75
|
+
for (let i = 0;i < bytes.length; i += CHUNK) {
|
|
76
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
77
|
+
}
|
|
78
|
+
base64 = btoa(binary);
|
|
79
|
+
}
|
|
80
|
+
let trimEnd = base64.length;
|
|
81
|
+
while (trimEnd > 0 && base64.charCodeAt(trimEnd - 1) === 61) {
|
|
82
|
+
trimEnd--;
|
|
83
|
+
}
|
|
84
|
+
const urlSafe = base64.slice(0, trimEnd).replace(/\+/g, "-").replace(/\//g, "_");
|
|
85
|
+
if (urlSafe.length > MAX_CURSOR_LENGTH) {
|
|
86
|
+
throw new StorageValidationError(`Encoded cursor exceeds maximum length (${urlSafe.length} > ${MAX_CURSOR_LENGTH})`);
|
|
87
|
+
}
|
|
88
|
+
return urlSafe;
|
|
89
|
+
}
|
|
90
|
+
function decodeCursor(cursor) {
|
|
91
|
+
if (typeof cursor !== "string" || cursor.length === 0) {
|
|
92
|
+
throw new StorageValidationError("Cursor must be a non-empty string");
|
|
93
|
+
}
|
|
94
|
+
if (cursor.length > MAX_CURSOR_LENGTH) {
|
|
95
|
+
throw new StorageValidationError(`Cursor exceeds maximum length (${cursor.length} > ${MAX_CURSOR_LENGTH})`);
|
|
96
|
+
}
|
|
97
|
+
const padded = cursor.replace(/-/g, "+").replace(/_/g, "/");
|
|
98
|
+
const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
|
|
99
|
+
let json;
|
|
100
|
+
try {
|
|
101
|
+
if (typeof Buffer !== "undefined") {
|
|
102
|
+
json = Buffer.from(padded + padding, "base64").toString("utf8");
|
|
103
|
+
} else {
|
|
104
|
+
const binary = atob(padded + padding);
|
|
105
|
+
const bytes = new Uint8Array(binary.length);
|
|
106
|
+
for (let i = 0;i < binary.length; i++)
|
|
107
|
+
bytes[i] = binary.charCodeAt(i);
|
|
108
|
+
json = new TextDecoder().decode(bytes);
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
throw new StorageValidationError("Cursor is not valid base64url");
|
|
112
|
+
}
|
|
113
|
+
let parsed;
|
|
114
|
+
try {
|
|
115
|
+
parsed = JSON.parse(json);
|
|
116
|
+
} catch {
|
|
117
|
+
throw new StorageValidationError("Cursor payload is not valid JSON");
|
|
118
|
+
}
|
|
119
|
+
const p = parsed;
|
|
120
|
+
if (!p || typeof p !== "object" || p.v !== CURSOR_VERSION || !Array.isArray(p.c) || !Array.isArray(p.n) || !Array.isArray(p.d) || p.n.length !== p.c.length || p.n.length !== p.d.length || !p.n.every((name) => typeof name === "string") || !p.d.every((dir) => dir === "a" || dir === "d") || !p.c.every((v) => v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean")) {
|
|
121
|
+
throw new StorageValidationError(`Cursor format is unsupported (expected v${CURSOR_VERSION})`);
|
|
122
|
+
}
|
|
123
|
+
return p;
|
|
124
|
+
}
|
|
125
|
+
function assertCursorMatches(payload, effectiveOrder) {
|
|
126
|
+
if (payload.n.length !== effectiveOrder.length) {
|
|
127
|
+
throw new StorageValidationError(`Cursor has ${payload.n.length} component(s); request expects ${effectiveOrder.length}`);
|
|
128
|
+
}
|
|
129
|
+
for (let i = 0;i < effectiveOrder.length; i++) {
|
|
130
|
+
if (payload.n[i] !== effectiveOrder[i].column) {
|
|
131
|
+
throw new StorageValidationError(`Cursor column ${i} is "${payload.n[i]}"; request expects "${effectiveOrder[i].column}"`);
|
|
132
|
+
}
|
|
133
|
+
const expected = effectiveOrder[i].direction === "ASC" ? "a" : "d";
|
|
134
|
+
if (payload.d[i] !== expected) {
|
|
135
|
+
throw new StorageValidationError(`Cursor column "${effectiveOrder[i].column}" was minted for ${payload.d[i] === "a" ? "ASC" : "DESC"}; request expects ${effectiveOrder[i].direction}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// src/migrations/MigrationRunner.ts
|
|
140
|
+
var MIGRATIONS_TABLE = "_storage_migrations";
|
|
141
|
+
function sortMigrations(migrations) {
|
|
142
|
+
return [...migrations].sort((a, b) => {
|
|
143
|
+
if (a.component !== b.component)
|
|
144
|
+
return a.component < b.component ? -1 : 1;
|
|
145
|
+
return a.version - b.version;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// src/migrations/TabularMigrationOrchestrator.ts
|
|
149
|
+
async function runTabularMigrations(applier, defaultComponent, migrations, options = {}) {
|
|
150
|
+
if (migrations.length === 0)
|
|
151
|
+
return;
|
|
152
|
+
await applier.ensureBookkeeping();
|
|
153
|
+
const byComponent = new Map;
|
|
154
|
+
for (const m of migrations) {
|
|
155
|
+
const c = m.component ?? defaultComponent;
|
|
156
|
+
let bucket = byComponent.get(c);
|
|
157
|
+
if (!bucket) {
|
|
158
|
+
bucket = [];
|
|
159
|
+
byComponent.set(c, bucket);
|
|
160
|
+
}
|
|
161
|
+
bucket.push(m);
|
|
162
|
+
}
|
|
163
|
+
for (const [component, group] of byComponent) {
|
|
164
|
+
const sorted = [...group].sort((a, b) => a.version - b.version);
|
|
165
|
+
const applied = await applier.appliedVersions(component);
|
|
166
|
+
const fresh = options.freshTable ?? !await applier.tableExists();
|
|
167
|
+
if (applied.size === 0 && fresh) {
|
|
168
|
+
await applier.markAllApplied(component, sorted.map((m) => ({ version: m.version, description: m.description })));
|
|
169
|
+
for (const m of sorted) {
|
|
170
|
+
options.onProgress?.({
|
|
171
|
+
component,
|
|
172
|
+
version: m.version,
|
|
173
|
+
phase: "completed",
|
|
174
|
+
description: m.description,
|
|
175
|
+
fraction: 1
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
for (const m of sorted) {
|
|
181
|
+
if (applied.has(m.version))
|
|
182
|
+
continue;
|
|
183
|
+
options.onProgress?.({
|
|
184
|
+
component,
|
|
185
|
+
version: m.version,
|
|
186
|
+
phase: "starting",
|
|
187
|
+
description: m.description
|
|
188
|
+
});
|
|
189
|
+
try {
|
|
190
|
+
await applier.applyMigration(component, m.version, m.description, m.ops, (fraction) => {
|
|
191
|
+
options.onProgress?.({
|
|
192
|
+
component,
|
|
193
|
+
version: m.version,
|
|
194
|
+
phase: "running",
|
|
195
|
+
description: m.description,
|
|
196
|
+
fraction
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
options.onProgress?.({
|
|
200
|
+
component,
|
|
201
|
+
version: m.version,
|
|
202
|
+
phase: "completed",
|
|
203
|
+
description: m.description,
|
|
204
|
+
fraction: 1
|
|
205
|
+
});
|
|
206
|
+
} catch (err) {
|
|
207
|
+
options.onProgress?.({
|
|
208
|
+
component,
|
|
209
|
+
version: m.version,
|
|
210
|
+
phase: "failed",
|
|
211
|
+
description: m.description,
|
|
212
|
+
error: err
|
|
213
|
+
});
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// src/migrations/runBackfill.ts
|
|
220
|
+
async function runBackfill(storage, batchSize, transform) {
|
|
221
|
+
let cursor;
|
|
222
|
+
while (true) {
|
|
223
|
+
const page = await storage.getPage({ limit: batchSize, cursor });
|
|
224
|
+
for (const row of page.items) {
|
|
225
|
+
const out = await transform(row);
|
|
226
|
+
if (out === row)
|
|
227
|
+
continue;
|
|
228
|
+
if (out === undefined) {
|
|
229
|
+
await storage.delete(row);
|
|
230
|
+
} else {
|
|
231
|
+
await storage.put(out);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (!page.nextCursor)
|
|
235
|
+
break;
|
|
236
|
+
cursor = page.nextCursor;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
63
239
|
// src/tabular/BaseTabularStorage.ts
|
|
64
240
|
var TABULAR_REPOSITORY = createServiceToken("storage.tabularRepository");
|
|
241
|
+
function toCursorValue(value) {
|
|
242
|
+
if (value === null || value === undefined)
|
|
243
|
+
return null;
|
|
244
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
if (value instanceof Date)
|
|
248
|
+
return value.toISOString();
|
|
249
|
+
if (typeof value === "bigint") {
|
|
250
|
+
throw new StorageValidationError("bigint values are not supported as cursor keys — string-encoded bigints don't sort numerically. Use a number column or override `getPage` for this storage.");
|
|
251
|
+
}
|
|
252
|
+
throw new StorageValidationError(`Cannot encode value of type ${typeof value} into a pagination cursor; use a key column with a primitive type.`);
|
|
253
|
+
}
|
|
254
|
+
function compareKeyValues(a, b) {
|
|
255
|
+
const an = toCursorValue(a);
|
|
256
|
+
const bn = toCursorValue(b);
|
|
257
|
+
if (an === null && bn === null)
|
|
258
|
+
return 0;
|
|
259
|
+
if (an === null)
|
|
260
|
+
return -1;
|
|
261
|
+
if (bn === null)
|
|
262
|
+
return 1;
|
|
263
|
+
const av = typeof an === "boolean" ? Number(an) : an;
|
|
264
|
+
const bv = typeof bn === "boolean" ? Number(bn) : bn;
|
|
265
|
+
if (av < bv)
|
|
266
|
+
return -1;
|
|
267
|
+
if (av > bv)
|
|
268
|
+
return 1;
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
65
271
|
|
|
66
272
|
class BaseTabularStorage {
|
|
67
273
|
schema;
|
|
@@ -70,12 +276,18 @@ class BaseTabularStorage {
|
|
|
70
276
|
indexes;
|
|
71
277
|
primaryKeySchema;
|
|
72
278
|
valueSchema;
|
|
279
|
+
tabularMigrations;
|
|
280
|
+
migrationComponent = "tabular:unnamed";
|
|
73
281
|
autoGeneratedKeyName = null;
|
|
74
282
|
autoGeneratedKeyStrategy = null;
|
|
75
283
|
clientProvidedKeys;
|
|
76
|
-
constructor(schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing") {
|
|
284
|
+
constructor(schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing", tabularMigrations, migrationName) {
|
|
77
285
|
this.schema = schema;
|
|
78
286
|
this.primaryKeyNames = primaryKeyNames;
|
|
287
|
+
this.tabularMigrations = tabularMigrations;
|
|
288
|
+
if (migrationName) {
|
|
289
|
+
this.migrationComponent = `tabular:${migrationName}`;
|
|
290
|
+
}
|
|
79
291
|
this.clientProvidedKeys = clientProvidedKeys;
|
|
80
292
|
const primaryKeyProps = {};
|
|
81
293
|
const valueProps = {};
|
|
@@ -196,37 +408,160 @@ class BaseTabularStorage {
|
|
|
196
408
|
if (pageSize <= 0) {
|
|
197
409
|
throw new RangeError(`pageSize must be greater than 0, got ${pageSize}`);
|
|
198
410
|
}
|
|
199
|
-
let
|
|
411
|
+
let cursor;
|
|
200
412
|
while (true) {
|
|
201
|
-
const page = await this.
|
|
202
|
-
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
for (const entity of page) {
|
|
413
|
+
const page = await this.getPage({ limit: pageSize, cursor });
|
|
414
|
+
for (const entity of page.items) {
|
|
206
415
|
yield entity;
|
|
207
416
|
}
|
|
208
|
-
if (page.length
|
|
417
|
+
if (!page.nextCursor || page.items.length === 0)
|
|
209
418
|
break;
|
|
210
|
-
|
|
211
|
-
offset += pageSize;
|
|
419
|
+
cursor = page.nextCursor;
|
|
212
420
|
}
|
|
213
421
|
}
|
|
214
422
|
async* pages(pageSize = 100) {
|
|
215
423
|
if (pageSize <= 0) {
|
|
216
424
|
throw new RangeError(`pageSize must be greater than 0, got ${pageSize}`);
|
|
217
425
|
}
|
|
218
|
-
let
|
|
426
|
+
let cursor;
|
|
219
427
|
while (true) {
|
|
220
|
-
const page = await this.
|
|
221
|
-
if (
|
|
222
|
-
|
|
428
|
+
const page = await this.getPage({ limit: pageSize, cursor });
|
|
429
|
+
if (page.items.length > 0) {
|
|
430
|
+
yield page.items.slice();
|
|
223
431
|
}
|
|
224
|
-
|
|
225
|
-
if (page.length < pageSize) {
|
|
432
|
+
if (!page.nextCursor || page.items.length === 0)
|
|
226
433
|
break;
|
|
434
|
+
cursor = page.nextCursor;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async getPage(request = {}) {
|
|
438
|
+
return this.runPage(undefined, request);
|
|
439
|
+
}
|
|
440
|
+
async queryPage(criteria, request = {}) {
|
|
441
|
+
return this.runPage(criteria, request);
|
|
442
|
+
}
|
|
443
|
+
async runPage(criteria, request) {
|
|
444
|
+
this.validatePageRequest(request);
|
|
445
|
+
const limit = request.limit ?? 100;
|
|
446
|
+
const pkColumns = this.primaryKeyColumns();
|
|
447
|
+
const orderBy = request.orderBy;
|
|
448
|
+
const effectiveOrderBy = this.buildEffectiveOrderBy(orderBy, pkColumns);
|
|
449
|
+
const effectiveOrderForCursor = effectiveOrderBy.map((o) => ({
|
|
450
|
+
column: String(o.column),
|
|
451
|
+
direction: o.direction
|
|
452
|
+
}));
|
|
453
|
+
let cursorPayload;
|
|
454
|
+
if (request.cursor !== undefined) {
|
|
455
|
+
cursorPayload = decodeCursor(request.cursor);
|
|
456
|
+
assertCursorMatches(cursorPayload, effectiveOrderForCursor);
|
|
457
|
+
}
|
|
458
|
+
const pkCol = pkColumns[0];
|
|
459
|
+
const userCriteria = criteria ?? {};
|
|
460
|
+
const userTouchesPk = pkColumns.length === 1 && Object.prototype.hasOwnProperty.call(userCriteria, pkCol);
|
|
461
|
+
const canPushKeyset = pkColumns.length === 1 && effectiveOrderBy.length === 1 && effectiveOrderBy[0].column === pkCol && !userTouchesPk;
|
|
462
|
+
let queryCriteria = userCriteria;
|
|
463
|
+
const useFallback = !canPushKeyset;
|
|
464
|
+
if (cursorPayload && canPushKeyset) {
|
|
465
|
+
const direction = effectiveOrderBy[0].direction;
|
|
466
|
+
const op = direction === "ASC" ? ">" : "<";
|
|
467
|
+
const lastPk = cursorPayload.c[0];
|
|
468
|
+
const keysetCondition = {
|
|
469
|
+
value: lastPk,
|
|
470
|
+
operator: op
|
|
471
|
+
};
|
|
472
|
+
queryCriteria = {
|
|
473
|
+
...userCriteria,
|
|
474
|
+
[pkCol]: keysetCondition
|
|
475
|
+
};
|
|
476
|
+
} else if (cursorPayload && useFallback) {
|
|
477
|
+
const leading = effectiveOrderBy[0];
|
|
478
|
+
const leadingCol = leading.column;
|
|
479
|
+
const leadingCursor = cursorPayload.c[0];
|
|
480
|
+
const userTouchesLeading = Object.prototype.hasOwnProperty.call(userCriteria, leadingCol);
|
|
481
|
+
if (leading.direction === "ASC" && leadingCursor !== null && !userTouchesLeading) {
|
|
482
|
+
const leadingCondition = {
|
|
483
|
+
value: leadingCursor,
|
|
484
|
+
operator: ">="
|
|
485
|
+
};
|
|
486
|
+
queryCriteria = {
|
|
487
|
+
...userCriteria,
|
|
488
|
+
[leadingCol]: leadingCondition
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const fetchLimit = useFallback ? undefined : limit;
|
|
493
|
+
const queryOptions = {
|
|
494
|
+
orderBy: effectiveOrderBy,
|
|
495
|
+
...fetchLimit !== undefined ? { limit: fetchLimit } : {}
|
|
496
|
+
};
|
|
497
|
+
let rows;
|
|
498
|
+
let forcedFallback = false;
|
|
499
|
+
if (Object.keys(queryCriteria).length === 0) {
|
|
500
|
+
rows = await this.getAll(queryOptions);
|
|
501
|
+
} else {
|
|
502
|
+
try {
|
|
503
|
+
rows = await this.query(queryCriteria, queryOptions);
|
|
504
|
+
} catch (err) {
|
|
505
|
+
const userHadNoCriteria = !criteria || Object.keys(criteria).length === 0;
|
|
506
|
+
if (err instanceof StorageUnsupportedError && userHadNoCriteria) {
|
|
507
|
+
rows = await this.getAll({ orderBy: effectiveOrderBy });
|
|
508
|
+
forcedFallback = true;
|
|
509
|
+
} else {
|
|
510
|
+
throw err;
|
|
511
|
+
}
|
|
227
512
|
}
|
|
228
|
-
offset += pageSize;
|
|
229
513
|
}
|
|
514
|
+
let items = rows ?? [];
|
|
515
|
+
if (useFallback || forcedFallback) {
|
|
516
|
+
items = this.sortInMemory(items.slice(), effectiveOrderBy);
|
|
517
|
+
if (cursorPayload) {
|
|
518
|
+
items = this.applyKeysetFilter(items, cursorPayload, effectiveOrderBy, pkColumns);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (items.length > limit) {
|
|
522
|
+
items = items.slice(0, limit);
|
|
523
|
+
}
|
|
524
|
+
const nextCursor = items.length === limit ? this.buildCursor(items[items.length - 1], effectiveOrderBy) : undefined;
|
|
525
|
+
return { items, nextCursor };
|
|
526
|
+
}
|
|
527
|
+
buildEffectiveOrderBy(orderBy, pkColumns) {
|
|
528
|
+
const result = orderBy ? orderBy.slice() : [];
|
|
529
|
+
const seen = new Set(result.map((o) => o.column));
|
|
530
|
+
for (const pk of pkColumns) {
|
|
531
|
+
if (!seen.has(pk)) {
|
|
532
|
+
result.push({ column: pk, direction: "ASC" });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
sortInMemory(rows, orderBy) {
|
|
538
|
+
rows.sort((a, b) => {
|
|
539
|
+
for (const { column, direction } of orderBy) {
|
|
540
|
+
const cmp = compareKeyValues(a[column], b[column]);
|
|
541
|
+
if (cmp !== 0)
|
|
542
|
+
return direction === "ASC" ? cmp : -cmp;
|
|
543
|
+
}
|
|
544
|
+
return 0;
|
|
545
|
+
});
|
|
546
|
+
return rows;
|
|
547
|
+
}
|
|
548
|
+
applyKeysetFilter(rows, cursor, effectiveOrderBy, _pkColumns) {
|
|
549
|
+
return rows.filter((row) => {
|
|
550
|
+
for (let i = 0;i < effectiveOrderBy.length; i++) {
|
|
551
|
+
const { column, direction } = effectiveOrderBy[i];
|
|
552
|
+
const cmp = compareKeyValues(row[column], cursor.c[i]);
|
|
553
|
+
if (cmp === 0)
|
|
554
|
+
continue;
|
|
555
|
+
return direction === "ASC" ? cmp > 0 : cmp < 0;
|
|
556
|
+
}
|
|
557
|
+
return false;
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
buildCursor(row, effectiveOrderBy) {
|
|
561
|
+
const n = effectiveOrderBy.map((spec) => String(spec.column));
|
|
562
|
+
const d = effectiveOrderBy.map((spec) => spec.direction === "ASC" ? "a" : "d");
|
|
563
|
+
const c = effectiveOrderBy.map((spec) => toCursorValue(row[spec.column]));
|
|
564
|
+
return encodeCursor({ v: 1, n, d, c });
|
|
230
565
|
}
|
|
231
566
|
subscribeToChanges(_callback, _options) {
|
|
232
567
|
throw new Error(`subscribeToChanges is not implemented for ${this.constructor.name}. ` + `All concrete repository implementations should override this method.`);
|
|
@@ -254,17 +589,7 @@ class BaseTabularStorage {
|
|
|
254
589
|
}
|
|
255
590
|
}
|
|
256
591
|
}
|
|
257
|
-
|
|
258
|
-
const validDirections = ["ASC", "DESC"];
|
|
259
|
-
for (const { column, direction } of options.orderBy) {
|
|
260
|
-
if (!(column in this.schema.properties)) {
|
|
261
|
-
throw new StorageInvalidColumnError(String(column));
|
|
262
|
-
}
|
|
263
|
-
if (!validDirections.includes(direction)) {
|
|
264
|
-
throw new StorageValidationError(`Invalid sort direction "${direction}". Must be "ASC" or "DESC"`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
592
|
+
this.validateOrderBy(options?.orderBy);
|
|
268
593
|
}
|
|
269
594
|
validateGetAllOptions(options) {
|
|
270
595
|
if (!options)
|
|
@@ -275,18 +600,32 @@ class BaseTabularStorage {
|
|
|
275
600
|
if (options.offset !== undefined && options.offset < 0) {
|
|
276
601
|
throw new StorageValidationError(`Query offset must be non-negative, got ${options.offset}`);
|
|
277
602
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
603
|
+
this.validateOrderBy(options.orderBy);
|
|
604
|
+
}
|
|
605
|
+
validateOrderBy(orderBy) {
|
|
606
|
+
if (!orderBy)
|
|
607
|
+
return;
|
|
608
|
+
const validDirections = ["ASC", "DESC"];
|
|
609
|
+
for (const { column, direction } of orderBy) {
|
|
610
|
+
if (typeof column !== "string") {
|
|
611
|
+
throw new StorageInvalidColumnError(String(column));
|
|
612
|
+
}
|
|
613
|
+
if (!(column in this.schema.properties)) {
|
|
614
|
+
throw new StorageInvalidColumnError(String(column));
|
|
615
|
+
}
|
|
616
|
+
if (!validDirections.includes(direction)) {
|
|
617
|
+
throw new StorageValidationError(`Invalid sort direction "${direction}". Must be "ASC" or "DESC"`);
|
|
287
618
|
}
|
|
288
619
|
}
|
|
289
620
|
}
|
|
621
|
+
validatePageRequest(request) {
|
|
622
|
+
if (request.limit !== undefined) {
|
|
623
|
+
if (!Number.isInteger(request.limit) || request.limit <= 0) {
|
|
624
|
+
throw new StorageInvalidLimitError(request.limit);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
this.validateOrderBy(request.orderBy);
|
|
628
|
+
}
|
|
290
629
|
validateSelect(options) {
|
|
291
630
|
if (!options.select || options.select.length === 0) {
|
|
292
631
|
throw new StorageValidationError("queryIndex requires a non-empty select array");
|
|
@@ -405,6 +744,21 @@ class BaseTabularStorage {
|
|
|
405
744
|
generateKeyValue(columnName, strategy) {
|
|
406
745
|
throw new Error(`generateKeyValue not implemented for ${this.constructor.name}. ` + `Column: ${columnName}, Strategy: ${strategy}`);
|
|
407
746
|
}
|
|
747
|
+
async withTransaction(fn) {
|
|
748
|
+
return await fn(this);
|
|
749
|
+
}
|
|
750
|
+
getMigrationApplier() {
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
async applyTabularMigrations(options) {
|
|
754
|
+
if (!this.tabularMigrations || this.tabularMigrations.length === 0)
|
|
755
|
+
return;
|
|
756
|
+
const applier = this.getMigrationApplier();
|
|
757
|
+
if (!applier) {
|
|
758
|
+
throw new Error(`${this.constructor.name} declared migrations but has no migration applier wired up.`);
|
|
759
|
+
}
|
|
760
|
+
await runTabularMigrations(applier, this.migrationComponent, this.tabularMigrations, options);
|
|
761
|
+
}
|
|
408
762
|
async setupDatabase() {}
|
|
409
763
|
destroy() {}
|
|
410
764
|
async[Symbol.asyncDispose]() {
|
|
@@ -421,8 +775,8 @@ class BaseSqlTabularStorage extends BaseTabularStorage {
|
|
|
421
775
|
_valColsCache = new Map;
|
|
422
776
|
_pkColListCache = new Map;
|
|
423
777
|
_valColListCache = new Map;
|
|
424
|
-
constructor(table = "tabular_store", schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing") {
|
|
425
|
-
super(schema, primaryKeyNames, indexes, clientProvidedKeys);
|
|
778
|
+
constructor(table = "tabular_store", schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing", tabularMigrations, migrationName) {
|
|
779
|
+
super(schema, primaryKeyNames, indexes, clientProvidedKeys, tabularMigrations, migrationName ?? table);
|
|
426
780
|
this.table = table;
|
|
427
781
|
this.validateTableAndSchema();
|
|
428
782
|
}
|
|
@@ -612,6 +966,90 @@ class BaseSqlTabularStorage extends BaseTabularStorage {
|
|
|
612
966
|
throw new Error(`Duplicate keys found in schemas: ${duplicates.join(", ")}`);
|
|
613
967
|
}
|
|
614
968
|
}
|
|
969
|
+
buildKeysetWhere(effectiveOrderBy, cursorValues, quote, placeholder, startIndex) {
|
|
970
|
+
if (effectiveOrderBy.length !== cursorValues.length) {
|
|
971
|
+
throw new Error(`Keyset arity mismatch: ${effectiveOrderBy.length} order columns vs ${cursorValues.length} cursor values`);
|
|
972
|
+
}
|
|
973
|
+
const clauses = [];
|
|
974
|
+
const params = [];
|
|
975
|
+
let idx = startIndex;
|
|
976
|
+
for (let i = 0;i < effectiveOrderBy.length; i++) {
|
|
977
|
+
const parts = [];
|
|
978
|
+
for (let j = 0;j < i; j++) {
|
|
979
|
+
const colExpr2 = `${quote}${String(effectiveOrderBy[j].column)}${quote}`;
|
|
980
|
+
if (cursorValues[j] === null) {
|
|
981
|
+
parts.push(`${colExpr2} IS NULL`);
|
|
982
|
+
} else {
|
|
983
|
+
parts.push(`${colExpr2} = ${placeholder(idx)}`);
|
|
984
|
+
params.push(cursorValues[j]);
|
|
985
|
+
idx++;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
const colExpr = `${quote}${String(effectiveOrderBy[i].column)}${quote}`;
|
|
989
|
+
const v = cursorValues[i];
|
|
990
|
+
const dir = effectiveOrderBy[i].direction;
|
|
991
|
+
if (v === null) {
|
|
992
|
+
parts.push(dir === "ASC" ? `${colExpr} IS NOT NULL` : `1 = 0`);
|
|
993
|
+
} else {
|
|
994
|
+
if (dir === "ASC") {
|
|
995
|
+
parts.push(`${colExpr} > ${placeholder(idx)}`);
|
|
996
|
+
} else {
|
|
997
|
+
parts.push(`(${colExpr} < ${placeholder(idx)} OR ${colExpr} IS NULL)`);
|
|
998
|
+
}
|
|
999
|
+
params.push(v);
|
|
1000
|
+
idx++;
|
|
1001
|
+
}
|
|
1002
|
+
clauses.push(`(${parts.join(" AND ")})`);
|
|
1003
|
+
}
|
|
1004
|
+
return { whereClause: clauses.join(" OR "), params, nextIndex: idx };
|
|
1005
|
+
}
|
|
1006
|
+
async runSqlPage(criteria, request, dialect) {
|
|
1007
|
+
this.validatePageRequest(request);
|
|
1008
|
+
const limit = request.limit ?? 100;
|
|
1009
|
+
const pkColumns = this.primaryKeyColumns();
|
|
1010
|
+
const orderBy = request.orderBy;
|
|
1011
|
+
const effectiveOrderBy = this.buildEffectiveOrderBy(orderBy, pkColumns);
|
|
1012
|
+
const effectiveOrderForCursor = effectiveOrderBy.map((o) => ({
|
|
1013
|
+
column: String(o.column),
|
|
1014
|
+
direction: o.direction
|
|
1015
|
+
}));
|
|
1016
|
+
let cursorPayload;
|
|
1017
|
+
if (request.cursor !== undefined) {
|
|
1018
|
+
cursorPayload = decodeCursor(request.cursor);
|
|
1019
|
+
assertCursorMatches(cursorPayload, effectiveOrderForCursor);
|
|
1020
|
+
}
|
|
1021
|
+
const params = [];
|
|
1022
|
+
let paramIdx = 1;
|
|
1023
|
+
const whereClauses = [];
|
|
1024
|
+
if (criteria && Object.keys(criteria).length > 0) {
|
|
1025
|
+
const built = dialect.buildSearchWhere(criteria, paramIdx);
|
|
1026
|
+
whereClauses.push(built.whereClause);
|
|
1027
|
+
params.push(...built.params);
|
|
1028
|
+
paramIdx = built.nextIndex;
|
|
1029
|
+
}
|
|
1030
|
+
if (cursorPayload) {
|
|
1031
|
+
const cursorValues = cursorPayload.c.map((v, i) => this.jsToSqlValue(String(effectiveOrderBy[i].column), v));
|
|
1032
|
+
const built = this.buildKeysetWhere(effectiveOrderBy, cursorValues, dialect.quote, dialect.placeholder, paramIdx);
|
|
1033
|
+
whereClauses.push(`(${built.whereClause})`);
|
|
1034
|
+
params.push(...built.params);
|
|
1035
|
+
paramIdx = built.nextIndex;
|
|
1036
|
+
}
|
|
1037
|
+
const q = dialect.quote;
|
|
1038
|
+
const orderByClause = effectiveOrderBy.map((o) => {
|
|
1039
|
+
const nulls = o.direction === "ASC" ? "NULLS FIRST" : "NULLS LAST";
|
|
1040
|
+
return `${q}${String(o.column)}${q} ${o.direction} ${nulls}`;
|
|
1041
|
+
}).join(", ");
|
|
1042
|
+
let sql = `SELECT * FROM ${q}${this.table}${q}`;
|
|
1043
|
+
if (whereClauses.length > 0) {
|
|
1044
|
+
sql += ` WHERE ${whereClauses.join(" AND ")}`;
|
|
1045
|
+
}
|
|
1046
|
+
sql += ` ORDER BY ${orderByClause}`;
|
|
1047
|
+
sql += ` LIMIT ${dialect.placeholder(paramIdx)}`;
|
|
1048
|
+
params.push(limit);
|
|
1049
|
+
const items = await dialect.executeSelect(sql, params);
|
|
1050
|
+
const nextCursor = items.length === limit ? this.buildCursor(items[items.length - 1], effectiveOrderBy) : undefined;
|
|
1051
|
+
return { items, nextCursor };
|
|
1052
|
+
}
|
|
615
1053
|
}
|
|
616
1054
|
// src/tabular/CachedTabularStorage.ts
|
|
617
1055
|
import { createServiceToken as createServiceToken3, getLogger } from "@workglow/util";
|
|
@@ -619,6 +1057,55 @@ import { createServiceToken as createServiceToken3, getLogger } from "@workglow/
|
|
|
619
1057
|
// src/tabular/InMemoryTabularStorage.ts
|
|
620
1058
|
import { createServiceToken as createServiceToken2, makeFingerprint as makeFingerprint2, uuid4 } from "@workglow/util";
|
|
621
1059
|
|
|
1060
|
+
// src/tabular/InMemoryTabularMigrationApplier.ts
|
|
1061
|
+
class InMemoryTabularMigrationApplier {
|
|
1062
|
+
storage;
|
|
1063
|
+
storeName;
|
|
1064
|
+
applied = new Map;
|
|
1065
|
+
constructor(storage, storeName) {
|
|
1066
|
+
this.storage = storage;
|
|
1067
|
+
this.storeName = storeName;
|
|
1068
|
+
}
|
|
1069
|
+
async ensureBookkeeping() {}
|
|
1070
|
+
async appliedVersions(component) {
|
|
1071
|
+
return new Set(this.applied.get(component) ?? []);
|
|
1072
|
+
}
|
|
1073
|
+
async tableExists() {
|
|
1074
|
+
return await this.storage.size() > 0;
|
|
1075
|
+
}
|
|
1076
|
+
async markAllApplied(component, versions) {
|
|
1077
|
+
if (versions.length === 0)
|
|
1078
|
+
return;
|
|
1079
|
+
let set = this.applied.get(component);
|
|
1080
|
+
if (!set) {
|
|
1081
|
+
set = new Set;
|
|
1082
|
+
this.applied.set(component, set);
|
|
1083
|
+
}
|
|
1084
|
+
for (const v of versions)
|
|
1085
|
+
set.add(v.version);
|
|
1086
|
+
await this.persist();
|
|
1087
|
+
}
|
|
1088
|
+
async applyMigration(component, version, _description, ops, onProgress) {
|
|
1089
|
+
let processed = 0;
|
|
1090
|
+
const total = Math.max(ops.length, 1);
|
|
1091
|
+
for (const op of ops) {
|
|
1092
|
+
if (op.kind === "backfill") {
|
|
1093
|
+
await runBackfill(this.storage, op.batchSize ?? 500, op.transform);
|
|
1094
|
+
}
|
|
1095
|
+
processed++;
|
|
1096
|
+
onProgress?.(processed / total);
|
|
1097
|
+
}
|
|
1098
|
+
let set = this.applied.get(component);
|
|
1099
|
+
if (!set) {
|
|
1100
|
+
set = new Set;
|
|
1101
|
+
this.applied.set(component, set);
|
|
1102
|
+
}
|
|
1103
|
+
set.add(version);
|
|
1104
|
+
await this.persist();
|
|
1105
|
+
}
|
|
1106
|
+
async persist() {}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
622
1109
|
// src/tabular/coveringIndexPicker.ts
|
|
623
1110
|
function pickCoveringIndex(input) {
|
|
624
1111
|
const { table, indexes, criteriaColumns, orderByColumns, selectColumns, primaryKeyColumns } = input;
|
|
@@ -679,10 +1166,17 @@ class InMemoryTabularStorage extends BaseTabularStorage {
|
|
|
679
1166
|
values = new Map;
|
|
680
1167
|
autoIncrementCounter = 0;
|
|
681
1168
|
_lastPutWasInsert = false;
|
|
682
|
-
constructor(schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing") {
|
|
683
|
-
super(schema, primaryKeyNames, indexes, clientProvidedKeys);
|
|
1169
|
+
constructor(schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing", tabularMigrations, migrationName = "inmemory") {
|
|
1170
|
+
super(schema, primaryKeyNames, indexes, clientProvidedKeys, tabularMigrations, migrationName);
|
|
1171
|
+
}
|
|
1172
|
+
async setupDatabase() {
|
|
1173
|
+
if (this.tabularMigrations && this.tabularMigrations.length > 0) {
|
|
1174
|
+
await this.applyTabularMigrations();
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
getMigrationApplier() {
|
|
1178
|
+
return new InMemoryTabularMigrationApplier(this, "inmemory");
|
|
684
1179
|
}
|
|
685
|
-
async setupDatabase() {}
|
|
686
1180
|
generateKeyValue(columnName, strategy) {
|
|
687
1181
|
if (strategy === "autoincrement") {
|
|
688
1182
|
return ++this.autoIncrementCounter;
|
|
@@ -1064,6 +1558,7 @@ class CachedTabularStorage extends BaseTabularStorage {
|
|
|
1064
1558
|
await this.cache.deleteAll();
|
|
1065
1559
|
}
|
|
1066
1560
|
async getAll(options) {
|
|
1561
|
+
this.validateGetAllOptions(options);
|
|
1067
1562
|
await this.initializeCache();
|
|
1068
1563
|
let results = await this.cache.getAll();
|
|
1069
1564
|
if (!results || results.length === 0) {
|
|
@@ -1098,6 +1593,14 @@ class CachedTabularStorage extends BaseTabularStorage {
|
|
|
1098
1593
|
await this.durable.deleteSearch(criteria);
|
|
1099
1594
|
await this.cache.deleteSearch(criteria);
|
|
1100
1595
|
}
|
|
1596
|
+
async withTransaction(fn) {
|
|
1597
|
+
await this.initializeCache();
|
|
1598
|
+
try {
|
|
1599
|
+
return await this.durable.withTransaction(fn);
|
|
1600
|
+
} finally {
|
|
1601
|
+
await this.invalidateCache();
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1101
1604
|
async invalidateCache() {
|
|
1102
1605
|
await this.cache.deleteAll();
|
|
1103
1606
|
this.cacheInitialized = false;
|
|
@@ -1121,6 +1624,14 @@ class CachedTabularStorage extends BaseTabularStorage {
|
|
|
1121
1624
|
callback(change);
|
|
1122
1625
|
}, options);
|
|
1123
1626
|
}
|
|
1627
|
+
async setupDatabase() {
|
|
1628
|
+
await this.durable.setupDatabase();
|
|
1629
|
+
await this.cache.setupDatabase();
|
|
1630
|
+
}
|
|
1631
|
+
getMigrationApplier() {
|
|
1632
|
+
const inner = this.durable;
|
|
1633
|
+
return inner.getMigrationApplier?.() ?? null;
|
|
1634
|
+
}
|
|
1124
1635
|
destroy() {
|
|
1125
1636
|
this.durable.destroy();
|
|
1126
1637
|
this.cache.destroy();
|
|
@@ -1129,6 +1640,21 @@ class CachedTabularStorage extends BaseTabularStorage {
|
|
|
1129
1640
|
// src/tabular/HuggingFaceTabularStorage.ts
|
|
1130
1641
|
import { createServiceToken as createServiceToken4 } from "@workglow/util";
|
|
1131
1642
|
var HF_TABULAR_REPOSITORY = createServiceToken4("storage.tabularRepository.huggingface");
|
|
1643
|
+
var HF_OFFSET_CURSOR_NAME = "hfOffset";
|
|
1644
|
+
function encodeOffsetCursor(offset) {
|
|
1645
|
+
return encodeCursor({ v: 1, n: [HF_OFFSET_CURSOR_NAME], d: ["a"], c: [offset] });
|
|
1646
|
+
}
|
|
1647
|
+
function decodeOffsetCursor(cursor) {
|
|
1648
|
+
const payload = decodeCursor(cursor);
|
|
1649
|
+
if (payload.n[0] !== HF_OFFSET_CURSOR_NAME) {
|
|
1650
|
+
throw new StorageValidationError("Cursor was not produced by HuggingFaceTabularStorage");
|
|
1651
|
+
}
|
|
1652
|
+
const value = payload.c[0];
|
|
1653
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
1654
|
+
throw new StorageValidationError("Invalid HuggingFace pagination cursor");
|
|
1655
|
+
}
|
|
1656
|
+
return value;
|
|
1657
|
+
}
|
|
1132
1658
|
|
|
1133
1659
|
class HuggingFaceTabularStorage extends BaseTabularStorage {
|
|
1134
1660
|
dataset;
|
|
@@ -1136,8 +1662,8 @@ class HuggingFaceTabularStorage extends BaseTabularStorage {
|
|
|
1136
1662
|
split;
|
|
1137
1663
|
token;
|
|
1138
1664
|
baseUrl;
|
|
1139
|
-
constructor(dataset, config, split, schema, primaryKeyNames, options) {
|
|
1140
|
-
super(schema, primaryKeyNames, options?.indexes ?? [], "never");
|
|
1665
|
+
constructor(dataset, config, split, schema, primaryKeyNames, options, tabularMigrations) {
|
|
1666
|
+
super(schema, primaryKeyNames, options?.indexes ?? [], "never", tabularMigrations, `hf:${dataset}/${config}/${split}`);
|
|
1141
1667
|
this.dataset = dataset;
|
|
1142
1668
|
this.config = config;
|
|
1143
1669
|
this.split = split;
|
|
@@ -1189,6 +1715,12 @@ class HuggingFaceTabularStorage extends BaseTabularStorage {
|
|
|
1189
1715
|
}
|
|
1190
1716
|
}
|
|
1191
1717
|
}
|
|
1718
|
+
if (this.tabularMigrations && this.tabularMigrations.length > 0) {
|
|
1719
|
+
await this.applyTabularMigrations();
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
getMigrationApplier() {
|
|
1723
|
+
return new InMemoryTabularMigrationApplier(this, `hf:${this.dataset}/${this.config}/${this.split}`);
|
|
1192
1724
|
}
|
|
1193
1725
|
async get(key) {
|
|
1194
1726
|
const keyObj = this.separateKeyValueFromCombined({ ...key }).key;
|
|
@@ -1272,6 +1804,34 @@ class HuggingFaceTabularStorage extends BaseTabularStorage {
|
|
|
1272
1804
|
}
|
|
1273
1805
|
return entities;
|
|
1274
1806
|
}
|
|
1807
|
+
async getPage(request = {}) {
|
|
1808
|
+
this.validatePageRequest(request);
|
|
1809
|
+
const limit = request.limit ?? 100;
|
|
1810
|
+
if (request.orderBy && request.orderBy.length > 0) {
|
|
1811
|
+
throw new StorageUnsupportedError("orderBy in getPage", "HuggingFaceTabularStorage");
|
|
1812
|
+
}
|
|
1813
|
+
const HF_PAGE_CAP = 100;
|
|
1814
|
+
let offset = request.cursor ? decodeOffsetCursor(request.cursor) : 0;
|
|
1815
|
+
const items = [];
|
|
1816
|
+
let endOfDataset = false;
|
|
1817
|
+
while (items.length < limit) {
|
|
1818
|
+
const remaining = limit - items.length;
|
|
1819
|
+
const chunkSize = Math.min(remaining, HF_PAGE_CAP);
|
|
1820
|
+
const rows = await this.getBulk(offset, chunkSize) ?? [];
|
|
1821
|
+
if (rows.length === 0) {
|
|
1822
|
+
endOfDataset = true;
|
|
1823
|
+
break;
|
|
1824
|
+
}
|
|
1825
|
+
items.push(...rows);
|
|
1826
|
+
offset += rows.length;
|
|
1827
|
+
if (rows.length < chunkSize) {
|
|
1828
|
+
endOfDataset = true;
|
|
1829
|
+
break;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
const nextCursor = endOfDataset ? undefined : encodeOffsetCursor(offset);
|
|
1833
|
+
return { items, nextCursor };
|
|
1834
|
+
}
|
|
1275
1835
|
async size() {
|
|
1276
1836
|
const data = await this.fetchApi("/size", {});
|
|
1277
1837
|
return data.size.num_rows;
|
|
@@ -1478,6 +2038,155 @@ function registerTabularStorageDefaults(registry = globalServiceRegistry) {
|
|
|
1478
2038
|
registerInputCompactor("storage:tabular", compactTabularRepository, registry);
|
|
1479
2039
|
}
|
|
1480
2040
|
registerTabularStorageDefaults();
|
|
2041
|
+
// src/sql/Dialect.ts
|
|
2042
|
+
var SqliteDialect = {
|
|
2043
|
+
name: "sqlite",
|
|
2044
|
+
quoteId(id) {
|
|
2045
|
+
return "`" + id.replace(/`/g, "``") + "`";
|
|
2046
|
+
},
|
|
2047
|
+
placeholder(_index) {
|
|
2048
|
+
return "?";
|
|
2049
|
+
}
|
|
2050
|
+
};
|
|
2051
|
+
var PostgresDialect = {
|
|
2052
|
+
name: "postgres",
|
|
2053
|
+
quoteId(id) {
|
|
2054
|
+
return '"' + id.replace(/"/g, '""') + '"';
|
|
2055
|
+
},
|
|
2056
|
+
placeholder(index) {
|
|
2057
|
+
return `$${index}`;
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
|
|
2061
|
+
// src/tabular/sqlMigrationDdl.ts
|
|
2062
|
+
function selectDialect(name) {
|
|
2063
|
+
return name === "sqlite" ? SqliteDialect : PostgresDialect;
|
|
2064
|
+
}
|
|
2065
|
+
function buildAddColumnSql(dialect, table, column, sqlType, nullable, hasDefault = false, defaultLiteralSql) {
|
|
2066
|
+
const d = selectDialect(dialect);
|
|
2067
|
+
let sql = `ALTER TABLE ${d.quoteId(table)} ADD COLUMN ${d.quoteId(column)} ${sqlType}`;
|
|
2068
|
+
if (!nullable)
|
|
2069
|
+
sql += " NOT NULL";
|
|
2070
|
+
if (hasDefault && defaultLiteralSql !== undefined) {
|
|
2071
|
+
sql += ` DEFAULT ${defaultLiteralSql}`;
|
|
2072
|
+
}
|
|
2073
|
+
return sql;
|
|
2074
|
+
}
|
|
2075
|
+
function buildDropColumnSql(dialect, table, column) {
|
|
2076
|
+
const d = selectDialect(dialect);
|
|
2077
|
+
return `ALTER TABLE ${d.quoteId(table)} DROP COLUMN ${d.quoteId(column)}`;
|
|
2078
|
+
}
|
|
2079
|
+
function buildRenameColumnSql(dialect, table, from, to) {
|
|
2080
|
+
const d = selectDialect(dialect);
|
|
2081
|
+
return `ALTER TABLE ${d.quoteId(table)} RENAME COLUMN ${d.quoteId(from)} TO ${d.quoteId(to)}`;
|
|
2082
|
+
}
|
|
2083
|
+
function buildAddIndexSql(dialect, table, indexName, columns, unique) {
|
|
2084
|
+
const d = selectDialect(dialect);
|
|
2085
|
+
const cols = columns.map((c) => d.quoteId(c)).join(", ");
|
|
2086
|
+
return `CREATE ${unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ` + `${d.quoteId(indexName)} ON ${d.quoteId(table)} (${cols})`;
|
|
2087
|
+
}
|
|
2088
|
+
function buildDropIndexSql(dialect, indexName) {
|
|
2089
|
+
const d = selectDialect(dialect);
|
|
2090
|
+
return `DROP INDEX IF EXISTS ${d.quoteId(indexName)}`;
|
|
2091
|
+
}
|
|
2092
|
+
// src/tabular/SqlTabularMigrationApplier.ts
|
|
2093
|
+
class SqlTabularMigrationApplier {
|
|
2094
|
+
async ensureBookkeeping() {
|
|
2095
|
+
await this.executeSql(this.bookkeepingDdl());
|
|
2096
|
+
}
|
|
2097
|
+
async appliedVersions(component) {
|
|
2098
|
+
return this.queryAppliedVersions(component);
|
|
2099
|
+
}
|
|
2100
|
+
async tableExists() {
|
|
2101
|
+
return this.probeTableExists();
|
|
2102
|
+
}
|
|
2103
|
+
async markAllApplied(component, versions) {
|
|
2104
|
+
if (versions.length === 0)
|
|
2105
|
+
return;
|
|
2106
|
+
for (const v of versions) {
|
|
2107
|
+
await this.recordApplied(component, v.version, v.description);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
async applyMigration(component, version, description, ops, onProgress) {
|
|
2111
|
+
const storage = this.storage();
|
|
2112
|
+
await storage.withTransaction(async (tx) => {
|
|
2113
|
+
let processed = 0;
|
|
2114
|
+
const total = Math.max(ops.length, 1);
|
|
2115
|
+
for (const op of ops) {
|
|
2116
|
+
await this.applyOp(op, tx);
|
|
2117
|
+
processed++;
|
|
2118
|
+
onProgress?.(processed / total);
|
|
2119
|
+
}
|
|
2120
|
+
await this.recordAppliedTx(component, version, description, tx);
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
async applyOp(op, tx) {
|
|
2124
|
+
switch (op.kind) {
|
|
2125
|
+
case "addColumn": {
|
|
2126
|
+
const sqlType = this.mapTypeToSQL(op.schema);
|
|
2127
|
+
const nullable = this.isNullableSchema(op.schema);
|
|
2128
|
+
const hasDefault = op.default !== undefined;
|
|
2129
|
+
const sql = buildAddColumnSql(this.dialectName(), this.table(), op.name, sqlType, nullable, hasDefault, hasDefault ? this.literalSql(op.default) : undefined);
|
|
2130
|
+
await this.executeSqlTx(sql, tx);
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
case "dropColumn": {
|
|
2134
|
+
await this.executeSqlTx(buildDropColumnSql(this.dialectName(), this.table(), op.name), tx);
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
case "renameColumn": {
|
|
2138
|
+
await this.executeSqlTx(buildRenameColumnSql(this.dialectName(), this.table(), op.from, op.to), tx);
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
case "addIndex": {
|
|
2142
|
+
await this.executeSqlTx(buildAddIndexSql(this.dialectName(), this.table(), op.name, op.columns, op.unique ?? false), tx);
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
case "dropIndex": {
|
|
2146
|
+
await this.executeSqlTx(buildDropIndexSql(this.dialectName(), op.name), tx);
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
case "backfill": {
|
|
2150
|
+
await runBackfill(tx, op.batchSize ?? 500, op.transform);
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
literalSql(value) {
|
|
2156
|
+
if (value === null)
|
|
2157
|
+
return "NULL";
|
|
2158
|
+
if (typeof value === "string")
|
|
2159
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
2160
|
+
if (typeof value === "number") {
|
|
2161
|
+
if (!Number.isFinite(value)) {
|
|
2162
|
+
throw new Error(`Unsupported numeric default for tabular migration: ${value} (must be finite)`);
|
|
2163
|
+
}
|
|
2164
|
+
return String(value);
|
|
2165
|
+
}
|
|
2166
|
+
if (typeof value === "boolean") {
|
|
2167
|
+
return this.dialectName() === "sqlite" ? value ? "1" : "0" : value ? "TRUE" : "FALSE";
|
|
2168
|
+
}
|
|
2169
|
+
throw new Error(`Unsupported default value for tabular migration: ${typeof value} (${String(value)})`);
|
|
2170
|
+
}
|
|
2171
|
+
bookkeepingDdl() {
|
|
2172
|
+
if (this.dialectName() === "sqlite") {
|
|
2173
|
+
return `CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
|
|
2174
|
+
component TEXT NOT NULL,
|
|
2175
|
+
version INTEGER NOT NULL,
|
|
2176
|
+
description TEXT,
|
|
2177
|
+
applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
2178
|
+
PRIMARY KEY (component, version)
|
|
2179
|
+
)`;
|
|
2180
|
+
}
|
|
2181
|
+
return `CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
|
|
2182
|
+
component TEXT NOT NULL,
|
|
2183
|
+
version INTEGER NOT NULL,
|
|
2184
|
+
description TEXT,
|
|
2185
|
+
applied_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
2186
|
+
PRIMARY KEY (component, version)
|
|
2187
|
+
)`;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
1481
2190
|
// src/tabular/TelemetryTabularStorage.ts
|
|
1482
2191
|
import { traced } from "@workglow/util";
|
|
1483
2192
|
|
|
@@ -1518,6 +2227,12 @@ class TelemetryTabularStorage {
|
|
|
1518
2227
|
getBulk(offset, limit) {
|
|
1519
2228
|
return traced("workglow.storage.tabular.getBulk", this.storageName, () => this.inner.getBulk(offset, limit));
|
|
1520
2229
|
}
|
|
2230
|
+
getPage(request) {
|
|
2231
|
+
return traced("workglow.storage.tabular.getPage", this.storageName, () => this.inner.getPage(request));
|
|
2232
|
+
}
|
|
2233
|
+
queryPage(criteria, request) {
|
|
2234
|
+
return traced("workglow.storage.tabular.queryPage", this.storageName, () => this.inner.queryPage(criteria, request));
|
|
2235
|
+
}
|
|
1521
2236
|
query(criteria, options) {
|
|
1522
2237
|
return traced("workglow.storage.tabular.query", this.storageName, () => this.inner.query(criteria, options));
|
|
1523
2238
|
}
|
|
@@ -1533,9 +2248,19 @@ class TelemetryTabularStorage {
|
|
|
1533
2248
|
subscribeToChanges(callback, options) {
|
|
1534
2249
|
return this.inner.subscribeToChanges(callback, options);
|
|
1535
2250
|
}
|
|
2251
|
+
withTransaction(fn) {
|
|
2252
|
+
return traced("workglow.storage.tabular.withTransaction", this.storageName, () => this.inner.withTransaction((innerTx) => {
|
|
2253
|
+
const txWrapper = new TelemetryTabularStorage(this.storageName, innerTx);
|
|
2254
|
+
return fn(txWrapper);
|
|
2255
|
+
}));
|
|
2256
|
+
}
|
|
1536
2257
|
setupDatabase() {
|
|
1537
2258
|
return this.inner.setupDatabase();
|
|
1538
2259
|
}
|
|
2260
|
+
getMigrationApplier() {
|
|
2261
|
+
const inner = this.inner;
|
|
2262
|
+
return inner.getMigrationApplier?.() ?? null;
|
|
2263
|
+
}
|
|
1539
2264
|
destroy() {
|
|
1540
2265
|
return this.inner.destroy();
|
|
1541
2266
|
}
|
|
@@ -2016,6 +2741,176 @@ class PollingSubscriptionManager {
|
|
|
2016
2741
|
this.initializing = false;
|
|
2017
2742
|
}
|
|
2018
2743
|
}
|
|
2744
|
+
// src/sql/PredicateBuilder.ts
|
|
2745
|
+
function buildSearchWhere(dialect, criteria, schemaProps, convertValue, startIndex = 1) {
|
|
2746
|
+
const conditions = [];
|
|
2747
|
+
const params = [];
|
|
2748
|
+
let paramIndex = startIndex;
|
|
2749
|
+
for (const column of Object.keys(criteria)) {
|
|
2750
|
+
if (!(column in schemaProps)) {
|
|
2751
|
+
throw new Error(`Schema must have a "${String(column)}" field to use it in search criteria`);
|
|
2752
|
+
}
|
|
2753
|
+
const criterion = criteria[column];
|
|
2754
|
+
let operator = "=";
|
|
2755
|
+
let value;
|
|
2756
|
+
if (isSearchCondition(criterion)) {
|
|
2757
|
+
operator = criterion.operator;
|
|
2758
|
+
value = criterion.value;
|
|
2759
|
+
} else {
|
|
2760
|
+
value = criterion;
|
|
2761
|
+
}
|
|
2762
|
+
conditions.push(`${dialect.quoteId(String(column))} ${operator} ${dialect.placeholder(paramIndex)}`);
|
|
2763
|
+
params.push(convertValue(column, value));
|
|
2764
|
+
paramIndex++;
|
|
2765
|
+
}
|
|
2766
|
+
return {
|
|
2767
|
+
whereClause: conditions.join(" AND "),
|
|
2768
|
+
params
|
|
2769
|
+
};
|
|
2770
|
+
}
|
|
2771
|
+
// src/sql/PrefixDdl.ts
|
|
2772
|
+
var SAFE_IDENTIFIER = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
2773
|
+
var SQL_RESERVED_WORDS = new Set([
|
|
2774
|
+
"all",
|
|
2775
|
+
"alter",
|
|
2776
|
+
"and",
|
|
2777
|
+
"as",
|
|
2778
|
+
"asc",
|
|
2779
|
+
"between",
|
|
2780
|
+
"by",
|
|
2781
|
+
"case",
|
|
2782
|
+
"check",
|
|
2783
|
+
"column",
|
|
2784
|
+
"constraint",
|
|
2785
|
+
"create",
|
|
2786
|
+
"cross",
|
|
2787
|
+
"current",
|
|
2788
|
+
"default",
|
|
2789
|
+
"delete",
|
|
2790
|
+
"desc",
|
|
2791
|
+
"distinct",
|
|
2792
|
+
"drop",
|
|
2793
|
+
"else",
|
|
2794
|
+
"end",
|
|
2795
|
+
"exists",
|
|
2796
|
+
"false",
|
|
2797
|
+
"for",
|
|
2798
|
+
"foreign",
|
|
2799
|
+
"from",
|
|
2800
|
+
"full",
|
|
2801
|
+
"function",
|
|
2802
|
+
"grant",
|
|
2803
|
+
"group",
|
|
2804
|
+
"having",
|
|
2805
|
+
"in",
|
|
2806
|
+
"index",
|
|
2807
|
+
"inner",
|
|
2808
|
+
"insert",
|
|
2809
|
+
"into",
|
|
2810
|
+
"is",
|
|
2811
|
+
"join",
|
|
2812
|
+
"key",
|
|
2813
|
+
"left",
|
|
2814
|
+
"like",
|
|
2815
|
+
"limit",
|
|
2816
|
+
"natural",
|
|
2817
|
+
"not",
|
|
2818
|
+
"null",
|
|
2819
|
+
"offset",
|
|
2820
|
+
"on",
|
|
2821
|
+
"or",
|
|
2822
|
+
"order",
|
|
2823
|
+
"outer",
|
|
2824
|
+
"primary",
|
|
2825
|
+
"references",
|
|
2826
|
+
"returning",
|
|
2827
|
+
"right",
|
|
2828
|
+
"select",
|
|
2829
|
+
"set",
|
|
2830
|
+
"table",
|
|
2831
|
+
"then",
|
|
2832
|
+
"true",
|
|
2833
|
+
"union",
|
|
2834
|
+
"unique",
|
|
2835
|
+
"update",
|
|
2836
|
+
"user",
|
|
2837
|
+
"using",
|
|
2838
|
+
"values",
|
|
2839
|
+
"view",
|
|
2840
|
+
"when",
|
|
2841
|
+
"where",
|
|
2842
|
+
"with"
|
|
2843
|
+
]);
|
|
2844
|
+
function assertPrefixesSafe(prefixes) {
|
|
2845
|
+
for (const p of prefixes) {
|
|
2846
|
+
if (!SAFE_IDENTIFIER.test(p.name)) {
|
|
2847
|
+
throw new Error(`Prefix column name must start with a letter and contain only letters, digits, and underscores, got: ${p.name}`);
|
|
2848
|
+
}
|
|
2849
|
+
if (SQL_RESERVED_WORDS.has(p.name.toLowerCase())) {
|
|
2850
|
+
throw new Error(`Prefix column name "${p.name}" is a reserved SQL keyword. Pick a different identifier (e.g. "${p.name}_id").`);
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
function assertPrefixValuesPresent(prefixes, prefixValues) {
|
|
2855
|
+
for (const p of prefixes) {
|
|
2856
|
+
const v = prefixValues[p.name];
|
|
2857
|
+
if (v === undefined || v === null) {
|
|
2858
|
+
throw new Error(`Missing prefix value for column "${p.name}". Every prefix declared in \`prefixes\` ` + `must have a corresponding entry in \`prefixValues\`.`);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
function prefixColumnType(dialect, type) {
|
|
2863
|
+
if (type === "uuid") {
|
|
2864
|
+
return dialect.name === "postgres" ? "UUID" : "TEXT";
|
|
2865
|
+
}
|
|
2866
|
+
return "INTEGER";
|
|
2867
|
+
}
|
|
2868
|
+
function buildPrefixColumnsSql(dialect, prefixes) {
|
|
2869
|
+
if (prefixes.length === 0)
|
|
2870
|
+
return "";
|
|
2871
|
+
assertPrefixesSafe(prefixes);
|
|
2872
|
+
return prefixes.map((p) => `${p.name} ${prefixColumnType(dialect, p.type)} NOT NULL`).join(`,
|
|
2873
|
+
`) + `,
|
|
2874
|
+
`;
|
|
2875
|
+
}
|
|
2876
|
+
function getPrefixColumnNames(prefixes) {
|
|
2877
|
+
return prefixes.map((p) => p.name);
|
|
2878
|
+
}
|
|
2879
|
+
function getPrefixIndexPrefix(prefixes) {
|
|
2880
|
+
if (prefixes.length === 0)
|
|
2881
|
+
return "";
|
|
2882
|
+
assertPrefixesSafe(prefixes);
|
|
2883
|
+
return prefixes.map((p) => p.name).join(", ") + ", ";
|
|
2884
|
+
}
|
|
2885
|
+
function getPrefixIndexSuffix(prefixes) {
|
|
2886
|
+
if (prefixes.length === 0)
|
|
2887
|
+
return "";
|
|
2888
|
+
assertPrefixesSafe(prefixes);
|
|
2889
|
+
return "_" + prefixes.map((p) => p.name).join("_");
|
|
2890
|
+
}
|
|
2891
|
+
function buildPrefixWhereClause(dialect, prefixes, prefixValues, startParam = 1) {
|
|
2892
|
+
if (prefixes.length === 0)
|
|
2893
|
+
return { conditions: "", params: [] };
|
|
2894
|
+
assertPrefixesSafe(prefixes);
|
|
2895
|
+
assertPrefixValuesPresent(prefixes, prefixValues);
|
|
2896
|
+
const conditions = prefixes.map((p, i) => `${p.name} = ${dialect.placeholder(startParam + i)}`).join(" AND ");
|
|
2897
|
+
const params = prefixes.map((p) => prefixValues[p.name]);
|
|
2898
|
+
return { conditions: " AND " + conditions, params };
|
|
2899
|
+
}
|
|
2900
|
+
function getPrefixParamValues(prefixes, prefixValues) {
|
|
2901
|
+
if (prefixes.length === 0)
|
|
2902
|
+
return [];
|
|
2903
|
+
assertPrefixValuesPresent(prefixes, prefixValues);
|
|
2904
|
+
return prefixes.map((p) => prefixValues[p.name]);
|
|
2905
|
+
}
|
|
2906
|
+
function buildPrefixInsertFragments(dialect, prefixes, startParam = 1) {
|
|
2907
|
+
if (prefixes.length === 0)
|
|
2908
|
+
return { columns: "", placeholders: "" };
|
|
2909
|
+
assertPrefixesSafe(prefixes);
|
|
2910
|
+
const columns = prefixes.map((p) => p.name).join(", ") + ", ";
|
|
2911
|
+
const placeholders = prefixes.map((_, i) => dialect.placeholder(startParam + i)).join(", ") + ", ";
|
|
2912
|
+
return { columns, placeholders };
|
|
2913
|
+
}
|
|
2019
2914
|
// src/vector/InMemoryVectorStorage.ts
|
|
2020
2915
|
import { cosineSimilarity } from "@workglow/util/schema";
|
|
2021
2916
|
|
|
@@ -2290,8 +3185,8 @@ class SharedInMemoryTabularStorage extends BaseTabularStorage {
|
|
|
2290
3185
|
isInitialized = false;
|
|
2291
3186
|
syncInProgress = false;
|
|
2292
3187
|
pendingMessages = [];
|
|
2293
|
-
constructor(channelName = "tabular_store", schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing") {
|
|
2294
|
-
super(schema, primaryKeyNames, indexes, clientProvidedKeys);
|
|
3188
|
+
constructor(channelName = "tabular_store", schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing", tabularMigrations) {
|
|
3189
|
+
super(schema, primaryKeyNames, indexes, clientProvidedKeys, tabularMigrations, channelName);
|
|
2295
3190
|
this.channelName = channelName;
|
|
2296
3191
|
this.inMemoryRepo = new InMemoryTabularStorage(schema, primaryKeyNames, indexes, clientProvidedKeys);
|
|
2297
3192
|
this.setupEventForwarding();
|
|
@@ -2415,6 +3310,12 @@ class SharedInMemoryTabularStorage extends BaseTabularStorage {
|
|
|
2415
3310
|
return;
|
|
2416
3311
|
this.isInitialized = true;
|
|
2417
3312
|
await this.syncFromOtherTabs();
|
|
3313
|
+
if (this.tabularMigrations && this.tabularMigrations.length > 0) {
|
|
3314
|
+
await this.applyTabularMigrations();
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
getMigrationApplier() {
|
|
3318
|
+
return new InMemoryTabularMigrationApplier(this, this.channelName);
|
|
2418
3319
|
}
|
|
2419
3320
|
async put(value) {
|
|
2420
3321
|
const result = await this.inMemoryRepo.put(value);
|
|
@@ -2472,14 +3373,36 @@ class SharedInMemoryTabularStorage extends BaseTabularStorage {
|
|
|
2472
3373
|
}
|
|
2473
3374
|
}
|
|
2474
3375
|
export {
|
|
3376
|
+
sortMigrations,
|
|
3377
|
+
runTabularMigrations,
|
|
3378
|
+
runBackfill,
|
|
2475
3379
|
registerTabularStorageDefaults,
|
|
2476
3380
|
registerTabularRepository,
|
|
3381
|
+
prefixColumnType,
|
|
2477
3382
|
pickCoveringIndex,
|
|
2478
3383
|
isSearchCondition,
|
|
2479
3384
|
getVectorProperty,
|
|
2480
3385
|
getTabularRepository,
|
|
3386
|
+
getPrefixParamValues,
|
|
3387
|
+
getPrefixIndexSuffix,
|
|
3388
|
+
getPrefixIndexPrefix,
|
|
3389
|
+
getPrefixColumnNames,
|
|
2481
3390
|
getMetadataProperty,
|
|
2482
3391
|
getGlobalTabularRepositories,
|
|
3392
|
+
encodeCursor,
|
|
3393
|
+
decodeCursor,
|
|
3394
|
+
buildSearchWhere,
|
|
3395
|
+
buildRenameColumnSql,
|
|
3396
|
+
buildPrefixWhereClause,
|
|
3397
|
+
buildPrefixInsertFragments,
|
|
3398
|
+
buildPrefixColumnsSql,
|
|
3399
|
+
buildDropIndexSql,
|
|
3400
|
+
buildDropColumnSql,
|
|
3401
|
+
buildAddIndexSql,
|
|
3402
|
+
buildAddColumnSql,
|
|
3403
|
+
assertPrefixesSafe,
|
|
3404
|
+
assertPrefixValuesPresent,
|
|
3405
|
+
assertCursorMatches,
|
|
2483
3406
|
TelemetryVectorStorage,
|
|
2484
3407
|
TelemetryTabularStorage,
|
|
2485
3408
|
TelemetryKvStorage,
|
|
@@ -2491,17 +3414,23 @@ export {
|
|
|
2491
3414
|
StorageInvalidColumnError,
|
|
2492
3415
|
StorageError,
|
|
2493
3416
|
StorageEmptyCriteriaError,
|
|
3417
|
+
SqliteDialect,
|
|
3418
|
+
SqlTabularMigrationApplier,
|
|
2494
3419
|
SharedInMemoryTabularStorage,
|
|
2495
3420
|
SHARED_IN_MEMORY_TABULAR_REPOSITORY,
|
|
3421
|
+
PostgresDialect,
|
|
2496
3422
|
PollingSubscriptionManager,
|
|
3423
|
+
MIGRATIONS_TABLE,
|
|
2497
3424
|
MEMORY_TABULAR_REPOSITORY,
|
|
2498
3425
|
MEMORY_KV_REPOSITORY,
|
|
3426
|
+
MAX_CURSOR_LENGTH,
|
|
2499
3427
|
LazyEncryptedCredentialStore,
|
|
2500
3428
|
KvViaTabularStorage,
|
|
2501
3429
|
KvStorage,
|
|
2502
3430
|
KV_REPOSITORY,
|
|
2503
3431
|
InMemoryVectorStorage,
|
|
2504
3432
|
InMemoryTabularStorage,
|
|
3433
|
+
InMemoryTabularMigrationApplier,
|
|
2505
3434
|
InMemoryKvStorage,
|
|
2506
3435
|
HybridSubscriptionManager,
|
|
2507
3436
|
HuggingFaceTabularStorage,
|
|
@@ -2516,4 +3445,4 @@ export {
|
|
|
2516
3445
|
BaseSqlTabularStorage
|
|
2517
3446
|
};
|
|
2518
3447
|
|
|
2519
|
-
//# debugId=
|
|
3448
|
+
//# debugId=2D7847D1C483B82F64756E2164756E21
|