@workglow/postgres 0.2.28

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.
Files changed (41) hide show
  1. package/dist/job-queue/PostgresQueueStorage.d.ts +155 -0
  2. package/dist/job-queue/PostgresQueueStorage.d.ts.map +1 -0
  3. package/dist/job-queue/PostgresRateLimiterStorage.d.ts +57 -0
  4. package/dist/job-queue/PostgresRateLimiterStorage.d.ts.map +1 -0
  5. package/dist/job-queue/browser.d.ts +7 -0
  6. package/dist/job-queue/browser.d.ts.map +1 -0
  7. package/dist/job-queue/browser.js +732 -0
  8. package/dist/job-queue/browser.js.map +11 -0
  9. package/dist/job-queue/bun.d.ts +7 -0
  10. package/dist/job-queue/bun.d.ts.map +1 -0
  11. package/dist/job-queue/common.d.ts +8 -0
  12. package/dist/job-queue/common.d.ts.map +1 -0
  13. package/dist/job-queue/node.d.ts +7 -0
  14. package/dist/job-queue/node.d.ts.map +1 -0
  15. package/dist/job-queue/node.js +732 -0
  16. package/dist/job-queue/node.js.map +11 -0
  17. package/dist/storage/PostgresKvStorage.d.ts +27 -0
  18. package/dist/storage/PostgresKvStorage.d.ts.map +1 -0
  19. package/dist/storage/PostgresTabularStorage.d.ts +194 -0
  20. package/dist/storage/PostgresTabularStorage.d.ts.map +1 -0
  21. package/dist/storage/PostgresVectorStorage.d.ts +39 -0
  22. package/dist/storage/PostgresVectorStorage.d.ts.map +1 -0
  23. package/dist/storage/_postgres/browser.d.ts +32 -0
  24. package/dist/storage/_postgres/browser.d.ts.map +1 -0
  25. package/dist/storage/_postgres/node-bun.d.ts +26 -0
  26. package/dist/storage/_postgres/node-bun.d.ts.map +1 -0
  27. package/dist/storage/_postgres/pglite-pool.d.ts +21 -0
  28. package/dist/storage/_postgres/pglite-pool.d.ts.map +1 -0
  29. package/dist/storage/browser.d.ts +10 -0
  30. package/dist/storage/browser.d.ts.map +1 -0
  31. package/dist/storage/browser.js +951 -0
  32. package/dist/storage/browser.js.map +14 -0
  33. package/dist/storage/bun.d.ts +7 -0
  34. package/dist/storage/bun.d.ts.map +1 -0
  35. package/dist/storage/common.d.ts +10 -0
  36. package/dist/storage/common.d.ts.map +1 -0
  37. package/dist/storage/node.d.ts +7 -0
  38. package/dist/storage/node.d.ts.map +1 -0
  39. package/dist/storage/node.js +842 -0
  40. package/dist/storage/node.js.map +13 -0
  41. package/package.json +78 -0
@@ -0,0 +1,842 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ // src/storage/_postgres/node-bun.ts
5
+ var _pg;
6
+ async function loadPostgres() {
7
+ if (_pg) {
8
+ return;
9
+ }
10
+ try {
11
+ _pg = await import("pg");
12
+ } catch {
13
+ throw new Error('The "pg" package is required for @workglow/postgres/storage on Node.js or Bun. Install: bun add pg');
14
+ }
15
+ }
16
+ function getPostgres() {
17
+ if (!_pg) {
18
+ throw new Error("Postgres is not ready. Await Postgres.init() before using getPostgres() or Postgres.module.");
19
+ }
20
+ return _pg;
21
+ }
22
+ async function createPool(config) {
23
+ await loadPostgres();
24
+ return new _pg.Pool(config);
25
+ }
26
+ var Postgres = {
27
+ init: loadPostgres,
28
+ load: loadPostgres,
29
+ get module() {
30
+ return getPostgres();
31
+ },
32
+ createPool
33
+ };
34
+ // src/storage/PostgresKvStorage.ts
35
+ import { createServiceToken as createServiceToken2 } from "@workglow/util";
36
+
37
+ // src/storage/PostgresTabularStorage.ts
38
+ import { createServiceToken } from "@workglow/util";
39
+ import {
40
+ BaseSqlTabularStorage,
41
+ isSearchCondition,
42
+ pickCoveringIndex
43
+ } from "@workglow/storage";
44
+ var POSTGRES_TABULAR_REPOSITORY = createServiceToken("storage.tabularRepository.postgres");
45
+
46
+ class PostgresTabularStorage extends BaseSqlTabularStorage {
47
+ db;
48
+ constructor(db, table = "tabular_store", schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing") {
49
+ super(table, schema, primaryKeyNames, indexes, clientProvidedKeys);
50
+ this.db = db;
51
+ }
52
+ async setupDatabase() {
53
+ const sql = `
54
+ CREATE TABLE IF NOT EXISTS "${this.table}" (
55
+ ${this.constructPrimaryKeyColumns('"')} ${this.constructValueColumns('"')},
56
+ PRIMARY KEY (${this.primaryKeyColumnList()})
57
+ )
58
+ `;
59
+ await this.db.query(sql);
60
+ await this.createVectorIndexes();
61
+ const pkColumns = this.primaryKeyColumns();
62
+ const createdIndexes = new Set;
63
+ for (const columns of this.indexes) {
64
+ if (columns.length <= pkColumns.length) {
65
+ const isPkPrefix = columns.every((col, idx) => col === pkColumns[idx]);
66
+ if (isPkPrefix)
67
+ continue;
68
+ }
69
+ const indexName = `${this.table}_${columns.join("_")}`;
70
+ const columnList = columns.map((col) => `"${String(col)}"`).join(", ");
71
+ const columnKey = columns.join(",");
72
+ if (createdIndexes.has(columnKey))
73
+ continue;
74
+ const isRedundant = Array.from(createdIndexes).some((existing) => {
75
+ const existingCols = existing.split(",");
76
+ return existingCols.length >= columns.length && columns.every((col, idx) => col === existingCols[idx]);
77
+ });
78
+ if (!isRedundant) {
79
+ await this.db.query(`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${this.table}" (${columnList})`);
80
+ createdIndexes.add(columnKey);
81
+ }
82
+ }
83
+ }
84
+ isVectorFormat(format) {
85
+ if (!format)
86
+ return false;
87
+ return format.startsWith("TypedArray:") || format === "TypedArray";
88
+ }
89
+ getVectorDimensions(typeDef) {
90
+ return;
91
+ }
92
+ mapTypeToSQL(typeDef) {
93
+ const actualType = this.getNonNullType(typeDef);
94
+ if (typeof actualType === "boolean") {
95
+ return "TEXT /* boolean schema */";
96
+ }
97
+ if (actualType.contentEncoding === "blob")
98
+ return "BYTEA";
99
+ switch (actualType.type) {
100
+ case "string":
101
+ if (actualType.format === "date-time")
102
+ return "TIMESTAMP";
103
+ if (actualType.format === "date")
104
+ return "DATE";
105
+ if (actualType.format === "email")
106
+ return "VARCHAR(255)";
107
+ if (actualType.format === "uri")
108
+ return "VARCHAR(2048)";
109
+ if (actualType.format === "uuid")
110
+ return "UUID";
111
+ if (this.isVectorFormat(actualType.format)) {
112
+ const dimension = this.getVectorDimensions(actualType);
113
+ if (typeof dimension === "number") {
114
+ return `vector(${dimension})`;
115
+ }
116
+ }
117
+ if (typeof actualType.maxLength === "number") {
118
+ return `VARCHAR(${actualType.maxLength})`;
119
+ }
120
+ return "TEXT";
121
+ case "number":
122
+ case "integer":
123
+ if (actualType.multipleOf === 1 || actualType.type === "integer") {
124
+ if (typeof actualType.minimum === "number") {
125
+ if (actualType.minimum >= 0) {
126
+ if (typeof actualType.maximum === "number") {
127
+ if (actualType.maximum <= 32767)
128
+ return "SMALLINT";
129
+ if (actualType.maximum <= 2147483647)
130
+ return "INTEGER";
131
+ }
132
+ return "BIGINT";
133
+ }
134
+ }
135
+ return "INTEGER";
136
+ }
137
+ if (actualType.format === "float")
138
+ return "REAL";
139
+ if (actualType.format === "double")
140
+ return "DOUBLE PRECISION";
141
+ if (typeof actualType.multipleOf === "number") {
142
+ const decimalPlaces = String(actualType.multipleOf).split(".")[1]?.length || 0;
143
+ if (decimalPlaces > 0) {
144
+ return `NUMERIC(38, ${decimalPlaces})`;
145
+ }
146
+ }
147
+ return "NUMERIC";
148
+ case "boolean":
149
+ return "BOOLEAN";
150
+ case "array":
151
+ if (actualType.items && typeof actualType.items === "object" && !Array.isArray(actualType.items)) {
152
+ const itemType = this.mapTypeToSQL(actualType.items);
153
+ const supportedArrayElementTypes = [
154
+ "TEXT",
155
+ "VARCHAR",
156
+ "CHAR",
157
+ "INTEGER",
158
+ "SMALLINT",
159
+ "BIGINT",
160
+ "REAL",
161
+ "DOUBLE PRECISION",
162
+ "NUMERIC",
163
+ "BOOLEAN",
164
+ "UUID",
165
+ "DATE",
166
+ "TIMESTAMP"
167
+ ];
168
+ const isSupported = supportedArrayElementTypes.some((type) => itemType === type || itemType.startsWith(type + "(") && type !== "VARCHAR");
169
+ if (isSupported) {
170
+ return `${itemType}[]`;
171
+ } else {
172
+ return "JSONB /* complex array */";
173
+ }
174
+ }
175
+ return "JSONB /* generic array */";
176
+ case "object":
177
+ return "JSONB /* object */";
178
+ default:
179
+ return "TEXT /* unknown type */";
180
+ }
181
+ }
182
+ constructPrimaryKeyColumns($delimiter = "") {
183
+ const cols = Object.entries(this.primaryKeySchema.properties).map(([key, typeDef]) => {
184
+ if (this.isAutoGeneratedKey(key)) {
185
+ if (this.autoGeneratedKeyStrategy === "autoincrement") {
186
+ const sqlType2 = this.mapTypeToSQL(typeDef);
187
+ const isSmallInt = sqlType2.includes("SMALLINT");
188
+ const isBigInt = sqlType2.includes("BIGINT");
189
+ const serialType = isBigInt ? "BIGSERIAL" : isSmallInt ? "SMALLSERIAL" : "SERIAL";
190
+ return `${$delimiter}${key}${$delimiter} ${serialType}`;
191
+ } else if (this.autoGeneratedKeyStrategy === "uuid") {
192
+ return `${$delimiter}${key}${$delimiter} UUID DEFAULT gen_random_uuid()`;
193
+ }
194
+ }
195
+ const sqlType = this.mapTypeToSQL(typeDef);
196
+ let constraints = "NOT NULL";
197
+ if (this.shouldBeUnsigned(typeDef)) {
198
+ constraints += ` CHECK (${$delimiter}${key}${$delimiter} >= 0)`;
199
+ }
200
+ return `${$delimiter}${key}${$delimiter} ${sqlType} ${constraints}`;
201
+ }).join(", ");
202
+ return cols;
203
+ }
204
+ constructValueColumns($delimiter = "") {
205
+ const requiredSet = new Set(this.valueSchema.required ?? []);
206
+ const cols = Object.entries(this.valueSchema.properties).map(([key, typeDef]) => {
207
+ const sqlType = this.mapTypeToSQL(typeDef);
208
+ const isRequired = requiredSet.has(key);
209
+ const nullable = !isRequired || this.isNullable(typeDef);
210
+ let constraints = nullable ? "NULL" : "NOT NULL";
211
+ if (this.shouldBeUnsigned(typeDef)) {
212
+ constraints += ` CHECK (${$delimiter}${key}${$delimiter} >= 0)`;
213
+ }
214
+ return `${$delimiter}${key}${$delimiter} ${sqlType} ${constraints}`;
215
+ }).join(", ");
216
+ if (cols.length > 0) {
217
+ return `, ${cols}`;
218
+ } else {
219
+ return "";
220
+ }
221
+ }
222
+ jsToSqlValue(column, value) {
223
+ const typeDef = this.schema.properties[column];
224
+ if (typeDef) {
225
+ const actualType = this.getNonNullType(typeDef);
226
+ if (typeof actualType !== "boolean" && this.isVectorFormat(actualType.format)) {
227
+ if (value && ArrayBuffer.isView(value) && !(value instanceof DataView)) {
228
+ const array = Array.from(value);
229
+ return `[${array.join(",")}]`;
230
+ }
231
+ if (typeof value === "string") {
232
+ return value;
233
+ }
234
+ }
235
+ }
236
+ return super.jsToSqlValue(column, value);
237
+ }
238
+ sqlToJsValue(column, value) {
239
+ const typeDef = this.schema.properties[column];
240
+ if (typeDef) {
241
+ if (value === null && this.isNullable(typeDef)) {
242
+ return null;
243
+ }
244
+ const actualType = this.getNonNullType(typeDef);
245
+ if (typeof actualType !== "boolean" && this.isVectorFormat(actualType.format)) {
246
+ if (typeof value === "string") {
247
+ try {
248
+ const array = JSON.parse(value);
249
+ return new Float32Array(array);
250
+ } catch (e) {
251
+ console.warn(`Failed to parse vector for column ${column}:`, e);
252
+ }
253
+ }
254
+ if (value && typeof value === "object") {
255
+ return value;
256
+ }
257
+ }
258
+ if (typeof actualType !== "boolean" && (actualType.type === "number" || actualType.type === "integer")) {
259
+ if (typeof value === "number")
260
+ return value;
261
+ if (typeof value === "string") {
262
+ const parsed = Number(value);
263
+ if (!isNaN(parsed))
264
+ return parsed;
265
+ }
266
+ }
267
+ }
268
+ return super.sqlToJsValue(column, value);
269
+ }
270
+ shouldBeUnsigned(typeDef) {
271
+ const actualType = this.getNonNullType(typeDef);
272
+ if (typeof actualType === "boolean") {
273
+ return false;
274
+ }
275
+ if ((actualType.type === "number" || actualType.type === "integer") && typeof actualType.minimum === "number" && actualType.minimum >= 0) {
276
+ return true;
277
+ }
278
+ return false;
279
+ }
280
+ getVectorColumns() {
281
+ const vectorColumns = [];
282
+ for (const [key, typeDef] of Object.entries(this.schema.properties)) {
283
+ const actualType = this.getNonNullType(typeDef);
284
+ if (typeof actualType !== "boolean" && this.isVectorFormat(actualType.format)) {
285
+ const dimension = this.getVectorDimensions(actualType);
286
+ if (typeof dimension === "number") {
287
+ vectorColumns.push({ column: key, dimension });
288
+ } else {
289
+ console.warn(`Invalid vector format for column ${key}: ${actualType.format}, skipping`);
290
+ }
291
+ }
292
+ }
293
+ return vectorColumns;
294
+ }
295
+ async createVectorIndexes() {
296
+ const vectorColumns = this.getVectorColumns();
297
+ if (vectorColumns.length === 0) {
298
+ return;
299
+ }
300
+ try {
301
+ await this.db.query("CREATE EXTENSION IF NOT EXISTS vector");
302
+ } catch (error) {
303
+ console.warn("pgvector extension not available, vector columns will use TEXT fallback:", error);
304
+ return;
305
+ }
306
+ for (const { column } of vectorColumns) {
307
+ const indexName = `${this.table}_${column}_hnsw_idx`;
308
+ try {
309
+ await this.db.query(`
310
+ CREATE INDEX IF NOT EXISTS "${indexName}"
311
+ ON "${this.table}"
312
+ USING hnsw ("${column}" vector_cosine_ops)
313
+ `);
314
+ } catch (error) {
315
+ console.warn(`Failed to create HNSW index on ${column}:`, error);
316
+ }
317
+ }
318
+ }
319
+ async put(entity) {
320
+ const db = this.db;
321
+ const columnsToInsert = [];
322
+ const paramsToInsert = [];
323
+ const pkColumns = this.primaryKeyColumns();
324
+ const entityRecord = entity;
325
+ for (const col of pkColumns) {
326
+ const colStr = String(col);
327
+ if (this.isAutoGeneratedKey(colStr)) {
328
+ const clientProvidedValue = entityRecord[colStr];
329
+ const hasClientValue = clientProvidedValue !== undefined && clientProvidedValue !== null;
330
+ let shouldUseClientValue = false;
331
+ if (this.clientProvidedKeys === "never") {
332
+ shouldUseClientValue = false;
333
+ } else if (this.clientProvidedKeys === "always") {
334
+ if (!hasClientValue) {
335
+ throw new Error(`Auto-generated key "${colStr}" is required when clientProvidedKeys is "always"`);
336
+ }
337
+ shouldUseClientValue = true;
338
+ } else {
339
+ shouldUseClientValue = hasClientValue;
340
+ }
341
+ if (shouldUseClientValue) {
342
+ columnsToInsert.push(colStr);
343
+ paramsToInsert.push(this.jsToSqlValue(colStr, clientProvidedValue));
344
+ }
345
+ continue;
346
+ }
347
+ columnsToInsert.push(colStr);
348
+ const value = entityRecord[colStr];
349
+ paramsToInsert.push(this.jsToSqlValue(colStr, value));
350
+ }
351
+ const valueColumns = this.valueColumns();
352
+ for (const col of valueColumns) {
353
+ const colStr = String(col);
354
+ columnsToInsert.push(colStr);
355
+ const value = entityRecord[colStr];
356
+ paramsToInsert.push(this.jsToSqlValue(colStr, value));
357
+ }
358
+ const columnList = columnsToInsert.map((c) => `"${c}"`).join(", ");
359
+ const placeholders = columnsToInsert.map((_, i) => `$${i + 1}`).join(", ");
360
+ const conflictClause = valueColumns.length > 0 ? `
361
+ ON CONFLICT (${this.primaryKeyColumnList('"')}) DO UPDATE
362
+ SET
363
+ ${valueColumns.map((col) => {
364
+ const colIdx = columnsToInsert.indexOf(String(col));
365
+ return `"${col}" = $${colIdx + 1}`;
366
+ }).join(", ")}
367
+ ` : "";
368
+ const sql = `
369
+ INSERT INTO "${this.table}" (${columnList})
370
+ VALUES (${placeholders})
371
+ ${conflictClause}
372
+ RETURNING *
373
+ `;
374
+ const params = paramsToInsert;
375
+ const result = await db.query(sql, params);
376
+ const updatedEntity = result.rows[0];
377
+ const updatedRecord = updatedEntity;
378
+ for (const key in this.schema.properties) {
379
+ updatedRecord[key] = this.sqlToJsValue(key, updatedRecord[key]);
380
+ }
381
+ this.events.emit("put", updatedEntity);
382
+ return updatedEntity;
383
+ }
384
+ async putBulk(entities) {
385
+ if (entities.length === 0)
386
+ return [];
387
+ return await Promise.all(entities.map((entity) => this.put(entity)));
388
+ }
389
+ async get(key) {
390
+ const db = this.db;
391
+ const whereClauses = this.primaryKeyColumns().map((discriminatorKey, i) => `"${discriminatorKey}" = $${i + 1}`).join(" AND ");
392
+ const sql = `SELECT * FROM "${this.table}" WHERE ${whereClauses}`;
393
+ const params = this.getPrimaryKeyAsOrderedArray(key);
394
+ const result = await db.query(sql, params);
395
+ let val;
396
+ if (result.rows.length > 0) {
397
+ val = result.rows[0];
398
+ const valRecord = val;
399
+ for (const key2 in this.schema.properties) {
400
+ valRecord[key2] = this.sqlToJsValue(key2, valRecord[key2]);
401
+ }
402
+ } else {
403
+ val = undefined;
404
+ }
405
+ this.events.emit("get", key, val);
406
+ return val;
407
+ }
408
+ async delete(value) {
409
+ const db = this.db;
410
+ const { key } = this.separateKeyValueFromCombined(value);
411
+ const whereClauses = this.primaryKeyColumns().map((key2, i) => `${key2} = $${i + 1}`).join(" AND ");
412
+ const params = this.getPrimaryKeyAsOrderedArray(key);
413
+ await db.query(`DELETE FROM "${this.table}" WHERE ${whereClauses}`, params);
414
+ this.events.emit("delete", key);
415
+ }
416
+ async getAll(options) {
417
+ this.validateGetAllOptions(options);
418
+ const db = this.db;
419
+ let sql = `SELECT * FROM "${this.table}"`;
420
+ const params = [];
421
+ if (options?.orderBy && options.orderBy.length > 0) {
422
+ const orderClauses = options.orderBy.map((o) => `"${String(o.column)}" ${o.direction}`);
423
+ sql += ` ORDER BY ${orderClauses.join(", ")}`;
424
+ }
425
+ if (options?.limit !== undefined) {
426
+ sql += ` LIMIT $${params.length + 1}`;
427
+ params.push(options.limit);
428
+ }
429
+ if (options?.offset !== undefined) {
430
+ sql += ` OFFSET $${params.length + 1}`;
431
+ params.push(options.offset);
432
+ }
433
+ const result = params.length > 0 ? await db.query(sql, params) : await db.query(sql);
434
+ if (result.rows.length > 0) {
435
+ for (const row of result.rows) {
436
+ const record = row;
437
+ for (const key in this.schema.properties) {
438
+ record[key] = this.sqlToJsValue(key, record[key]);
439
+ }
440
+ }
441
+ return result.rows;
442
+ }
443
+ return;
444
+ }
445
+ async deleteAll() {
446
+ const db = this.db;
447
+ await db.query(`DELETE FROM "${this.table}"`);
448
+ this.events.emit("clearall");
449
+ }
450
+ async size() {
451
+ const db = this.db;
452
+ const result = await db.query(`SELECT COUNT(*) FROM "${this.table}"`);
453
+ return parseInt(result.rows[0].count, 10);
454
+ }
455
+ async count(criteria) {
456
+ if (!criteria || Object.keys(criteria).length === 0) {
457
+ return await this.size();
458
+ }
459
+ this.validateQueryParams(criteria);
460
+ const db = this.db;
461
+ const { whereClause, params } = this.buildDeleteSearchWhere(criteria);
462
+ const result = await db.query(`SELECT COUNT(*) FROM "${this.table}" WHERE ${whereClause}`, params);
463
+ return parseInt(result.rows[0].count, 10);
464
+ }
465
+ async getBulk(offset, limit) {
466
+ const db = this.db;
467
+ const orderByClause = this.primaryKeyColumns().map((col) => `"${String(col)}"`).join(", ");
468
+ const result = await db.query(`SELECT * FROM "${this.table}" ORDER BY ${orderByClause} LIMIT $1 OFFSET $2`, [limit, offset]);
469
+ if (!result.rows || result.rows.length === 0) {
470
+ return;
471
+ }
472
+ for (const row of result.rows) {
473
+ const record = row;
474
+ for (const key in this.schema.properties) {
475
+ record[key] = this.sqlToJsValue(key, record[key]);
476
+ }
477
+ }
478
+ return result.rows;
479
+ }
480
+ buildDeleteSearchWhere(criteria) {
481
+ const conditions = [];
482
+ const params = [];
483
+ let paramIndex = 1;
484
+ for (const column of Object.keys(criteria)) {
485
+ if (!(column in this.schema.properties)) {
486
+ throw new Error(`Schema must have a ${String(column)} field to use deleteSearch`);
487
+ }
488
+ const criterion = criteria[column];
489
+ let operator = "=";
490
+ let value;
491
+ if (isSearchCondition(criterion)) {
492
+ operator = criterion.operator;
493
+ value = criterion.value;
494
+ } else {
495
+ value = criterion;
496
+ }
497
+ conditions.push(`"${String(column)}" ${operator} $${paramIndex}`);
498
+ params.push(this.jsToSqlValue(column, value));
499
+ paramIndex++;
500
+ }
501
+ return {
502
+ whereClause: conditions.join(" AND "),
503
+ params
504
+ };
505
+ }
506
+ async deleteSearch(criteria) {
507
+ const criteriaKeys = Object.keys(criteria);
508
+ if (criteriaKeys.length === 0) {
509
+ return;
510
+ }
511
+ const db = this.db;
512
+ const { whereClause, params } = this.buildDeleteSearchWhere(criteria);
513
+ await db.query(`DELETE FROM "${this.table}" WHERE ${whereClause}`, params);
514
+ this.events.emit("delete", criteriaKeys[0]);
515
+ }
516
+ async query(criteria, options) {
517
+ this.validateQueryParams(criteria, options);
518
+ const db = this.db;
519
+ let sql = `SELECT * FROM "${this.table}"`;
520
+ const { whereClause, params } = this.buildDeleteSearchWhere(criteria);
521
+ sql += ` WHERE ${whereClause}`;
522
+ if (options?.orderBy && options.orderBy.length > 0) {
523
+ const orderClauses = options.orderBy.map((o) => `"${String(o.column)}" ${o.direction}`);
524
+ sql += ` ORDER BY ${orderClauses.join(", ")}`;
525
+ }
526
+ if (options?.limit !== undefined) {
527
+ sql += ` LIMIT $${params.length + 1}`;
528
+ params.push(options.limit);
529
+ }
530
+ if (options?.offset !== undefined) {
531
+ sql += ` OFFSET $${params.length + 1}`;
532
+ params.push(options.offset);
533
+ }
534
+ const result = await db.query(sql, params);
535
+ if (result.rows.length > 0) {
536
+ for (const row of result.rows) {
537
+ const record = row;
538
+ for (const k in this.schema.properties) {
539
+ record[k] = this.sqlToJsValue(k, record[k]);
540
+ }
541
+ }
542
+ this.events.emit("query", criteria, result.rows);
543
+ return result.rows;
544
+ }
545
+ this.events.emit("query", criteria, undefined);
546
+ return;
547
+ }
548
+ async queryIndex(criteria, options) {
549
+ this.validateSelect(options);
550
+ this.validateQueryParams(criteria, options);
551
+ const registered = this.indexes.map((cols2, i) => {
552
+ const cs = Array.isArray(cols2) ? cols2 : [cols2];
553
+ return { name: `idx_${i}`, keyPath: cs };
554
+ });
555
+ pickCoveringIndex({
556
+ table: this.table,
557
+ indexes: registered,
558
+ criteriaColumns: Object.keys(criteria),
559
+ orderByColumns: (options.orderBy ?? []).map((o) => ({
560
+ column: String(o.column),
561
+ direction: o.direction
562
+ })),
563
+ selectColumns: options.select.map(String),
564
+ primaryKeyColumns: this.primaryKeyNames.map(String)
565
+ });
566
+ const cols = options.select.map((c) => `"${String(c)}"`).join(", ");
567
+ let sql = `SELECT ${cols} FROM "${this.table}"`;
568
+ const { whereClause, params } = this.buildDeleteSearchWhere(criteria);
569
+ sql += ` WHERE ${whereClause}`;
570
+ if (options.orderBy && options.orderBy.length > 0) {
571
+ sql += ` ORDER BY ` + options.orderBy.map((o) => `"${String(o.column)}" ${o.direction}`).join(", ");
572
+ }
573
+ if (options.limit !== undefined) {
574
+ sql += ` LIMIT $${params.length + 1}`;
575
+ params.push(options.limit);
576
+ }
577
+ if (options.offset !== undefined) {
578
+ sql += ` OFFSET $${params.length + 1}`;
579
+ params.push(options.offset);
580
+ }
581
+ const db = this.db;
582
+ const result = await db.query(sql, params);
583
+ for (const row of result.rows) {
584
+ const record = row;
585
+ for (const k of Object.keys(record)) {
586
+ record[k] = this.sqlToJsValue(k, record[k]);
587
+ }
588
+ }
589
+ return result.rows;
590
+ }
591
+ subscribeToChanges(callback, options) {
592
+ throw new Error("subscribeToChanges is not supported for PostgresTabularStorage");
593
+ }
594
+ destroy() {
595
+ super.destroy();
596
+ }
597
+ }
598
+
599
+ // src/storage/PostgresKvStorage.ts
600
+ import {
601
+ DefaultKeyValueKey,
602
+ DefaultKeyValueSchema,
603
+ KvViaTabularStorage
604
+ } from "@workglow/storage";
605
+ var POSTGRES_KV_REPOSITORY = createServiceToken2("storage.kvRepository.postgres");
606
+
607
+ class PostgresKvStorage extends KvViaTabularStorage {
608
+ db;
609
+ dbName;
610
+ tabularRepository;
611
+ constructor(db, dbName, keySchema = { type: "string" }, valueSchema = {}) {
612
+ super(keySchema, valueSchema);
613
+ this.db = db;
614
+ this.dbName = dbName;
615
+ this.tabularRepository = new PostgresTabularStorage(db, dbName, DefaultKeyValueSchema, DefaultKeyValueKey);
616
+ }
617
+ }
618
+ // src/storage/PostgresVectorStorage.ts
619
+ import { cosineSimilarity } from "@workglow/util/schema";
620
+ import { StorageValidationError, getMetadataProperty, getVectorProperty } from "@workglow/storage";
621
+ var SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
622
+
623
+ class PostgresVectorStorage extends PostgresTabularStorage {
624
+ vectorDimensions;
625
+ vectorCtor;
626
+ vectorPropertyName;
627
+ metadataPropertyName;
628
+ constructor(db, table, schema, primaryKeyNames, indexes = [], dimensions, vectorCtor = Float32Array) {
629
+ super(db, table, schema, primaryKeyNames, indexes);
630
+ this.vectorDimensions = dimensions;
631
+ this.vectorCtor = vectorCtor;
632
+ const vectorProp = getVectorProperty(schema);
633
+ if (!vectorProp) {
634
+ throw new Error("Schema must have a property with type array and format TypedArray");
635
+ }
636
+ this.vectorPropertyName = vectorProp;
637
+ this.metadataPropertyName = getMetadataProperty(schema);
638
+ }
639
+ getVectorDimensions() {
640
+ return this.vectorDimensions;
641
+ }
642
+ async similaritySearch(query, options = {}) {
643
+ const { topK = 10, filter, scoreThreshold = 0 } = options;
644
+ try {
645
+ const queryVector = `[${Array.from(query).join(",")}]`;
646
+ const vectorCol = String(this.vectorPropertyName);
647
+ const metadataCol = this.metadataPropertyName ? String(this.metadataPropertyName) : null;
648
+ let sql = `
649
+ SELECT
650
+ *,
651
+ 1 - (${vectorCol} <=> $1::vector) as score
652
+ FROM "${this.table}"
653
+ `;
654
+ const params = [queryVector];
655
+ let paramIndex = 2;
656
+ if (filter && Object.keys(filter).length > 0 && metadataCol) {
657
+ const conditions = [];
658
+ for (const [key, value] of Object.entries(filter)) {
659
+ if (!SAFE_IDENTIFIER_RE.test(key)) {
660
+ throw new StorageValidationError(`Invalid metadata filter key: "${key}". Keys must match /^[a-zA-Z_][a-zA-Z0-9_]*$/.`);
661
+ }
662
+ conditions.push(`${metadataCol}->>'${key}' = $${paramIndex}`);
663
+ params.push(String(value));
664
+ paramIndex++;
665
+ }
666
+ sql += ` WHERE ${conditions.join(" AND ")}`;
667
+ }
668
+ if (scoreThreshold > 0) {
669
+ sql += filter ? " AND" : " WHERE";
670
+ sql += ` (1 - (${vectorCol} <=> $1::vector)) >= $${paramIndex}`;
671
+ params.push(scoreThreshold);
672
+ paramIndex++;
673
+ }
674
+ sql += ` ORDER BY ${vectorCol} <=> $1::vector LIMIT $${paramIndex}`;
675
+ params.push(topK);
676
+ const result = await this.db.query(sql, params);
677
+ const results = [];
678
+ for (const row of result.rows) {
679
+ const vectorResult = await this.db.query(`SELECT ${vectorCol}::text FROM "${this.table}" WHERE ${this.getPrimaryKeyWhereClause()}`, this.getPrimaryKeyValues(row));
680
+ const vectorStr = vectorResult.rows[0]?.[vectorCol] || "[]";
681
+ const vectorArray = JSON.parse(vectorStr);
682
+ results.push({
683
+ ...row,
684
+ [this.vectorPropertyName]: new this.vectorCtor(vectorArray),
685
+ score: parseFloat(row.score)
686
+ });
687
+ }
688
+ return results;
689
+ } catch (error) {
690
+ if (error instanceof StorageValidationError) {
691
+ throw error;
692
+ }
693
+ console.error("pgvector query failed, falling back to in-memory search:", error);
694
+ return this.searchFallback(query, options);
695
+ }
696
+ }
697
+ async hybridSearch(query, options) {
698
+ const { topK = 10, filter, scoreThreshold = 0, textQuery, vectorWeight = 0.7 } = options;
699
+ if (!textQuery || textQuery.trim().length === 0) {
700
+ return this.similaritySearch(query, { topK, filter, scoreThreshold });
701
+ }
702
+ try {
703
+ const queryVector = `[${Array.from(query).join(",")}]`;
704
+ const tsQueryText = textQuery;
705
+ const vectorCol = String(this.vectorPropertyName);
706
+ const metadataCol = this.metadataPropertyName ? String(this.metadataPropertyName) : null;
707
+ let sql = `
708
+ SELECT
709
+ *,
710
+ (
711
+ $2 * (1 - (${vectorCol} <=> $1::vector)) +
712
+ $3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text), plainto_tsquery('english', $4))
713
+ ) as score
714
+ FROM "${this.table}"
715
+ `;
716
+ const params = [queryVector, vectorWeight, 1 - vectorWeight, tsQueryText];
717
+ let paramIndex = 5;
718
+ if (filter && Object.keys(filter).length > 0 && metadataCol) {
719
+ const conditions = [];
720
+ for (const [key, value] of Object.entries(filter)) {
721
+ if (!SAFE_IDENTIFIER_RE.test(key)) {
722
+ throw new StorageValidationError(`Invalid metadata filter key: "${key}". Keys must match /^[a-zA-Z_][a-zA-Z0-9_]*$/.`);
723
+ }
724
+ conditions.push(`${metadataCol}->>'${key}' = $${paramIndex}`);
725
+ params.push(String(value));
726
+ paramIndex++;
727
+ }
728
+ sql += ` WHERE ${conditions.join(" AND ")}`;
729
+ }
730
+ if (scoreThreshold > 0) {
731
+ sql += filter ? " AND" : " WHERE";
732
+ sql += ` (
733
+ $2 * (1 - (${vectorCol} <=> $1::vector)) +
734
+ $3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text), plainto_tsquery('english', $4))
735
+ ) >= $${paramIndex}`;
736
+ params.push(scoreThreshold);
737
+ paramIndex++;
738
+ }
739
+ sql += ` ORDER BY score DESC LIMIT $${paramIndex}`;
740
+ params.push(topK);
741
+ const result = await this.db.query(sql, params);
742
+ const results = [];
743
+ for (const row of result.rows) {
744
+ const vectorResult = await this.db.query(`SELECT ${vectorCol}::text FROM "${this.table}" WHERE ${this.getPrimaryKeyWhereClause()}`, this.getPrimaryKeyValues(row));
745
+ const vectorStr = vectorResult.rows[0]?.[vectorCol] || "[]";
746
+ const vectorArray = JSON.parse(vectorStr);
747
+ results.push({
748
+ ...row,
749
+ [this.vectorPropertyName]: new this.vectorCtor(vectorArray),
750
+ score: parseFloat(row.score)
751
+ });
752
+ }
753
+ return results;
754
+ } catch (error) {
755
+ if (error instanceof StorageValidationError) {
756
+ throw error;
757
+ }
758
+ console.error("pgvector hybrid query failed, falling back to in-memory search:", error);
759
+ return this.hybridSearchFallback(query, options);
760
+ }
761
+ }
762
+ async searchFallback(query, options) {
763
+ const { topK = 10, filter, scoreThreshold = 0 } = options;
764
+ const allRows = await this.getAll() || [];
765
+ const results = [];
766
+ for (const row of allRows) {
767
+ const vector = row[this.vectorPropertyName];
768
+ const metadata = this.metadataPropertyName ? row[this.metadataPropertyName] : {};
769
+ if (filter && !this.matchesFilter(metadata, filter)) {
770
+ continue;
771
+ }
772
+ const score = cosineSimilarity(query, vector);
773
+ if (score >= scoreThreshold) {
774
+ results.push({ ...row, score });
775
+ }
776
+ }
777
+ results.sort((a, b) => b.score - a.score);
778
+ const topResults = results.slice(0, topK);
779
+ return topResults;
780
+ }
781
+ async hybridSearchFallback(query, options) {
782
+ const { topK = 10, filter, scoreThreshold = 0, textQuery, vectorWeight = 0.7 } = options;
783
+ const allRows = await this.getAll() || [];
784
+ const results = [];
785
+ const queryLower = textQuery.toLowerCase();
786
+ const queryWords = queryLower.split(/\s+/).filter((w) => w.length > 0);
787
+ for (const row of allRows) {
788
+ const vector = row[this.vectorPropertyName];
789
+ const metadata = this.metadataPropertyName ? row[this.metadataPropertyName] : {};
790
+ if (filter && !this.matchesFilter(metadata, filter)) {
791
+ continue;
792
+ }
793
+ const vectorScore = cosineSimilarity(query, vector);
794
+ const metadataText = Object.values(metadata ?? {}).join(" ").toLowerCase();
795
+ let textScore = 0;
796
+ if (queryWords.length > 0) {
797
+ let matches = 0;
798
+ for (const word of queryWords) {
799
+ if (metadataText.includes(word)) {
800
+ matches++;
801
+ }
802
+ }
803
+ textScore = matches / queryWords.length;
804
+ }
805
+ const combinedScore = vectorWeight * vectorScore + (1 - vectorWeight) * textScore;
806
+ if (combinedScore >= scoreThreshold) {
807
+ results.push({ ...row, score: combinedScore });
808
+ }
809
+ }
810
+ results.sort((a, b) => b.score - a.score);
811
+ const topResults = results.slice(0, topK);
812
+ return topResults;
813
+ }
814
+ getPrimaryKeyWhereClause() {
815
+ const conditions = this.primaryKeyNames.map((key, idx) => `${String(key)} = $${idx + 1}`);
816
+ return conditions.join(" AND ");
817
+ }
818
+ getPrimaryKeyValues(row) {
819
+ return this.primaryKeyNames.map((key) => row[key]);
820
+ }
821
+ matchesFilter(metadata, filter) {
822
+ for (const [key, value] of Object.entries(filter)) {
823
+ if (metadata[key] !== value) {
824
+ return false;
825
+ }
826
+ }
827
+ return true;
828
+ }
829
+ }
830
+ export {
831
+ loadPostgres,
832
+ getPostgres,
833
+ createPool,
834
+ PostgresVectorStorage,
835
+ PostgresTabularStorage,
836
+ PostgresKvStorage,
837
+ Postgres,
838
+ POSTGRES_TABULAR_REPOSITORY,
839
+ POSTGRES_KV_REPOSITORY
840
+ };
841
+
842
+ //# debugId=EAED4AF57300AAC364756E2164756E21