@workglow/storage 0.2.30 → 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.
Files changed (62) hide show
  1. package/dist/browser.js +998 -61
  2. package/dist/browser.js.map +24 -14
  3. package/dist/bun.js +1051 -66
  4. package/dist/bun.js.map +25 -15
  5. package/dist/common.d.ts +7 -0
  6. package/dist/common.d.ts.map +1 -1
  7. package/dist/migrations/IMigration.d.ts +57 -0
  8. package/dist/migrations/IMigration.d.ts.map +1 -0
  9. package/dist/migrations/MigrationRunner.d.ts +44 -0
  10. package/dist/migrations/MigrationRunner.d.ts.map +1 -0
  11. package/dist/migrations/TabularMigration.d.ts +85 -0
  12. package/dist/migrations/TabularMigration.d.ts.map +1 -0
  13. package/dist/migrations/TabularMigrationOrchestrator.d.ts +34 -0
  14. package/dist/migrations/TabularMigrationOrchestrator.d.ts.map +1 -0
  15. package/dist/migrations/index.d.ts +11 -0
  16. package/dist/migrations/index.d.ts.map +1 -0
  17. package/dist/migrations/runBackfill.d.ts +19 -0
  18. package/dist/migrations/runBackfill.d.ts.map +1 -0
  19. package/dist/node.js +1051 -66
  20. package/dist/node.js.map +25 -15
  21. package/dist/sql/Dialect.d.ts +26 -0
  22. package/dist/sql/Dialect.d.ts.map +1 -0
  23. package/dist/sql/PredicateBuilder.d.ts +30 -0
  24. package/dist/sql/PredicateBuilder.d.ts.map +1 -0
  25. package/dist/sql/PrefixDdl.d.ts +79 -0
  26. package/dist/sql/PrefixDdl.d.ts.map +1 -0
  27. package/dist/sql/index.d.ts +9 -0
  28. package/dist/sql/index.d.ts.map +1 -0
  29. package/dist/tabular/BaseSqlTabularStorage.d.ts +63 -2
  30. package/dist/tabular/BaseSqlTabularStorage.d.ts.map +1 -1
  31. package/dist/tabular/BaseTabularStorage.d.ts +111 -6
  32. package/dist/tabular/BaseTabularStorage.d.ts.map +1 -1
  33. package/dist/tabular/CachedTabularStorage.d.ts +38 -0
  34. package/dist/tabular/CachedTabularStorage.d.ts.map +1 -1
  35. package/dist/tabular/Cursor.d.ts +79 -0
  36. package/dist/tabular/Cursor.d.ts.map +1 -0
  37. package/dist/tabular/FsFolderTabularStorage.d.ts +5 -1
  38. package/dist/tabular/FsFolderTabularStorage.d.ts.map +1 -1
  39. package/dist/tabular/HuggingFaceTabularStorage.d.ts +26 -2
  40. package/dist/tabular/HuggingFaceTabularStorage.d.ts.map +1 -1
  41. package/dist/tabular/ITabularStorage.d.ts +203 -3
  42. package/dist/tabular/ITabularStorage.d.ts.map +1 -1
  43. package/dist/tabular/InMemoryTabularMigrationApplier.d.ts +39 -0
  44. package/dist/tabular/InMemoryTabularMigrationApplier.d.ts.map +1 -0
  45. package/dist/tabular/InMemoryTabularStorage.d.ts +6 -2
  46. package/dist/tabular/InMemoryTabularStorage.d.ts.map +1 -1
  47. package/dist/tabular/SharedInMemoryTabularStorage.d.ts +4 -1
  48. package/dist/tabular/SharedInMemoryTabularStorage.d.ts.map +1 -1
  49. package/dist/tabular/SqlTabularMigrationApplier.d.ts +53 -0
  50. package/dist/tabular/SqlTabularMigrationApplier.d.ts.map +1 -0
  51. package/dist/tabular/StorageError.d.ts.map +1 -1
  52. package/dist/tabular/TabularStorageRegistry.d.ts +13 -10
  53. package/dist/tabular/TabularStorageRegistry.d.ts.map +1 -1
  54. package/dist/tabular/TelemetryTabularStorage.d.ts +11 -1
  55. package/dist/tabular/TelemetryTabularStorage.d.ts.map +1 -1
  56. package/dist/tabular/sqlMigrationDdl.d.ts +11 -0
  57. package/dist/tabular/sqlMigrationDdl.d.ts.map +1 -0
  58. package/dist/vector/IVectorStorage.d.ts +61 -1
  59. package/dist/vector/IVectorStorage.d.ts.map +1 -1
  60. package/package.json +3 -3
  61. package/src/tabular/README.md +73 -0
  62. package/src/vector/README.md +79 -0
package/dist/node.js.map CHANGED
@@ -1,18 +1,26 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/tabular/BaseTabularStorage.ts", "../src/tabular/ITabularStorage.ts", "../src/tabular/StorageError.ts", "../src/tabular/CoveringIndexMissingError.ts", "../src/tabular/BaseSqlTabularStorage.ts", "../src/tabular/CachedTabularStorage.ts", "../src/tabular/InMemoryTabularStorage.ts", "../src/tabular/coveringIndexPicker.ts", "../src/tabular/HuggingFaceTabularStorage.ts", "../src/tabular/TabularStorageRegistry.ts", "../src/tabular/TelemetryTabularStorage.ts", "../src/kv/IKvStorage.ts", "../src/kv/InMemoryKvStorage.ts", "../src/kv/KvStorage.ts", "../src/kv/KvViaTabularStorage.ts", "../src/kv/TelemetryKvStorage.ts", "../src/util/HybridSubscriptionManager.ts", "../src/util/PollingSubscriptionManager.ts", "../src/vector/InMemoryVectorStorage.ts", "../src/vector/IVectorStorage.ts", "../src/vector/TelemetryVectorStorage.ts", "../src/credentials/EncryptedKvCredentialStore.ts", "../src/credentials/LazyEncryptedCredentialStore.ts", "../src/tabular/FsFolderTabularStorage.ts", "../src/kv/FsFolderJsonKvStorage.ts", "../src/kv/FsFolderKvStorage.ts", "../src/tabular/SharedInMemoryTabularStorage.ts"],
3
+ "sources": ["../src/tabular/BaseTabularStorage.ts", "../src/tabular/ITabularStorage.ts", "../src/tabular/StorageError.ts", "../src/tabular/CoveringIndexMissingError.ts", "../src/tabular/Cursor.ts", "../src/migrations/MigrationRunner.ts", "../src/migrations/TabularMigrationOrchestrator.ts", "../src/migrations/runBackfill.ts", "../src/tabular/BaseSqlTabularStorage.ts", "../src/tabular/CachedTabularStorage.ts", "../src/tabular/InMemoryTabularStorage.ts", "../src/tabular/InMemoryTabularMigrationApplier.ts", "../src/tabular/coveringIndexPicker.ts", "../src/tabular/HuggingFaceTabularStorage.ts", "../src/tabular/TabularStorageRegistry.ts", "../src/sql/Dialect.ts", "../src/tabular/sqlMigrationDdl.ts", "../src/tabular/SqlTabularMigrationApplier.ts", "../src/tabular/TelemetryTabularStorage.ts", "../src/kv/IKvStorage.ts", "../src/kv/InMemoryKvStorage.ts", "../src/kv/KvStorage.ts", "../src/kv/KvViaTabularStorage.ts", "../src/kv/TelemetryKvStorage.ts", "../src/util/HybridSubscriptionManager.ts", "../src/util/PollingSubscriptionManager.ts", "../src/sql/PredicateBuilder.ts", "../src/sql/PrefixDdl.ts", "../src/vector/InMemoryVectorStorage.ts", "../src/vector/IVectorStorage.ts", "../src/vector/TelemetryVectorStorage.ts", "../src/credentials/EncryptedKvCredentialStore.ts", "../src/credentials/LazyEncryptedCredentialStore.ts", "../src/tabular/FsFolderTabularStorage.ts", "../src/kv/FsFolderJsonKvStorage.ts", "../src/kv/FsFolderKvStorage.ts", "../src/tabular/SharedInMemoryTabularStorage.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken, EventEmitter, makeFingerprint } from \"@workglow/util\";\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n isSearchCondition,\n ITabularStorage,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularEventListener,\n TabularEventListeners,\n TabularEventName,\n TabularEventParameters,\n TabularSubscribeOptions,\n ValueOptionType,\n} from \"./ITabularStorage\";\nimport { CoveringIndexMissingError } from \"./CoveringIndexMissingError\";\nimport {\n StorageEmptyCriteriaError,\n StorageInvalidColumnError,\n StorageInvalidLimitError,\n StorageValidationError,\n} from \"./StorageError\";\n\nexport const TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository\"\n);\n\n/**\n * Options for controlling how client-provided values for auto-generated keys are handled\n */\nexport type ClientProvidedKeysOption = \"never\" | \"if-missing\" | \"always\";\n\n/**\n * Generation strategy for auto-generated keys\n */\nexport type KeyGenerationStrategy = \"autoincrement\" | \"uuid\";\n\n/**\n * Abstract base class for tabular storage repositories.\n * Provides functionality for storing and retrieving data with typed\n * primary keys and values, and supports compound keys and partial key lookup.\n * Has a basic event emitter for listening to repository events.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport abstract class BaseTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType = InsertEntity<Entity, AutoGeneratedKeys<Schema>>,\n> implements ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, InsertType> {\n /** Event emitter for repository events */\n protected events = new EventEmitter<TabularEventListeners<PrimaryKey, Entity>>();\n\n protected indexes: Array<keyof Entity>[];\n protected primaryKeySchema: DataPortSchemaObject;\n protected valueSchema: DataPortSchemaObject;\n\n /** Name of the auto-generated key column (at most one primary key column may be auto-generated) */\n protected autoGeneratedKeyName: keyof Entity | null = null;\n /** Strategy for generating the auto-generated key */\n protected autoGeneratedKeyStrategy: KeyGenerationStrategy | null = null;\n /** How to handle client-provided values for auto-generated keys */\n protected clientProvidedKeys: ClientProvidedKeysOption;\n\n /**\n * Creates a new BaseTabularStorage instance\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n constructor(\n protected schema: Schema,\n protected primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\"\n ) {\n this.clientProvidedKeys = clientProvidedKeys;\n const primaryKeyProps: Record<string, any> = {};\n const valueProps: Record<string, any> = {};\n const primaryKeySet = new Set(primaryKeyNames);\n\n // Split the schema properties into primary key and value properties\n for (const [key, typeDef] of Object.entries(schema.properties)) {\n if (primaryKeySet.has(key as keyof Schema[\"properties\"])) {\n primaryKeyProps[key] = Object.assign({}, typeDef);\n } else {\n valueProps[key] = Object.assign({}, typeDef);\n }\n }\n\n // Filter required array to only include primary key fields\n const primaryKeyRequired =\n schema.required?.filter((key) => primaryKeySet.has(key as keyof Schema[\"properties\"])) ?? [];\n // Filter required array to only include value fields\n const valueRequired =\n schema.required?.filter((key) => !primaryKeySet.has(key as keyof Schema[\"properties\"])) ?? [];\n\n this.primaryKeySchema = {\n type: \"object\",\n properties: primaryKeyProps,\n required: primaryKeyRequired,\n additionalProperties: false,\n } as DataPortSchemaObject;\n this.valueSchema = {\n type: \"object\",\n properties: valueProps,\n required: valueRequired,\n additionalProperties: false,\n } as DataPortSchemaObject;\n\n // validate all combined columns names are \"identifier\" names\n const combinedColumns = [...this.primaryKeyColumns(), ...this.valueColumns()];\n for (const column of combinedColumns) {\n if (typeof column !== \"string\") {\n throw new Error(\"Column names must be strings\");\n }\n if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(column)) {\n throw new Error(\n \"Column names must start with a letter and contain only letters, digits, and underscores\"\n );\n }\n }\n\n // Normalize searchable into array of arrays\n this.indexes = indexes.map((spec) => (Array.isArray(spec) ? spec : [spec])) as Array<\n Array<keyof Entity>\n >;\n\n // searchable is an array of compound keys, which are arrays of columns\n // clean up searchable array by removing any compoundkeys that are a prefix of another compoundkey\n // or a prefix of the primary key\n this.indexes = this.filterCompoundKeys(\n this.primaryKeyColumns() as unknown as Array<keyof Entity>,\n this.indexes\n );\n\n // Validate searchable columns\n for (const compoundIndex of this.indexes) {\n for (const column of compoundIndex) {\n if (\n !(column in this.primaryKeySchema.properties) &&\n !(column in this.valueSchema.properties)\n ) {\n throw new Error(\n `Searchable column ${String(column)} is not in the primary key schema or value schema`\n );\n }\n }\n }\n\n // Detect and validate auto-generated keys (at most one PK column; any PK position is allowed).\n // Composite keys often put a scope column first (e.g. kb_id) and auto-generate a second id.\n const autoGeneratedKeys: string[] = [];\n for (const key of primaryKeyNames) {\n const keyStr = String(key);\n const propDef = (schema.properties as any)[keyStr];\n if (propDef && typeof propDef === \"object\" && \"x-auto-generated\" in propDef) {\n if (propDef[\"x-auto-generated\"] === true) {\n autoGeneratedKeys.push(keyStr);\n }\n }\n }\n\n if (autoGeneratedKeys.length > 1) {\n throw new Error(\n `Multiple auto-generated keys detected: ${autoGeneratedKeys.join(\", \")}. ` +\n `At most one primary key column can be auto-generated.`\n );\n }\n\n if (autoGeneratedKeys.length > 0) {\n const autoGenKeyName = autoGeneratedKeys[0];\n\n this.autoGeneratedKeyName = autoGenKeyName as keyof Entity;\n this.autoGeneratedKeyStrategy = this.determineGenerationStrategy(\n autoGenKeyName,\n schema.properties[autoGenKeyName as keyof Schema[\"properties\"]]\n );\n }\n }\n\n protected filterCompoundKeys(\n primaryKey: Array<keyof Entity>,\n potentialKeys: Array<keyof Entity>[]\n ): Array<keyof Entity>[] {\n // Function to check if one array is a prefix of another\n const isPrefix = (prefix: Array<keyof Entity>, arr: Array<keyof Entity>): boolean => {\n if (prefix.length > arr.length) return false;\n return prefix.every((val, index) => val === arr[index]);\n };\n\n // Sort potential keys by length\n potentialKeys.sort((a, b) => a.length - b.length);\n\n let filteredKeys: Array<keyof Entity>[] = [];\n\n for (let i = 0; i < potentialKeys.length; i++) {\n let key = potentialKeys[i];\n\n if (isPrefix(key, primaryKey)) continue;\n\n // Keep single-column indexes regardless of being a prefix\n if (key.length === 1) {\n filteredKeys.push(key);\n continue;\n }\n\n // Skip if the key is a prefix of a later key in the list\n let isRedundant = potentialKeys.some((otherKey, j) => j > i && isPrefix(key, otherKey));\n\n if (!isRedundant) {\n filteredKeys.push(key);\n }\n }\n\n return filteredKeys;\n }\n\n /**\n * Adds an event listener for a specific event\n * @param name The name of the event to listen for\n * @param fn The callback function to execute when the event occurs\n */\n on<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ) {\n this.events.on(name, fn);\n }\n\n /**\n * Removes an event listener for a specific event\n * @param name The name of the event to remove the listener from\n * @param fn The callback function to remove\n */\n off<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ) {\n this.events.off(name, fn);\n }\n\n /**\n * Adds an event listener that will only be called once\n * @param name The name of the event to listen for\n * @param fn The callback function to execute when the event occurs\n */\n once<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ) {\n this.events.once(name, fn);\n }\n\n /**\n * Emits an event with the specified name and arguments\n * @param name The name of the event to emit\n * @param args The arguments to pass to the event listeners\n */\n emit<Event extends TabularEventName>(\n name: Event,\n ...args: TabularEventParameters<Event, PrimaryKey, Entity>\n ) {\n this.events.emit(name, ...args);\n }\n\n /**\n * Returns when the event was emitted (promise form of once)\n * @param name The name of the event to check\n * @returns true if the event has listeners, false otherwise\n */\n waitOn<Event extends TabularEventName>(\n name: Event\n ): Promise<TabularEventParameters<Event, PrimaryKey, Entity>> {\n return this.events.waitOn(name) as Promise<TabularEventParameters<Event, PrimaryKey, Entity>>;\n }\n\n /**\n * Core abstract methods that must be implemented by concrete repositories\n */\n abstract put(value: InsertType): Promise<Entity>;\n abstract putBulk(values: InsertType[]): Promise<Entity[]>;\n abstract get(key: PrimaryKey): Promise<Entity | undefined>;\n abstract delete(key: PrimaryKey | Entity): Promise<void>;\n abstract getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined>;\n abstract deleteAll(): Promise<void>;\n abstract size(): Promise<number>;\n\n /**\n * Counts entries matching the specified search criteria.\n * Concrete storage implementations can override this with native count APIs.\n */\n async count(criteria?: SearchCriteria<Entity>): Promise<number> {\n if (!criteria || Object.keys(criteria).length === 0) {\n return await this.size();\n }\n const entities = await this.query(criteria);\n return entities?.length ?? 0;\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n */\n abstract deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void>;\n\n /**\n * Queries entries matching the specified search criteria with optional ordering and limit.\n * Unlike `search`, this method does not require an index and supports comparison operators,\n * ORDER BY, and LIMIT.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering and limit options\n * @returns Array of matching entities or undefined if no matches found\n */\n abstract query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined>;\n\n /**\n * Strict, projected query served entirely by a covering compound index.\n * Subclasses that support index-only reads (e.g. IndexedDB, InMemory) should\n * override this with an efficient implementation. The default throws\n * {@link CoveringIndexMissingError} so callers get a consistent error when\n * the storage backend has not implemented index-only projection.\n */\n queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n this.validateSelect(options);\n const requiredColumns = [\n ...Object.keys(criteria),\n ...(options.orderBy ?? []).map((o) => String(o.column)),\n ...options.select.map(String),\n ];\n throw new CoveringIndexMissingError(this.constructor.name, requiredColumns, []);\n }\n\n /**\n * Abstract method to fetch a page of records.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Promise resolving to an array of entities or undefined if no records found\n */\n abstract getBulk(offset: number, limit: number): Promise<Entity[] | undefined>;\n\n /**\n * Async generator that yields records one at a time.\n * Uses getBulk internally to fetch pages of data.\n * @param pageSize - Number of records to fetch per page (default: 100)\n * @yields Individual entity records\n */\n async *records(pageSize: number = 100): AsyncGenerator<Entity, void, undefined> {\n if (pageSize <= 0) {\n throw new RangeError(`pageSize must be greater than 0, got ${pageSize}`);\n }\n let offset = 0;\n while (true) {\n const page = await this.getBulk(offset, pageSize);\n if (!page || page.length === 0) {\n break;\n }\n for (const entity of page) {\n yield entity;\n }\n if (page.length < pageSize) {\n break;\n }\n offset += pageSize;\n }\n }\n\n /**\n * Async generator that yields pages of records.\n * Uses getBulk internally to fetch pages of data.\n * @param pageSize - Number of records per page (default: 100)\n * @yields Arrays of entities\n */\n async *pages(pageSize: number = 100): AsyncGenerator<Entity[], void, undefined> {\n if (pageSize <= 0) {\n throw new RangeError(`pageSize must be greater than 0, got ${pageSize}`);\n }\n let offset = 0;\n while (true) {\n const page = await this.getBulk(offset, pageSize);\n if (!page || page.length === 0) {\n break;\n }\n yield page;\n if (page.length < pageSize) {\n break;\n }\n offset += pageSize;\n }\n }\n\n /**\n * Subscribes to changes in the repository (including remote changes).\n * Default implementation throws an error - override in subclasses that support subscriptions.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options (e.g., polling interval)\n * @returns Unsubscribe function\n * @throws Error if not implemented by the concrete repository\n */\n public subscribeToChanges(\n _callback: (change: TabularChangePayload<Entity>) => void,\n _options?: TabularSubscribeOptions\n ): () => void {\n throw new Error(\n `subscribeToChanges is not implemented for ${this.constructor.name}. ` +\n `All concrete repository implementations should override this method.`\n );\n }\n\n /**\n * Validates query parameters: criteria columns, options, and operator values.\n * @throws StorageEmptyCriteriaError if criteria is empty\n * @throws StorageInvalidLimitError if limit <= 0\n * @throws StorageValidationError if offset < 0\n * @throws StorageInvalidColumnError if any column name is not in the schema\n */\n protected validateQueryParams(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): void {\n const criteriaKeys = Object.keys(criteria) as Array<keyof Entity>;\n\n if (criteriaKeys.length === 0) {\n throw new StorageEmptyCriteriaError();\n }\n\n if (options?.limit !== undefined && options.limit <= 0) {\n throw new StorageInvalidLimitError(options.limit);\n }\n\n if (options?.offset !== undefined && options.offset < 0) {\n throw new StorageValidationError(`Query offset must be non-negative, got ${options.offset}`);\n }\n\n // Validate criteria column names exist in schema\n for (const column of criteriaKeys) {\n if (!(column in this.schema.properties)) {\n throw new StorageInvalidColumnError(String(column));\n }\n // Validate operator values at runtime\n const criterion = criteria[column];\n if (isSearchCondition(criterion)) {\n const validOperators = [\"=\", \"<\", \"<=\", \">\", \">=\"];\n if (!validOperators.includes(criterion.operator)) {\n throw new StorageValidationError(\n `Invalid operator \"${criterion.operator}\". Must be one of: ${validOperators.join(\", \")}`\n );\n }\n }\n }\n\n // Validate orderBy column names\n if (options?.orderBy) {\n const validDirections = [\"ASC\", \"DESC\"];\n for (const { column, direction } of options.orderBy) {\n if (!(column in this.schema.properties)) {\n throw new StorageInvalidColumnError(String(column));\n }\n if (!validDirections.includes(direction)) {\n throw new StorageValidationError(\n `Invalid sort direction \"${direction}\". Must be \"ASC\" or \"DESC\"`\n );\n }\n }\n }\n }\n\n /**\n * Validates getAll options (orderBy, limit, offset) without criteria.\n */\n protected validateGetAllOptions(options?: QueryOptions<Entity>): void {\n if (!options) return;\n\n if (options.limit !== undefined && options.limit <= 0) {\n throw new StorageInvalidLimitError(options.limit);\n }\n\n if (options.offset !== undefined && options.offset < 0) {\n throw new StorageValidationError(`Query offset must be non-negative, got ${options.offset}`);\n }\n\n if (options.orderBy) {\n const validDirections = [\"ASC\", \"DESC\"];\n for (const { column, direction } of options.orderBy) {\n if (!(column in this.schema.properties)) {\n throw new StorageInvalidColumnError(String(column));\n }\n if (!validDirections.includes(direction)) {\n throw new StorageValidationError(\n `Invalid sort direction \"${direction}\". Must be \"ASC\" or \"DESC\"`\n );\n }\n }\n }\n }\n\n /**\n * Validates the `select` array in a {@link CoveringIndexQueryOptions}.\n * Throws {@link StorageValidationError} when `select` is empty or contains\n * a column that is not in the schema's `properties`.\n */\n protected validateSelect<K extends keyof Entity & string>(\n options: CoveringIndexQueryOptions<Entity, K>\n ): void {\n if (!options.select || options.select.length === 0) {\n throw new StorageValidationError(\"queryIndex requires a non-empty select array\");\n }\n const schemaProps = Object.keys(this.schema.properties);\n for (const col of options.select) {\n const colStr = String(col);\n if (!schemaProps.includes(colStr)) {\n throw new StorageValidationError(`queryIndex select column \"${colStr}\" is not in schema`);\n }\n }\n }\n\n protected primaryKeyColumns(): Array<keyof PrimaryKey> {\n const columns: Array<keyof PrimaryKey> = [];\n for (const key of Object.keys(this.primaryKeySchema.properties)) {\n columns.push(key as keyof PrimaryKey);\n }\n return columns;\n }\n\n protected valueColumns(): Array<keyof Value> {\n const columns: Array<keyof Value> = [];\n for (const key of Object.keys(this.valueSchema.properties)) {\n columns.push(key as keyof Value);\n }\n return columns;\n }\n\n /**\n * Utility method to separate a combined object into its key and value components\n * for storage.\n * @param obj - Entity row object\n * @returns Separated key and value objects\n */\n protected separateKeyValueFromCombined(obj: Entity): { value: Value; key: PrimaryKey } {\n if (obj === null) {\n console.warn(\"Key is null\");\n return { value: {} as Value, key: {} as PrimaryKey };\n }\n if (typeof obj !== \"object\") {\n console.warn(\"Object is not an object\");\n return { value: {} as Value, key: {} as PrimaryKey };\n }\n const primaryKeyNames = this.primaryKeyColumns();\n const valueNames = this.valueColumns();\n const value: Partial<Value> = {};\n const key: Partial<PrimaryKey> = {};\n const objRecord = obj as Record<string, unknown>;\n for (const k of primaryKeyNames) {\n (key as Record<string, unknown>)[k as string] = objRecord[k as string];\n }\n for (const k of valueNames) {\n (value as Record<string, unknown>)[k as string] = objRecord[k as string];\n }\n\n return { value: value as Value, key: key as PrimaryKey };\n }\n\n /**\n * Generates a consistent string identifier for a given key.\n *\n * @param key - Primary key to convert\n * @returns Promise resolving to a string fingerprint of the key\n */\n protected async getKeyAsIdString(key: PrimaryKey): Promise<string> {\n return await makeFingerprint(key);\n }\n\n /**\n * Converts a primary key object into an ordered array based on the schema\n * This ensures consistent parameter ordering for storage operations\n * @param key - The primary key object to convert\n * @returns Array of key values ordered according to the schema\n */\n protected getPrimaryKeyAsOrderedArray(key: PrimaryKey): ValueOptionType[] {\n const orderedParams: ValueOptionType[] = [];\n const keyObj = key as Record<string, ValueOptionType>;\n for (const k in this.primaryKeySchema.properties) {\n if (k in keyObj) {\n orderedParams.push(keyObj[k]);\n } else {\n throw new Error(`Missing required primary key field: ${k}`);\n }\n }\n return orderedParams;\n }\n\n /**\n * Finds the best matching index for a set of search keys.\n * @param unorderedSearchKey - Unordered array of keys being searched, can be reordered\n * @returns Array of column names representing the best matching index, or undefined if no suitable index is found\n */\n public findBestMatchingIndex(\n unorderedSearchKey: Array<keyof Entity>\n ): Array<keyof Entity> | undefined {\n if (!unorderedSearchKey.length) return undefined;\n\n const allKeys: Array<keyof Entity>[] = [\n this.primaryKeyColumns() as unknown as Array<keyof Entity>,\n ...(this.indexes as Array<keyof Entity>[]),\n ];\n\n const searchKeySet = new Set(unorderedSearchKey);\n\n const hasMatchingPrefix = (index: Array<keyof Entity>): boolean => {\n // Check if the first column of the index is in our search keys\n return index.length > 0 && searchKeySet.has(index[0]);\n };\n\n let bestMatch: Array<keyof Entity> | undefined;\n let bestMatchScore = 0;\n\n for (const index of allKeys) {\n if (hasMatchingPrefix(index)) {\n // Calculate how many consecutive search keys we can use from this index\n let score = 0;\n for (const col of index) {\n if (!searchKeySet.has(col)) break;\n score++;\n }\n\n if (score > bestMatchScore) {\n bestMatch = index;\n bestMatchScore = score;\n }\n }\n }\n\n return bestMatch;\n }\n\n /**\n * Checks if this storage has an auto-generated key configured\n * @returns true if an auto-generated key is configured\n */\n protected hasAutoGeneratedKey(): boolean {\n return this.autoGeneratedKeyName !== null;\n }\n\n /**\n * Checks if a given column name is the auto-generated key\n * @param name - Column name to check\n * @returns true if the column is the auto-generated key\n */\n protected isAutoGeneratedKey(name: string): boolean {\n return this.autoGeneratedKeyName !== null && String(this.autoGeneratedKeyName) === name;\n }\n\n /**\n * Determines the generation strategy for an auto-generated key based on its type\n * @param columnName - Name of the column\n * @param typeDef - JSON Schema type definition for the column\n * @returns The generation strategy to use\n */\n protected determineGenerationStrategy(columnName: string, typeDef: any): KeyGenerationStrategy {\n // Extract the actual type if it's a union with null\n let actualType = typeDef;\n if (typeDef && typeof typeDef === \"object\") {\n if (typeDef.anyOf && Array.isArray(typeDef.anyOf)) {\n actualType = typeDef.anyOf.find((t: any) => t.type !== \"null\") || typeDef;\n } else if (typeDef.oneOf && Array.isArray(typeDef.oneOf)) {\n actualType = typeDef.oneOf.find((t: any) => t.type !== \"null\") || typeDef;\n }\n }\n\n if (typeof actualType !== \"object\") {\n return \"uuid\";\n }\n\n // Integer types use autoincrement\n if (actualType.type === \"integer\") {\n return \"autoincrement\";\n }\n\n // Default to UUID for strings and other types\n return \"uuid\";\n }\n\n /**\n * Generates a key value for client-side key generation\n * Override in storage classes that generate keys client-side (InMemory, IndexedDB for UUIDs)\n * SQL-based storages typically generate keys server-side\n * @param columnName - Name of the column to generate a key for\n * @param strategy - The generation strategy to use\n * @returns The generated key value\n */\n protected generateKeyValue(\n columnName: string,\n strategy: KeyGenerationStrategy\n ): Promise<string | number> | string | number {\n throw new Error(\n `generateKeyValue not implemented for ${this.constructor.name}. ` +\n `Column: ${columnName}, Strategy: ${strategy}`\n );\n }\n\n /**\n * Sets up the database/storage for the repository.\n * Must be called before using any other methods (except for in-memory implementations).\n * Default implementation is a no-op - override in subclasses that need setup.\n */\n async setupDatabase(): Promise<void> {\n // no op by default\n }\n\n /**\n * Destroys the repository and frees up resources.\n */\n destroy(): void {\n // no op by default\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n this.destroy();\n }\n\n [Symbol.dispose](): void {\n this.destroy();\n }\n}\n",
6
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { EventParameters } from \"@workglow/util\";\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\n\n// Generic type for possible value types in the repository\nexport type ValueOptionType = string | number | bigint | boolean | null | Uint8Array;\n\n/**\n * Type definitions for tabular repository events\n */\nexport type TabularEventListeners<PrimaryKey, Entity> = {\n put: (entity: Entity) => void;\n get: (key: PrimaryKey, entity: Entity | undefined) => void;\n query: (key: Partial<Entity>, entities: Entity[] | undefined) => void;\n delete: (key: keyof Entity) => void;\n clearall: () => void;\n};\n\nexport type TabularEventName = keyof TabularEventListeners<any, any>;\nexport type TabularEventListener<\n Event extends TabularEventName,\n PrimaryKey,\n Entity,\n> = TabularEventListeners<PrimaryKey, Entity>[Event];\n\nexport type TabularEventParameters<\n Event extends TabularEventName,\n PrimaryKey,\n Entity,\n> = EventParameters<TabularEventListeners<PrimaryKey, Entity>, Event>;\n\n/**\n * Type of change that occurred in the repository\n */\nexport type TabularChangeType = \"INSERT\" | \"UPDATE\" | \"DELETE\";\n\n/**\n * Payload describing a change to an entity\n */\nexport interface TabularChangePayload<Entity> {\n readonly type: TabularChangeType;\n readonly old?: Entity;\n readonly new?: Entity;\n}\n\n/**\n * Options for subscribing to changes in a tabular repository\n */\nexport interface TabularSubscribeOptions {\n /** Polling interval in milliseconds (used by implementations that rely on polling) */\n readonly pollingIntervalMs?: number;\n}\n\nexport type JSONValue =\n | string\n | number\n | boolean\n | null\n | JSONValue[]\n | { [key: string]: JSONValue };\n\n/**\n * Comparison operators for search and deleteSearch operations\n */\nexport type SearchOperator = \"=\" | \"<\" | \"<=\" | \">\" | \">=\";\n\n/**\n * A search condition with a value and comparison operator\n */\nexport interface SearchCondition<T> {\n readonly value: T;\n readonly operator: SearchOperator;\n}\n\n/**\n * Criteria for deleteSearch operations supporting multiple columns.\n * Each column can have either a direct value (equality) or a SearchCondition with an operator.\n *\n * @example\n * // Equality match\n * { category: \"electronics\" }\n *\n * // With operator\n * { createdAt: { value: date, operator: \"<\" } }\n *\n * // Multiple columns\n * { category: \"electronics\", createdAt: { value: date, operator: \"<\" } }\n */\nexport type DeleteSearchCriteria<Entity> = {\n readonly [K in keyof Entity]?: Entity[K] | SearchCondition<Entity[K]>;\n};\n\nexport type SearchCriteria<Entity> = DeleteSearchCriteria<Entity>;\n\nexport type SortDirection = \"ASC\" | \"DESC\";\n\nexport interface OrderBy<Entity> {\n readonly column: keyof Entity;\n readonly direction: SortDirection;\n}\n\nexport interface QueryOptions<Entity> {\n readonly orderBy?: ReadonlyArray<OrderBy<Entity>>;\n readonly limit?: number;\n readonly offset?: number;\n}\n\nexport interface CoveringIndexQueryOptions<Entity, K extends keyof Entity & string> {\n readonly select: readonly K[];\n readonly orderBy?: ReadonlyArray<OrderBy<Entity>>;\n readonly limit?: number;\n readonly offset?: number;\n}\n\n/**\n * Type guard to check if a value is a SearchCondition\n */\nexport function isSearchCondition<T>(value: unknown): value is SearchCondition<T> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"value\" in value &&\n \"operator\" in value &&\n typeof (value as SearchCondition<T>).operator === \"string\"\n );\n}\n\n/**\n * Helper type to compute PrimaryKey while deferring Entity resolution.\n * Uses a conditional type to avoid forcing full Entity resolution at class definition time.\n *\n */\nexport type SimplifyPrimaryKey<\n Entity,\n KeyName extends ReadonlyArray<keyof any>,\n> = Entity extends any ? Pick<Entity, Extract<KeyName[number], keyof Entity>> : never;\n\n/**\n * Extracts property names marked as auto-generated from the schema.\n * Properties with `x-auto-generated: true` are considered auto-generated.\n */\nexport type AutoGeneratedKeys<Schema extends DataPortSchemaObject> = {\n [K in keyof Schema[\"properties\"]]: Schema[\"properties\"][K] extends { \"x-auto-generated\": true }\n ? K\n : never;\n}[keyof Schema[\"properties\"]];\n\n/**\n * Entity type for insertion - auto-generated keys are optional.\n * This allows clients to omit auto-generated keys when inserting entities.\n */\nexport type InsertEntity<Entity, AutoGenKeys> = Omit<Entity, AutoGenKeys & keyof Entity> &\n Partial<Pick<Entity, AutoGenKeys & keyof Entity>>;\n\n/**\n * Interface defining the contract for tabular storage repositories.\n * Provides a flexible interface for storing and retrieving data with typed\n * primary keys and values, and supports compound keys and partial key lookup.\n *\n * @typeParam Schema - The schema definition for the entity using JSON Schema\n * @typeParam PrimaryKeyNames - Array of property names that form the primary key\n */\nexport interface ITabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n InsertType = InsertEntity<Entity, AutoGeneratedKeys<Schema>>,\n> {\n // Core methods\n put(value: InsertType): Promise<Entity>;\n putBulk(values: InsertType[]): Promise<Entity[]>;\n get(key: PrimaryKey): Promise<Entity | undefined>;\n delete(key: PrimaryKey | Entity): Promise<void>;\n getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined>;\n deleteAll(): Promise<void>;\n size(): Promise<number>;\n /**\n * Counts rows matching the specified search criteria without requiring\n * callers to load the matching entities.\n *\n * @param criteria - Optional object with column names as keys and values or SearchConditions\n * @returns Count of matching rows\n */\n count(criteria?: SearchCriteria<Entity>): Promise<number>;\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @example\n * // Delete by equality\n * await repo.deleteSearch({ category: \"electronics\" });\n *\n * // Delete with operator\n * await repo.deleteSearch({ createdAt: { value: date, operator: \"<\" } });\n *\n * // Delete with multiple criteria (AND)\n * await repo.deleteSearch({ category: \"electronics\", value: { value: 100, operator: \"<\" } });\n */\n deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void>;\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n getBulk(offset: number, limit: number): Promise<Entity[] | undefined>;\n\n /**\n * Async generator that yields records one at a time.\n * @param pageSize - Number of records to fetch per page (default: 100)\n * @yields Individual entity records\n */\n records(pageSize?: number): AsyncGenerator<Entity, void, undefined>;\n\n /**\n * Async generator that yields pages of records.\n * @param pageSize - Number of records per page (default: 100)\n * @yields Arrays of entities\n */\n pages(pageSize?: number): AsyncGenerator<Entity[], void, undefined>;\n\n // Event handling methods\n on<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void;\n off<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void;\n emit<Event extends TabularEventName>(\n name: Event,\n ...args: TabularEventParameters<Event, PrimaryKey, Entity>\n ): void;\n once<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void;\n waitOn<Event extends TabularEventName>(\n name: Event\n ): Promise<TabularEventParameters<Event, PrimaryKey, Entity>>;\n\n /**\n * Queries entries matching the specified search criteria with optional ordering, limit, and offset.\n * Uses optimized index paths when possible, falls back to full scan otherwise.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of matching entities or undefined if no matches found\n */\n query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined>;\n\n /**\n * Strict, projected query served entirely by a covering compound index.\n * Throws CoveringIndexMissingError when no registered index can serve\n * (criteria + orderBy + select). Returns Pick<Entity, K>[] — never the heavy fields.\n *\n * @param criteria - equality (and optionally non-equality) filters\n * @param options - select (required), orderBy, limit, offset\n * @returns array of projected rows (empty array, not undefined, when no matches)\n */\n queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]>;\n\n /**\n * Subscribes to changes in the repository (including remote changes).\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options (e.g., polling interval)\n * @returns Unsubscribe function\n */\n subscribeToChanges(\n callback: (change: TabularChangePayload<Entity>) => void,\n options?: TabularSubscribeOptions\n ): () => void;\n\n /**\n * Sets up the database/storage for the repository.\n * Must be called before using any other methods (except for in-memory implementations).\n * @returns Promise that resolves when setup is complete\n */\n setupDatabase(): Promise<void>;\n\n // Destroy the repository and frees up resources.\n destroy(): void;\n [Symbol.dispose](): void;\n [Symbol.asyncDispose](): Promise<void>;\n}\n\nexport type AnyTabularStorage = Omit<ITabularStorage<any, any, any, any, any>, \"queryIndex\"> & {\n queryIndex(criteria: any, options: any): Promise<any[]>;\n};\n",
7
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { BaseError } from \"@workglow/util\";\n\nexport class StorageError extends BaseError {\n static override readonly type: string = \"StorageError\";\n}\n\nexport class StorageValidationError extends StorageError {\n static override readonly type: string = \"StorageValidationError\";\n}\n\nexport class StorageEmptyCriteriaError extends StorageValidationError {\n static override readonly type: string = \"StorageEmptyCriteriaError\";\n constructor() {\n super(\"Query criteria must not be empty. Use getAll() to retrieve all records.\");\n }\n}\n\nexport class StorageInvalidLimitError extends StorageValidationError {\n static override readonly type: string = \"StorageInvalidLimitError\";\n constructor(limit: number) {\n super(`Query limit must be greater than 0, got ${limit}`);\n }\n}\n\nexport class StorageInvalidColumnError extends StorageValidationError {\n static override readonly type: string = \"StorageInvalidColumnError\";\n constructor(column: string) {\n super(`Column \"${column}\" does not exist in the schema`);\n }\n}\n\nexport class StorageUnsupportedError extends StorageError {\n static override readonly type: string = \"StorageUnsupportedError\";\n constructor(operation: string, backend: string) {\n super(`${operation} is not supported for ${backend}`);\n }\n}\n",
5
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken, EventEmitter, makeFingerprint } from \"@workglow/util\";\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n isSearchCondition,\n ITabularStorage,\n OrderBy,\n Page,\n PageRequest,\n QueryOptions,\n SearchCondition,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularEventListener,\n TabularEventListeners,\n TabularEventName,\n TabularEventParameters,\n TabularSubscribeOptions,\n ValueOptionType,\n} from \"./ITabularStorage\";\nimport { CoveringIndexMissingError } from \"./CoveringIndexMissingError\";\nimport {\n assertCursorMatches,\n CursorPayload,\n decodeCursor,\n encodeCursor,\n PageCursor,\n} from \"./Cursor\";\nimport {\n StorageEmptyCriteriaError,\n StorageInvalidColumnError,\n StorageInvalidLimitError,\n StorageUnsupportedError,\n StorageValidationError,\n} from \"./StorageError\";\nimport {\n ITabularMigration,\n ITabularMigrationApplier,\n runTabularMigrations,\n RunTabularMigrationsOptions,\n} from \"../migrations\";\n\nexport const TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository\"\n);\n\n/**\n * Converts a row column value into a JSON-serialisable form for cursor\n * encoding, and is also used by the in-memory comparator so that row\n * values and decoded cursor values are normalised the same way before\n * comparison. Returning `string | number | boolean | null` keeps the\n * comparison space simple (lexicographic on strings, numeric on numbers).\n *\n * - `Date` becomes its ISO 8601 string. ISO 8601 sorts chronologically\n * under lexicographic comparison, which is the property we rely on to\n * compare a row's `Date` against a cursor's encoded date string.\n * - `bigint` is rejected. JS `bigint > bigint` works numerically, but\n * once a bigint is encoded into the cursor as a decimal string, naive\n * string comparison no longer matches the numeric order (`\"10\" < \"9\"`).\n * Mixing the two in the in-memory comparator would silently mis-order\n * pages. Until the cursor format carries explicit type information,\n * keyset paging on a bigint column is unsupported — fail loudly so\n * callers either change the column type or override `getPage`.\n * - `Uint8Array` and other object types are not orderable as cursor keys.\n */\nfunction toCursorValue(value: unknown): string | number | boolean | null {\n if (value === null || value === undefined) return null;\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n return value;\n }\n if (value instanceof Date) return value.toISOString();\n // Use `StorageValidationError` (not generic `Error`) so the failure\n // mode for an unsupported key type matches the rest of the cursor\n // codec — `decodeCursor` already throws the same class for malformed\n // input, and downstream callers translating cursor failures into 4xx\n // responses can catch one consistent type instead of an unclassified\n // 500.\n if (typeof value === \"bigint\") {\n throw new StorageValidationError(\n \"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.\"\n );\n }\n throw new StorageValidationError(\n `Cannot encode value of type ${typeof value} into a pagination cursor; use a key column with a primitive type.`\n );\n}\n\n/**\n * Compares two cursor key values, normalising each side through\n * {@link toCursorValue} first so a row's runtime value (which may be a\n * `Date` or other rich type) is comparable against a cursor's decoded\n * primitive. Nulls sort before non-nulls, then standard `<`/`>`. Booleans\n * are coerced to numbers (`false` < `true`) so the sort and the keyset\n * filter can never disagree on boolean ordering. Returns -1, 0, or 1.\n */\nfunction compareKeyValues(a: unknown, b: unknown): number {\n const an = toCursorValue(a);\n const bn = toCursorValue(b);\n if (an === null && bn === null) return 0;\n if (an === null) return -1;\n if (bn === null) return 1;\n const av = typeof an === \"boolean\" ? Number(an) : an;\n const bv = typeof bn === \"boolean\" ? Number(bn) : bn;\n if (av < bv) return -1;\n if (av > bv) return 1;\n return 0;\n}\n\n/**\n * Options for controlling how client-provided values for auto-generated keys are handled\n */\nexport type ClientProvidedKeysOption = \"never\" | \"if-missing\" | \"always\";\n\n/**\n * Generation strategy for auto-generated keys\n */\nexport type KeyGenerationStrategy = \"autoincrement\" | \"uuid\";\n\n/**\n * Abstract base class for tabular storage repositories.\n * Provides functionality for storing and retrieving data with typed\n * primary keys and values, and supports compound keys and partial key lookup.\n * Has a basic event emitter for listening to repository events.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport abstract class BaseTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType = InsertEntity<Entity, AutoGeneratedKeys<Schema>>,\n> implements ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, InsertType> {\n /** Event emitter for repository events */\n protected events = new EventEmitter<TabularEventListeners<PrimaryKey, Entity>>();\n\n protected indexes: Array<keyof Entity>[];\n protected primaryKeySchema: DataPortSchemaObject;\n protected valueSchema: DataPortSchemaObject;\n\n /**\n * Optional declarative migrations evolving older deployments to the\n * current target schema. When set + non-empty, `setupDatabase()` on\n * concrete subclasses delegates to {@link applyTabularMigrations}.\n */\n protected readonly tabularMigrations: ReadonlyArray<ITabularMigration> | undefined;\n\n /**\n * Default component name used when an `ITabularMigration.component` is\n * omitted. Defaults to `tabular:${storageName}` once the subclass has\n * supplied a name.\n */\n protected migrationComponent: string = \"tabular:unnamed\";\n\n /** Name of the auto-generated key column (at most one primary key column may be auto-generated) */\n protected autoGeneratedKeyName: keyof Entity | null = null;\n /** Strategy for generating the auto-generated key */\n protected autoGeneratedKeyStrategy: KeyGenerationStrategy | null = null;\n /** How to handle client-provided values for auto-generated keys */\n protected clientProvidedKeys: ClientProvidedKeysOption;\n\n /**\n * Creates a new BaseTabularStorage instance\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n constructor(\n protected schema: Schema,\n protected primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\",\n tabularMigrations?: ReadonlyArray<ITabularMigration>,\n migrationName?: string\n ) {\n this.tabularMigrations = tabularMigrations;\n if (migrationName) {\n this.migrationComponent = `tabular:${migrationName}`;\n }\n this.clientProvidedKeys = clientProvidedKeys;\n const primaryKeyProps: Record<string, any> = {};\n const valueProps: Record<string, any> = {};\n const primaryKeySet = new Set(primaryKeyNames);\n\n // Split the schema properties into primary key and value properties\n for (const [key, typeDef] of Object.entries(schema.properties)) {\n if (primaryKeySet.has(key as keyof Schema[\"properties\"])) {\n primaryKeyProps[key] = Object.assign({}, typeDef);\n } else {\n valueProps[key] = Object.assign({}, typeDef);\n }\n }\n\n // Filter required array to only include primary key fields\n const primaryKeyRequired =\n schema.required?.filter((key) => primaryKeySet.has(key as keyof Schema[\"properties\"])) ?? [];\n // Filter required array to only include value fields\n const valueRequired =\n schema.required?.filter((key) => !primaryKeySet.has(key as keyof Schema[\"properties\"])) ?? [];\n\n this.primaryKeySchema = {\n type: \"object\",\n properties: primaryKeyProps,\n required: primaryKeyRequired,\n additionalProperties: false,\n } as DataPortSchemaObject;\n this.valueSchema = {\n type: \"object\",\n properties: valueProps,\n required: valueRequired,\n additionalProperties: false,\n } as DataPortSchemaObject;\n\n // validate all combined columns names are \"identifier\" names\n const combinedColumns = [...this.primaryKeyColumns(), ...this.valueColumns()];\n for (const column of combinedColumns) {\n if (typeof column !== \"string\") {\n throw new Error(\"Column names must be strings\");\n }\n if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(column)) {\n throw new Error(\n \"Column names must start with a letter and contain only letters, digits, and underscores\"\n );\n }\n }\n\n // Normalize searchable into array of arrays\n this.indexes = indexes.map((spec) => (Array.isArray(spec) ? spec : [spec])) as Array<\n Array<keyof Entity>\n >;\n\n // searchable is an array of compound keys, which are arrays of columns\n // clean up searchable array by removing any compoundkeys that are a prefix of another compoundkey\n // or a prefix of the primary key\n this.indexes = this.filterCompoundKeys(\n this.primaryKeyColumns() as unknown as Array<keyof Entity>,\n this.indexes\n );\n\n // Validate searchable columns\n for (const compoundIndex of this.indexes) {\n for (const column of compoundIndex) {\n if (\n !(column in this.primaryKeySchema.properties) &&\n !(column in this.valueSchema.properties)\n ) {\n throw new Error(\n `Searchable column ${String(column)} is not in the primary key schema or value schema`\n );\n }\n }\n }\n\n // Detect and validate auto-generated keys (at most one PK column; any PK position is allowed).\n // Composite keys often put a scope column first (e.g. kb_id) and auto-generate a second id.\n const autoGeneratedKeys: string[] = [];\n for (const key of primaryKeyNames) {\n const keyStr = String(key);\n const propDef = (schema.properties as any)[keyStr];\n if (propDef && typeof propDef === \"object\" && \"x-auto-generated\" in propDef) {\n if (propDef[\"x-auto-generated\"] === true) {\n autoGeneratedKeys.push(keyStr);\n }\n }\n }\n\n if (autoGeneratedKeys.length > 1) {\n throw new Error(\n `Multiple auto-generated keys detected: ${autoGeneratedKeys.join(\", \")}. ` +\n `At most one primary key column can be auto-generated.`\n );\n }\n\n if (autoGeneratedKeys.length > 0) {\n const autoGenKeyName = autoGeneratedKeys[0];\n\n this.autoGeneratedKeyName = autoGenKeyName as keyof Entity;\n this.autoGeneratedKeyStrategy = this.determineGenerationStrategy(\n autoGenKeyName,\n schema.properties[autoGenKeyName as keyof Schema[\"properties\"]]\n );\n }\n }\n\n protected filterCompoundKeys(\n primaryKey: Array<keyof Entity>,\n potentialKeys: Array<keyof Entity>[]\n ): Array<keyof Entity>[] {\n // Function to check if one array is a prefix of another\n const isPrefix = (prefix: Array<keyof Entity>, arr: Array<keyof Entity>): boolean => {\n if (prefix.length > arr.length) return false;\n return prefix.every((val, index) => val === arr[index]);\n };\n\n // Sort potential keys by length\n potentialKeys.sort((a, b) => a.length - b.length);\n\n let filteredKeys: Array<keyof Entity>[] = [];\n\n for (let i = 0; i < potentialKeys.length; i++) {\n let key = potentialKeys[i];\n\n if (isPrefix(key, primaryKey)) continue;\n\n // Keep single-column indexes regardless of being a prefix\n if (key.length === 1) {\n filteredKeys.push(key);\n continue;\n }\n\n // Skip if the key is a prefix of a later key in the list\n let isRedundant = potentialKeys.some((otherKey, j) => j > i && isPrefix(key, otherKey));\n\n if (!isRedundant) {\n filteredKeys.push(key);\n }\n }\n\n return filteredKeys;\n }\n\n /**\n * Adds an event listener for a specific event\n * @param name The name of the event to listen for\n * @param fn The callback function to execute when the event occurs\n */\n on<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ) {\n this.events.on(name, fn);\n }\n\n /**\n * Removes an event listener for a specific event\n * @param name The name of the event to remove the listener from\n * @param fn The callback function to remove\n */\n off<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ) {\n this.events.off(name, fn);\n }\n\n /**\n * Adds an event listener that will only be called once\n * @param name The name of the event to listen for\n * @param fn The callback function to execute when the event occurs\n */\n once<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ) {\n this.events.once(name, fn);\n }\n\n /**\n * Emits an event with the specified name and arguments\n * @param name The name of the event to emit\n * @param args The arguments to pass to the event listeners\n */\n emit<Event extends TabularEventName>(\n name: Event,\n ...args: TabularEventParameters<Event, PrimaryKey, Entity>\n ) {\n this.events.emit(name, ...args);\n }\n\n /**\n * Returns when the event was emitted (promise form of once)\n * @param name The name of the event to check\n * @returns true if the event has listeners, false otherwise\n */\n waitOn<Event extends TabularEventName>(\n name: Event\n ): Promise<TabularEventParameters<Event, PrimaryKey, Entity>> {\n return this.events.waitOn(name) as Promise<TabularEventParameters<Event, PrimaryKey, Entity>>;\n }\n\n /**\n * Core abstract methods that must be implemented by concrete repositories\n */\n abstract put(value: InsertType): Promise<Entity>;\n abstract putBulk(values: InsertType[]): Promise<Entity[]>;\n abstract get(key: PrimaryKey): Promise<Entity | undefined>;\n abstract delete(key: PrimaryKey | Entity): Promise<void>;\n abstract getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined>;\n abstract deleteAll(): Promise<void>;\n abstract size(): Promise<number>;\n\n /**\n * Counts entries matching the specified search criteria.\n * Concrete storage implementations can override this with native count APIs.\n */\n async count(criteria?: SearchCriteria<Entity>): Promise<number> {\n if (!criteria || Object.keys(criteria).length === 0) {\n return await this.size();\n }\n const entities = await this.query(criteria);\n return entities?.length ?? 0;\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n */\n abstract deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void>;\n\n /**\n * Queries entries matching the specified search criteria with optional ordering and limit.\n * Unlike `search`, this method does not require an index and supports comparison operators,\n * ORDER BY, and LIMIT.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering and limit options\n * @returns Array of matching entities or undefined if no matches found\n */\n abstract query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined>;\n\n /**\n * Strict, projected query served entirely by a covering compound index.\n * Subclasses that support index-only reads (e.g. IndexedDB, InMemory) should\n * override this with an efficient implementation. The default throws\n * {@link CoveringIndexMissingError} so callers get a consistent error when\n * the storage backend has not implemented index-only projection.\n */\n queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n this.validateSelect(options);\n const requiredColumns = [\n ...Object.keys(criteria),\n ...(options.orderBy ?? []).map((o) => String(o.column)),\n ...options.select.map(String),\n ];\n throw new CoveringIndexMissingError(this.constructor.name, requiredColumns, []);\n }\n\n /**\n * Abstract method to fetch a page of records.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Promise resolving to an array of entities or undefined if no records found\n */\n abstract getBulk(offset: number, limit: number): Promise<Entity[] | undefined>;\n\n /**\n * Async generator that yields records one at a time.\n *\n * Backed by cursor-based pagination so iteration is stable when rows are\n * inserted or deleted concurrently — unlike offset paging, which can skip\n * or duplicate rows under those conditions.\n *\n * @param pageSize - Number of records to fetch per page (default: 100)\n */\n async *records(pageSize: number = 100): AsyncGenerator<Entity, void, undefined> {\n if (pageSize <= 0) {\n throw new RangeError(`pageSize must be greater than 0, got ${pageSize}`);\n }\n let cursor: PageCursor | undefined;\n while (true) {\n const page = await this.getPage({ limit: pageSize, cursor });\n for (const entity of page.items) {\n yield entity;\n }\n if (!page.nextCursor || page.items.length === 0) break;\n cursor = page.nextCursor;\n }\n }\n\n /**\n * Async generator that yields pages of records.\n *\n * Backed by cursor-based pagination — stable under concurrent writes.\n * Each yielded array is a fresh copy of `Page.items`; mutating the\n * yielded array won't affect the underlying storage or subsequent\n * pages, at the cost of one allocation per page.\n *\n * @param pageSize - Number of records per page (default: 100)\n */\n async *pages(pageSize: number = 100): AsyncGenerator<Entity[], void, undefined> {\n if (pageSize <= 0) {\n throw new RangeError(`pageSize must be greater than 0, got ${pageSize}`);\n }\n let cursor: PageCursor | undefined;\n while (true) {\n const page = await this.getPage({ limit: pageSize, cursor });\n if (page.items.length > 0) {\n yield page.items.slice() as Entity[];\n }\n if (!page.nextCursor || page.items.length === 0) break;\n cursor = page.nextCursor;\n }\n }\n\n /**\n * Default keyset-pagination implementation. Backends that can express\n * tuple comparison natively (SQL row-value comparisons) should override\n * this for efficiency, but the default works against any implementation\n * of {@link query} / {@link getAll}.\n */\n async getPage(request: PageRequest<Entity> = {}): Promise<Page<Entity>> {\n return this.runPage(undefined, request);\n }\n\n /** Default cursor-paginated form of {@link query}. */\n async queryPage(\n criteria: SearchCriteria<Entity>,\n request: PageRequest<Entity> = {}\n ): Promise<Page<Entity>> {\n return this.runPage(criteria, request);\n }\n\n /**\n * Shared keyset-pagination engine for both {@link getPage} and\n * {@link queryPage}. Translates the cursor into AND criteria using the\n * primary key column(s) and runs the existing {@link query}/{@link getAll}\n * machinery, then re-encodes the position of the last returned row.\n *\n * For composite primary keys this performs the keyset comparison in\n * memory after fetching a candidate window. SQL backends should override\n * {@link getPage}/{@link queryPage} to push it down to the database.\n */\n protected async runPage(\n criteria: SearchCriteria<Entity> | undefined,\n request: PageRequest<Entity>\n ): Promise<Page<Entity>> {\n this.validatePageRequest(request);\n const limit = request.limit ?? 100;\n\n const pkColumns = this.primaryKeyColumns() as unknown as Array<keyof Entity>;\n const orderBy = request.orderBy;\n const effectiveOrderBy = this.buildEffectiveOrderBy(orderBy, pkColumns);\n const effectiveOrderForCursor = effectiveOrderBy.map((o) => ({\n column: String(o.column),\n direction: o.direction,\n }));\n\n let cursorPayload: CursorPayload | undefined;\n if (request.cursor !== undefined) {\n cursorPayload = decodeCursor(request.cursor);\n assertCursorMatches(cursorPayload, effectiveOrderForCursor);\n }\n\n const pkCol = pkColumns[0];\n const userCriteria = criteria ?? ({} as SearchCriteria<Entity>);\n const userTouchesPk =\n pkColumns.length === 1 && Object.prototype.hasOwnProperty.call(userCriteria, pkCol);\n\n // The simple-keyset path pushes one `pk OP ?` predicate into `query()`.\n // It works whenever the effective ordering is exactly \"primary key in\n // some direction\" — that's true both when the caller passed no orderBy\n // (we default to PK ASC) and when they passed exactly the primary key\n // themselves (any direction). We still bail out if the caller already\n // has a criterion on the PK column, because we'd otherwise overwrite it.\n const canPushKeyset =\n pkColumns.length === 1 &&\n effectiveOrderBy.length === 1 &&\n effectiveOrderBy[0].column === pkCol &&\n !userTouchesPk;\n\n let queryCriteria: SearchCriteria<Entity> = userCriteria;\n // Three pushdown tiers, in decreasing precision:\n //\n // 1. Simple keyset (`canPushKeyset` true): the effective ordering is\n // a single PK column, so one `pk OP ?` inequality is exactly the\n // keyset filter. Backend returns at most `limit` correct rows.\n //\n // 2. Leading-column pushdown (the fallback below): the effective\n // ordering is compound (or non-PK + PK tiebreaker). We can't\n // express the full keyset through `query()` (which is AND-only),\n // but we *can* push the leading column's inequality to prune the\n // bulk of the table, then sort and apply the full multi-column\n // keyset in memory. Bounded fetch as long as the leading column\n // isn't degenerate (single value across the whole table).\n //\n // 3. Full scan (last resort): leading column has a NULL cursor value\n // we can't express, or the user already filters on that column,\n // or there's no cursor (page 1). We fetch everything, sort, slice.\n // Backends with tuple comparison should override `getPage` to\n // avoid this.\n const useFallback = !canPushKeyset;\n\n if (cursorPayload && canPushKeyset) {\n const direction = effectiveOrderBy[0].direction;\n const op = direction === \"ASC\" ? \">\" : \"<\";\n // canPushKeyset implies a single-column PK as the only effective\n // ordering, so cursorPayload has exactly one entry and `c[0]` is the\n // last seen PK value. PK columns are NOT NULL so there's no need for\n // the IS NULL gymnastics the SQL/in-memory keyset paths handle.\n // The simple-keyset path's correctness depends on the backend's\n // `query()` calling `jsToSqlValue` (or equivalent) on the\n // SearchCondition value — every concrete backend in this monorepo\n // does, but a third-party backend would need to as well.\n const lastPk = cursorPayload.c[0] as Entity[keyof Entity];\n const keysetCondition: SearchCondition<Entity[keyof Entity]> = {\n value: lastPk,\n operator: op,\n };\n queryCriteria = {\n ...userCriteria,\n [pkCol]: keysetCondition,\n } as SearchCriteria<Entity>;\n } else if (cursorPayload && useFallback) {\n // Tier 2: prune the bulk of the table by pushing the leading\n // ordering column's inequality into `query()`. We push only for\n // ASC because:\n // - ASC NULLs-first: rows `>= cursor[0]` include the cursor row\n // plus anything strictly greater; the in-memory keyset filter\n // then drops the cursor row and any earlier-on-tiebreaker rows.\n // NULL rows (which sort before in ASC NULLs-first) are\n // correctly excluded by `>=` because SQL `null >= x` is null.\n // - DESC NULLs-last would need `(col <= ? OR col IS NULL)` to\n // keep the NULL trailer rows that sort *after* the cursor.\n // `query()` is AND-only and can't express the OR, so pushing\n // `col <= ?` would silently drop the NULL trailer. We fall\n // through to a full scan instead — slower, but correct on\n // nullable DESC orderings. SQL backends override `getPage`\n // entirely so they don't pay this price.\n // Skipped when the user already filters on the leading column\n // (we'd trample their criterion) or the cursor's leading value\n // is NULL (no representable `col > NULL` in NULLs-first ASC).\n const leading = effectiveOrderBy[0];\n const leadingCol = leading.column;\n const leadingCursor = cursorPayload.c[0];\n const userTouchesLeading = Object.prototype.hasOwnProperty.call(userCriteria, leadingCol);\n if (leading.direction === \"ASC\" && leadingCursor !== null && !userTouchesLeading) {\n const leadingCondition: SearchCondition<Entity[keyof Entity]> = {\n value: leadingCursor as Entity[keyof Entity],\n operator: \">=\",\n };\n queryCriteria = {\n ...userCriteria,\n [leadingCol]: leadingCondition,\n } as SearchCriteria<Entity>;\n }\n }\n\n const fetchLimit = useFallback ? undefined : limit;\n\n const queryOptions: QueryOptions<Entity> = {\n orderBy: effectiveOrderBy,\n ...(fetchLimit !== undefined ? { limit: fetchLimit } : {}),\n };\n\n let rows: Entity[] | undefined;\n // Tracks whether we wound up using `getAll()` because `query()` was\n // unsupported and we fell back. In that case the backend never\n // applied our keyset / leading-column predicate, so we must drive\n // the in-memory sort + keyset filter ourselves even if `useFallback`\n // is false (which it would be when the *simple-keyset* path tried to\n // push `pk OP ?` to `query()` and got rejected — without this flag\n // the cursor predicate would never run and pages would repeat rows).\n let forcedFallback = false;\n if (Object.keys(queryCriteria).length === 0) {\n rows = await this.getAll(queryOptions);\n } else {\n try {\n rows = await this.query(queryCriteria, queryOptions);\n } catch (err) {\n // Backends like FsFolder don't implement `query()`. Falling back\n // to a full scan keeps the call correct as long as we then sort\n // and filter in memory ourselves. User-supplied criteria still\n // need to fail loudly, so we only swallow the error when we'd\n // added our own pushdown criterion AND the user passed none.\n const userHadNoCriteria = !criteria || Object.keys(criteria).length === 0;\n if (err instanceof StorageUnsupportedError && userHadNoCriteria) {\n rows = await this.getAll({ orderBy: effectiveOrderBy });\n forcedFallback = true;\n } else {\n throw err;\n }\n }\n }\n\n let items: Entity[] = rows ?? [];\n\n if (useFallback || forcedFallback) {\n // Keyset paging requires rows to be seen in a definite order. Some\n // backends don't honour `orderBy` reliably (FsFolder, certain remote\n // services), so we re-sort here to guarantee correctness rather than\n // trust the backend.\n items = this.sortInMemory(items.slice(), effectiveOrderBy);\n if (cursorPayload) {\n items = this.applyKeysetFilter(items, cursorPayload, effectiveOrderBy, pkColumns);\n }\n }\n\n if (items.length > limit) {\n items = items.slice(0, limit);\n }\n\n const nextCursor =\n items.length === limit\n ? this.buildCursor(items[items.length - 1], effectiveOrderBy)\n : undefined;\n\n return { items, nextCursor };\n }\n\n /**\n * Returns the order spec actually used to drive a keyset page: the\n * caller's `orderBy` (if any) followed by every primary-key column as a\n * stable tiebreaker. PK columns are appended in ASC direction unless the\n * caller already specified a direction for them.\n */\n protected buildEffectiveOrderBy(\n orderBy: ReadonlyArray<OrderBy<Entity>> | undefined,\n pkColumns: ReadonlyArray<keyof Entity>\n ): ReadonlyArray<OrderBy<Entity>> {\n const result: OrderBy<Entity>[] = orderBy ? orderBy.slice() : [];\n const seen = new Set(result.map((o) => o.column));\n for (const pk of pkColumns) {\n if (!seen.has(pk)) {\n result.push({ column: pk, direction: \"ASC\" });\n }\n }\n return result;\n }\n\n /**\n * Sorts rows in place by the given order spec, using the same null-first\n * comparison rules as the cursor filter so results are consistent.\n */\n protected sortInMemory(rows: Entity[], orderBy: ReadonlyArray<OrderBy<Entity>>): Entity[] {\n rows.sort((a, b) => {\n for (const { column, direction } of orderBy) {\n // `compareKeyValues` runs each side through `toCursorValue`, so\n // we don't need (and shouldn't) coerce types here — passing the\n // raw row value lets `Date` (and any other type the encoder\n // supports) compare correctly against a decoded cursor value.\n const cmp = compareKeyValues(a[column], b[column]);\n if (cmp !== 0) return direction === \"ASC\" ? cmp : -cmp;\n }\n return 0;\n });\n return rows;\n }\n\n /**\n * In-memory keyset filter: drops rows that come at or before the cursor\n * row according to `orderBy + primaryKey`. Used by the default\n * implementation when the backend can't express tuple comparison.\n */\n protected applyKeysetFilter(\n rows: Entity[],\n cursor: CursorPayload,\n effectiveOrderBy: ReadonlyArray<OrderBy<Entity>>,\n _pkColumns: ReadonlyArray<keyof Entity>\n ): Entity[] {\n return rows.filter((row) => {\n for (let i = 0; i < effectiveOrderBy.length; i++) {\n const { column, direction } = effectiveOrderBy[i];\n // `compareKeyValues` normalises both sides through `toCursorValue`,\n // so passing the raw row value here lets a `Date` row column be\n // compared against the decoded ISO string in the cursor without a\n // type-mismatch surprise.\n const cmp = compareKeyValues(row[column], cursor.c[i]);\n if (cmp === 0) continue;\n return direction === \"ASC\" ? cmp > 0 : cmp < 0;\n }\n return false; // exact match -> already returned, skip\n });\n }\n\n /**\n * Encodes the position of `row` as an opaque cursor. The caller passes\n * the effective ordering it just used — caller-supplied `orderBy`\n * followed by any primary-key columns not already covered by `orderBy`\n * — and this writes one value per effective column, in the same order.\n * Computing that ordering is the caller's responsibility because they\n * already have it in hand and we don't want to recompute it here.\n */\n protected buildCursor(row: Entity, effectiveOrderBy: ReadonlyArray<OrderBy<Entity>>): PageCursor {\n const n = effectiveOrderBy.map((spec) => String(spec.column));\n const d = effectiveOrderBy.map((spec): \"a\" | \"d\" => (spec.direction === \"ASC\" ? \"a\" : \"d\"));\n const c = effectiveOrderBy.map((spec) => toCursorValue(row[spec.column]));\n return encodeCursor({ v: 1, n, d, c });\n }\n\n /**\n * Subscribes to changes in the repository (including remote changes).\n * Default implementation throws an error - override in subclasses that support subscriptions.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options (e.g., polling interval)\n * @returns Unsubscribe function\n * @throws Error if not implemented by the concrete repository\n */\n public subscribeToChanges(\n _callback: (change: TabularChangePayload<Entity>) => void,\n _options?: TabularSubscribeOptions\n ): () => void {\n throw new Error(\n `subscribeToChanges is not implemented for ${this.constructor.name}. ` +\n `All concrete repository implementations should override this method.`\n );\n }\n\n /**\n * Validates query parameters: criteria columns, options, and operator values.\n * @throws StorageEmptyCriteriaError if criteria is empty\n * @throws StorageInvalidLimitError if limit <= 0\n * @throws StorageValidationError if offset < 0\n * @throws StorageInvalidColumnError if any column name is not in the schema\n */\n protected validateQueryParams(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): void {\n const criteriaKeys = Object.keys(criteria) as Array<keyof Entity>;\n\n if (criteriaKeys.length === 0) {\n throw new StorageEmptyCriteriaError();\n }\n\n if (options?.limit !== undefined && options.limit <= 0) {\n throw new StorageInvalidLimitError(options.limit);\n }\n\n if (options?.offset !== undefined && options.offset < 0) {\n throw new StorageValidationError(`Query offset must be non-negative, got ${options.offset}`);\n }\n\n // Validate criteria column names exist in schema\n for (const column of criteriaKeys) {\n if (!(column in this.schema.properties)) {\n throw new StorageInvalidColumnError(String(column));\n }\n // Validate operator values at runtime\n const criterion = criteria[column];\n if (isSearchCondition(criterion)) {\n const validOperators = [\"=\", \"<\", \"<=\", \">\", \">=\"];\n if (!validOperators.includes(criterion.operator)) {\n throw new StorageValidationError(\n `Invalid operator \"${criterion.operator}\". Must be one of: ${validOperators.join(\", \")}`\n );\n }\n }\n }\n\n this.validateOrderBy(options?.orderBy);\n }\n\n /**\n * Validates getAll options (orderBy, limit, offset) without criteria.\n */\n protected validateGetAllOptions(options?: QueryOptions<Entity>): void {\n if (!options) return;\n\n if (options.limit !== undefined && options.limit <= 0) {\n throw new StorageInvalidLimitError(options.limit);\n }\n\n if (options.offset !== undefined && options.offset < 0) {\n throw new StorageValidationError(`Query offset must be non-negative, got ${options.offset}`);\n }\n\n this.validateOrderBy(options.orderBy);\n }\n\n /**\n * Validates the `orderBy` clause of a {@link PageRequest}: column names\n * must exist in the schema and directions must be `\"ASC\"` or `\"DESC\"`.\n *\n * Page paths (`getPage`/`queryPage`) must call this before any backend\n * builds SQL, because column names from `request.orderBy` end up\n * interpolated directly into `ORDER BY` and the keyset `WHERE` clauses.\n * Without this guard a caller passing arbitrary strings would either\n * trigger a backend SQL error or, worse, allow SQL injection on backends\n * that emit unquoted identifiers.\n */\n protected validateOrderBy(orderBy: ReadonlyArray<OrderBy<Entity>> | undefined): void {\n if (!orderBy) return;\n const validDirections = [\"ASC\", \"DESC\"];\n for (const { column, direction } of orderBy) {\n if (typeof column !== \"string\") {\n throw new StorageInvalidColumnError(String(column));\n }\n if (!(column in this.schema.properties)) {\n throw new StorageInvalidColumnError(String(column));\n }\n if (!validDirections.includes(direction)) {\n throw new StorageValidationError(\n `Invalid sort direction \"${direction}\". Must be \"ASC\" or \"DESC\"`\n );\n }\n }\n }\n\n /**\n * Validates the components of a cursor-paginated request that are about\n * to be used to build a query. Centralises the schema-level guarantees\n * that `runPage`/`runSqlPage`/Supabase's `runSupabasePage` all rely on\n * before they interpolate column names into SQL or PostgREST filters.\n */\n protected validatePageRequest(request: PageRequest<Entity>): void {\n if (request.limit !== undefined) {\n if (!Number.isInteger(request.limit) || request.limit <= 0) {\n throw new StorageInvalidLimitError(request.limit);\n }\n }\n this.validateOrderBy(request.orderBy);\n }\n\n /**\n * Validates the `select` array in a {@link CoveringIndexQueryOptions}.\n * Throws {@link StorageValidationError} when `select` is empty or contains\n * a column that is not in the schema's `properties`.\n */\n protected validateSelect<K extends keyof Entity & string>(\n options: CoveringIndexQueryOptions<Entity, K>\n ): void {\n if (!options.select || options.select.length === 0) {\n throw new StorageValidationError(\"queryIndex requires a non-empty select array\");\n }\n const schemaProps = Object.keys(this.schema.properties);\n for (const col of options.select) {\n const colStr = String(col);\n if (!schemaProps.includes(colStr)) {\n throw new StorageValidationError(`queryIndex select column \"${colStr}\" is not in schema`);\n }\n }\n }\n\n protected primaryKeyColumns(): Array<keyof PrimaryKey> {\n const columns: Array<keyof PrimaryKey> = [];\n for (const key of Object.keys(this.primaryKeySchema.properties)) {\n columns.push(key as keyof PrimaryKey);\n }\n return columns;\n }\n\n protected valueColumns(): Array<keyof Value> {\n const columns: Array<keyof Value> = [];\n for (const key of Object.keys(this.valueSchema.properties)) {\n columns.push(key as keyof Value);\n }\n return columns;\n }\n\n /**\n * Utility method to separate a combined object into its key and value components\n * for storage.\n * @param obj - Entity row object\n * @returns Separated key and value objects\n */\n protected separateKeyValueFromCombined(obj: Entity): { value: Value; key: PrimaryKey } {\n if (obj === null) {\n console.warn(\"Key is null\");\n return { value: {} as Value, key: {} as PrimaryKey };\n }\n if (typeof obj !== \"object\") {\n console.warn(\"Object is not an object\");\n return { value: {} as Value, key: {} as PrimaryKey };\n }\n const primaryKeyNames = this.primaryKeyColumns();\n const valueNames = this.valueColumns();\n const value: Partial<Value> = {};\n const key: Partial<PrimaryKey> = {};\n const objRecord = obj as Record<string, unknown>;\n for (const k of primaryKeyNames) {\n (key as Record<string, unknown>)[k as string] = objRecord[k as string];\n }\n for (const k of valueNames) {\n (value as Record<string, unknown>)[k as string] = objRecord[k as string];\n }\n\n return { value: value as Value, key: key as PrimaryKey };\n }\n\n /**\n * Generates a consistent string identifier for a given key.\n *\n * @param key - Primary key to convert\n * @returns Promise resolving to a string fingerprint of the key\n */\n protected async getKeyAsIdString(key: PrimaryKey): Promise<string> {\n return await makeFingerprint(key);\n }\n\n /**\n * Converts a primary key object into an ordered array based on the schema\n * This ensures consistent parameter ordering for storage operations\n * @param key - The primary key object to convert\n * @returns Array of key values ordered according to the schema\n */\n protected getPrimaryKeyAsOrderedArray(key: PrimaryKey): ValueOptionType[] {\n const orderedParams: ValueOptionType[] = [];\n const keyObj = key as Record<string, ValueOptionType>;\n for (const k in this.primaryKeySchema.properties) {\n if (k in keyObj) {\n orderedParams.push(keyObj[k]);\n } else {\n throw new Error(`Missing required primary key field: ${k}`);\n }\n }\n return orderedParams;\n }\n\n /**\n * Finds the best matching index for a set of search keys.\n * @param unorderedSearchKey - Unordered array of keys being searched, can be reordered\n * @returns Array of column names representing the best matching index, or undefined if no suitable index is found\n */\n public findBestMatchingIndex(\n unorderedSearchKey: Array<keyof Entity>\n ): Array<keyof Entity> | undefined {\n if (!unorderedSearchKey.length) return undefined;\n\n const allKeys: Array<keyof Entity>[] = [\n this.primaryKeyColumns() as unknown as Array<keyof Entity>,\n ...(this.indexes as Array<keyof Entity>[]),\n ];\n\n const searchKeySet = new Set(unorderedSearchKey);\n\n const hasMatchingPrefix = (index: Array<keyof Entity>): boolean => {\n // Check if the first column of the index is in our search keys\n return index.length > 0 && searchKeySet.has(index[0]);\n };\n\n let bestMatch: Array<keyof Entity> | undefined;\n let bestMatchScore = 0;\n\n for (const index of allKeys) {\n if (hasMatchingPrefix(index)) {\n // Calculate how many consecutive search keys we can use from this index\n let score = 0;\n for (const col of index) {\n if (!searchKeySet.has(col)) break;\n score++;\n }\n\n if (score > bestMatchScore) {\n bestMatch = index;\n bestMatchScore = score;\n }\n }\n }\n\n return bestMatch;\n }\n\n /**\n * Checks if this storage has an auto-generated key configured\n * @returns true if an auto-generated key is configured\n */\n protected hasAutoGeneratedKey(): boolean {\n return this.autoGeneratedKeyName !== null;\n }\n\n /**\n * Checks if a given column name is the auto-generated key\n * @param name - Column name to check\n * @returns true if the column is the auto-generated key\n */\n protected isAutoGeneratedKey(name: string): boolean {\n return this.autoGeneratedKeyName !== null && String(this.autoGeneratedKeyName) === name;\n }\n\n /**\n * Determines the generation strategy for an auto-generated key based on its type\n * @param columnName - Name of the column\n * @param typeDef - JSON Schema type definition for the column\n * @returns The generation strategy to use\n */\n protected determineGenerationStrategy(columnName: string, typeDef: any): KeyGenerationStrategy {\n // Extract the actual type if it's a union with null\n let actualType = typeDef;\n if (typeDef && typeof typeDef === \"object\") {\n if (typeDef.anyOf && Array.isArray(typeDef.anyOf)) {\n actualType = typeDef.anyOf.find((t: any) => t.type !== \"null\") || typeDef;\n } else if (typeDef.oneOf && Array.isArray(typeDef.oneOf)) {\n actualType = typeDef.oneOf.find((t: any) => t.type !== \"null\") || typeDef;\n }\n }\n\n if (typeof actualType !== \"object\") {\n return \"uuid\";\n }\n\n // Integer types use autoincrement\n if (actualType.type === \"integer\") {\n return \"autoincrement\";\n }\n\n // Default to UUID for strings and other types\n return \"uuid\";\n }\n\n /**\n * Generates a key value for client-side key generation\n * Override in storage classes that generate keys client-side (InMemory, IndexedDB for UUIDs)\n * SQL-based storages typically generate keys server-side\n * @param columnName - Name of the column to generate a key for\n * @param strategy - The generation strategy to use\n * @returns The generated key value\n */\n protected generateKeyValue(\n columnName: string,\n strategy: KeyGenerationStrategy\n ): Promise<string | number> | string | number {\n throw new Error(\n `generateKeyValue not implemented for ${this.constructor.name}. ` +\n `Column: ${columnName}, Strategy: ${strategy}`\n );\n }\n\n /**\n * Runs `fn` inside a transaction. The default implementation provides no\n * rollback semantics — it simply invokes `fn(this)`. Concrete subclasses\n * with native transaction support (SQLite, PostgreSQL) override this.\n */\n async withTransaction<T>(fn: (tx: this) => Promise<T>): Promise<T> {\n return await fn(this);\n }\n\n /**\n * Subclass override returning a backend-specific applier. Returning `null`\n * disables tabular migrations for the storage. The base class returns\n * `null`; only backends that have wired up their applier override this.\n */\n public getMigrationApplier(): ITabularMigrationApplier | null {\n return null;\n }\n\n /**\n * Runs `this.tabularMigrations` through the orchestrator using the\n * backend-supplied applier. Idempotent (no-op when no migrations are\n * declared, or when all are already applied).\n */\n protected async applyTabularMigrations(options?: RunTabularMigrationsOptions): Promise<void> {\n if (!this.tabularMigrations || this.tabularMigrations.length === 0) return;\n const applier = this.getMigrationApplier();\n if (!applier) {\n throw new Error(\n `${this.constructor.name} declared migrations but has no migration applier wired up.`\n );\n }\n await runTabularMigrations(applier, this.migrationComponent, this.tabularMigrations, options);\n }\n\n /**\n * Sets up the database/storage for the repository.\n * Must be called before using any other methods (except for in-memory implementations).\n * Default implementation is a no-op - override in subclasses that need setup.\n */\n async setupDatabase(): Promise<void> {\n // no op by default\n }\n\n /**\n * Destroys the repository and frees up resources.\n */\n destroy(): void {\n // no op by default\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n this.destroy();\n }\n\n [Symbol.dispose](): void {\n this.destroy();\n }\n}\n",
6
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { EventParameters } from \"@workglow/util\";\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport type { PageCursor } from \"./Cursor\";\n\nexport type { PageCursor } from \"./Cursor\";\n\n// Generic type for possible value types in the repository\nexport type ValueOptionType = string | number | bigint | boolean | null | Uint8Array;\n\n/**\n * Type definitions for tabular repository events\n */\nexport type TabularEventListeners<PrimaryKey, Entity> = {\n put: (entity: Entity) => void;\n get: (key: PrimaryKey, entity: Entity | undefined) => void;\n query: (key: Partial<Entity>, entities: Entity[] | undefined) => void;\n delete: (key: keyof Entity) => void;\n clearall: () => void;\n};\n\nexport type TabularEventName = keyof TabularEventListeners<any, any>;\nexport type TabularEventListener<\n Event extends TabularEventName,\n PrimaryKey,\n Entity,\n> = TabularEventListeners<PrimaryKey, Entity>[Event];\n\nexport type TabularEventParameters<\n Event extends TabularEventName,\n PrimaryKey,\n Entity,\n> = EventParameters<TabularEventListeners<PrimaryKey, Entity>, Event>;\n\n/**\n * Type of change that occurred in the repository\n */\nexport type TabularChangeType = \"INSERT\" | \"UPDATE\" | \"DELETE\";\n\n/**\n * Payload describing a change to an entity\n */\nexport interface TabularChangePayload<Entity> {\n readonly type: TabularChangeType;\n readonly old?: Entity;\n readonly new?: Entity;\n}\n\n/**\n * Options for subscribing to changes in a tabular repository\n */\nexport interface TabularSubscribeOptions {\n /** Polling interval in milliseconds (used by implementations that rely on polling) */\n readonly pollingIntervalMs?: number;\n}\n\nexport type JSONValue =\n | string\n | number\n | boolean\n | null\n | JSONValue[]\n | { [key: string]: JSONValue };\n\n/**\n * Comparison operators for search and deleteSearch operations\n */\nexport type SearchOperator = \"=\" | \"<\" | \"<=\" | \">\" | \">=\";\n\n/**\n * A search condition with a value and comparison operator\n */\nexport interface SearchCondition<T> {\n readonly value: T;\n readonly operator: SearchOperator;\n}\n\n/**\n * Criteria for deleteSearch operations supporting multiple columns.\n * Each column can have either a direct value (equality) or a SearchCondition with an operator.\n *\n * @example\n * // Equality match\n * { category: \"electronics\" }\n *\n * // With operator\n * { createdAt: { value: date, operator: \"<\" } }\n *\n * // Multiple columns\n * { category: \"electronics\", createdAt: { value: date, operator: \"<\" } }\n */\nexport type DeleteSearchCriteria<Entity> = {\n readonly [K in keyof Entity]?: Entity[K] | SearchCondition<Entity[K]>;\n};\n\nexport type SearchCriteria<Entity> = DeleteSearchCriteria<Entity>;\n\nexport type SortDirection = \"ASC\" | \"DESC\";\n\nexport interface OrderBy<Entity> {\n readonly column: keyof Entity;\n readonly direction: SortDirection;\n}\n\nexport interface QueryOptions<Entity> {\n readonly orderBy?: ReadonlyArray<OrderBy<Entity>>;\n readonly limit?: number;\n /**\n * @deprecated Offset-based paging is unstable when rows are inserted or\n * deleted between page fetches (entries can be skipped or duplicated).\n * Use {@link ITabularStorage.getPage} / {@link ITabularStorage.queryPage}\n * with a {@link PageCursor} instead.\n *\n * @example\n * ```ts\n * // Before (offset paging):\n * const rows = await storage.getAll({ orderBy, limit: 50, offset: 100 });\n *\n * // After (cursor paging — also stable under concurrent writes):\n * let cursor: PageCursor | undefined;\n * for (let i = 0; i < 2; i++) {\n * const skip = await storage.getPage({ orderBy, limit: 50, cursor });\n * cursor = skip.nextCursor;\n * if (!cursor) break;\n * }\n * const page = await storage.getPage({ orderBy, limit: 50, cursor });\n * const rows = page.items;\n * ```\n */\n readonly offset?: number;\n}\n\nexport interface CoveringIndexQueryOptions<Entity, K extends keyof Entity & string> {\n readonly select: readonly K[];\n readonly orderBy?: ReadonlyArray<OrderBy<Entity>>;\n readonly limit?: number;\n readonly offset?: number;\n}\n\n/**\n * Request for a cursor-paginated read.\n *\n * Pagination is keyset-based: the next page resumes after the row encoded\n * in `cursor`, with the primary key acting as the stable tiebreaker.\n * This is stable under concurrent inserts and deletes — unlike offset-based\n * paging, which can skip or duplicate rows when the underlying data\n * shifts between calls.\n *\n * If `orderBy` is omitted, rows are returned in primary-key order ascending.\n * If `orderBy` is provided, the effective ordering is `[...orderBy, ...primaryKey]`\n * so iteration remains deterministic when sort columns contain duplicates.\n */\nexport interface PageRequest<Entity> {\n readonly orderBy?: ReadonlyArray<OrderBy<Entity>>;\n /** Maximum number of rows to return. Defaults to 100. */\n readonly limit?: number;\n /** Opaque cursor returned by a previous call; omit to start from the beginning. */\n readonly cursor?: PageCursor;\n}\n\n/**\n * A page of results from a cursor-paginated read.\n *\n * `nextCursor` is `undefined` when there are no more rows to fetch.\n * When `nextCursor` is present, callers should pass it back via\n * {@link PageRequest.cursor} to fetch the next page.\n *\n * **Termination contract.** A defined `nextCursor` does NOT guarantee\n * additional rows exist — concurrent deletes can produce an empty page\n * mid-iteration even though `nextCursor` was set. Loops MUST therefore\n * terminate on either condition, not just on `nextCursor`:\n *\n * ```ts\n * // CORRECT — terminates on both `nextCursor` and empty `items`:\n * let cursor: PageCursor | undefined;\n * do {\n * const page = await storage.getPage({ limit: 100, cursor });\n * for (const row of page.items) handle(row);\n * if (page.items.length === 0) break;\n * cursor = page.nextCursor;\n * } while (cursor);\n *\n * // WRONG — can spin forever if a concurrent delete empties the next page\n * // while leaving rows further along the cursor that get deleted in turn:\n * while (page.nextCursor) { page = await storage.getPage({ cursor: page.nextCursor }); }\n * ```\n *\n * The bundled async generators ({@link ITabularStorage.records},\n * {@link ITabularStorage.pages}) honour this contract; reach for them\n * instead of writing the loop manually.\n */\nexport interface Page<Entity> {\n readonly items: ReadonlyArray<Entity>;\n readonly nextCursor: PageCursor | undefined;\n}\n\n/**\n * Type guard to check if a value is a SearchCondition\n */\nexport function isSearchCondition<T>(value: unknown): value is SearchCondition<T> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"value\" in value &&\n \"operator\" in value &&\n typeof (value as SearchCondition<T>).operator === \"string\"\n );\n}\n\n/**\n * Helper type to compute PrimaryKey while deferring Entity resolution.\n * Uses a conditional type to avoid forcing full Entity resolution at class definition time.\n *\n */\nexport type SimplifyPrimaryKey<\n Entity,\n KeyName extends ReadonlyArray<keyof any>,\n> = Entity extends any ? Pick<Entity, Extract<KeyName[number], keyof Entity>> : never;\n\n/**\n * Extracts property names marked as auto-generated from the schema.\n * Properties with `x-auto-generated: true` are considered auto-generated.\n */\nexport type AutoGeneratedKeys<Schema extends DataPortSchemaObject> = {\n [K in keyof Schema[\"properties\"]]: Schema[\"properties\"][K] extends { \"x-auto-generated\": true }\n ? K\n : never;\n}[keyof Schema[\"properties\"]];\n\n/**\n * Entity type for insertion - auto-generated keys are optional.\n * This allows clients to omit auto-generated keys when inserting entities.\n */\nexport type InsertEntity<Entity, AutoGenKeys> = Omit<Entity, AutoGenKeys & keyof Entity> &\n Partial<Pick<Entity, AutoGenKeys & keyof Entity>>;\n\n/**\n * Interface defining the contract for tabular storage repositories.\n * Provides a flexible interface for storing and retrieving data with typed\n * primary keys and values, and supports compound keys and partial key lookup.\n *\n * @typeParam Schema - The schema definition for the entity using JSON Schema\n * @typeParam PrimaryKeyNames - Array of property names that form the primary key\n */\nexport interface ITabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n InsertType = InsertEntity<Entity, AutoGeneratedKeys<Schema>>,\n> {\n // Core methods\n put(value: InsertType): Promise<Entity>;\n /**\n * Stores multiple entities in a single bulk operation.\n *\n * **Ordering guarantee:** the returned array is in the same order as the\n * input — `result[i]` always corresponds to `values[i]`. Callers may rely on\n * this to align bulk inserts with parallel arrays (e.g. chunks paired with\n * embeddings). Backends are responsible for preserving the order even when\n * the underlying engine does not formally guarantee it (see each backend's\n * implementation).\n *\n * **Caveat for integer auto-generated keys on remote backends.** Supplying\n * inputs that omit a backend-assigned integer-autoincrement primary key\n * leaves the wrapper with no key to match a returned row to a request row\n * (UUIDs are filled in client-side, so they don't have this problem). Such\n * inputs fall back to the server's response order, which Postgres does not\n * formally contract for `INSERT ... RETURNING`. The fallback is reliable in\n * practice but if `result[i] === values[i]` matters for correctness, supply\n * the primary key on every input — for example by minting it client-side\n * — or split the call into per-row `put`s.\n */\n putBulk(values: InsertType[]): Promise<Entity[]>;\n get(key: PrimaryKey): Promise<Entity | undefined>;\n delete(key: PrimaryKey | Entity): Promise<void>;\n getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined>;\n deleteAll(): Promise<void>;\n size(): Promise<number>;\n /**\n * Counts rows matching the specified search criteria without requiring\n * callers to load the matching entities.\n *\n * @param criteria - Optional object with column names as keys and values or SearchConditions\n * @returns Count of matching rows\n */\n count(criteria?: SearchCriteria<Entity>): Promise<number>;\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @example\n * // Delete by equality\n * await repo.deleteSearch({ category: \"electronics\" });\n *\n * // Delete with operator\n * await repo.deleteSearch({ createdAt: { value: date, operator: \"<\" } });\n *\n * // Delete with multiple criteria (AND)\n * await repo.deleteSearch({ category: \"electronics\", value: { value: 100, operator: \"<\" } });\n */\n deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void>;\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n * @deprecated Offset-based paging is unstable under concurrent writes.\n * Use {@link getPage} for stable, keyset-based pagination.\n */\n getBulk(offset: number, limit: number): Promise<Entity[] | undefined>;\n\n /**\n * Fetches a page of records using cursor-based (keyset) pagination.\n *\n * Stable under concurrent inserts and deletes: the cursor encodes the\n * last seen primary key so the next page resumes from a precise position\n * rather than a numeric offset that shifts as rows are added or removed.\n *\n * @param request - Optional ordering, limit, and cursor.\n * @returns A {@link Page} with the rows for this page and a `nextCursor`\n * to use for the next call (or `undefined` when iteration is complete).\n */\n getPage(request?: PageRequest<Entity>): Promise<Page<Entity>>;\n\n /**\n * Cursor-paginated form of {@link query}.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param request - Optional ordering, limit, and cursor.\n */\n queryPage(criteria: SearchCriteria<Entity>, request?: PageRequest<Entity>): Promise<Page<Entity>>;\n\n /**\n * Async generator that yields records one at a time.\n * @param pageSize - Number of records to fetch per page (default: 100)\n * @yields Individual entity records\n */\n records(pageSize?: number): AsyncGenerator<Entity, void, undefined>;\n\n /**\n * Async generator that yields pages of records.\n * @param pageSize - Number of records per page (default: 100)\n * @yields Arrays of entities\n */\n pages(pageSize?: number): AsyncGenerator<Entity[], void, undefined>;\n\n // Event handling methods\n on<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void;\n off<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void;\n emit<Event extends TabularEventName>(\n name: Event,\n ...args: TabularEventParameters<Event, PrimaryKey, Entity>\n ): void;\n once<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void;\n waitOn<Event extends TabularEventName>(\n name: Event\n ): Promise<TabularEventParameters<Event, PrimaryKey, Entity>>;\n\n /**\n * Queries entries matching the specified search criteria with optional ordering, limit, and offset.\n * Uses optimized index paths when possible, falls back to full scan otherwise.\n *\n * Implementation contract for third-party backends: when binding a\n * `SearchCondition` value into the underlying datastore, run it\n * through the same conversion path as a row value going *into* the\n * store (e.g. `jsToSqlValue` for SQL backends — Date → ISO string,\n * etc.). The cursor pagination machinery in {@link getPage} relies\n * on this round-trip to compare a row's stored representation\n * against a cursor's decoded value; any backend that skips the\n * conversion would silently mis-page on Date or other rich types.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of matching entities or undefined if no matches found\n */\n query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined>;\n\n /**\n * Strict, projected query served entirely by a covering compound index.\n * Throws CoveringIndexMissingError when no registered index can serve\n * (criteria + orderBy + select). Returns Pick<Entity, K>[] — never the heavy fields.\n *\n * @param criteria - equality (and optionally non-equality) filters\n * @param options - select (required), orderBy, limit, offset\n * @returns array of projected rows (empty array, not undefined, when no matches)\n */\n queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]>;\n\n /**\n * Subscribes to changes in the repository (including remote changes).\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options (e.g., polling interval)\n * @returns Unsubscribe function\n */\n subscribeToChanges(\n callback: (change: TabularChangePayload<Entity>) => void,\n options?: TabularSubscribeOptions\n ): () => void;\n\n /**\n * Runs `fn` inside a single transaction. If `fn` throws, all writes performed\n * inside it are rolled back; otherwise they commit atomically. Mutation\n * events (e.g. `put`) emitted inside `fn` are buffered and delivered after\n * the transaction commits, so listeners never observe rows that are about\n * to roll back.\n *\n * Backends differ in how strong the guarantee is:\n * - **SQLite**: real `BEGIN` / `COMMIT` / `ROLLBACK`.\n * - **PostgreSQL**: real `BEGIN` / `COMMIT` / `ROLLBACK`. On a real\n * `pg.Pool` (anything exposing `connect()`) the implementation\n * dedicates a client via `pool.connect()` and runs the transaction on\n * that client, leaving the parent's pool free for external traffic\n * in parallel. On single-connection wrappers (PGLitePool, raw PGlite)\n * the transaction runs on the shared session and concurrent calls on\n * the same instance are serialized behind a per-instance mutex so\n * they cannot slip into the open transaction.\n * - **Supabase, in-memory, file system, IndexedDB**: best-effort. The\n * callback runs to completion and rejection propagates, but partial\n * writes are not rolled back because the backend does not expose a\n * transaction surface usable by this API.\n *\n * **Concurrency contract:**\n * - On backends with native transaction support (SQLite, PostgreSQL),\n * concurrent calls on the same storage instance are isolated from the\n * open transaction: SQLite and the single-connection Postgres path\n * serialize them through a per-instance mutex; the real-pool Postgres\n * path runs them on independent pool clients in parallel. Either way,\n * unrelated writes never accidentally commit or roll back along with\n * `fn`.\n * - On best-effort backends concurrent writes have no atomicity barrier\n * to begin with — the contract on those backends is \"runs `fn`\", not\n * \"isolates `fn`\".\n *\n * The `tx` handle passed to `fn` is **not** the same object as `this` for\n * backends with native transaction support — it is a Proxy that routes\n * writes through the transaction-bound resources (the dedicated client on\n * real `pg.Pool`, the bypass-mutex internal methods on SQLite/PGlite) and\n * routes events through the transaction's deferred-emit queue. Callers\n * MUST use `tx` for everything inside `fn`. Capturing the outer `this` and\n * calling methods on it from inside `fn` will deadlock against the held\n * mutex (single-connection backends) or run on the wrong connection\n * (`pg.Pool`), and is unsupported.\n *\n * **Nested calls.** Calls made through the `tx` handle always throw —\n * `tx.withTransaction(...)` is a hard error on every backend. Calls made\n * through the *original* (captured `this`) handle behave per backend:\n * - **SQLite, single-connection Postgres** (PGlite, PGLitePool): throw,\n * because the backend has no autonomous `BEGIN` and reusing the open\n * transaction implicitly would be ambiguous.\n * - **Real `pg.Pool` Postgres**: acquire an *independent* client and run\n * as an *independent* transaction with its own commit/rollback boundary.\n * This is the natural Postgres concurrency model on a pool — nothing\n * ties the two transactions together. If you want the inner work to\n * roll back when the outer throws, do not use a captured `this`; use\n * `tx` (which throws) and a SAVEPOINT instead.\n *\n * Use SAVEPOINT directly if you need nested rollback boundaries within a\n * single logical transaction.\n */\n withTransaction<T>(fn: (tx: this) => Promise<T>): Promise<T>;\n\n /**\n * Creates the underlying table/object store. Idempotent: a second call on\n * an already-set-up storage adapts the schema to any new indexes if the\n * backend supports it (SQL `CREATE INDEX IF NOT EXISTS`, IndexedDB\n * version bump for new indexes), and is a no-op otherwise.\n *\n * When the storage was constructed with `tabularMigrations`, this method\n * also applies any pending migrations through the unified tabular\n * migration runner (see `TabularMigrationOrchestrator`). Otherwise it is\n * a pure DDL setup primitive — tabular schemas are derived from the JSON\n * Schema passed at construction rather than from versioned migrations.\n *\n * @returns Promise that resolves when setup is complete\n */\n setupDatabase(): Promise<void>;\n\n // Destroy the repository and frees up resources.\n destroy(): void;\n [Symbol.dispose](): void;\n [Symbol.asyncDispose](): Promise<void>;\n}\n\nexport type AnyTabularStorage = Omit<\n ITabularStorage<any, any, any, any, any>,\n \"queryIndex\" | \"withTransaction\"\n> & {\n queryIndex(criteria: any, options: any): Promise<any[]>;\n withTransaction<T>(fn: (tx: any) => Promise<T>): Promise<T>;\n};\n",
7
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { BaseError } from \"@workglow/util\";\n\nexport class StorageError extends BaseError {\n static override readonly type: string = \"StorageError\";\n}\n\nexport class StorageValidationError extends StorageError {\n static override readonly type: string = \"StorageValidationError\";\n}\n\nexport class StorageEmptyCriteriaError extends StorageValidationError {\n static override readonly type: string = \"StorageEmptyCriteriaError\";\n constructor() {\n super(\"Query criteria must not be empty. Use getAll() to retrieve all records.\");\n }\n}\n\nexport class StorageInvalidLimitError extends StorageValidationError {\n static override readonly type: string = \"StorageInvalidLimitError\";\n constructor(limit: number) {\n // Message names both constraints (positive AND integer) so callers\n // hitting the error from `runPage` (which rejects non-integer limits)\n // see the same wording as offset paths — and a user staring at\n // `limit: 1.5` isn't left guessing that fractional values are the\n // problem.\n super(`Query limit must be a positive integer, got ${limit}`);\n }\n}\n\nexport class StorageInvalidColumnError extends StorageValidationError {\n static override readonly type: string = \"StorageInvalidColumnError\";\n constructor(column: string) {\n super(`Column \"${column}\" does not exist in the schema`);\n }\n}\n\nexport class StorageUnsupportedError extends StorageError {\n static override readonly type: string = \"StorageUnsupportedError\";\n constructor(operation: string, backend: string) {\n super(`${operation} is not supported for ${backend}`);\n }\n}\n",
8
8
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { StorageError } from \"./StorageError\";\n\nexport class CoveringIndexMissingError extends StorageError {\n static override readonly type: string = \"CoveringIndexMissingError\";\n\n public readonly table: string;\n public readonly requiredColumns: readonly string[];\n public readonly registeredIndexes: ReadonlyArray<readonly string[]>;\n\n constructor(\n table: string,\n requiredColumns: readonly string[],\n registeredIndexes: ReadonlyArray<readonly string[]>\n ) {\n const indexList = registeredIndexes.map((cols) => `[${cols.join(\", \")}]`).join(\", \");\n super(\n `No covering index for table \"${table}\". ` +\n `Required columns: [${requiredColumns.join(\", \")}]. ` +\n `Registered indexes: ${indexList || \"(none)\"}.`\n );\n this.table = table;\n this.requiredColumns = requiredColumns;\n this.registeredIndexes = registeredIndexes;\n }\n}\n",
9
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n DataPortSchemaObject,\n FromSchema,\n JsonSchema,\n TypedArraySchemaOptions,\n} from \"@workglow/util/schema\";\nimport { BaseTabularStorage, ClientProvidedKeysOption } from \"./BaseTabularStorage\";\nimport {\n AutoGeneratedKeys,\n InsertEntity,\n SimplifyPrimaryKey,\n ValueOptionType,\n} from \"./ITabularStorage\";\n\n// BaseTabularStorage is a tabular store that uses SQLite and Postgres use as common code\n\n/**\n * Base class for SQL-based tabular repositories that implements common functionality\n * for both SQLite and PostgreSQL database implementations.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport abstract class BaseSqlTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n /**\n * Creates a new instance of BaseSqlTabularStorage\n * @param table - The name of the database table to use for storage\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n /** Caches for computed column definition strings, keyed by delimiter */\n private readonly _pkColsCache = new Map<string, string>();\n private readonly _valColsCache = new Map<string, string>();\n private readonly _pkColListCache = new Map<string, string>();\n private readonly _valColListCache = new Map<string, string>();\n\n constructor(\n protected readonly table: string = \"tabular_store\",\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\"\n ) {\n super(schema, primaryKeyNames, indexes, clientProvidedKeys);\n this.validateTableAndSchema();\n }\n\n /**\n * Maps JavaScript/TypeScript types to their corresponding SQL type\n * Must be implemented by derived classes for specific SQL dialects\n */\n protected abstract mapTypeToSQL(typeDef: JsonSchema): string;\n\n /**\n * Generates the SQL column definitions for primary key fields\n * @returns SQL string containing primary key column definitions\n */\n protected constructPrimaryKeyColumns($delimiter: string = \"\"): string {\n let cached = this._pkColsCache.get($delimiter);\n if (cached === undefined) {\n cached = Object.entries<JsonSchema>(this.primaryKeySchema.properties)\n .map(([key, typeDef]) => {\n const sqlType = this.mapTypeToSQL(typeDef);\n return `${$delimiter}${key}${$delimiter} ${sqlType} NOT NULL`;\n })\n .join(\", \");\n this._pkColsCache.set($delimiter, cached);\n }\n return cached;\n }\n\n /**\n * Generates the SQL column definitions for value fields\n * @returns SQL string containing value column definitions\n */\n protected constructValueColumns($delimiter: string = \"\"): string {\n let cached = this._valColsCache.get($delimiter);\n if (cached === undefined) {\n const requiredSet = new Set(this.valueSchema.required ?? []);\n const cols = Object.entries<JsonSchema>(this.valueSchema.properties)\n .map(([key, typeDef]) => {\n const sqlType = this.mapTypeToSQL(typeDef);\n const isRequired = requiredSet.has(key);\n const nullable = !isRequired || this.isNullable(typeDef);\n return `${$delimiter}${key}${$delimiter} ${sqlType}${nullable ? \" NULL\" : \" NOT NULL\"}`;\n })\n .join(\", \");\n cached = cols.length > 0 ? `, ${cols}` : \"\";\n this._valColsCache.set($delimiter, cached);\n }\n return cached;\n }\n\n /**\n * Determines if a schema type allows null values\n * @param typeDef - The schema type definition\n * @returns true if the type allows null values\n */\n protected isNullable(typeDef: JsonSchema): boolean {\n if (typeof typeDef === \"boolean\") return typeDef;\n\n // Check for direct null type\n if (typeDef.type === \"null\") {\n return true;\n }\n\n // Check for type as an array that includes null (e.g., type: [\"null\", \"string\"])\n if (Array.isArray(typeDef.type)) {\n return typeDef.type.includes(\"null\");\n }\n\n // Check for union types that include null (anyOf)\n if (typeDef.anyOf && Array.isArray(typeDef.anyOf)) {\n return typeDef.anyOf.some((type: any) => type.type === \"null\");\n }\n\n // Check for union types that include null (oneOf)\n if (typeDef.oneOf && Array.isArray(typeDef.oneOf)) {\n return typeDef.oneOf.some((type: any) => type.type === \"null\");\n }\n\n return false;\n }\n\n /**\n * Returns a comma-separated list of primary key column names\n * @returns Formatted string of primary key column names\n */\n protected primaryKeyColumnList($delimiter: string = \"\"): string {\n let cached = this._pkColListCache.get($delimiter);\n if (cached === undefined) {\n cached =\n $delimiter + this.primaryKeyColumns().join(`${$delimiter}, ${$delimiter}`) + $delimiter;\n this._pkColListCache.set($delimiter, cached);\n }\n return cached;\n }\n\n /**\n * Returns a comma-separated list of value column names\n * @returns Formatted string of value column names\n */\n protected valueColumnList($delimiter: string = \"\"): string {\n let cached = this._valColListCache.get($delimiter);\n if (cached === undefined) {\n cached = $delimiter + this.valueColumns().join(`${$delimiter}, ${$delimiter}`) + $delimiter;\n this._valColListCache.set($delimiter, cached);\n }\n return cached;\n }\n\n /**\n * Gets the real underlying type from possibly union types\n * For example, for a union with null, this extracts the non-null type\n * @param typeDef - The schema to extract from\n * @returns The non-null type from the schema\n */\n protected getNonNullType(typeDef: JsonSchema): JsonSchema {\n if (typeof typeDef === \"boolean\") return typeDef;\n\n if (typeDef.anyOf && Array.isArray(typeDef.anyOf)) {\n const nonNullType = typeDef.anyOf.find((t: any) => t.type !== \"null\");\n if (nonNullType) {\n return nonNullType;\n }\n }\n if (typeDef.oneOf && Array.isArray(typeDef.oneOf)) {\n const nonNullType = typeDef.oneOf.find((t: any) => t.type !== \"null\");\n if (nonNullType) {\n return nonNullType;\n }\n }\n return typeDef;\n }\n\n /**\n * Converts a value object into an ordered array based on the valueSchema\n * This ensures consistent parameter ordering for SQL queries\n * @param value - The value object to convert\n * @returns Array of values ordered according to the schema\n * @throws Error if a required field is missing\n */\n protected getValueAsOrderedArray(value: Value): ValueOptionType[] {\n const orderedParams: ValueOptionType[] = [];\n const valueAsRecord = value as Record<string, Entity[keyof Entity]>;\n const requiredSet = new Set(this.valueSchema.required ?? []);\n for (const key in this.valueSchema.properties) {\n if (Object.prototype.hasOwnProperty.call(valueAsRecord, key)) {\n const val = valueAsRecord[key];\n // Convert undefined to null for optional fields\n if (val === undefined && !requiredSet.has(key)) {\n orderedParams.push(null);\n } else {\n orderedParams.push(this.jsToSqlValue(key, val));\n }\n } else {\n // If the field is required, throw an error\n if (requiredSet.has(key)) {\n throw new Error(`Missing required value field: ${key}`);\n }\n // If the field is optional, use null\n orderedParams.push(null);\n }\n }\n return orderedParams;\n }\n\n /**\n * Converts a primary key object into an ordered array based on the schema\n * This ensures consistent parameter ordering for storage operations\n * @param key - The primary key object to convert\n * @returns Array of key values ordered according to the schema\n */\n protected override getPrimaryKeyAsOrderedArray(key: PrimaryKey): ValueOptionType[] {\n const orderedParams: ValueOptionType[] = [];\n const keyObj = key as Record<string, Entity[keyof Entity]>;\n for (const k of Object.keys(this.primaryKeySchema.properties)) {\n if (k in keyObj) {\n const value = keyObj[k];\n if (value === null) {\n throw new Error(`Primary key field ${k} cannot be null`);\n }\n orderedParams.push(this.jsToSqlValue(k, value));\n } else {\n throw new Error(`Missing required primary key field: ${k}`);\n }\n }\n return orderedParams;\n }\n\n protected jsToSqlValue(column: string, value: Entity[keyof Entity]): ValueOptionType {\n const typeDef = this.schema.properties[column];\n if (!typeDef) {\n return value as ValueOptionType;\n }\n\n // Handle null values for nullable columns\n if (value === null && this.isNullable(typeDef)) {\n return null;\n }\n\n // Extract the non-null type for proper handling\n const actualType = this.getNonNullType(typeDef);\n if (typeof actualType === \"boolean\") {\n return value as ValueOptionType;\n }\n\n if (actualType.contentEncoding === \"blob\") {\n if (value instanceof Uint8Array) {\n return value;\n }\n if (typeof Buffer !== \"undefined\" && value instanceof Buffer) {\n return new Uint8Array(value);\n }\n if (Array.isArray(value)) {\n return new Uint8Array(value);\n }\n return value as ValueOptionType;\n } else if (value instanceof Date) {\n // Convert all Date objects to ISO string regardless of type definition\n return value.toISOString();\n } else {\n return value as ValueOptionType;\n }\n }\n\n protected sqlToJsValue(column: string, value: ValueOptionType): Entity[keyof Entity] {\n // Get the type definition\n const typeDef = this.schema.properties[column];\n if (!typeDef) {\n return value as Entity[keyof Entity];\n }\n\n // Handle null values\n if (value === null && this.isNullable(typeDef)) {\n return null as Entity[keyof Entity];\n }\n\n // Extract the non-null type for proper handling\n const actualType = this.getNonNullType(typeDef);\n if (typeof actualType === \"boolean\") {\n return value as Entity[keyof Entity];\n }\n\n if (actualType.contentEncoding === \"blob\") {\n if (typeof Buffer !== \"undefined\" && value instanceof Buffer) {\n return new Uint8Array(value) as Entity[keyof Entity];\n }\n if (value instanceof Uint8Array) {\n return value as Entity[keyof Entity];\n }\n return value as Entity[keyof Entity];\n } else {\n return value as Entity[keyof Entity];\n }\n }\n\n /**\n * Validates table name and schema configurations\n * Checks for:\n * 1. Valid table name format\n * 2. Valid schema key names\n * 3. No duplicate keys between primary key and value schemas\n * This is a sanity check to make sure the table and schema are valid,\n * and to prevent dumb mistakes and mischevious behavior.\n * @throws Error if validation fails\n */\n protected validateTableAndSchema(): void {\n // Validate table name\n if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(this.table)) {\n throw new Error(\n \"Table name must start with a letter and contain only letters, digits, and underscores, got: \" +\n this.table\n );\n }\n\n // Validate schema keys\n const validateSchemaKeys = (schema: DataPortSchemaObject) => {\n for (const key in schema.properties) {\n if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(key)) {\n throw new Error(\n \"Schema keys must start with a letter and contain only letters, digits, and underscores, got: \" +\n key\n );\n }\n }\n };\n\n validateSchemaKeys(this.primaryKeySchema);\n validateSchemaKeys(this.valueSchema);\n\n // Check for key name collisions between schemas\n const primaryKeys = new Set(Object.keys(this.primaryKeySchema.properties));\n const valueKeys = Object.keys(this.valueSchema.properties);\n const duplicates = valueKeys.filter((key) => primaryKeys.has(key));\n if (duplicates.length > 0) {\n throw new Error(`Duplicate keys found in schemas: ${duplicates.join(\", \")}`);\n }\n }\n}\n",
10
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport { createServiceToken, getLogger } from \"@workglow/util\";\nimport { BaseTabularStorage, ClientProvidedKeysOption } from \"./BaseTabularStorage\";\nimport { InMemoryTabularStorage } from \"./InMemoryTabularStorage\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n ITabularStorage,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\n\nexport const CACHED_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.cached\"\n);\n\n/**\n * A tabular repository wrapper that adds caching layer to a durable repository.\n * Uses InMemoryTabularStorage or SharedInMemoryTabularStorage as a cache\n * for faster access to frequently used data.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class CachedTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n public readonly cache: ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>;\n private durable: ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>;\n private cacheInitialized = false;\n private cacheInitPromise: Promise<void> | null = null;\n\n /**\n * Creates a new CachedTabularStorage instance\n * @param durable - The durable repository to use as the source of truth\n * @param cache - Optional cache repository (InMemoryTabularStorage or SharedInMemoryTabularStorage).\n * If not provided, a new InMemoryTabularStorage will be created.\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n constructor(\n durable: ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>,\n cache?: ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>,\n schema?: Schema,\n primaryKeyNames?: PrimaryKeyNames,\n indexes?: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\"\n ) {\n // Extract schema and primaryKeyNames from durable repository if not provided\n // Note: This is a limitation - we can't always extract these from an interface\n // So we require them to be provided or assume they match\n if (!schema || !primaryKeyNames) {\n throw new Error(\n \"Schema and primaryKeyNames must be provided when creating CachedTabularStorage\"\n );\n }\n\n super(schema, primaryKeyNames, indexes || [], clientProvidedKeys);\n this.durable = durable;\n\n // Create cache if not provided\n if (cache) {\n this.cache = cache;\n } else {\n this.cache = new InMemoryTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>(\n schema,\n primaryKeyNames,\n indexes || [],\n clientProvidedKeys\n );\n }\n\n // Forward events from both cache and durable\n this.setupEventForwarding();\n }\n\n /**\n * Sets up event forwarding from cache and durable repositories\n */\n private setupEventForwarding(): void {\n // Forward cache events\n this.cache.on(\"put\", (entity) => {\n this.events.emit(\"put\", entity);\n });\n this.cache.on(\"get\", (key, entity) => {\n this.events.emit(\"get\", key, entity);\n });\n this.cache.on(\"query\", (key, entities) => {\n this.events.emit(\"query\", key, entities);\n });\n this.cache.on(\"delete\", (key) => {\n this.events.emit(\"delete\", key);\n });\n this.cache.on(\"clearall\", () => {\n this.events.emit(\"clearall\");\n });\n }\n\n /**\n * Initializes the cache by loading all data from the durable repository.\n * Uses a promise-based lock so concurrent callers await the same initialization.\n */\n private async initializeCache(): Promise<void> {\n if (this.cacheInitialized) return;\n\n if (this.cacheInitPromise) {\n return this.cacheInitPromise;\n }\n\n this.cacheInitPromise = (async () => {\n try {\n const all = await this.durable.getAll();\n if (all && all.length > 0) {\n await this.cache.putBulk(all);\n }\n this.cacheInitialized = true;\n } catch (error) {\n getLogger().warn(\"Failed to initialize cache from durable repository:\", { error });\n // Don't mark as initialized on error — allow retry on next access\n } finally {\n this.cacheInitPromise = null;\n }\n })();\n\n return this.cacheInitPromise;\n }\n\n /**\n * Stores a key-value pair in both cache and durable repository\n * @param value - The combined object to store\n * @returns The stored entity\n * @emits 'put' event with the stored entity when successful\n */\n async put(value: InsertType): Promise<Entity> {\n await this.initializeCache();\n\n // Write to durable first (source of truth)\n const result = await this.durable.put(value);\n\n // Then update cache\n await this.cache.put(result);\n\n return result;\n }\n\n /**\n * Stores multiple key-value pairs in both cache and durable repository\n * @param values - Array of combined objects to store\n * @returns Array of stored entities\n * @emits 'put' event for each value stored\n */\n async putBulk(values: InsertType[]): Promise<Entity[]> {\n await this.initializeCache();\n\n // Write to durable first (source of truth)\n const results = await this.durable.putBulk(values);\n\n // Then update cache\n await this.cache.putBulk(results);\n\n return results;\n }\n\n /**\n * Retrieves a value by its key, checking cache first, then durable repository\n * @param key - The primary key object to look up\n * @returns The value object if found, undefined otherwise\n * @emits 'get' event with the fingerprint ID and value when found\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n await this.initializeCache();\n\n // Try cache first\n let result = await this.cache.get(key);\n\n // If not in cache, get from durable and cache it\n if (result === undefined) {\n result = await this.durable.get(key);\n if (result) {\n await this.cache.put(result);\n }\n }\n\n return result;\n }\n\n /**\n * Deletes an entry from both cache and durable repository\n * @param value - The primary key object or entity of the entry to delete\n * @emits 'delete' event with the fingerprint ID when successful\n */\n async delete(value: PrimaryKey | Entity): Promise<void> {\n await this.initializeCache();\n\n // Delete from durable first (source of truth)\n await this.durable.delete(value);\n\n // Then delete from cache\n await this.cache.delete(value);\n }\n\n /**\n * Removes all entries from both cache and durable repository\n * @emits 'clearall' event when successful\n */\n async deleteAll(): Promise<void> {\n await this.initializeCache();\n\n // Delete from durable first (source of truth)\n await this.durable.deleteAll();\n\n // Then delete from cache\n await this.cache.deleteAll();\n }\n\n /**\n * Returns an array of all entries in the repository, with optional ordering, offset, and limit.\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of all entries in the repository\n */\n async getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n await this.initializeCache();\n\n // Try cache first (without options for population check)\n let results = await this.cache.getAll();\n\n // If cache is empty, get from durable and populate cache\n if (!results || results.length === 0) {\n results = await this.durable.getAll();\n if (results && results.length > 0) {\n await this.cache.putBulk(results);\n }\n }\n\n // If options provided, apply them via the cache\n if (options && results && results.length > 0) {\n return await this.cache.getAll(options);\n }\n\n return results;\n }\n\n /**\n * Returns the number of entries in the repository\n * @returns The total count of stored entries\n */\n async size(): Promise<number> {\n await this.initializeCache();\n\n // Get size from durable (source of truth)\n return await this.durable.size();\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n await this.initializeCache();\n\n // Delegate to durable storage (source of truth) to avoid inconsistency\n return await this.durable.getBulk(offset, limit);\n }\n\n /**\n * Queries entries matching the specified search criteria with optional ordering and limit.\n * Delegates to the cache (which has all data after initialization) for best performance.\n * Falls back to durable if cache returns no results.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering and limit options\n * @returns Array of matching entities or undefined if no matches found\n */\n async query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n await this.initializeCache();\n return await this.cache.query(criteria, options);\n }\n\n override async queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n await this.initializeCache();\n return await this.cache.queryIndex(criteria, options);\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n */\n async deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n await this.initializeCache();\n\n // Delete from durable first (source of truth)\n await this.durable.deleteSearch(criteria);\n\n // Then delete from cache\n await this.cache.deleteSearch(criteria);\n }\n\n /**\n * Invalidates the cache by clearing it and resetting initialization flag\n */\n async invalidateCache(): Promise<void> {\n await this.cache.deleteAll();\n this.cacheInitialized = false;\n }\n\n /**\n * Refreshes the cache by reloading all data from the durable repository\n */\n async refreshCache(): Promise<void> {\n await this.cache.deleteAll();\n this.cacheInitialized = false;\n await this.initializeCache();\n }\n\n /**\n * Subscribes to changes in the repository.\n * Delegates to the durable repository to detect changes (including from other sources).\n * Also updates the cache when changes are detected.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options (e.g., polling interval)\n * @returns Unsubscribe function\n */\n override subscribeToChanges(\n callback: (change: any) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n // Subscribe to durable repository to detect all changes\n return this.durable.subscribeToChanges(async (change) => {\n // Update cache based on the change\n if (change.type === \"INSERT\" || change.type === \"UPDATE\") {\n if (change.new) {\n await this.cache.put(change.new);\n }\n } else if (change.type === \"DELETE\") {\n if (change.old) {\n await this.cache.delete(change.old);\n }\n }\n\n // Forward the change to the callback\n callback(change);\n }, options);\n }\n\n /**\n * Destroys the durable and cache repositories.\n */\n override destroy(): void {\n this.durable.destroy();\n this.cache.destroy();\n }\n}\n",
11
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport { createServiceToken, makeFingerprint, uuid4 } from \"@workglow/util\";\nimport {\n BaseTabularStorage,\n ClientProvidedKeysOption,\n KeyGenerationStrategy,\n} from \"./BaseTabularStorage\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n isSearchCondition,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\nimport { pickCoveringIndex } from \"./coveringIndexPicker\";\n\nexport const MEMORY_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.inMemory\"\n);\n\n/**\n * A generic in-memory key-value repository implementation.\n * Provides a simple, non-persistent storage solution suitable for testing and caching scenarios.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class InMemoryTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n /** Internal storage using a Map with fingerprint strings as keys */\n values = new Map<string, Entity>();\n /** Counter for auto-incrementing integer keys */\n private autoIncrementCounter = 0;\n /** Tracks whether the last put was an insert (new key) or update (existing key) */\n private _lastPutWasInsert = false;\n\n /**\n * Creates a new InMemoryTabularStorage instance\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n constructor(\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\"\n ) {\n super(schema, primaryKeyNames, indexes, clientProvidedKeys);\n }\n\n /**\n * Sets up the database for the repository (no-op for in-memory)\n */\n override async setupDatabase(): Promise<void> {\n // No setup needed for in-memory storage\n }\n\n /**\n * Generates a key value for auto-generated keys\n * @param columnName - Name of the column to generate a key for\n * @param strategy - The generation strategy to use\n * @returns The generated key value\n */\n protected override generateKeyValue(\n columnName: string,\n strategy: KeyGenerationStrategy\n ): string | number {\n if (strategy === \"autoincrement\") {\n return ++this.autoIncrementCounter;\n } else {\n return uuid4();\n }\n }\n\n /**\n * Stores a key-value pair in the repository\n * @param value - The combined object to store (may be missing auto-generated keys)\n * @returns The stored entity with all keys filled in\n * @emits 'put' event with the stored entity when successful\n */\n async put(value: InsertType): Promise<Entity> {\n let entityToStore = value as unknown as Entity;\n const savedCounter = this.autoIncrementCounter;\n\n try {\n // Handle auto-generated keys\n if (this.hasAutoGeneratedKey() && this.autoGeneratedKeyName) {\n const keyName = this.autoGeneratedKeyName as string;\n const clientProvidedValue = (value as Record<string, unknown>)[keyName];\n const hasClientValue = clientProvidedValue !== undefined && clientProvidedValue !== null;\n\n let shouldGenerate = false;\n if (this.clientProvidedKeys === \"never\") {\n shouldGenerate = true;\n } else if (this.clientProvidedKeys === \"always\") {\n if (!hasClientValue) {\n throw new Error(\n `Auto-generated key \"${keyName}\" is required when clientProvidedKeys is \"always\"`\n );\n }\n shouldGenerate = false;\n } else {\n shouldGenerate = !hasClientValue;\n }\n\n if (shouldGenerate) {\n const generatedValue = this.generateKeyValue(keyName, this.autoGeneratedKeyStrategy!);\n entityToStore = { ...value, [keyName]: generatedValue } as Entity;\n }\n }\n\n const { key } = this.separateKeyValueFromCombined(entityToStore);\n const id = await makeFingerprint(key);\n this._lastPutWasInsert = !this.values.has(id);\n this.values.set(id, entityToStore);\n } catch (e) {\n this.autoIncrementCounter = savedCounter;\n throw e;\n }\n\n this.events.emit(\"put\", entityToStore);\n return entityToStore;\n }\n\n /**\n * Stores multiple key-value pairs in the repository in a bulk operation\n * @param values - Array of combined objects to store (may be missing auto-generated keys)\n * @returns Array of stored entities with all keys filled in\n * @emits 'put' event for each value stored\n */\n async putBulk(values: InsertType[]): Promise<Entity[]> {\n return await Promise.all(values.map(async (value) => this.put(value)));\n }\n\n /**\n * Retrieves a value by its key\n * @param key - The primary key object to look up\n * @returns The value object if found, undefined otherwise\n * @emits 'get' event with the fingerprint ID and value when found\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n const id = await makeFingerprint(key);\n const out = this.values.get(id);\n this.events.emit(\"get\", key, out);\n return out;\n }\n\n /**\n * Deletes an entry by its key\n * @param key - The primary key object of the entry to delete\n * @emits 'delete' event with the fingerprint ID when successful\n */\n async delete(value: PrimaryKey | Entity): Promise<void> {\n const { key } = this.separateKeyValueFromCombined(value as Entity);\n const id = await makeFingerprint(key);\n this.values.delete(id);\n this.events.emit(\"delete\", key as keyof Entity);\n }\n\n /**\n * Removes all entries from the repository\n * @emits 'clearall' event when successful\n */\n async deleteAll(): Promise<void> {\n this.values.clear();\n this.events.emit(\"clearall\");\n }\n\n /**\n * Returns an array of all entries in the repository, with optional ordering, offset, and limit.\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of all entries in the repository\n */\n async getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n this.validateGetAllOptions(options);\n let all = Array.from(this.values.values());\n\n if (options?.orderBy && options.orderBy.length > 0) {\n all.sort((a, b) => {\n for (const { column, direction } of options.orderBy!) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n });\n }\n\n if (options?.offset !== undefined) {\n all = all.slice(options.offset);\n }\n\n if (options?.limit !== undefined) {\n all = all.slice(0, options.limit);\n }\n\n return all.length > 0 ? all : undefined;\n }\n\n /**\n * Returns the number of entries in the repository\n * @returns The total count of stored entries\n */\n async size(): Promise<number> {\n return this.values.size;\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n const all = Array.from(this.values.values());\n\n // Ensure deterministic ordering by sorting by primary key(s) before pagination\n all.sort((a, b) => {\n for (const key of this.primaryKeyNames) {\n const aVal = (a as Record<string, string | number>)[key as string];\n const bVal = (b as Record<string, string | number>)[key as string];\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n }\n return 0;\n });\n\n const page = all.slice(offset, offset + limit);\n return page.length > 0 ? page : undefined;\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n */\n async deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n const criteriaKeys = Object.keys(criteria) as Array<keyof Entity>;\n if (criteriaKeys.length === 0) {\n return;\n }\n\n // Convert to array first to avoid iterator issues when modifying the Map\n const entries = Array.from(this.values.entries());\n\n const entriesToDelete = entries.filter(([_, entity]) => {\n // All criteria must match (AND logic)\n for (const column of criteriaKeys) {\n const criterion = criteria[column];\n const columnValue = entity[column];\n\n if (isSearchCondition(criterion)) {\n const { value, operator } = criterion;\n // Cast needed for generic comparison operators across Entity value types\n const v = value as string | number;\n const cv = columnValue as string | number | null | undefined;\n switch (operator) {\n case \"=\":\n if (cv !== v) return false;\n break;\n case \"<\":\n if (cv === null || cv === undefined || !(cv < v)) return false;\n break;\n case \"<=\":\n if (cv === null || cv === undefined || !(cv <= v)) return false;\n break;\n case \">\":\n if (cv === null || cv === undefined || !(cv > v)) return false;\n break;\n case \">=\":\n if (cv === null || cv === undefined || !(cv >= v)) return false;\n break;\n default:\n return false;\n }\n } else {\n // Direct value means equality\n if (columnValue !== criterion) return false;\n }\n }\n return true;\n });\n\n // Delete the filtered entries and emit events for each\n for (const [id, entity] of entriesToDelete) {\n this.values.delete(id);\n const { key } = this.separateKeyValueFromCombined(entity);\n this.events.emit(\"delete\", key as keyof Entity);\n }\n }\n\n /**\n * Queries entries matching the specified search criteria with optional ordering, limit, and offset.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of matching entities or undefined if no matches found\n */\n async query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n this.validateQueryParams(criteria, options);\n\n const criteriaKeys = Object.keys(criteria) as Array<keyof Entity>;\n\n let results: Entity[] = Array.from(this.values.values()).filter((entity) => {\n for (const column of criteriaKeys) {\n const criterion = criteria[column];\n const columnValue = entity[column];\n\n if (isSearchCondition(criterion)) {\n const { value, operator } = criterion;\n const v = value as string | number;\n const cv = columnValue as string | number | null | undefined;\n switch (operator) {\n case \"=\":\n if (cv !== v) return false;\n break;\n case \"<\":\n if (cv === null || cv === undefined || !(cv < v)) return false;\n break;\n case \"<=\":\n if (cv === null || cv === undefined || !(cv <= v)) return false;\n break;\n case \">\":\n if (cv === null || cv === undefined || !(cv > v)) return false;\n break;\n case \">=\":\n if (cv === null || cv === undefined || !(cv >= v)) return false;\n break;\n default:\n return false;\n }\n } else {\n if (columnValue !== criterion) return false;\n }\n }\n return true;\n });\n\n if (options?.orderBy && options.orderBy.length > 0) {\n results.sort((a, b) => {\n for (const { column, direction } of options.orderBy!) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n });\n }\n\n if (options?.offset !== undefined) {\n results = results.slice(options.offset);\n }\n\n if (options?.limit !== undefined) {\n results = results.slice(0, options.limit);\n }\n\n const result = results.length > 0 ? results : undefined;\n this.events.emit(\"query\", criteria as Partial<Entity>, result);\n return result;\n }\n\n /**\n * Strict, projected query served entirely by a covering compound index.\n * Throws CoveringIndexMissingError when no registered index can serve the request.\n * Returns Pick<Entity, K>[] — never undefined, empty array on no matches.\n */\n override async queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n this.validateSelect(options);\n this.validateQueryParams(criteria, options);\n\n const registered = this.indexes.map((cols, i) => ({\n name: `idx_${i}`,\n keyPath: cols as string[],\n }));\n\n pickCoveringIndex({\n table: \"InMemoryTabularStorage\",\n indexes: registered,\n criteriaColumns: Object.keys(criteria),\n orderByColumns: (options.orderBy ?? []).map((o) => ({\n column: String(o.column),\n direction: o.direction,\n })),\n selectColumns: options.select.map(String),\n primaryKeyColumns: this.primaryKeyNames.map(String),\n });\n\n // Validation passed — reuse query() then project\n const full = await this.query(criteria, {\n orderBy: options.orderBy,\n limit: options.limit,\n offset: options.offset,\n });\n if (!full) return [];\n return full.map((row) => {\n const out = {} as Pick<Entity, K>;\n for (const k of options.select) out[k] = row[k];\n return out;\n });\n }\n\n /**\n * Subscribes to changes in the repository.\n * Since InMemory is both client and server, changes are detected via local events.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options (not used for in-memory)\n * @returns Unsubscribe function\n */\n public override subscribeToChanges(\n callback: (change: TabularChangePayload<Entity>) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n const handlePut = (entity: Entity) => {\n callback({ type: this._lastPutWasInsert ? \"INSERT\" : \"UPDATE\", new: entity });\n };\n\n const handleDelete = (_key: keyof Entity) => {\n callback({ type: \"DELETE\" });\n };\n\n const handleClearAll = () => {\n callback({ type: \"DELETE\" });\n };\n\n this.events.on(\"put\", handlePut);\n this.events.on(\"delete\", handleDelete);\n this.events.on(\"clearall\", handleClearAll);\n\n return () => {\n this.events.off(\"put\", handlePut);\n this.events.off(\"delete\", handleDelete);\n this.events.off(\"clearall\", handleClearAll);\n };\n }\n\n /**\n * Destroys the repository and frees up resources.\n */\n public override destroy(): void {\n this.values.clear();\n }\n}\n",
9
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { StorageValidationError } from \"./StorageError\";\n\n/**\n * Opaque cursor token returned by paginated queries.\n *\n * Encodes the position of the last seen row so the next page can resume\n * from there using keyset (a.k.a. seek) pagination. Callers must treat\n * cursors as opaque — the encoding is an implementation detail.\n *\n * Named `PageCursor` (not `Cursor`) to avoid colliding with the unrelated\n * generic iterator type `Cursor<T>` exported from `@workglow/util`.\n */\nexport type PageCursor = string & { readonly __pageCursorBrand: unique symbol };\n\n/**\n * Internal cursor payload.\n *\n * - `v` is a format version (currently 1) so older cursors can be rejected\n * if the encoding ever changes.\n * - `n` (names), `d` (directions, `\"a\"` ASC / `\"d\"` DESC), and `c` (column\n * values) are parallel arrays describing the effective ordering of the\n * row the cursor is parked on: caller-supplied `orderBy` columns first,\n * then any primary-key columns not already covered by `orderBy` as a\n * tiebreaker. The flat layout means columns that appear in both the\n * user's `orderBy` and the primary key are stored exactly once (avoids\n * double-encoding the value in DESC-by-PK pagination). Storing column\n * names AND directions lets us reject cursors handed back with a\n * different ordering — same-name + flipped direction would otherwise\n * compute the next page with the wrong comparison operators.\n */\nexport interface CursorPayload {\n readonly v: 1;\n readonly n: ReadonlyArray<string>;\n readonly d: ReadonlyArray<\"a\" | \"d\">;\n readonly c: ReadonlyArray<string | number | boolean | null>;\n}\n\n/** Cursor format version recognised by this build. */\nexport const CURSOR_VERSION = 1 as const;\n\n/**\n * Maximum accepted cursor length, in characters of the encoded form.\n * Real cursors are well under 1 KB (a few primary-key values plus an\n * orderBy worth of column names); the cap exists so a hostile client\n * can't force the server to base64-decode and JSON-parse arbitrarily\n * large strings, which would be cheap memory/CPU abuse.\n */\nexport const MAX_CURSOR_LENGTH = 8 * 1024;\n\n/**\n * Encodes a cursor payload as an opaque, URL-safe string.\n *\n * The transport is base64url(JSON(payload)). The format is intentionally\n * not documented for callers — it's an implementation detail and may be\n * tightened in future versions (signed, length-limited, etc.).\n */\nexport function encodeCursor(payload: CursorPayload): PageCursor {\n // Caller is trusted (we construct payloads internally); the n/c arity\n // mismatch check lives on the decode side, where cursors come from the\n // wire and must be validated before use.\n const json = JSON.stringify(payload);\n let base64: string;\n if (typeof Buffer !== \"undefined\") {\n base64 = Buffer.from(json, \"utf8\").toString(\"base64\");\n } else {\n // `String.fromCharCode(...bytes)` blows past the JS engine's argument\n // limit on long inputs (~64K bytes on V8, smaller on JSC). Build the\n // binary string a chunk at a time so cursors stay safe even when an\n // orderBy is wide or values are long.\n const bytes = new TextEncoder().encode(json);\n let binary = \"\";\n const CHUNK = 0x8000;\n for (let i = 0; i < bytes.length; i += CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));\n }\n base64 = btoa(binary);\n }\n // URL-safe variant, no padding. Strip trailing `=` with a counted slice\n // rather than a regex — `/=+$/` is a quantifier the static analyser flags\n // as polynomial-on-uncontrolled-input even though it's actually linear,\n // and a counted slice is both faster and obviously bounded.\n let trimEnd = base64.length;\n while (trimEnd > 0 && base64.charCodeAt(trimEnd - 1) === 0x3d /* '=' */) {\n trimEnd--;\n }\n const urlSafe = base64.slice(0, trimEnd).replace(/\\+/g, \"-\").replace(/\\//g, \"_\");\n // A server-emitted cursor must round-trip through `decodeCursor`. If a\n // row's sort key encodes large enough to push past the cap, fail loudly\n // here rather than minting a `nextCursor` that the very next request\n // would reject as oversized.\n if (urlSafe.length > MAX_CURSOR_LENGTH) {\n throw new StorageValidationError(\n `Encoded cursor exceeds maximum length (${urlSafe.length} > ${MAX_CURSOR_LENGTH})`\n );\n }\n return urlSafe as PageCursor;\n}\n\n/**\n * Decodes an opaque cursor string into its payload.\n *\n * @throws {StorageValidationError} when the cursor is malformed, exceeds\n * {@link MAX_CURSOR_LENGTH}, or the format version is unknown. Callers\n * should surface these as 4xx errors rather than retrying.\n */\nexport function decodeCursor(cursor: PageCursor | string): CursorPayload {\n if (typeof cursor !== \"string\" || cursor.length === 0) {\n throw new StorageValidationError(\"Cursor must be a non-empty string\");\n }\n if (cursor.length > MAX_CURSOR_LENGTH) {\n // Reject before allocating any of the decode buffers — a hostile client\n // can otherwise force base64-decode + JSON.parse on megabytes of input.\n throw new StorageValidationError(\n `Cursor exceeds maximum length (${cursor.length} > ${MAX_CURSOR_LENGTH})`\n );\n }\n const padded = cursor.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padding = padded.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (padded.length % 4));\n let json: string;\n try {\n if (typeof Buffer !== \"undefined\") {\n json = Buffer.from(padded + padding, \"base64\").toString(\"utf8\");\n } else {\n const binary = atob(padded + padding);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n json = new TextDecoder().decode(bytes);\n }\n } catch {\n throw new StorageValidationError(\"Cursor is not valid base64url\");\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new StorageValidationError(\"Cursor payload is not valid JSON\");\n }\n\n const p = parsed as Partial<CursorPayload>;\n // Validate every shape the rest of the system relies on. The `c` element\n // type check is what stops a hostile cursor from smuggling an object or\n // array into a downstream SQL parameter binder or string comparator —\n // every consumer assumes `c[i]` is a primitive (or null).\n if (\n !p ||\n typeof p !== \"object\" ||\n p.v !== CURSOR_VERSION ||\n !Array.isArray(p.c) ||\n !Array.isArray(p.n) ||\n !Array.isArray(p.d) ||\n p.n.length !== p.c.length ||\n p.n.length !== p.d.length ||\n !p.n.every((name) => typeof name === \"string\") ||\n !p.d.every((dir) => dir === \"a\" || dir === \"d\") ||\n !p.c.every(\n (v) => v === null || typeof v === \"string\" || typeof v === \"number\" || typeof v === \"boolean\"\n )\n ) {\n throw new StorageValidationError(`Cursor format is unsupported (expected v${CURSOR_VERSION})`);\n }\n\n return p as CursorPayload;\n}\n\n/**\n * Validates that a decoded cursor matches the effective ordering the\n * current request would use, by exact column-name AND direction sequence.\n * A mismatch usually means the caller switched ordering mid-iteration —\n * e.g. asked for `orderBy: [a ASC]` on page 1 and `orderBy: [a DESC]` on\n * page 2, or swapped to a different column entirely — both of which would\n * produce nonsensical results that arity matching alone can't catch.\n */\nexport function assertCursorMatches(\n payload: CursorPayload,\n effectiveOrder: ReadonlyArray<{ readonly column: string; readonly direction: \"ASC\" | \"DESC\" }>\n): void {\n if (payload.n.length !== effectiveOrder.length) {\n throw new StorageValidationError(\n `Cursor has ${payload.n.length} component(s); request expects ${effectiveOrder.length}`\n );\n }\n for (let i = 0; i < effectiveOrder.length; i++) {\n if (payload.n[i] !== effectiveOrder[i].column) {\n throw new StorageValidationError(\n `Cursor column ${i} is \"${payload.n[i]}\"; request expects \"${effectiveOrder[i].column}\"`\n );\n }\n const expected = effectiveOrder[i].direction === \"ASC\" ? \"a\" : \"d\";\n if (payload.d[i] !== expected) {\n throw new StorageValidationError(\n `Cursor column \"${effectiveOrder[i].column}\" was minted for ${\n payload.d[i] === \"a\" ? \"ASC\" : \"DESC\"\n }; request expects ${effectiveOrder[i].direction}`\n );\n }\n }\n}\n",
10
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { IMigration, MigrationProgressListener } from \"./IMigration\";\n\n/**\n * Name of the bookkeeping table used by every runner. The leading underscore\n * is intentional — it sorts before user tables in catalog views and signals\n * \"system table, don't touch\".\n */\nexport const MIGRATIONS_TABLE = \"_storage_migrations\";\n\n/**\n * Optional knobs accepted by {@link IMigrationRunner.run}.\n *\n * `onProgress` is invoked synchronously from inside the migration's\n * transaction (or upgrade callback for IndexedDB), so listeners must NOT\n * await IO that touches the same database — they would deadlock against\n * the open transaction. Use them for logging, telemetry, or in-memory\n * progress UI updates.\n */\nexport interface RunMigrationsOptions {\n readonly onProgress?: MigrationProgressListener;\n}\n\n/**\n * Common runner contract — runs the supplied migrations in (component, version)\n * order, skipping any that have already been recorded. Returns the migrations\n * that were actually applied this call (useful for logging/tests).\n *\n * Concrete runners live in driver-specific packages (`@workglow/postgres`,\n * `@workglow/sqlite`) so that `@workglow/storage` does not have to depend on\n * any database driver.\n */\nexport interface IMigrationRunner<DB> {\n ensureBookkeepingTable(): Promise<void>;\n appliedVersions(component: string): Promise<Set<number>>;\n run(\n migrations: ReadonlyArray<IMigration<DB>>,\n options?: RunMigrationsOptions\n ): Promise<ReadonlyArray<IMigration<DB>>>;\n}\n\n/**\n * Stable sort by `(component asc, version asc)`. Exposed so concrete runners\n * in driver packages don't need their own copy.\n */\nexport function sortMigrations<DB>(migrations: ReadonlyArray<IMigration<DB>>): IMigration<DB>[] {\n return [...migrations].sort((a, b) => {\n if (a.component !== b.component) return a.component < b.component ? -1 : 1;\n return a.version - b.version;\n });\n}\n",
11
+ "/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {\n ITabularMigration,\n ITabularMigrationApplier,\n TabularMigrationProgressListener,\n} from \"./TabularMigration\";\n\n/**\n * Sequences pending tabular migrations through a backend's\n * {@link ITabularMigrationApplier}.\n *\n * Two paths:\n * - **fresh-DB fast path** — applied is empty AND the caller signaled (via\n * `freshTable: true`) that the underlying table/store did not exist\n * before this run, OR `applier.tableExists()` returns false. The\n * orchestrator records every declared migration as already-applied\n * without running its ops.\n * - **run-pending path** — sorted-by-version, skip already-applied,\n * call `applyMigration` for each remaining one.\n *\n * Bookkeeping is owned by the applier (one row per `(component, version)`\n * in the existing `_storage_migrations` table).\n *\n * The `freshTable` option exists because `setupDatabase()` typically creates\n * the table at the target schema *before* invoking the orchestrator, which\n * defeats the `tableExists()` probe (the store always looks \"existing\" by\n * the time the orchestrator runs). Callers should pass the freshness probe\n * they took *before* creating the table.\n */\nexport interface RunTabularMigrationsOptions {\n readonly onProgress?: TabularMigrationProgressListener;\n readonly freshTable?: boolean;\n}\n\nexport async function runTabularMigrations(\n applier: ITabularMigrationApplier,\n defaultComponent: string,\n migrations: ReadonlyArray<ITabularMigration>,\n options: RunTabularMigrationsOptions = {}\n): Promise<void> {\n if (migrations.length === 0) return;\n await applier.ensureBookkeeping();\n\n // Group migrations by their resolved component so applied lookups stay\n // accurate when callers override `component` per migration.\n const byComponent = new Map<string, ITabularMigration[]>();\n for (const m of migrations) {\n const c = m.component ?? defaultComponent;\n let bucket = byComponent.get(c);\n if (!bucket) {\n bucket = [];\n byComponent.set(c, bucket);\n }\n bucket.push(m);\n }\n\n for (const [component, group] of byComponent) {\n const sorted = [...group].sort((a, b) => a.version - b.version);\n const applied = await applier.appliedVersions(component);\n\n const fresh = options.freshTable ?? !(await applier.tableExists());\n if (applied.size === 0 && fresh) {\n // Fresh-DB fast path: caller created the table at target; skip ops.\n await applier.markAllApplied(\n component,\n sorted.map((m) => ({ version: m.version, description: m.description }))\n );\n for (const m of sorted) {\n options.onProgress?.({\n component,\n version: m.version,\n phase: \"completed\",\n description: m.description,\n fraction: 1,\n });\n }\n continue;\n }\n\n for (const m of sorted) {\n if (applied.has(m.version)) continue;\n options.onProgress?.({\n component,\n version: m.version,\n phase: \"starting\",\n description: m.description,\n });\n try {\n await applier.applyMigration(component, m.version, m.description, m.ops, (fraction) => {\n options.onProgress?.({\n component,\n version: m.version,\n phase: \"running\",\n description: m.description,\n fraction,\n });\n });\n options.onProgress?.({\n component,\n version: m.version,\n phase: \"completed\",\n description: m.description,\n fraction: 1,\n });\n } catch (err) {\n options.onProgress?.({\n component,\n version: m.version,\n phase: \"failed\",\n description: m.description,\n error: err,\n });\n throw err;\n }\n }\n }\n}\n",
12
+ "/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { AnyTabularStorage, PageCursor } from \"../tabular/ITabularStorage\";\n\n/**\n * Backend-agnostic, page-based backfill loop. Iterates every row in the\n * storage in `batchSize`-row pages using cursor pagination so iteration\n * is stable under concurrent writes.\n *\n * For each row, calls `transform` and:\n * - if it returns the **same reference** as the input, skips the write\n * (the row is unchanged);\n * - if it returns `undefined`, deletes the row;\n * - otherwise, writes the new row via `put`.\n */\nexport async function runBackfill(\n storage: AnyTabularStorage,\n batchSize: number,\n transform: (\n row: Record<string, unknown>\n ) => Promise<Record<string, unknown> | undefined> | Record<string, unknown> | undefined\n): Promise<void> {\n let cursor: PageCursor | undefined;\n while (true) {\n const page = await storage.getPage({ limit: batchSize, cursor });\n for (const row of page.items) {\n const out = await transform(row as Record<string, unknown>);\n if (out === row) continue;\n if (out === undefined) {\n await storage.delete(row);\n } else {\n await storage.put(out);\n }\n }\n if (!page.nextCursor) break;\n cursor = page.nextCursor;\n }\n}\n",
13
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n DataPortSchemaObject,\n FromSchema,\n JsonSchema,\n TypedArraySchemaOptions,\n} from \"@workglow/util/schema\";\nimport { BaseTabularStorage, ClientProvidedKeysOption } from \"./BaseTabularStorage\";\nimport {\n AutoGeneratedKeys,\n InsertEntity,\n OrderBy,\n Page,\n PageRequest,\n SearchCriteria,\n SimplifyPrimaryKey,\n ValueOptionType,\n} from \"./ITabularStorage\";\nimport { assertCursorMatches, decodeCursor } from \"./Cursor\";\n\n// BaseTabularStorage is a tabular store that uses SQLite and Postgres use as common code\n\n/**\n * Base class for SQL-based tabular repositories that implements common functionality\n * for both SQLite and PostgreSQL database implementations.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport abstract class BaseSqlTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n /**\n * Creates a new instance of BaseSqlTabularStorage\n * @param table - The name of the database table to use for storage\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n /** Caches for computed column definition strings, keyed by delimiter */\n private readonly _pkColsCache = new Map<string, string>();\n private readonly _valColsCache = new Map<string, string>();\n private readonly _pkColListCache = new Map<string, string>();\n private readonly _valColListCache = new Map<string, string>();\n\n constructor(\n protected readonly table: string = \"tabular_store\",\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\",\n tabularMigrations?: ReadonlyArray<import(\"../migrations\").ITabularMigration>,\n migrationName?: string\n ) {\n super(\n schema,\n primaryKeyNames,\n indexes,\n clientProvidedKeys,\n tabularMigrations,\n migrationName ?? table\n );\n this.validateTableAndSchema();\n }\n\n /**\n * Maps JavaScript/TypeScript types to their corresponding SQL type\n * Must be implemented by derived classes for specific SQL dialects\n */\n protected abstract mapTypeToSQL(typeDef: JsonSchema): string;\n\n /**\n * Generates the SQL column definitions for primary key fields\n * @returns SQL string containing primary key column definitions\n */\n protected constructPrimaryKeyColumns($delimiter: string = \"\"): string {\n let cached = this._pkColsCache.get($delimiter);\n if (cached === undefined) {\n cached = Object.entries<JsonSchema>(this.primaryKeySchema.properties)\n .map(([key, typeDef]) => {\n const sqlType = this.mapTypeToSQL(typeDef);\n return `${$delimiter}${key}${$delimiter} ${sqlType} NOT NULL`;\n })\n .join(\", \");\n this._pkColsCache.set($delimiter, cached);\n }\n return cached;\n }\n\n /**\n * Generates the SQL column definitions for value fields\n * @returns SQL string containing value column definitions\n */\n protected constructValueColumns($delimiter: string = \"\"): string {\n let cached = this._valColsCache.get($delimiter);\n if (cached === undefined) {\n const requiredSet = new Set(this.valueSchema.required ?? []);\n const cols = Object.entries<JsonSchema>(this.valueSchema.properties)\n .map(([key, typeDef]) => {\n const sqlType = this.mapTypeToSQL(typeDef);\n const isRequired = requiredSet.has(key);\n const nullable = !isRequired || this.isNullable(typeDef);\n return `${$delimiter}${key}${$delimiter} ${sqlType}${nullable ? \" NULL\" : \" NOT NULL\"}`;\n })\n .join(\", \");\n cached = cols.length > 0 ? `, ${cols}` : \"\";\n this._valColsCache.set($delimiter, cached);\n }\n return cached;\n }\n\n /**\n * Determines if a schema type allows null values\n * @param typeDef - The schema type definition\n * @returns true if the type allows null values\n */\n protected isNullable(typeDef: JsonSchema): boolean {\n if (typeof typeDef === \"boolean\") return typeDef;\n\n // Check for direct null type\n if (typeDef.type === \"null\") {\n return true;\n }\n\n // Check for type as an array that includes null (e.g., type: [\"null\", \"string\"])\n if (Array.isArray(typeDef.type)) {\n return typeDef.type.includes(\"null\");\n }\n\n // Check for union types that include null (anyOf)\n if (typeDef.anyOf && Array.isArray(typeDef.anyOf)) {\n return typeDef.anyOf.some((type: any) => type.type === \"null\");\n }\n\n // Check for union types that include null (oneOf)\n if (typeDef.oneOf && Array.isArray(typeDef.oneOf)) {\n return typeDef.oneOf.some((type: any) => type.type === \"null\");\n }\n\n return false;\n }\n\n /**\n * Returns a comma-separated list of primary key column names\n * @returns Formatted string of primary key column names\n */\n protected primaryKeyColumnList($delimiter: string = \"\"): string {\n let cached = this._pkColListCache.get($delimiter);\n if (cached === undefined) {\n cached =\n $delimiter + this.primaryKeyColumns().join(`${$delimiter}, ${$delimiter}`) + $delimiter;\n this._pkColListCache.set($delimiter, cached);\n }\n return cached;\n }\n\n /**\n * Returns a comma-separated list of value column names\n * @returns Formatted string of value column names\n */\n protected valueColumnList($delimiter: string = \"\"): string {\n let cached = this._valColListCache.get($delimiter);\n if (cached === undefined) {\n cached = $delimiter + this.valueColumns().join(`${$delimiter}, ${$delimiter}`) + $delimiter;\n this._valColListCache.set($delimiter, cached);\n }\n return cached;\n }\n\n /**\n * Gets the real underlying type from possibly union types\n * For example, for a union with null, this extracts the non-null type\n * @param typeDef - The schema to extract from\n * @returns The non-null type from the schema\n */\n protected getNonNullType(typeDef: JsonSchema): JsonSchema {\n if (typeof typeDef === \"boolean\") return typeDef;\n\n if (typeDef.anyOf && Array.isArray(typeDef.anyOf)) {\n const nonNullType = typeDef.anyOf.find((t: any) => t.type !== \"null\");\n if (nonNullType) {\n return nonNullType;\n }\n }\n if (typeDef.oneOf && Array.isArray(typeDef.oneOf)) {\n const nonNullType = typeDef.oneOf.find((t: any) => t.type !== \"null\");\n if (nonNullType) {\n return nonNullType;\n }\n }\n return typeDef;\n }\n\n /**\n * Converts a value object into an ordered array based on the valueSchema\n * This ensures consistent parameter ordering for SQL queries\n * @param value - The value object to convert\n * @returns Array of values ordered according to the schema\n * @throws Error if a required field is missing\n */\n protected getValueAsOrderedArray(value: Value): ValueOptionType[] {\n const orderedParams: ValueOptionType[] = [];\n const valueAsRecord = value as Record<string, Entity[keyof Entity]>;\n const requiredSet = new Set(this.valueSchema.required ?? []);\n for (const key in this.valueSchema.properties) {\n if (Object.prototype.hasOwnProperty.call(valueAsRecord, key)) {\n const val = valueAsRecord[key];\n // Convert undefined to null for optional fields\n if (val === undefined && !requiredSet.has(key)) {\n orderedParams.push(null);\n } else {\n orderedParams.push(this.jsToSqlValue(key, val));\n }\n } else {\n // If the field is required, throw an error\n if (requiredSet.has(key)) {\n throw new Error(`Missing required value field: ${key}`);\n }\n // If the field is optional, use null\n orderedParams.push(null);\n }\n }\n return orderedParams;\n }\n\n /**\n * Converts a primary key object into an ordered array based on the schema\n * This ensures consistent parameter ordering for storage operations\n * @param key - The primary key object to convert\n * @returns Array of key values ordered according to the schema\n */\n protected override getPrimaryKeyAsOrderedArray(key: PrimaryKey): ValueOptionType[] {\n const orderedParams: ValueOptionType[] = [];\n const keyObj = key as Record<string, Entity[keyof Entity]>;\n for (const k of Object.keys(this.primaryKeySchema.properties)) {\n if (k in keyObj) {\n const value = keyObj[k];\n if (value === null) {\n throw new Error(`Primary key field ${k} cannot be null`);\n }\n orderedParams.push(this.jsToSqlValue(k, value));\n } else {\n throw new Error(`Missing required primary key field: ${k}`);\n }\n }\n return orderedParams;\n }\n\n protected jsToSqlValue(column: string, value: Entity[keyof Entity]): ValueOptionType {\n const typeDef = this.schema.properties[column];\n if (!typeDef) {\n return value as ValueOptionType;\n }\n\n // Handle null values for nullable columns\n if (value === null && this.isNullable(typeDef)) {\n return null;\n }\n\n // Extract the non-null type for proper handling\n const actualType = this.getNonNullType(typeDef);\n if (typeof actualType === \"boolean\") {\n return value as ValueOptionType;\n }\n\n if (actualType.contentEncoding === \"blob\") {\n if (value instanceof Uint8Array) {\n return value;\n }\n if (typeof Buffer !== \"undefined\" && value instanceof Buffer) {\n return new Uint8Array(value);\n }\n if (Array.isArray(value)) {\n return new Uint8Array(value);\n }\n return value as ValueOptionType;\n } else if (value instanceof Date) {\n // Convert all Date objects to ISO string regardless of type definition\n return value.toISOString();\n } else {\n return value as ValueOptionType;\n }\n }\n\n protected sqlToJsValue(column: string, value: ValueOptionType): Entity[keyof Entity] {\n // Get the type definition\n const typeDef = this.schema.properties[column];\n if (!typeDef) {\n return value as Entity[keyof Entity];\n }\n\n // Handle null values\n if (value === null && this.isNullable(typeDef)) {\n return null as Entity[keyof Entity];\n }\n\n // Extract the non-null type for proper handling\n const actualType = this.getNonNullType(typeDef);\n if (typeof actualType === \"boolean\") {\n return value as Entity[keyof Entity];\n }\n\n if (actualType.contentEncoding === \"blob\") {\n if (typeof Buffer !== \"undefined\" && value instanceof Buffer) {\n return new Uint8Array(value) as Entity[keyof Entity];\n }\n if (value instanceof Uint8Array) {\n return value as Entity[keyof Entity];\n }\n return value as Entity[keyof Entity];\n } else {\n return value as Entity[keyof Entity];\n }\n }\n\n /**\n * Validates table name and schema configurations\n * Checks for:\n * 1. Valid table name format\n * 2. Valid schema key names\n * 3. No duplicate keys between primary key and value schemas\n * This is a sanity check to make sure the table and schema are valid,\n * and to prevent dumb mistakes and mischevious behavior.\n * @throws Error if validation fails\n */\n protected validateTableAndSchema(): void {\n // Validate table name\n if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(this.table)) {\n throw new Error(\n \"Table name must start with a letter and contain only letters, digits, and underscores, got: \" +\n this.table\n );\n }\n\n // Validate schema keys\n const validateSchemaKeys = (schema: DataPortSchemaObject) => {\n for (const key in schema.properties) {\n if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(key)) {\n throw new Error(\n \"Schema keys must start with a letter and contain only letters, digits, and underscores, got: \" +\n key\n );\n }\n }\n };\n\n validateSchemaKeys(this.primaryKeySchema);\n validateSchemaKeys(this.valueSchema);\n\n // Check for key name collisions between schemas\n const primaryKeys = new Set(Object.keys(this.primaryKeySchema.properties));\n const valueKeys = Object.keys(this.valueSchema.properties);\n const duplicates = valueKeys.filter((key) => primaryKeys.has(key));\n if (duplicates.length > 0) {\n throw new Error(`Duplicate keys found in schemas: ${duplicates.join(\", \")}`);\n }\n }\n\n /**\n * Builds a keyset (seek) WHERE clause that selects rows strictly after\n * the cursor position according to the provided ordering. Handles mixed\n * ASC/DESC by expanding into the OR-of-AND form:\n *\n * (col1 OP1 v1)\n * OR (col1 = v1 AND col2 OP2 v2)\n * OR (col1 = v1 AND col2 = v2 AND col3 OP3 v3)\n * ...\n *\n * NULL handling. We adopt the same NULL ordering as the in-memory\n * comparator: NULLs sort *before* non-null values for ASC, *after* them\n * for DESC. The pair `runSqlPage` / `buildKeysetWhere` enforce this with\n * explicit `NULLS FIRST` / `NULLS LAST` in the ORDER BY plus NULL-aware\n * predicates here. Because the cursor's values are known at SQL-build\n * time, we branch at build time on NULL vs non-NULL rather than relying\n * on dialect-specific operators like `IS NOT DISTINCT FROM`:\n *\n * - Equality on a preceding column:\n * NULL -> `col IS NULL`\n * value -> `col = ?`\n * - Strict comparison on the i-th column:\n * ASC, NULL -> `col IS NOT NULL` (NULL is the smallest)\n * ASC, value -> `col > ?` (NULLs sort before, excluded)\n * DESC, NULL -> `1 = 0` (nothing comes after a NULL trailer)\n * DESC, value-> `(col < ? OR col IS NULL)` (smaller, then NULLs)\n *\n * @param effectiveOrderBy - Ordering to satisfy; must include the primary\n * key columns at the tail so the comparison is total.\n * @param cursorValues - Values pulled from the cursor in the same order\n * as `effectiveOrderBy`.\n * @param quote - Identifier quote character (e.g. `` ` `` or `\"`).\n * @param placeholder - Function returning the placeholder string for the\n * given 1-based parameter index.\n * @param startIndex - Param index to start numbering from. Returns the\n * index to use after this clause's params.\n */\n protected buildKeysetWhere(\n effectiveOrderBy: ReadonlyArray<OrderBy<Entity>>,\n cursorValues: ReadonlyArray<ValueOptionType>,\n quote: string,\n placeholder: (index: number) => string,\n startIndex: number\n ): { whereClause: string; params: ValueOptionType[]; nextIndex: number } {\n if (effectiveOrderBy.length !== cursorValues.length) {\n throw new Error(\n `Keyset arity mismatch: ${effectiveOrderBy.length} order columns vs ${cursorValues.length} cursor values`\n );\n }\n const clauses: string[] = [];\n const params: ValueOptionType[] = [];\n let idx = startIndex;\n for (let i = 0; i < effectiveOrderBy.length; i++) {\n const parts: string[] = [];\n // Equality on all preceding columns. NULL-aware: collapse to IS NULL\n // when the cursor value is null, since SQL `col = NULL` is never true.\n for (let j = 0; j < i; j++) {\n const colExpr = `${quote}${String(effectiveOrderBy[j].column)}${quote}`;\n if (cursorValues[j] === null) {\n parts.push(`${colExpr} IS NULL`);\n } else {\n parts.push(`${colExpr} = ${placeholder(idx)}`);\n params.push(cursorValues[j]);\n idx++;\n }\n }\n // Strict comparison on the i-th column in its declared direction.\n const colExpr = `${quote}${String(effectiveOrderBy[i].column)}${quote}`;\n const v = cursorValues[i];\n const dir = effectiveOrderBy[i].direction;\n if (v === null) {\n // ASC NULLS FIRST: anything non-null comes after a NULL cursor.\n // DESC NULLS LAST: NULL is the trailing run; nothing comes after.\n parts.push(dir === \"ASC\" ? `${colExpr} IS NOT NULL` : `1 = 0`);\n } else {\n if (dir === \"ASC\") {\n parts.push(`${colExpr} > ${placeholder(idx)}`);\n } else {\n // DESC: rows smaller than v, then any NULLs after them.\n parts.push(`(${colExpr} < ${placeholder(idx)} OR ${colExpr} IS NULL)`);\n }\n params.push(v);\n idx++;\n }\n clauses.push(`(${parts.join(\" AND \")})`);\n }\n return { whereClause: clauses.join(\" OR \"), params, nextIndex: idx };\n }\n\n /**\n * Shared SQL `getPage`/`queryPage` driver. Builds a single SELECT that\n * pushes the keyset predicate, ordering, and limit down to the database\n * so memory and wire traffic are O(pageSize) regardless of table size.\n *\n * Concrete SQL backends call this from their `getPage`/`queryPage`\n * overrides, supplying the dialect-specific quoting, placeholder\n * formatting, search-criteria WHERE builder, and select executor.\n */\n protected async runSqlPage(\n criteria: SearchCriteria<Entity> | undefined,\n request: PageRequest<Entity>,\n dialect: {\n readonly quote: string;\n readonly placeholder: (index: number) => string;\n readonly buildSearchWhere: (\n criteria: SearchCriteria<Entity>,\n startIndex: number\n ) => { whereClause: string; params: ValueOptionType[]; nextIndex: number };\n readonly executeSelect: (sql: string, params: ValueOptionType[]) => Promise<Entity[]>;\n }\n ): Promise<Page<Entity>> {\n // Validate before we interpolate any column names into SQL — the caller\n // is the trust boundary for orderBy contents.\n this.validatePageRequest(request);\n const limit = request.limit ?? 100;\n const pkColumns = this.primaryKeyColumns() as unknown as Array<keyof Entity>;\n const orderBy = request.orderBy;\n const effectiveOrderBy = this.buildEffectiveOrderBy(orderBy, pkColumns);\n const effectiveOrderForCursor = effectiveOrderBy.map((o) => ({\n column: String(o.column),\n direction: o.direction,\n }));\n\n let cursorPayload;\n if (request.cursor !== undefined) {\n cursorPayload = decodeCursor(request.cursor);\n assertCursorMatches(cursorPayload, effectiveOrderForCursor);\n }\n\n const params: ValueOptionType[] = [];\n let paramIdx = 1;\n const whereClauses: string[] = [];\n\n if (criteria && Object.keys(criteria).length > 0) {\n const built = dialect.buildSearchWhere(criteria, paramIdx);\n whereClauses.push(built.whereClause);\n params.push(...built.params);\n paramIdx = built.nextIndex;\n }\n\n if (cursorPayload) {\n // Re-encode cursor values through `jsToSqlValue` so dates and other\n // non-primitive types round-trip through the SQL driver the same way\n // the original row values did.\n const cursorValues = (cursorPayload.c as ValueOptionType[]).map((v, i) =>\n this.jsToSqlValue(String(effectiveOrderBy[i].column), v as Entity[keyof Entity])\n );\n const built = this.buildKeysetWhere(\n effectiveOrderBy,\n cursorValues,\n dialect.quote,\n dialect.placeholder,\n paramIdx\n );\n whereClauses.push(`(${built.whereClause})`);\n params.push(...built.params);\n paramIdx = built.nextIndex;\n }\n\n const q = dialect.quote;\n // Emit NULLS FIRST / NULLS LAST explicitly because SQLite (NULLS first\n // for ASC) and Postgres (NULLS last for ASC) disagree by default. The\n // keyset predicates in `buildKeysetWhere` assume NULLs-first-for-ASC,\n // NULLs-last-for-DESC; the ORDER BY must match that for resumed pages\n // to land in the right slot.\n const orderByClause = effectiveOrderBy\n .map((o) => {\n const nulls = o.direction === \"ASC\" ? \"NULLS FIRST\" : \"NULLS LAST\";\n return `${q}${String(o.column)}${q} ${o.direction} ${nulls}`;\n })\n .join(\", \");\n\n let sql = `SELECT * FROM ${q}${this.table}${q}`;\n if (whereClauses.length > 0) {\n sql += ` WHERE ${whereClauses.join(\" AND \")}`;\n }\n sql += ` ORDER BY ${orderByClause}`;\n sql += ` LIMIT ${dialect.placeholder(paramIdx)}`;\n params.push(limit);\n\n const items = await dialect.executeSelect(sql, params);\n\n const nextCursor =\n items.length === limit\n ? this.buildCursor(items[items.length - 1], effectiveOrderBy)\n : undefined;\n\n return { items, nextCursor };\n }\n}\n",
14
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport { createServiceToken, getLogger } from \"@workglow/util\";\nimport { BaseTabularStorage, ClientProvidedKeysOption } from \"./BaseTabularStorage\";\nimport { InMemoryTabularStorage } from \"./InMemoryTabularStorage\";\nimport type { ITabularMigrationApplier } from \"../migrations\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n ITabularStorage,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\n\nexport const CACHED_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.cached\"\n);\n\n/**\n * A tabular repository wrapper that adds caching layer to a durable repository.\n * Uses InMemoryTabularStorage or SharedInMemoryTabularStorage as a cache\n * for faster access to frequently used data.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class CachedTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n public readonly cache: ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>;\n private durable: ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>;\n private cacheInitialized = false;\n private cacheInitPromise: Promise<void> | null = null;\n\n /**\n * Creates a new CachedTabularStorage instance\n * @param durable - The durable repository to use as the source of truth\n * @param cache - Optional cache repository (InMemoryTabularStorage or SharedInMemoryTabularStorage).\n * If not provided, a new InMemoryTabularStorage will be created.\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n constructor(\n durable: ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>,\n cache?: ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>,\n schema?: Schema,\n primaryKeyNames?: PrimaryKeyNames,\n indexes?: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\"\n ) {\n // Extract schema and primaryKeyNames from durable repository if not provided\n // Note: This is a limitation - we can't always extract these from an interface\n // So we require them to be provided or assume they match\n if (!schema || !primaryKeyNames) {\n throw new Error(\n \"Schema and primaryKeyNames must be provided when creating CachedTabularStorage\"\n );\n }\n\n super(schema, primaryKeyNames, indexes || [], clientProvidedKeys);\n this.durable = durable;\n\n // Create cache if not provided\n if (cache) {\n this.cache = cache;\n } else {\n this.cache = new InMemoryTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>(\n schema,\n primaryKeyNames,\n indexes || [],\n clientProvidedKeys\n );\n }\n\n // Forward events from both cache and durable\n this.setupEventForwarding();\n }\n\n /**\n * Sets up event forwarding from cache and durable repositories\n */\n private setupEventForwarding(): void {\n // Forward cache events\n this.cache.on(\"put\", (entity) => {\n this.events.emit(\"put\", entity);\n });\n this.cache.on(\"get\", (key, entity) => {\n this.events.emit(\"get\", key, entity);\n });\n this.cache.on(\"query\", (key, entities) => {\n this.events.emit(\"query\", key, entities);\n });\n this.cache.on(\"delete\", (key) => {\n this.events.emit(\"delete\", key);\n });\n this.cache.on(\"clearall\", () => {\n this.events.emit(\"clearall\");\n });\n }\n\n /**\n * Initializes the cache by loading all data from the durable repository.\n * Uses a promise-based lock so concurrent callers await the same initialization.\n */\n private async initializeCache(): Promise<void> {\n if (this.cacheInitialized) return;\n\n if (this.cacheInitPromise) {\n return this.cacheInitPromise;\n }\n\n this.cacheInitPromise = (async () => {\n try {\n const all = await this.durable.getAll();\n if (all && all.length > 0) {\n await this.cache.putBulk(all);\n }\n this.cacheInitialized = true;\n } catch (error) {\n getLogger().warn(\"Failed to initialize cache from durable repository:\", { error });\n // Don't mark as initialized on error — allow retry on next access\n } finally {\n this.cacheInitPromise = null;\n }\n })();\n\n return this.cacheInitPromise;\n }\n\n /**\n * Stores a key-value pair in both cache and durable repository\n * @param value - The combined object to store\n * @returns The stored entity\n * @emits 'put' event with the stored entity when successful\n */\n async put(value: InsertType): Promise<Entity> {\n await this.initializeCache();\n\n // Write to durable first (source of truth)\n const result = await this.durable.put(value);\n\n // Then update cache\n await this.cache.put(result);\n\n return result;\n }\n\n /**\n * Stores multiple key-value pairs in both cache and durable repository\n * @param values - Array of combined objects to store\n * @returns Array of stored entities\n * @emits 'put' event for each value stored\n */\n async putBulk(values: InsertType[]): Promise<Entity[]> {\n await this.initializeCache();\n\n // Write to durable first (source of truth)\n const results = await this.durable.putBulk(values);\n\n // Then update cache\n await this.cache.putBulk(results);\n\n return results;\n }\n\n /**\n * Retrieves a value by its key, checking cache first, then durable repository\n * @param key - The primary key object to look up\n * @returns The value object if found, undefined otherwise\n * @emits 'get' event with the fingerprint ID and value when found\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n await this.initializeCache();\n\n // Try cache first\n let result = await this.cache.get(key);\n\n // If not in cache, get from durable and cache it\n if (result === undefined) {\n result = await this.durable.get(key);\n if (result) {\n await this.cache.put(result);\n }\n }\n\n return result;\n }\n\n /**\n * Deletes an entry from both cache and durable repository\n * @param value - The primary key object or entity of the entry to delete\n * @emits 'delete' event with the fingerprint ID when successful\n */\n async delete(value: PrimaryKey | Entity): Promise<void> {\n await this.initializeCache();\n\n // Delete from durable first (source of truth)\n await this.durable.delete(value);\n\n // Then delete from cache\n await this.cache.delete(value);\n }\n\n /**\n * Removes all entries from both cache and durable repository\n * @emits 'clearall' event when successful\n */\n async deleteAll(): Promise<void> {\n await this.initializeCache();\n\n // Delete from durable first (source of truth)\n await this.durable.deleteAll();\n\n // Then delete from cache\n await this.cache.deleteAll();\n }\n\n /**\n * Returns an array of all entries in the repository, with optional ordering, offset, and limit.\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of all entries in the repository\n */\n async getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n this.validateGetAllOptions(options);\n await this.initializeCache();\n\n // Try cache first (without options for population check)\n let results = await this.cache.getAll();\n\n // If cache is empty, get from durable and populate cache\n if (!results || results.length === 0) {\n results = await this.durable.getAll();\n if (results && results.length > 0) {\n await this.cache.putBulk(results);\n }\n }\n\n // If options provided, apply them via the cache\n if (options && results && results.length > 0) {\n return await this.cache.getAll(options);\n }\n\n return results;\n }\n\n /**\n * Returns the number of entries in the repository\n * @returns The total count of stored entries\n */\n async size(): Promise<number> {\n await this.initializeCache();\n\n // Get size from durable (source of truth)\n return await this.durable.size();\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n await this.initializeCache();\n\n // Delegate to durable storage (source of truth) to avoid inconsistency\n return await this.durable.getBulk(offset, limit);\n }\n\n /**\n * Queries entries matching the specified search criteria with optional ordering and limit.\n * Delegates to the cache (which has all data after initialization) for best performance.\n * Falls back to durable if cache returns no results.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering and limit options\n * @returns Array of matching entities or undefined if no matches found\n */\n async query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n await this.initializeCache();\n return await this.cache.query(criteria, options);\n }\n\n override async queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n await this.initializeCache();\n return await this.cache.queryIndex(criteria, options);\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n */\n async deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n await this.initializeCache();\n\n // Delete from durable first (source of truth)\n await this.durable.deleteSearch(criteria);\n\n // Then delete from cache\n await this.cache.deleteSearch(criteria);\n }\n\n /**\n * Runs `fn` inside the durable store's transaction. The cache layer is\n * intentionally bypassed for the transaction's duration — coordinating\n * two-phase commit between the durable store and an in-memory cache is\n * out of scope, and callers asking for `withTransaction` are asking for\n * atomicity, which only the durable can provide. Inside `fn`, reads and\n * writes go straight through the durable's transaction handle.\n *\n * **`tx` identity:** the handle passed to `fn` is the durable store's\n * transaction proxy, not a `CachedTabularStorage`, despite the `(tx: this)`\n * type. Wrapper-specific members like `tx.cache` or `tx.invalidateCache()`\n * therefore type-check but throw at runtime — call them on the original\n * wrapper after `withTransaction` resolves instead.\n *\n * **Cache freshness on commit/rollback:** any rows the transaction touched\n * (committed inserts/updates/deletes, or rolled-back attempts that were\n * already mirrored into the cache via earlier non-tx writes) leave the\n * cache potentially out of step with durable. Invalidate unconditionally\n * after `fn` resolves so the next read repopulates the cache from durable\n * — over-invalidating on rollback is cheap and avoids leaking the\n * rollback boundary into the cache layer. Inheriting `BaseTabularStorage`'s\n * no-op default here would silently lose the rollback / atomicity guarantee,\n * since the default just runs `fn(this)` against the cached wrapper itself.\n */\n override async withTransaction<T>(fn: (tx: this) => Promise<T>): Promise<T> {\n await this.initializeCache();\n try {\n return await this.durable.withTransaction(\n fn as unknown as (\n tx: ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>\n ) => Promise<T>\n );\n } finally {\n // Conservative: forget what we cached and let subsequent reads re-warm\n // from durable. `invalidateCache()` is a deleteAll on the cache plus a\n // reset of the init flag, so the next read goes through `initializeCache`\n // and rebuilds from the durable's post-commit state.\n await this.invalidateCache();\n }\n }\n\n /**\n * Invalidates the cache by clearing it and resetting initialization flag\n */\n async invalidateCache(): Promise<void> {\n await this.cache.deleteAll();\n this.cacheInitialized = false;\n }\n\n /**\n * Refreshes the cache by reloading all data from the durable repository\n */\n async refreshCache(): Promise<void> {\n await this.cache.deleteAll();\n this.cacheInitialized = false;\n await this.initializeCache();\n }\n\n /**\n * Subscribes to changes in the repository.\n * Delegates to the durable repository to detect changes (including from other sources).\n * Also updates the cache when changes are detected.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options (e.g., polling interval)\n * @returns Unsubscribe function\n */\n override subscribeToChanges(\n callback: (change: any) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n // Subscribe to durable repository to detect all changes\n return this.durable.subscribeToChanges(async (change) => {\n // Update cache based on the change\n if (change.type === \"INSERT\" || change.type === \"UPDATE\") {\n if (change.new) {\n await this.cache.put(change.new);\n }\n } else if (change.type === \"DELETE\") {\n if (change.old) {\n await this.cache.delete(change.old);\n }\n }\n\n // Forward the change to the callback\n callback(change);\n }, options);\n }\n\n /**\n * Forwards `setupDatabase` to the durable storage (which owns any\n * declared tabular migrations) and to the cache. Migrations themselves\n * are passed when constructing the inner durable storage; the wrapper\n * does not declare its own.\n */\n override async setupDatabase(): Promise<void> {\n await this.durable.setupDatabase();\n await this.cache.setupDatabase();\n }\n\n /**\n * Delegates to the durable storage's applier so callers that use the\n * wrapper observe migrations declared on the inner durable layer.\n */\n public override getMigrationApplier(): ITabularMigrationApplier | null {\n const inner = this.durable as unknown as {\n getMigrationApplier?: () => ITabularMigrationApplier | null;\n };\n return inner.getMigrationApplier?.() ?? null;\n }\n\n /**\n * Destroys the durable and cache repositories.\n */\n override destroy(): void {\n this.durable.destroy();\n this.cache.destroy();\n }\n}\n",
15
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport { createServiceToken, makeFingerprint, uuid4 } from \"@workglow/util\";\nimport {\n BaseTabularStorage,\n ClientProvidedKeysOption,\n KeyGenerationStrategy,\n} from \"./BaseTabularStorage\";\nimport { type ITabularMigration, type ITabularMigrationApplier } from \"../migrations\";\nimport { InMemoryTabularMigrationApplier } from \"./InMemoryTabularMigrationApplier\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n isSearchCondition,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\nimport { pickCoveringIndex } from \"./coveringIndexPicker\";\n\nexport const MEMORY_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.inMemory\"\n);\n\n/**\n * A generic in-memory key-value repository implementation.\n * Provides a simple, non-persistent storage solution suitable for testing and caching scenarios.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class InMemoryTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n /** Internal storage using a Map with fingerprint strings as keys */\n values = new Map<string, Entity>();\n /** Counter for auto-incrementing integer keys */\n private autoIncrementCounter = 0;\n /** Tracks whether the last put was an insert (new key) or update (existing key) */\n private _lastPutWasInsert = false;\n\n /**\n * Creates a new InMemoryTabularStorage instance\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n * @param tabularMigrations - Optional declarative migrations to run on setup\n * @param migrationName - Optional name used for the migration component identifier\n */\n constructor(\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\",\n tabularMigrations?: ReadonlyArray<ITabularMigration>,\n migrationName: string = \"inmemory\"\n ) {\n super(schema, primaryKeyNames, indexes, clientProvidedKeys, tabularMigrations, migrationName);\n }\n\n /**\n * Sets up the database for the repository (no-op for in-memory unless migrations are declared)\n */\n override async setupDatabase(): Promise<void> {\n if (this.tabularMigrations && this.tabularMigrations.length > 0) {\n await this.applyTabularMigrations();\n }\n }\n\n public override getMigrationApplier(): ITabularMigrationApplier | null {\n return new InMemoryTabularMigrationApplier(this as unknown as AnyTabularStorage, \"inmemory\");\n }\n\n /**\n * Generates a key value for auto-generated keys\n * @param columnName - Name of the column to generate a key for\n * @param strategy - The generation strategy to use\n * @returns The generated key value\n */\n protected override generateKeyValue(\n columnName: string,\n strategy: KeyGenerationStrategy\n ): string | number {\n if (strategy === \"autoincrement\") {\n return ++this.autoIncrementCounter;\n } else {\n return uuid4();\n }\n }\n\n /**\n * Stores a key-value pair in the repository\n * @param value - The combined object to store (may be missing auto-generated keys)\n * @returns The stored entity with all keys filled in\n * @emits 'put' event with the stored entity when successful\n */\n async put(value: InsertType): Promise<Entity> {\n let entityToStore = value as unknown as Entity;\n const savedCounter = this.autoIncrementCounter;\n\n try {\n // Handle auto-generated keys\n if (this.hasAutoGeneratedKey() && this.autoGeneratedKeyName) {\n const keyName = this.autoGeneratedKeyName as string;\n const clientProvidedValue = (value as Record<string, unknown>)[keyName];\n const hasClientValue = clientProvidedValue !== undefined && clientProvidedValue !== null;\n\n let shouldGenerate = false;\n if (this.clientProvidedKeys === \"never\") {\n shouldGenerate = true;\n } else if (this.clientProvidedKeys === \"always\") {\n if (!hasClientValue) {\n throw new Error(\n `Auto-generated key \"${keyName}\" is required when clientProvidedKeys is \"always\"`\n );\n }\n shouldGenerate = false;\n } else {\n shouldGenerate = !hasClientValue;\n }\n\n if (shouldGenerate) {\n const generatedValue = this.generateKeyValue(keyName, this.autoGeneratedKeyStrategy!);\n entityToStore = { ...value, [keyName]: generatedValue } as Entity;\n }\n }\n\n const { key } = this.separateKeyValueFromCombined(entityToStore);\n const id = await makeFingerprint(key);\n this._lastPutWasInsert = !this.values.has(id);\n this.values.set(id, entityToStore);\n } catch (e) {\n this.autoIncrementCounter = savedCounter;\n throw e;\n }\n\n this.events.emit(\"put\", entityToStore);\n return entityToStore;\n }\n\n /**\n * Stores multiple key-value pairs in the repository in a bulk operation\n * @param values - Array of combined objects to store (may be missing auto-generated keys)\n * @returns Array of stored entities with all keys filled in\n * @emits 'put' event for each value stored\n */\n async putBulk(values: InsertType[]): Promise<Entity[]> {\n return await Promise.all(values.map(async (value) => this.put(value)));\n }\n\n /**\n * Retrieves a value by its key\n * @param key - The primary key object to look up\n * @returns The value object if found, undefined otherwise\n * @emits 'get' event with the fingerprint ID and value when found\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n const id = await makeFingerprint(key);\n const out = this.values.get(id);\n this.events.emit(\"get\", key, out);\n return out;\n }\n\n /**\n * Deletes an entry by its key\n * @param key - The primary key object of the entry to delete\n * @emits 'delete' event with the fingerprint ID when successful\n */\n async delete(value: PrimaryKey | Entity): Promise<void> {\n const { key } = this.separateKeyValueFromCombined(value as Entity);\n const id = await makeFingerprint(key);\n this.values.delete(id);\n this.events.emit(\"delete\", key as keyof Entity);\n }\n\n /**\n * Removes all entries from the repository\n * @emits 'clearall' event when successful\n */\n async deleteAll(): Promise<void> {\n this.values.clear();\n this.events.emit(\"clearall\");\n }\n\n /**\n * Returns an array of all entries in the repository, with optional ordering, offset, and limit.\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of all entries in the repository\n */\n async getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n this.validateGetAllOptions(options);\n let all = Array.from(this.values.values());\n\n if (options?.orderBy && options.orderBy.length > 0) {\n all.sort((a, b) => {\n for (const { column, direction } of options.orderBy!) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n });\n }\n\n if (options?.offset !== undefined) {\n all = all.slice(options.offset);\n }\n\n if (options?.limit !== undefined) {\n all = all.slice(0, options.limit);\n }\n\n return all.length > 0 ? all : undefined;\n }\n\n /**\n * Returns the number of entries in the repository\n * @returns The total count of stored entries\n */\n async size(): Promise<number> {\n return this.values.size;\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n const all = Array.from(this.values.values());\n\n // Ensure deterministic ordering by sorting by primary key(s) before pagination\n all.sort((a, b) => {\n for (const key of this.primaryKeyNames) {\n const aVal = (a as Record<string, string | number>)[key as string];\n const bVal = (b as Record<string, string | number>)[key as string];\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n }\n return 0;\n });\n\n const page = all.slice(offset, offset + limit);\n return page.length > 0 ? page : undefined;\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n */\n async deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n const criteriaKeys = Object.keys(criteria) as Array<keyof Entity>;\n if (criteriaKeys.length === 0) {\n return;\n }\n\n // Convert to array first to avoid iterator issues when modifying the Map\n const entries = Array.from(this.values.entries());\n\n const entriesToDelete = entries.filter(([_, entity]) => {\n // All criteria must match (AND logic)\n for (const column of criteriaKeys) {\n const criterion = criteria[column];\n const columnValue = entity[column];\n\n if (isSearchCondition(criterion)) {\n const { value, operator } = criterion;\n // Cast needed for generic comparison operators across Entity value types\n const v = value as string | number;\n const cv = columnValue as string | number | null | undefined;\n switch (operator) {\n case \"=\":\n if (cv !== v) return false;\n break;\n case \"<\":\n if (cv === null || cv === undefined || !(cv < v)) return false;\n break;\n case \"<=\":\n if (cv === null || cv === undefined || !(cv <= v)) return false;\n break;\n case \">\":\n if (cv === null || cv === undefined || !(cv > v)) return false;\n break;\n case \">=\":\n if (cv === null || cv === undefined || !(cv >= v)) return false;\n break;\n default:\n return false;\n }\n } else {\n // Direct value means equality\n if (columnValue !== criterion) return false;\n }\n }\n return true;\n });\n\n // Delete the filtered entries and emit events for each\n for (const [id, entity] of entriesToDelete) {\n this.values.delete(id);\n const { key } = this.separateKeyValueFromCombined(entity);\n this.events.emit(\"delete\", key as keyof Entity);\n }\n }\n\n /**\n * Queries entries matching the specified search criteria with optional ordering, limit, and offset.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of matching entities or undefined if no matches found\n */\n async query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n this.validateQueryParams(criteria, options);\n\n const criteriaKeys = Object.keys(criteria) as Array<keyof Entity>;\n\n let results: Entity[] = Array.from(this.values.values()).filter((entity) => {\n for (const column of criteriaKeys) {\n const criterion = criteria[column];\n const columnValue = entity[column];\n\n if (isSearchCondition(criterion)) {\n const { value, operator } = criterion;\n const v = value as string | number;\n const cv = columnValue as string | number | null | undefined;\n switch (operator) {\n case \"=\":\n if (cv !== v) return false;\n break;\n case \"<\":\n if (cv === null || cv === undefined || !(cv < v)) return false;\n break;\n case \"<=\":\n if (cv === null || cv === undefined || !(cv <= v)) return false;\n break;\n case \">\":\n if (cv === null || cv === undefined || !(cv > v)) return false;\n break;\n case \">=\":\n if (cv === null || cv === undefined || !(cv >= v)) return false;\n break;\n default:\n return false;\n }\n } else {\n if (columnValue !== criterion) return false;\n }\n }\n return true;\n });\n\n if (options?.orderBy && options.orderBy.length > 0) {\n results.sort((a, b) => {\n for (const { column, direction } of options.orderBy!) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n });\n }\n\n if (options?.offset !== undefined) {\n results = results.slice(options.offset);\n }\n\n if (options?.limit !== undefined) {\n results = results.slice(0, options.limit);\n }\n\n const result = results.length > 0 ? results : undefined;\n this.events.emit(\"query\", criteria as Partial<Entity>, result);\n return result;\n }\n\n /**\n * Strict, projected query served entirely by a covering compound index.\n * Throws CoveringIndexMissingError when no registered index can serve the request.\n * Returns Pick<Entity, K>[] — never undefined, empty array on no matches.\n */\n override async queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n this.validateSelect(options);\n this.validateQueryParams(criteria, options);\n\n const registered = this.indexes.map((cols, i) => ({\n name: `idx_${i}`,\n keyPath: cols as string[],\n }));\n\n pickCoveringIndex({\n table: \"InMemoryTabularStorage\",\n indexes: registered,\n criteriaColumns: Object.keys(criteria),\n orderByColumns: (options.orderBy ?? []).map((o) => ({\n column: String(o.column),\n direction: o.direction,\n })),\n selectColumns: options.select.map(String),\n primaryKeyColumns: this.primaryKeyNames.map(String),\n });\n\n // Validation passed — reuse query() then project\n const full = await this.query(criteria, {\n orderBy: options.orderBy,\n limit: options.limit,\n offset: options.offset,\n });\n if (!full) return [];\n return full.map((row) => {\n const out = {} as Pick<Entity, K>;\n for (const k of options.select) out[k] = row[k];\n return out;\n });\n }\n\n /**\n * Subscribes to changes in the repository.\n * Since InMemory is both client and server, changes are detected via local events.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options (not used for in-memory)\n * @returns Unsubscribe function\n */\n public override subscribeToChanges(\n callback: (change: TabularChangePayload<Entity>) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n const handlePut = (entity: Entity) => {\n callback({ type: this._lastPutWasInsert ? \"INSERT\" : \"UPDATE\", new: entity });\n };\n\n const handleDelete = (_key: keyof Entity) => {\n callback({ type: \"DELETE\" });\n };\n\n const handleClearAll = () => {\n callback({ type: \"DELETE\" });\n };\n\n this.events.on(\"put\", handlePut);\n this.events.on(\"delete\", handleDelete);\n this.events.on(\"clearall\", handleClearAll);\n\n return () => {\n this.events.off(\"put\", handlePut);\n this.events.off(\"delete\", handleDelete);\n this.events.off(\"clearall\", handleClearAll);\n };\n }\n\n /**\n * Destroys the repository and frees up resources.\n */\n public override destroy(): void {\n this.values.clear();\n }\n}\n",
16
+ "/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { type ITabularMigrationApplier, type TabularMigrationOp, runBackfill } from \"../migrations\";\nimport { type AnyTabularStorage } from \"./ITabularStorage\";\n\n/**\n * Applier for schemaless tabular backends (InMemory / Shared / FsFolder /\n * HuggingFace). DDL ops are no-ops because records are plain JS objects;\n * `backfill` runs through the storage's normal `getPage`/`put`/`delete`\n * API. Bookkeeping is held in the in-memory `applied` map by default;\n * subclasses (e.g. for FsFolder) may override `persist()` / `load()` to\n * make it durable.\n *\n * `tableExists` is a heuristic: returns true when the storage already\n * holds rows. Combined with the empty `applied` set, this makes a\n * brand-new InMemory storage take the orchestrator's fresh-DB fast path\n * (mark-all-applied without running ops); a storage that was rehydrated\n * from a dump will look like an existing-DB and have its migrations\n * actually run, matching dev/prod parity expectations.\n */\nexport class InMemoryTabularMigrationApplier implements ITabularMigrationApplier {\n protected applied = new Map<string, Set<number>>();\n\n constructor(\n protected readonly storage: AnyTabularStorage,\n protected readonly storeName: string\n ) {}\n\n async ensureBookkeeping(): Promise<void> {\n // Default: nothing to persist; subclasses override to load.\n }\n\n async appliedVersions(component: string): Promise<Set<number>> {\n return new Set(this.applied.get(component) ?? []);\n }\n\n async tableExists(): Promise<boolean> {\n return (await this.storage.size()) > 0;\n }\n\n async markAllApplied(\n component: string,\n versions: ReadonlyArray<{ version: number; description: string | undefined }>\n ): Promise<void> {\n if (versions.length === 0) return;\n let set = this.applied.get(component);\n if (!set) {\n set = new Set();\n this.applied.set(component, set);\n }\n for (const v of versions) set.add(v.version);\n await this.persist();\n }\n\n async applyMigration(\n component: string,\n version: number,\n _description: string | undefined,\n ops: ReadonlyArray<TabularMigrationOp>,\n onProgress?: (fraction: number) => void\n ): Promise<void> {\n let processed = 0;\n const total = Math.max(ops.length, 1);\n for (const op of ops) {\n if (op.kind === \"backfill\") {\n await runBackfill(this.storage, op.batchSize ?? 500, op.transform);\n }\n // DDL ops (addColumn / dropColumn / renameColumn / addIndex /\n // dropIndex) are no-ops on schemaless backends.\n processed++;\n onProgress?.(processed / total);\n }\n let set = this.applied.get(component);\n if (!set) {\n set = new Set();\n this.applied.set(component, set);\n }\n set.add(version);\n await this.persist();\n }\n\n /** Subclasses (e.g. FsFolder) override to flush bookkeeping to disk. */\n protected async persist(): Promise<void> {\n // no-op\n }\n}\n",
12
17
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { CoveringIndexMissingError } from \"./CoveringIndexMissingError\";\n\nexport interface RegisteredIndex {\n readonly name: string;\n readonly keyPath: readonly string[];\n}\n\nexport interface PickCoveringIndexInput {\n readonly table: string;\n readonly indexes: readonly RegisteredIndex[];\n readonly criteriaColumns: readonly string[];\n readonly orderByColumns: ReadonlyArray<{\n readonly column: string;\n readonly direction: \"ASC\" | \"DESC\";\n }>;\n readonly selectColumns: readonly string[];\n readonly primaryKeyColumns: readonly string[];\n}\n\nexport interface PickedIndex {\n readonly name: string;\n readonly keyPath: readonly string[];\n /** true if the keypath order is reverse of orderBy direction (caller uses \"prev\" cursor) */\n readonly reverseDirection: boolean;\n}\n\n/**\n * Pick the first registered index whose keypath:\n * 1. starts with all `criteriaColumns` (as a prefix, in any order),\n * 2. then has the `orderByColumns` immediately after, in matching direction or reversed,\n * 3. and contains every `selectColumns` value somewhere in the keypath\n * (primary-key columns are considered covered for free, e.g. via cursor.primaryKey).\n *\n * Throws {@link CoveringIndexMissingError} on no match.\n */\nexport function pickCoveringIndex(input: PickCoveringIndexInput): PickedIndex {\n const { table, indexes, criteriaColumns, orderByColumns, selectColumns, primaryKeyColumns } =\n input;\n const required = uniqueColumns([\n ...criteriaColumns,\n ...orderByColumns.map((o) => o.column),\n ...selectColumns,\n ]);\n const pkSet = new Set(primaryKeyColumns);\n\n for (const idx of indexes) {\n const fit = tryFitIndex(idx, criteriaColumns, orderByColumns, selectColumns, pkSet);\n if (fit !== undefined) {\n return { name: idx.name, keyPath: idx.keyPath, reverseDirection: fit.reverseDirection };\n }\n }\n\n throw new CoveringIndexMissingError(\n table,\n required,\n indexes.map((i) => i.keyPath)\n );\n}\n\nfunction tryFitIndex(\n index: RegisteredIndex,\n criteria: readonly string[],\n orderBy: ReadonlyArray<{ column: string; direction: \"ASC\" | \"DESC\" }>,\n select: readonly string[],\n pkSet: ReadonlySet<string>\n): { reverseDirection: boolean } | undefined {\n const keyPath = index.keyPath;\n const criteriaSet = new Set(criteria);\n\n if (criteria.length > keyPath.length) return undefined;\n for (let i = 0; i < criteria.length; i++) {\n if (!criteriaSet.has(keyPath[i])) return undefined;\n }\n\n let reverseDirection = false;\n if (orderBy.length > 0) {\n const start = criteria.length;\n if (start + orderBy.length > keyPath.length) return undefined;\n for (let i = 0; i < orderBy.length; i++) {\n if (keyPath[start + i] !== orderBy[i].column) return undefined;\n }\n const allDesc = orderBy.every((o) => o.direction === \"DESC\");\n const allAsc = orderBy.every((o) => o.direction === \"ASC\");\n if (allDesc) reverseDirection = true;\n else if (!allAsc) return undefined;\n }\n\n const keyPathSet = new Set(keyPath);\n for (const col of select) {\n if (!keyPathSet.has(col) && !pkSet.has(col)) return undefined;\n }\n\n return { reverseDirection };\n}\n\nfunction uniqueColumns(cols: readonly string[]): string[] {\n return Array.from(new Set(cols));\n}\n",
13
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken } from \"@workglow/util\";\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport { BaseTabularStorage } from \"./BaseTabularStorage\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n DeleteSearchCriteria,\n InsertEntity,\n isSearchCondition,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\nimport { StorageUnsupportedError } from \"./StorageError\";\n\nexport const HF_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.huggingface\"\n);\n\n/**\n * HuggingFace Dataset Viewer API response types\n */\ninterface HfFirstRowsResponse {\n features: Array<{ feature_idx: number; name: string; type: any }>;\n rows: Array<{ row_idx: number; row: Record<string, any>; truncated_cells: any[] }>;\n}\n\ninterface HfRowsResponse {\n features: Array<{ feature_idx: number; name: string; type: any }>;\n rows: Array<{ row_idx: number; row: Record<string, any>; truncated_cells: any[] }>;\n num_rows_total: number;\n num_rows_per_page: number;\n partial: boolean;\n}\n\ninterface HfFilterResponse {\n features: Array<{ feature_idx: number; name: string; type: any }>;\n rows: Array<{ row_idx: number; row: Record<string, any>; truncated_cells: any[] }>;\n num_rows_total: number;\n num_rows_per_page: number;\n partial: boolean;\n}\n\ninterface HfSizeResponse {\n size: {\n dataset: string;\n config: string;\n split: string;\n num_bytes_original_files: number;\n num_bytes_parquet_files: number;\n num_bytes_memory: number;\n num_rows: number;\n num_columns: number;\n };\n partial: boolean;\n}\n\n/**\n * Options for HuggingFaceTabularStorage\n */\nexport interface HuggingFaceTabularStorageOptions {\n /** HuggingFace API token for private datasets */\n token?: string;\n /** Base URL for the HuggingFace Dataset Viewer API */\n baseUrl?: string;\n /** Indexes for optimizing search operations */\n indexes?: readonly (keyof any | readonly (keyof any)[])[];\n}\n\n/**\n * Read-only tabular storage backed by HuggingFace Dataset Viewer API.\n * Supports both user-provided schemas and auto-detection from HF features.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class HuggingFaceTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n private readonly dataset: string;\n private readonly config: string;\n private readonly split: string;\n private readonly token?: string;\n private readonly baseUrl: string;\n\n /**\n * Creates a new HuggingFaceTabularStorage instance\n * @param dataset - HuggingFace dataset name (e.g., \"cornell-movie-review-data/rotten_tomatoes\")\n * @param config - Dataset configuration (e.g., \"default\")\n * @param split - Dataset split (e.g., \"train\", \"test\", \"validation\")\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param options - Optional configuration including token, baseUrl, and indexes\n */\n constructor(\n dataset: string,\n config: string,\n split: string,\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n options?: HuggingFaceTabularStorageOptions\n ) {\n super(\n schema,\n primaryKeyNames,\n (options?.indexes ?? []) as readonly (\n | keyof NoInfer<Entity>\n | readonly (keyof NoInfer<Entity>)[]\n )[],\n \"never\" // HF datasets don't support client-provided keys\n );\n this.dataset = dataset;\n this.config = config;\n this.split = split;\n this.token = options?.token;\n this.baseUrl = options?.baseUrl ?? \"https://datasets-server.huggingface.co\";\n }\n\n /**\n * Factory method to create a HuggingFaceTabularStorage instance with auto-detected schema.\n * Fetches the dataset features and converts them to a JSON Schema.\n *\n * @param dataset - HuggingFace dataset name\n * @param config - Dataset configuration\n * @param split - Dataset split\n * @param options - Optional configuration\n * @returns A new HuggingFaceTabularStorage instance with auto-detected schema\n */\n static async fromDataset<Entity = any>(\n dataset: string,\n config: string,\n split: string,\n options?: HuggingFaceTabularStorageOptions\n ): Promise<\n HuggingFaceTabularStorage<DataPortSchemaObject, readonly [\"row_idx\"], Entity, number, any, any>\n > {\n const baseUrl = options?.baseUrl ?? \"https://datasets-server.huggingface.co\";\n const token = options?.token;\n\n // Fetch first rows to get features\n const url = new URL(`${baseUrl}/first-rows`);\n url.searchParams.set(\"dataset\", dataset);\n url.searchParams.set(\"config\", config);\n url.searchParams.set(\"split\", split);\n\n const headers: Record<string, string> = {};\n if (token) {\n headers[\"Authorization\"] = `Bearer ${token}`;\n }\n\n const response = await fetch(url.toString(), { headers });\n if (!response.ok) {\n throw new Error(\n `Failed to fetch dataset features: ${response.status} ${response.statusText}`\n );\n }\n\n const data: HfFirstRowsResponse = await response.json();\n\n // Convert HF features to JSON Schema\n const properties: Record<string, any> = {};\n const required: string[] = [];\n\n // Add row_idx as primary key (auto-generated by HF)\n properties[\"row_idx\"] = { type: \"integer\", \"x-auto-generated\": true };\n required.push(\"row_idx\");\n\n for (const feature of data.features) {\n const jsonSchema = hfFeatureToJsonSchema(feature.type);\n properties[feature.name] = jsonSchema;\n // HF datasets typically don't have null values unless specified\n required.push(feature.name);\n }\n\n const schema: DataPortSchemaObject = {\n type: \"object\",\n properties,\n required,\n additionalProperties: false,\n };\n\n return new HuggingFaceTabularStorage(\n dataset,\n config,\n split,\n schema,\n [\"row_idx\"] as const,\n options\n ) as any;\n }\n\n /**\n * Sets up the database by validating the dataset exists and schema matches\n */\n override async setupDatabase(): Promise<void> {\n // Fetch first rows to validate dataset exists\n const data = await this.fetchApi<HfFirstRowsResponse>(\"/first-rows\", {});\n\n // Validate that schema columns match HF features (if schema was user-provided)\n const schemaColumns = Object.keys(this.schema.properties);\n const hfColumns = data.features.map((f) => f.name);\n\n // Check if row_idx is in schema (indicates auto-detected schema)\n const hasRowIdx = schemaColumns.includes(\"row_idx\");\n\n if (!hasRowIdx) {\n // User-provided schema - validate columns match\n for (const column of schemaColumns) {\n if (!hfColumns.includes(column) && !this.primaryKeyNames.includes(column as any)) {\n throw new Error(`Schema column \"${column}\" not found in HuggingFace dataset features`);\n }\n }\n }\n }\n\n /**\n * Retrieves a value by its key using the /filter endpoint\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n const keyObj = this.separateKeyValueFromCombined({ ...key } as any).key;\n const whereConditions: string[] = [];\n\n for (const [k, v] of Object.entries(keyObj as Record<string, any>)) {\n if (typeof v === \"string\") {\n // Escape backslashes first, then single quotes\n const escaped = v.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\");\n whereConditions.push(`${k}='${escaped}'`);\n } else {\n whereConditions.push(`${k}=${v}`);\n }\n }\n\n const where = whereConditions.join(\" AND \");\n const data = await this.fetchApi<HfFilterResponse>(\"/filter\", { where, limit: \"1\" });\n\n if (data.rows.length > 0) {\n const entity = this.rowToEntity(data.rows[0]);\n this.events.emit(\"get\", key, entity);\n return entity;\n }\n\n this.events.emit(\"get\", key, undefined);\n return undefined;\n }\n\n /**\n * Retrieves all entities by paginating through the /rows endpoint\n */\n async getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n this.validateGetAllOptions(options);\n\n const allEntities: Entity[] = [];\n let offset = 0;\n const pageSize = 100; // HF max per request\n\n while (true) {\n const page = await this.getBulk(offset, pageSize);\n\n if (!page || page.length === 0) {\n break;\n }\n\n allEntities.push(...page);\n offset += page.length;\n\n // If we got fewer records than requested, we've reached the end\n if (page.length < pageSize) {\n break;\n }\n }\n\n if (allEntities.length === 0) return undefined;\n\n let results = allEntities;\n\n // Apply JS-side orderBy\n if (options?.orderBy && options.orderBy.length > 0) {\n results = [...results];\n results.sort((a, b) => {\n for (const { column, direction } of options.orderBy!) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n });\n }\n\n // Apply offset\n if (options?.offset !== undefined) {\n results = results.slice(options.offset);\n }\n\n // Apply limit\n if (options?.limit !== undefined) {\n results = results.slice(0, options.limit);\n }\n\n return results.length > 0 ? results : undefined;\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n const data = await this.fetchApi<HfRowsResponse>(\"/rows\", {\n offset: offset.toString(),\n length: Math.min(limit, 100).toString(), // HF max is 100 per request\n });\n\n if (data.rows.length === 0) {\n return undefined;\n }\n\n const entities: Entity[] = [];\n for (const row of data.rows) {\n entities.push(this.rowToEntity(row));\n }\n\n return entities;\n }\n\n /**\n * Returns the number of rows in the dataset using the /size endpoint\n */\n async size(): Promise<number> {\n const data = await this.fetchApi<HfSizeResponse>(\"/size\", {});\n return data.size.num_rows;\n }\n\n /**\n * Write operations are not supported - throws readonly error\n */\n async put(_value: InsertType): Promise<Entity> {\n throw new Error(\"HuggingFaceTabularStorage is readonly\");\n }\n\n /**\n * Write operations are not supported - throws readonly error\n */\n async putBulk(_values: InsertType[]): Promise<Entity[]> {\n throw new Error(\"HuggingFaceTabularStorage is readonly\");\n }\n\n /**\n * Delete operations are not supported - throws readonly error\n */\n async delete(_value: PrimaryKey | Entity): Promise<void> {\n throw new Error(\"HuggingFaceTabularStorage is readonly\");\n }\n\n /**\n * Delete operations are not supported - throws readonly error\n */\n async deleteAll(): Promise<void> {\n throw new Error(\"HuggingFaceTabularStorage is readonly\");\n }\n\n /**\n * Queries entities using the /filter endpoint for equality criteria.\n * Non-equality operators are not supported by the HuggingFace API.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of matching entities or undefined if no matches found\n */\n async query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n this.validateQueryParams(criteria, options);\n\n // Build WHERE clause — only equality is supported by HF API\n const whereConditions: string[] = [];\n for (const [k, v] of Object.entries(criteria)) {\n if (v === undefined || v === null) continue;\n if (isSearchCondition(v)) {\n if (v.operator !== \"=\") {\n throw new StorageUnsupportedError(\n `Operator \"${v.operator}\" in query`,\n \"HuggingFaceTabularStorage\"\n );\n }\n const val = v.value;\n if (typeof val === \"string\") {\n const escaped = val.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\");\n whereConditions.push(`${k}='${escaped}'`);\n } else {\n whereConditions.push(`${k}=${val}`);\n }\n } else {\n if (typeof v === \"string\") {\n const escaped = v.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\");\n whereConditions.push(`${k}='${escaped}'`);\n } else {\n whereConditions.push(`${k}=${v}`);\n }\n }\n }\n\n if (whereConditions.length === 0) {\n return undefined;\n }\n\n const where = whereConditions.join(\" AND \");\n const allEntities: Entity[] = [];\n let fetchOffset = 0;\n const fetchLimit = 100;\n\n while (true) {\n const data = await this.fetchApi<HfFilterResponse>(\"/filter\", {\n where,\n offset: fetchOffset.toString(),\n limit: fetchLimit.toString(),\n });\n\n for (const row of data.rows) {\n allEntities.push(this.rowToEntity(row));\n }\n\n fetchOffset += data.rows.length;\n\n if (fetchOffset >= data.num_rows_total || data.rows.length < fetchLimit) {\n break;\n }\n }\n\n let results = allEntities;\n\n // Apply JS-side orderBy\n if (options?.orderBy && options.orderBy.length > 0) {\n results.sort((a, b) => {\n for (const { column, direction } of options.orderBy!) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n });\n }\n\n // Apply offset\n if (options?.offset !== undefined) {\n results = results.slice(options.offset);\n }\n\n // Apply limit\n if (options?.limit !== undefined) {\n results = results.slice(0, options.limit);\n }\n\n if (results.length > 0) {\n this.events.emit(\"query\", criteria as Partial<Entity>, results);\n return results;\n } else {\n this.events.emit(\"query\", criteria as Partial<Entity>, undefined);\n return undefined;\n }\n }\n\n /**\n * Delete operations are not supported - throws readonly error\n */\n async deleteSearch(_criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n throw new Error(\"HuggingFaceTabularStorage is readonly\");\n }\n\n /**\n * Subscriptions are not supported - HF datasets are static\n */\n override subscribeToChanges(\n _callback: (change: TabularChangePayload<Entity>) => void,\n _options?: TabularSubscribeOptions\n ): () => void {\n throw new Error(\"HuggingFaceTabularStorage does not support subscriptions\");\n }\n\n /**\n * No resources to clean up\n */\n override destroy(): void {\n // No-op - no resources to clean up\n }\n\n /**\n * Helper method to fetch from the HuggingFace Dataset Viewer API\n */\n private async fetchApi<T>(endpoint: string, params: Record<string, string>): Promise<T> {\n const url = new URL(`${this.baseUrl}${endpoint}`);\n url.searchParams.set(\"dataset\", this.dataset);\n url.searchParams.set(\"config\", this.config);\n url.searchParams.set(\"split\", this.split);\n\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n url.searchParams.set(key, value);\n }\n }\n\n const headers: Record<string, string> = {};\n if (this.token) {\n headers[\"Authorization\"] = `Bearer ${this.token}`;\n }\n\n const response = await fetch(url.toString(), { headers });\n if (!response.ok) {\n throw new Error(`HuggingFace API error: ${response.status} ${response.statusText}`);\n }\n\n return await response.json();\n }\n\n /**\n * Converts a HF row to an Entity\n */\n private rowToEntity(row: { row_idx: number; row: Record<string, any> }): Entity {\n return { row_idx: row.row_idx, ...row.row } as Entity;\n }\n}\n\n/**\n * Converts a HuggingFace feature type to a JSON Schema type definition\n */\nfunction hfFeatureToJsonSchema(feature: any): any {\n // Handle Value types\n if (feature._type === \"Value\") {\n switch (feature.dtype) {\n case \"string\":\n return { type: \"string\" };\n case \"int64\":\n case \"int32\":\n case \"int16\":\n case \"int8\":\n case \"uint64\":\n case \"uint32\":\n case \"uint16\":\n case \"uint8\":\n return { type: \"integer\" };\n case \"float64\":\n case \"float32\":\n case \"float16\":\n return { type: \"number\" };\n case \"bool\":\n return { type: \"boolean\" };\n default:\n return {}; // any type\n }\n }\n\n // Handle ClassLabel types\n if (feature._type === \"ClassLabel\") {\n return { type: \"integer\" };\n }\n\n // Handle Sequence types\n if (feature._type === \"Sequence\") {\n return {\n type: \"array\",\n items: hfFeatureToJsonSchema(feature.feature),\n };\n }\n\n // Handle other types as any\n return {};\n}\n",
14
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n createServiceToken,\n globalServiceRegistry,\n registerInputCompactor,\n registerInputResolver,\n ServiceRegistry,\n} from \"@workglow/util\";\nimport { AnyTabularStorage } from \"./ITabularStorage\";\n\n/**\n * Service token for the tabular repository registry\n * Maps repository IDs to ITabularStorage instances\n */\nexport const TABULAR_REPOSITORIES = createServiceToken<Map<string, AnyTabularStorage>>(\n \"storage.tabular.repositories\"\n);\n\n// Register default factory if not already registered\nglobalServiceRegistry.registerIfAbsent(\n TABULAR_REPOSITORIES,\n (): Map<string, AnyTabularStorage> => new Map(),\n true\n);\n\n/**\n * Gets the global tabular repository registry\n * @returns Map of tabular repository ID to instance\n */\nexport function getGlobalTabularRepositories(): Map<string, AnyTabularStorage> {\n return globalServiceRegistry.get(TABULAR_REPOSITORIES);\n}\n\n/**\n * Registers a tabular repository globally by ID\n * @param id The unique identifier for this repository\n * @param repository The repository instance to register\n */\nexport function registerTabularRepository(id: string, repository: AnyTabularStorage): void {\n const repos = getGlobalTabularRepositories();\n repos.set(id, repository);\n}\n\n/**\n * Gets a tabular repository by ID from the global registry\n * @param id The repository identifier\n * @returns The repository instance or undefined if not found\n */\nexport function getTabularRepository(id: string): AnyTabularStorage | undefined {\n return getGlobalTabularRepositories().get(id);\n}\n\n/**\n * Resolves a repository ID to an instance from the registry.\n * Used by the input resolver system.\n */\nfunction resolveRepositoryFromRegistry(\n id: string,\n format: string,\n registry: ServiceRegistry\n): AnyTabularStorage {\n const repos = registry.has(TABULAR_REPOSITORIES)\n ? registry.get(TABULAR_REPOSITORIES)\n : getGlobalTabularRepositories();\n const repo = repos.get(id);\n if (!repo) {\n throw new Error(`Tabular storage \"${id}\" not found in registry`);\n }\n return repo;\n}\n\n// Register the repository resolver for format: \"storage:tabular\"\nregisterInputResolver(\"storage:tabular\", resolveRepositoryFromRegistry);\n\n// Register the compactor reverse map lookup by identity\nregisterInputCompactor(\"storage:tabular\", (value, _format, registry) => {\n const repos = registry.has(TABULAR_REPOSITORIES)\n ? registry.get(TABULAR_REPOSITORIES)\n : getGlobalTabularRepositories();\n\n for (const [id, repo] of repos) {\n if (repo === value) return id;\n }\n return undefined;\n});\n",
15
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {\n DataPortSchemaObject,\n FromSchema,\n TypedArraySchemaOptions,\n} from \"@workglow/util/schema\";\nimport { traced } from \"@workglow/util\";\nimport type {\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n ITabularStorage,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularEventListener,\n TabularEventName,\n TabularEventParameters,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\n\n/**\n * Telemetry wrapper for any ITabularStorage implementation.\n * Creates spans for all storage operations.\n */\nexport class TelemetryTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n InsertType = InsertEntity<Entity, AutoGeneratedKeys<Schema>>,\n> implements ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, InsertType> {\n constructor(\n protected readonly storageName: string,\n protected readonly inner: ITabularStorage<\n Schema,\n PrimaryKeyNames,\n Entity,\n PrimaryKey,\n InsertType\n >\n ) {}\n\n put(value: InsertType): Promise<Entity> {\n return traced(\"workglow.storage.tabular.put\", this.storageName, () => this.inner.put(value));\n }\n\n putBulk(values: InsertType[]): Promise<Entity[]> {\n return traced(\"workglow.storage.tabular.putBulk\", this.storageName, () =>\n this.inner.putBulk(values)\n );\n }\n\n get(key: PrimaryKey): Promise<Entity | undefined> {\n return traced(\"workglow.storage.tabular.get\", this.storageName, () => this.inner.get(key));\n }\n\n delete(key: PrimaryKey | Entity): Promise<void> {\n return traced(\"workglow.storage.tabular.delete\", this.storageName, () =>\n this.inner.delete(key)\n );\n }\n\n getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n return traced(\"workglow.storage.tabular.getAll\", this.storageName, () =>\n this.inner.getAll(options)\n );\n }\n\n deleteAll(): Promise<void> {\n return traced(\"workglow.storage.tabular.deleteAll\", this.storageName, () =>\n this.inner.deleteAll()\n );\n }\n\n size(): Promise<number> {\n return traced(\"workglow.storage.tabular.size\", this.storageName, () => this.inner.size());\n }\n\n count(criteria?: SearchCriteria<Entity>): Promise<number> {\n return traced(\"workglow.storage.tabular.count\", this.storageName, () =>\n this.inner.count(criteria)\n );\n }\n\n deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n return traced(\"workglow.storage.tabular.deleteSearch\", this.storageName, () =>\n this.inner.deleteSearch(criteria)\n );\n }\n\n getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n return traced(\"workglow.storage.tabular.getBulk\", this.storageName, () =>\n this.inner.getBulk(offset, limit)\n );\n }\n\n query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n return traced(\"workglow.storage.tabular.query\", this.storageName, () =>\n this.inner.query(criteria, options)\n );\n }\n\n queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n return traced(\"workglow.storage.tabular.queryIndex\", this.storageName, () =>\n this.inner.queryIndex(criteria, options)\n );\n }\n\n // Forwarded directly (async generators, not worth tracing)\n records(pageSize?: number): AsyncGenerator<Entity, void, undefined> {\n return this.inner.records(pageSize);\n }\n\n pages(pageSize?: number): AsyncGenerator<Entity[], void, undefined> {\n return this.inner.pages(pageSize);\n }\n\n subscribeToChanges(\n callback: (change: TabularChangePayload<Entity>) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n return this.inner.subscribeToChanges(callback, options);\n }\n\n setupDatabase(): Promise<void> {\n return this.inner.setupDatabase();\n }\n\n destroy(): void {\n return this.inner.destroy();\n }\n\n [Symbol.dispose](): void {\n return this.inner[Symbol.dispose]();\n }\n\n [Symbol.asyncDispose](): Promise<void> {\n return this.inner[Symbol.asyncDispose]();\n }\n\n // Event delegation — no telemetry needed\n on<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void {\n this.inner.on(name, fn);\n }\n\n off<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void {\n this.inner.off(name, fn);\n }\n\n emit<Event extends TabularEventName>(\n name: Event,\n ...args: TabularEventParameters<Event, PrimaryKey, Entity>\n ): void {\n this.inner.emit(name, ...args);\n }\n\n once<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void {\n this.inner.once(name, fn);\n }\n\n waitOn<Event extends TabularEventName>(\n name: Event\n ): Promise<TabularEventParameters<Event, PrimaryKey, Entity>> {\n return this.inner.waitOn(name);\n }\n}\n",
18
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { createServiceToken } from \"@workglow/util\";\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport { BaseTabularStorage } from \"./BaseTabularStorage\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n DeleteSearchCriteria,\n InsertEntity,\n isSearchCondition,\n Page,\n PageRequest,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\nimport { decodeCursor, encodeCursor, PageCursor } from \"./Cursor\";\nimport { StorageUnsupportedError, StorageValidationError } from \"./StorageError\";\nimport { type ITabularMigration, type ITabularMigrationApplier } from \"../migrations\";\nimport { InMemoryTabularMigrationApplier } from \"./InMemoryTabularMigrationApplier\";\n\nexport const HF_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.huggingface\"\n);\n\n/**\n * Cursor for HuggingFace pagination. Encodes a numeric offset into the\n * standard cursor format (so callers always see opaque strings) but uses\n * the offset directly against the HF `/rows` endpoint, which doesn't\n * support tuple comparisons. Stability under concurrent writes is moot\n * because HF datasets are read-only.\n *\n * The synthetic `hfOffset` \"column name\" makes the cursor self-describing\n * for `decodeCursor`'s n/c arity check. HF's offset paging doesn't\n * actually reference a column, but the cursor format requires names\n * alongside values. The chosen identifier satisfies the schema\n * column-name regex (`^[a-zA-Z][a-zA-Z0-9_]*$`) so the cursor would pass\n * schema-membership validation if that ever broadens.\n */\nconst HF_OFFSET_CURSOR_NAME = \"hfOffset\";\n\nfunction encodeOffsetCursor(offset: number): PageCursor {\n return encodeCursor({ v: 1, n: [HF_OFFSET_CURSOR_NAME], d: [\"a\"], c: [offset] });\n}\n\nfunction decodeOffsetCursor(cursor: PageCursor | string): number {\n // Use `StorageValidationError` (not generic `Error`) so callers that\n // surface decode failures as 4xx responses can catch one consistent\n // error type across all backends — matches how `decodeCursor` itself\n // and every other cursor validator in the storage package behave.\n const payload = decodeCursor(cursor);\n if (payload.n[0] !== HF_OFFSET_CURSOR_NAME) {\n throw new StorageValidationError(\"Cursor was not produced by HuggingFaceTabularStorage\");\n }\n const value = payload.c[0];\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n throw new StorageValidationError(\"Invalid HuggingFace pagination cursor\");\n }\n return value;\n}\n\n/**\n * HuggingFace Dataset Viewer API response types\n */\ninterface HfFirstRowsResponse {\n features: Array<{ feature_idx: number; name: string; type: any }>;\n rows: Array<{ row_idx: number; row: Record<string, any>; truncated_cells: any[] }>;\n}\n\ninterface HfRowsResponse {\n features: Array<{ feature_idx: number; name: string; type: any }>;\n rows: Array<{ row_idx: number; row: Record<string, any>; truncated_cells: any[] }>;\n num_rows_total: number;\n num_rows_per_page: number;\n partial: boolean;\n}\n\ninterface HfFilterResponse {\n features: Array<{ feature_idx: number; name: string; type: any }>;\n rows: Array<{ row_idx: number; row: Record<string, any>; truncated_cells: any[] }>;\n num_rows_total: number;\n num_rows_per_page: number;\n partial: boolean;\n}\n\ninterface HfSizeResponse {\n size: {\n dataset: string;\n config: string;\n split: string;\n num_bytes_original_files: number;\n num_bytes_parquet_files: number;\n num_bytes_memory: number;\n num_rows: number;\n num_columns: number;\n };\n partial: boolean;\n}\n\n/**\n * Options for HuggingFaceTabularStorage\n */\nexport interface HuggingFaceTabularStorageOptions {\n /** HuggingFace API token for private datasets */\n token?: string;\n /** Base URL for the HuggingFace Dataset Viewer API */\n baseUrl?: string;\n /** Indexes for optimizing search operations */\n indexes?: readonly (keyof any | readonly (keyof any)[])[];\n}\n\n/**\n * Read-only tabular storage backed by HuggingFace Dataset Viewer API.\n * Supports both user-provided schemas and auto-detection from HF features.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class HuggingFaceTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n private readonly dataset: string;\n private readonly config: string;\n private readonly split: string;\n private readonly token?: string;\n private readonly baseUrl: string;\n\n /**\n * Creates a new HuggingFaceTabularStorage instance\n * @param dataset - HuggingFace dataset name (e.g., \"cornell-movie-review-data/rotten_tomatoes\")\n * @param config - Dataset configuration (e.g., \"default\")\n * @param split - Dataset split (e.g., \"train\", \"test\", \"validation\")\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param options - Optional configuration including token, baseUrl, and indexes\n * @param tabularMigrations - Optional declarative migrations to run on setup\n */\n constructor(\n dataset: string,\n config: string,\n split: string,\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n options?: HuggingFaceTabularStorageOptions,\n tabularMigrations?: ReadonlyArray<ITabularMigration>\n ) {\n super(\n schema,\n primaryKeyNames,\n (options?.indexes ?? []) as readonly (\n | keyof NoInfer<Entity>\n | readonly (keyof NoInfer<Entity>)[]\n )[],\n \"never\", // HF datasets don't support client-provided keys\n tabularMigrations,\n `hf:${dataset}/${config}/${split}`\n );\n this.dataset = dataset;\n this.config = config;\n this.split = split;\n this.token = options?.token;\n this.baseUrl = options?.baseUrl ?? \"https://datasets-server.huggingface.co\";\n }\n\n /**\n * Factory method to create a HuggingFaceTabularStorage instance with auto-detected schema.\n * Fetches the dataset features and converts them to a JSON Schema.\n *\n * @param dataset - HuggingFace dataset name\n * @param config - Dataset configuration\n * @param split - Dataset split\n * @param options - Optional configuration\n * @returns A new HuggingFaceTabularStorage instance with auto-detected schema\n */\n static async fromDataset<Entity = any>(\n dataset: string,\n config: string,\n split: string,\n options?: HuggingFaceTabularStorageOptions\n ): Promise<\n HuggingFaceTabularStorage<DataPortSchemaObject, readonly [\"row_idx\"], Entity, number, any, any>\n > {\n const baseUrl = options?.baseUrl ?? \"https://datasets-server.huggingface.co\";\n const token = options?.token;\n\n // Fetch first rows to get features\n const url = new URL(`${baseUrl}/first-rows`);\n url.searchParams.set(\"dataset\", dataset);\n url.searchParams.set(\"config\", config);\n url.searchParams.set(\"split\", split);\n\n const headers: Record<string, string> = {};\n if (token) {\n headers[\"Authorization\"] = `Bearer ${token}`;\n }\n\n const response = await fetch(url.toString(), { headers });\n if (!response.ok) {\n throw new Error(\n `Failed to fetch dataset features: ${response.status} ${response.statusText}`\n );\n }\n\n const data: HfFirstRowsResponse = await response.json();\n\n // Convert HF features to JSON Schema\n const properties: Record<string, any> = {};\n const required: string[] = [];\n\n // Add row_idx as primary key (auto-generated by HF)\n properties[\"row_idx\"] = { type: \"integer\", \"x-auto-generated\": true };\n required.push(\"row_idx\");\n\n for (const feature of data.features) {\n const jsonSchema = hfFeatureToJsonSchema(feature.type);\n properties[feature.name] = jsonSchema;\n // HF datasets typically don't have null values unless specified\n required.push(feature.name);\n }\n\n const schema: DataPortSchemaObject = {\n type: \"object\",\n properties,\n required,\n additionalProperties: false,\n };\n\n return new HuggingFaceTabularStorage(\n dataset,\n config,\n split,\n schema,\n [\"row_idx\"] as const,\n options\n ) as any;\n }\n\n /**\n * Sets up the database by validating the dataset exists and schema matches\n */\n override async setupDatabase(): Promise<void> {\n // Fetch first rows to validate dataset exists\n const data = await this.fetchApi<HfFirstRowsResponse>(\"/first-rows\", {});\n\n // Validate that schema columns match HF features (if schema was user-provided)\n const schemaColumns = Object.keys(this.schema.properties);\n const hfColumns = data.features.map((f) => f.name);\n\n // Check if row_idx is in schema (indicates auto-detected schema)\n const hasRowIdx = schemaColumns.includes(\"row_idx\");\n\n if (!hasRowIdx) {\n // User-provided schema - validate columns match\n for (const column of schemaColumns) {\n if (!hfColumns.includes(column) && !this.primaryKeyNames.includes(column as any)) {\n throw new Error(`Schema column \"${column}\" not found in HuggingFace dataset features`);\n }\n }\n }\n\n if (this.tabularMigrations && this.tabularMigrations.length > 0) {\n await this.applyTabularMigrations();\n }\n }\n\n /**\n * Returns an in-memory applier for HF tabular storage. NOTE: HF datasets\n * are read-only; backfill ops will throw because `put` is unsupported.\n * DDL ops are no-ops (records are JS objects). Migrations on HF storages\n * are useful only for advancing bookkeeping in lockstep with a producer\n * that re-publishes datasets.\n */\n public override getMigrationApplier(): ITabularMigrationApplier | null {\n return new InMemoryTabularMigrationApplier(\n this as unknown as AnyTabularStorage,\n `hf:${this.dataset}/${this.config}/${this.split}`\n );\n }\n\n /**\n * Retrieves a value by its key using the /filter endpoint\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n const keyObj = this.separateKeyValueFromCombined({ ...key } as any).key;\n const whereConditions: string[] = [];\n\n for (const [k, v] of Object.entries(keyObj as Record<string, any>)) {\n if (typeof v === \"string\") {\n // Escape backslashes first, then single quotes\n const escaped = v.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\");\n whereConditions.push(`${k}='${escaped}'`);\n } else {\n whereConditions.push(`${k}=${v}`);\n }\n }\n\n const where = whereConditions.join(\" AND \");\n const data = await this.fetchApi<HfFilterResponse>(\"/filter\", { where, limit: \"1\" });\n\n if (data.rows.length > 0) {\n const entity = this.rowToEntity(data.rows[0]);\n this.events.emit(\"get\", key, entity);\n return entity;\n }\n\n this.events.emit(\"get\", key, undefined);\n return undefined;\n }\n\n /**\n * Retrieves all entities by paginating through the /rows endpoint\n */\n async getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n this.validateGetAllOptions(options);\n\n const allEntities: Entity[] = [];\n let offset = 0;\n const pageSize = 100; // HF max per request\n\n while (true) {\n const page = await this.getBulk(offset, pageSize);\n\n if (!page || page.length === 0) {\n break;\n }\n\n allEntities.push(...page);\n offset += page.length;\n\n // If we got fewer records than requested, we've reached the end\n if (page.length < pageSize) {\n break;\n }\n }\n\n if (allEntities.length === 0) return undefined;\n\n let results = allEntities;\n\n // Apply JS-side orderBy\n if (options?.orderBy && options.orderBy.length > 0) {\n results = [...results];\n results.sort((a, b) => {\n for (const { column, direction } of options.orderBy!) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n });\n }\n\n // Apply offset\n if (options?.offset !== undefined) {\n results = results.slice(options.offset);\n }\n\n // Apply limit\n if (options?.limit !== undefined) {\n results = results.slice(0, options.limit);\n }\n\n return results.length > 0 ? results : undefined;\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n const data = await this.fetchApi<HfRowsResponse>(\"/rows\", {\n offset: offset.toString(),\n length: Math.min(limit, 100).toString(), // HF max is 100 per request\n });\n\n if (data.rows.length === 0) {\n return undefined;\n }\n\n const entities: Entity[] = [];\n for (const row of data.rows) {\n entities.push(this.rowToEntity(row));\n }\n\n return entities;\n }\n\n /**\n * HuggingFace datasets are read-only, so the concurrency-stability that\n * keyset pagination provides is unnecessary. The HF API also doesn't\n * expose tuple comparisons, so we drive cursor pagination from the\n * `/rows` endpoint's offset and encode the next offset in the cursor.\n *\n * The HF `/rows` endpoint caps each fetch at 100 rows, but the\n * {@link ITabularStorage.getPage} contract lets callers ask for any\n * positive limit. Loop in 100-row chunks until we either fill the\n * caller's `limit` or hit the end of the dataset, so a `getPage({ limit:\n * 200 })` doesn't silently return only 100 rows with a `nextCursor` of\n * `undefined` (which would terminate iteration despite more data).\n */\n override async getPage(request: PageRequest<Entity> = {}): Promise<Page<Entity>> {\n this.validatePageRequest(request);\n const limit = request.limit ?? 100;\n if (request.orderBy && request.orderBy.length > 0) {\n // The HF /rows endpoint returns rows in `row_idx` order with no\n // alternative; silently overriding the caller's choice would be a\n // correctness bug, so reject it explicitly instead.\n throw new StorageUnsupportedError(\"orderBy in getPage\", \"HuggingFaceTabularStorage\");\n }\n const HF_PAGE_CAP = 100;\n let offset = request.cursor ? decodeOffsetCursor(request.cursor) : 0;\n const items: Entity[] = [];\n let endOfDataset = false;\n while (items.length < limit) {\n const remaining = limit - items.length;\n const chunkSize = Math.min(remaining, HF_PAGE_CAP);\n const rows = (await this.getBulk(offset, chunkSize)) ?? [];\n if (rows.length === 0) {\n endOfDataset = true;\n break;\n }\n items.push(...rows);\n offset += rows.length;\n // A short response from the HF API means the dataset has no more\n // rows for this offset window — bail out rather than spinning.\n if (rows.length < chunkSize) {\n endOfDataset = true;\n break;\n }\n }\n const nextCursor = endOfDataset ? undefined : encodeOffsetCursor(offset);\n return { items, nextCursor };\n }\n\n /**\n * Returns the number of rows in the dataset using the /size endpoint\n */\n async size(): Promise<number> {\n const data = await this.fetchApi<HfSizeResponse>(\"/size\", {});\n return data.size.num_rows;\n }\n\n /**\n * Write operations are not supported - throws readonly error\n */\n async put(_value: InsertType): Promise<Entity> {\n throw new Error(\"HuggingFaceTabularStorage is readonly\");\n }\n\n /**\n * Write operations are not supported - throws readonly error\n */\n async putBulk(_values: InsertType[]): Promise<Entity[]> {\n throw new Error(\"HuggingFaceTabularStorage is readonly\");\n }\n\n /**\n * Delete operations are not supported - throws readonly error\n */\n async delete(_value: PrimaryKey | Entity): Promise<void> {\n throw new Error(\"HuggingFaceTabularStorage is readonly\");\n }\n\n /**\n * Delete operations are not supported - throws readonly error\n */\n async deleteAll(): Promise<void> {\n throw new Error(\"HuggingFaceTabularStorage is readonly\");\n }\n\n /**\n * Queries entities using the /filter endpoint for equality criteria.\n * Non-equality operators are not supported by the HuggingFace API.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of matching entities or undefined if no matches found\n */\n async query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n this.validateQueryParams(criteria, options);\n\n // Build WHERE clause — only equality is supported by HF API\n const whereConditions: string[] = [];\n for (const [k, v] of Object.entries(criteria)) {\n if (v === undefined || v === null) continue;\n if (isSearchCondition(v)) {\n if (v.operator !== \"=\") {\n throw new StorageUnsupportedError(\n `Operator \"${v.operator}\" in query`,\n \"HuggingFaceTabularStorage\"\n );\n }\n const val = v.value;\n if (typeof val === \"string\") {\n const escaped = val.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\");\n whereConditions.push(`${k}='${escaped}'`);\n } else {\n whereConditions.push(`${k}=${val}`);\n }\n } else {\n if (typeof v === \"string\") {\n const escaped = v.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\");\n whereConditions.push(`${k}='${escaped}'`);\n } else {\n whereConditions.push(`${k}=${v}`);\n }\n }\n }\n\n if (whereConditions.length === 0) {\n return undefined;\n }\n\n const where = whereConditions.join(\" AND \");\n const allEntities: Entity[] = [];\n let fetchOffset = 0;\n const fetchLimit = 100;\n\n while (true) {\n const data = await this.fetchApi<HfFilterResponse>(\"/filter\", {\n where,\n offset: fetchOffset.toString(),\n limit: fetchLimit.toString(),\n });\n\n for (const row of data.rows) {\n allEntities.push(this.rowToEntity(row));\n }\n\n fetchOffset += data.rows.length;\n\n if (fetchOffset >= data.num_rows_total || data.rows.length < fetchLimit) {\n break;\n }\n }\n\n let results = allEntities;\n\n // Apply JS-side orderBy\n if (options?.orderBy && options.orderBy.length > 0) {\n results.sort((a, b) => {\n for (const { column, direction } of options.orderBy!) {\n const aVal = a[column] as string | number | null | undefined;\n const bVal = b[column] as string | number | null | undefined;\n if (aVal == null && bVal == null) continue;\n if (aVal == null) return direction === \"ASC\" ? -1 : 1;\n if (bVal == null) return direction === \"ASC\" ? 1 : -1;\n if (aVal < bVal) return direction === \"ASC\" ? -1 : 1;\n if (aVal > bVal) return direction === \"ASC\" ? 1 : -1;\n }\n return 0;\n });\n }\n\n // Apply offset\n if (options?.offset !== undefined) {\n results = results.slice(options.offset);\n }\n\n // Apply limit\n if (options?.limit !== undefined) {\n results = results.slice(0, options.limit);\n }\n\n if (results.length > 0) {\n this.events.emit(\"query\", criteria as Partial<Entity>, results);\n return results;\n } else {\n this.events.emit(\"query\", criteria as Partial<Entity>, undefined);\n return undefined;\n }\n }\n\n /**\n * Delete operations are not supported - throws readonly error\n */\n async deleteSearch(_criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n throw new Error(\"HuggingFaceTabularStorage is readonly\");\n }\n\n /**\n * Subscriptions are not supported - HF datasets are static\n */\n override subscribeToChanges(\n _callback: (change: TabularChangePayload<Entity>) => void,\n _options?: TabularSubscribeOptions\n ): () => void {\n throw new Error(\"HuggingFaceTabularStorage does not support subscriptions\");\n }\n\n /**\n * No resources to clean up\n */\n override destroy(): void {\n // No-op - no resources to clean up\n }\n\n /**\n * Helper method to fetch from the HuggingFace Dataset Viewer API\n */\n private async fetchApi<T>(endpoint: string, params: Record<string, string>): Promise<T> {\n const url = new URL(`${this.baseUrl}${endpoint}`);\n url.searchParams.set(\"dataset\", this.dataset);\n url.searchParams.set(\"config\", this.config);\n url.searchParams.set(\"split\", this.split);\n\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n url.searchParams.set(key, value);\n }\n }\n\n const headers: Record<string, string> = {};\n if (this.token) {\n headers[\"Authorization\"] = `Bearer ${this.token}`;\n }\n\n const response = await fetch(url.toString(), { headers });\n if (!response.ok) {\n throw new Error(`HuggingFace API error: ${response.status} ${response.statusText}`);\n }\n\n return await response.json();\n }\n\n /**\n * Converts a HF row to an Entity\n */\n private rowToEntity(row: { row_idx: number; row: Record<string, any> }): Entity {\n return { row_idx: row.row_idx, ...row.row } as Entity;\n }\n}\n\n/**\n * Converts a HuggingFace feature type to a JSON Schema type definition\n */\nfunction hfFeatureToJsonSchema(feature: any): any {\n // Handle Value types\n if (feature._type === \"Value\") {\n switch (feature.dtype) {\n case \"string\":\n return { type: \"string\" };\n case \"int64\":\n case \"int32\":\n case \"int16\":\n case \"int8\":\n case \"uint64\":\n case \"uint32\":\n case \"uint16\":\n case \"uint8\":\n return { type: \"integer\" };\n case \"float64\":\n case \"float32\":\n case \"float16\":\n return { type: \"number\" };\n case \"bool\":\n return { type: \"boolean\" };\n default:\n return {}; // any type\n }\n }\n\n // Handle ClassLabel types\n if (feature._type === \"ClassLabel\") {\n return { type: \"integer\" };\n }\n\n // Handle Sequence types\n if (feature._type === \"Sequence\") {\n return {\n type: \"array\",\n items: hfFeatureToJsonSchema(feature.feature),\n };\n }\n\n // Handle other types as any\n return {};\n}\n",
19
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n createServiceToken,\n globalServiceRegistry,\n registerInputCompactor,\n registerInputResolver,\n ServiceRegistry,\n} from \"@workglow/util\";\nimport { AnyTabularStorage } from \"./ITabularStorage\";\n\n/**\n * Service token for the tabular repository registry\n * Maps repository IDs to ITabularStorage instances\n */\nexport const TABULAR_REPOSITORIES = createServiceToken<Map<string, AnyTabularStorage>>(\n \"storage.tabular.repositories\"\n);\n\n/**\n * Gets the tabular repository map from the given registry (defaults to global).\n * Lazy-registers an empty map if absent keeps scoped registries isolated.\n */\nexport function getGlobalTabularRepositories(\n registry: ServiceRegistry = globalServiceRegistry\n): Map<string, AnyTabularStorage> {\n if (!registry.has(TABULAR_REPOSITORIES)) {\n registerTabularStorageDefaults(registry);\n }\n return registry.get(TABULAR_REPOSITORIES);\n}\n\n/**\n * Registers a tabular repository on the given registry by ID (defaults to global).\n */\nexport function registerTabularRepository(\n id: string,\n repository: AnyTabularStorage,\n registry: ServiceRegistry = globalServiceRegistry\n): void {\n const repos = getGlobalTabularRepositories(registry);\n repos.set(id, repository);\n}\n\n/**\n * Gets a tabular repository by ID from the given registry (defaults to global).\n * @returns The repository instance or undefined if not found\n */\nexport function getTabularRepository(\n id: string,\n registry: ServiceRegistry = globalServiceRegistry\n): AnyTabularStorage | undefined {\n return getGlobalTabularRepositories(registry).get(id);\n}\n\n/**\n * Resolves a repository ID to an instance from the registry.\n * Used by the input resolver system. Always reads from the supplied registry —\n * no fallback to global state, so scoped registries stay isolated.\n */\nfunction resolveRepositoryFromRegistry(\n id: string,\n _format: string,\n registry: ServiceRegistry\n): AnyTabularStorage {\n const repos = getGlobalTabularRepositories(registry);\n const repo = repos.get(id);\n if (!repo) {\n throw new Error(`Tabular storage \"${id}\" not found in registry`);\n }\n return repo;\n}\n\nfunction compactTabularRepository(\n value: unknown,\n _format: string,\n registry: ServiceRegistry\n): string | undefined {\n const repos = getGlobalTabularRepositories(registry);\n for (const [id, repo] of repos) {\n if (repo === value) return id;\n }\n return undefined;\n}\n\n/**\n * Registers the tabular storage default factory and the \"storage:tabular\" input resolver/compactor\n * on the given registry. Called by `bootstrapWorkglow` and `createOrchestrationContext`.\n */\nexport function registerTabularStorageDefaults(\n registry: ServiceRegistry = globalServiceRegistry\n): void {\n registry.registerIfAbsent(\n TABULAR_REPOSITORIES,\n (): Map<string, AnyTabularStorage> => new Map(),\n true\n );\n registerInputResolver(\"storage:tabular\", resolveRepositoryFromRegistry, registry);\n registerInputCompactor(\"storage:tabular\", compactTabularRepository, registry);\n}\n\n// Self-register on the global registry. Idempotent.\nregisterTabularStorageDefaults();\n",
20
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * Abstracts the dialect-specific differences between SQLite and PostgreSQL\n * for the parts of the storage layer that build SQL by string concatenation.\n *\n * The two things that differ are:\n * - Identifier quoting (backticks vs double quotes)\n * - Parameter placeholders (`?` vs `$N`)\n *\n * Keeping these in one place lets the predicate builder, prefix DDL helpers\n * and migrations runner emit the same SQL shape against either backend.\n */\nexport interface ISqlDialect {\n readonly name: \"sqlite\" | \"postgres\";\n /** Quote a table or column identifier so it survives unsafe characters. */\n quoteId(id: string): string;\n /** Render a parameter placeholder for the given 1-based index. */\n placeholder(index: number): string;\n}\n\nexport const SqliteDialect: ISqlDialect = {\n name: \"sqlite\",\n quoteId(id: string): string {\n return \"`\" + id.replace(/`/g, \"``\") + \"`\";\n },\n placeholder(_index: number): string {\n return \"?\";\n },\n};\n\nexport const PostgresDialect: ISqlDialect = {\n name: \"postgres\",\n quoteId(id: string): string {\n return '\"' + id.replace(/\"/g, '\"\"') + '\"';\n },\n placeholder(index: number): string {\n return `$${index}`;\n },\n};\n",
21
+ "/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { PostgresDialect, SqliteDialect, type ISqlDialect } from \"../sql/Dialect\";\n\nfunction selectDialect(name: \"sqlite\" | \"postgres\"): ISqlDialect {\n return name === \"sqlite\" ? SqliteDialect : PostgresDialect;\n}\n\nexport function buildAddColumnSql(\n dialect: \"sqlite\" | \"postgres\",\n table: string,\n column: string,\n sqlType: string,\n nullable: boolean,\n hasDefault: boolean = false,\n defaultLiteralSql?: string\n): string {\n const d = selectDialect(dialect);\n let sql = `ALTER TABLE ${d.quoteId(table)} ADD COLUMN ${d.quoteId(column)} ${sqlType}`;\n if (!nullable) sql += \" NOT NULL\";\n if (hasDefault && defaultLiteralSql !== undefined) {\n sql += ` DEFAULT ${defaultLiteralSql}`;\n }\n return sql;\n}\n\nexport function buildDropColumnSql(\n dialect: \"sqlite\" | \"postgres\",\n table: string,\n column: string\n): string {\n const d = selectDialect(dialect);\n return `ALTER TABLE ${d.quoteId(table)} DROP COLUMN ${d.quoteId(column)}`;\n}\n\nexport function buildRenameColumnSql(\n dialect: \"sqlite\" | \"postgres\",\n table: string,\n from: string,\n to: string\n): string {\n const d = selectDialect(dialect);\n return `ALTER TABLE ${d.quoteId(table)} RENAME COLUMN ${d.quoteId(from)} TO ${d.quoteId(to)}`;\n}\n\nexport function buildAddIndexSql(\n dialect: \"sqlite\" | \"postgres\",\n table: string,\n indexName: string,\n columns: readonly string[],\n unique: boolean\n): string {\n const d = selectDialect(dialect);\n const cols = columns.map((c) => d.quoteId(c)).join(\", \");\n return (\n `CREATE ${unique ? \"UNIQUE \" : \"\"}INDEX IF NOT EXISTS ` +\n `${d.quoteId(indexName)} ON ${d.quoteId(table)} (${cols})`\n );\n}\n\nexport function buildDropIndexSql(dialect: \"sqlite\" | \"postgres\", indexName: string): string {\n const d = selectDialect(dialect);\n return `DROP INDEX IF EXISTS ${d.quoteId(indexName)}`;\n}\n",
22
+ "/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { JsonSchema } from \"@workglow/util/schema\";\nimport {\n type ITabularMigrationApplier,\n type TabularMigrationOp,\n MIGRATIONS_TABLE,\n runBackfill,\n} from \"../migrations\";\nimport { type AnyTabularStorage } from \"./ITabularStorage\";\nimport {\n buildAddColumnSql,\n buildAddIndexSql,\n buildDropColumnSql,\n buildDropIndexSql,\n buildRenameColumnSql,\n} from \"./sqlMigrationDdl\";\n\n/**\n * SQL-flavored {@link ITabularMigrationApplier}. Subclasses (one per dialect)\n * provide the connection-level primitives (`exec`, `tableExists`,\n * `withTransaction`) and the JSON-Schema-to-SQL mapper. The applier handles\n * op translation, atomicity, and bookkeeping.\n *\n * `applyMigration` runs all ops + the bookkeeping INSERT inside a single\n * `withTransaction` so DDL, backfill writes, and applied-version recording\n * commit (or roll back) together on backends that support real transactions.\n */\nexport abstract class SqlTabularMigrationApplier implements ITabularMigrationApplier {\n protected abstract dialectName(): \"sqlite\" | \"postgres\";\n protected abstract table(): string;\n protected abstract storage(): AnyTabularStorage;\n protected abstract mapTypeToSQL(typeDef: JsonSchema): string;\n protected abstract isNullableSchema(typeDef: JsonSchema): boolean;\n protected abstract executeSql(sql: string): Promise<void>;\n protected abstract executeSqlTx(sql: string, tx: AnyTabularStorage): Promise<void>;\n protected abstract recordAppliedTx(\n component: string,\n version: number,\n description: string | undefined,\n tx: AnyTabularStorage\n ): Promise<void>;\n protected abstract recordApplied(\n component: string,\n version: number,\n description: string | undefined\n ): Promise<void>;\n protected abstract queryAppliedVersions(component: string): Promise<Set<number>>;\n protected abstract probeTableExists(): Promise<boolean>;\n\n async ensureBookkeeping(): Promise<void> {\n await this.executeSql(this.bookkeepingDdl());\n }\n\n async appliedVersions(component: string): Promise<Set<number>> {\n return this.queryAppliedVersions(component);\n }\n\n async tableExists(): Promise<boolean> {\n return this.probeTableExists();\n }\n\n async markAllApplied(\n component: string,\n versions: ReadonlyArray<{ version: number; description: string | undefined }>\n ): Promise<void> {\n if (versions.length === 0) return;\n for (const v of versions) {\n await this.recordApplied(component, v.version, v.description);\n }\n }\n\n async applyMigration(\n component: string,\n version: number,\n description: string | undefined,\n ops: ReadonlyArray<TabularMigrationOp>,\n onProgress?: (fraction: number) => void\n ): Promise<void> {\n const storage = this.storage();\n await storage.withTransaction(async (tx) => {\n let processed = 0;\n const total = Math.max(ops.length, 1);\n for (const op of ops) {\n await this.applyOp(op, tx);\n processed++;\n onProgress?.(processed / total);\n }\n await this.recordAppliedTx(component, version, description, tx);\n });\n }\n\n protected async applyOp(op: TabularMigrationOp, tx: AnyTabularStorage): Promise<void> {\n switch (op.kind) {\n case \"addColumn\": {\n const sqlType = this.mapTypeToSQL(op.schema);\n const nullable = this.isNullableSchema(op.schema);\n const hasDefault = op.default !== undefined;\n const sql = buildAddColumnSql(\n this.dialectName(),\n this.table(),\n op.name,\n sqlType,\n nullable,\n hasDefault,\n hasDefault ? this.literalSql(op.default!) : undefined\n );\n await this.executeSqlTx(sql, tx);\n return;\n }\n case \"dropColumn\": {\n await this.executeSqlTx(buildDropColumnSql(this.dialectName(), this.table(), op.name), tx);\n return;\n }\n case \"renameColumn\": {\n await this.executeSqlTx(\n buildRenameColumnSql(this.dialectName(), this.table(), op.from, op.to),\n tx\n );\n return;\n }\n case \"addIndex\": {\n await this.executeSqlTx(\n buildAddIndexSql(\n this.dialectName(),\n this.table(),\n op.name,\n op.columns,\n op.unique ?? false\n ),\n tx\n );\n return;\n }\n case \"dropIndex\": {\n await this.executeSqlTx(buildDropIndexSql(this.dialectName(), op.name), tx);\n return;\n }\n case \"backfill\": {\n await runBackfill(tx, op.batchSize ?? 500, op.transform);\n return;\n }\n }\n }\n\n /**\n * Renders a JS literal as SQL. Strings are quoted with `'` doubling;\n * numbers / booleans / null are rendered raw. NaN / ±Infinity throw\n * rather than silently splicing as `\"NaN\"` / `\"Infinity\"` (neither is\n * valid SQL). Objects throw — defaults must be finite primitives.\n */\n protected literalSql(value: unknown): string {\n if (value === null) return \"NULL\";\n if (typeof value === \"string\") return `'${value.replace(/'/g, \"''\")}'`;\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) {\n throw new Error(\n `Unsupported numeric default for tabular migration: ${value} (must be finite)`\n );\n }\n return String(value);\n }\n if (typeof value === \"boolean\") {\n return this.dialectName() === \"sqlite\" ? (value ? \"1\" : \"0\") : value ? \"TRUE\" : \"FALSE\";\n }\n throw new Error(\n `Unsupported default value for tabular migration: ${typeof value} (${String(value)})`\n );\n }\n\n /**\n * DDL for the bookkeeping table. Same shape used by the existing\n * per-driver runners.\n */\n protected bookkeepingDdl(): string {\n if (this.dialectName() === \"sqlite\") {\n return `CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (\n component TEXT NOT NULL,\n version INTEGER NOT NULL,\n description TEXT,\n applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (component, version)\n )`;\n }\n return `CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (\n component TEXT NOT NULL,\n version INTEGER NOT NULL,\n description TEXT,\n applied_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n PRIMARY KEY (component, version)\n )`;\n }\n}\n",
23
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {\n DataPortSchemaObject,\n FromSchema,\n TypedArraySchemaOptions,\n} from \"@workglow/util/schema\";\nimport { traced } from \"@workglow/util\";\nimport type { ITabularMigrationApplier } from \"../migrations\";\nimport type {\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n ITabularStorage,\n Page,\n PageRequest,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularEventListener,\n TabularEventName,\n TabularEventParameters,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\n\n/**\n * Telemetry wrapper for any ITabularStorage implementation.\n * Creates spans for all storage operations.\n */\nexport class TelemetryTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n InsertType = InsertEntity<Entity, AutoGeneratedKeys<Schema>>,\n> implements ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, InsertType> {\n constructor(\n protected readonly storageName: string,\n protected readonly inner: ITabularStorage<\n Schema,\n PrimaryKeyNames,\n Entity,\n PrimaryKey,\n InsertType\n >\n ) {}\n\n put(value: InsertType): Promise<Entity> {\n return traced(\"workglow.storage.tabular.put\", this.storageName, () => this.inner.put(value));\n }\n\n putBulk(values: InsertType[]): Promise<Entity[]> {\n return traced(\"workglow.storage.tabular.putBulk\", this.storageName, () =>\n this.inner.putBulk(values)\n );\n }\n\n get(key: PrimaryKey): Promise<Entity | undefined> {\n return traced(\"workglow.storage.tabular.get\", this.storageName, () => this.inner.get(key));\n }\n\n delete(key: PrimaryKey | Entity): Promise<void> {\n return traced(\"workglow.storage.tabular.delete\", this.storageName, () =>\n this.inner.delete(key)\n );\n }\n\n getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n return traced(\"workglow.storage.tabular.getAll\", this.storageName, () =>\n this.inner.getAll(options)\n );\n }\n\n deleteAll(): Promise<void> {\n return traced(\"workglow.storage.tabular.deleteAll\", this.storageName, () =>\n this.inner.deleteAll()\n );\n }\n\n size(): Promise<number> {\n return traced(\"workglow.storage.tabular.size\", this.storageName, () => this.inner.size());\n }\n\n count(criteria?: SearchCriteria<Entity>): Promise<number> {\n return traced(\"workglow.storage.tabular.count\", this.storageName, () =>\n this.inner.count(criteria)\n );\n }\n\n deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n return traced(\"workglow.storage.tabular.deleteSearch\", this.storageName, () =>\n this.inner.deleteSearch(criteria)\n );\n }\n\n getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n return traced(\"workglow.storage.tabular.getBulk\", this.storageName, () =>\n this.inner.getBulk(offset, limit)\n );\n }\n\n getPage(request?: PageRequest<Entity>): Promise<Page<Entity>> {\n return traced(\"workglow.storage.tabular.getPage\", this.storageName, () =>\n this.inner.getPage(request)\n );\n }\n\n queryPage(\n criteria: SearchCriteria<Entity>,\n request?: PageRequest<Entity>\n ): Promise<Page<Entity>> {\n return traced(\"workglow.storage.tabular.queryPage\", this.storageName, () =>\n this.inner.queryPage(criteria, request)\n );\n }\n\n query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n return traced(\"workglow.storage.tabular.query\", this.storageName, () =>\n this.inner.query(criteria, options)\n );\n }\n\n queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n return traced(\"workglow.storage.tabular.queryIndex\", this.storageName, () =>\n this.inner.queryIndex(criteria, options)\n );\n }\n\n // Forwarded directly (async generators, not worth tracing)\n records(pageSize?: number): AsyncGenerator<Entity, void, undefined> {\n return this.inner.records(pageSize);\n }\n\n pages(pageSize?: number): AsyncGenerator<Entity[], void, undefined> {\n return this.inner.pages(pageSize);\n }\n\n subscribeToChanges(\n callback: (change: TabularChangePayload<Entity>) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n return this.inner.subscribeToChanges(callback, options);\n }\n\n withTransaction<T>(fn: (tx: this) => Promise<T>): Promise<T> {\n // Construct a tx-bound telemetry wrapper that forwards to the inner's\n // transaction handle (`innerTx`) — the proxy from the inner's\n // `createTxView`. Passing the outer `this` would re-enter `inner.put()`\n // through the public, mutex-acquiring path and deadlock against the held\n // mutex on single-connection backends, or run on the wrong connection on\n // real `pg.Pool`.\n return traced(\"workglow.storage.tabular.withTransaction\", this.storageName, () =>\n this.inner.withTransaction((innerTx) => {\n const txWrapper = new TelemetryTabularStorage(\n this.storageName,\n innerTx as ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, InsertType>\n ) as this;\n return fn(txWrapper);\n })\n );\n }\n\n setupDatabase(): Promise<void> {\n return this.inner.setupDatabase();\n }\n\n /**\n * Forwards to the inner storage's applier so callers that wrap a\n * migration-aware storage in TelemetryTabularStorage still observe\n * declared migrations.\n */\n getMigrationApplier(): ITabularMigrationApplier | null {\n const inner = this.inner as unknown as {\n getMigrationApplier?: () => ITabularMigrationApplier | null;\n };\n return inner.getMigrationApplier?.() ?? null;\n }\n\n destroy(): void {\n return this.inner.destroy();\n }\n\n [Symbol.dispose](): void {\n return this.inner[Symbol.dispose]();\n }\n\n [Symbol.asyncDispose](): Promise<void> {\n return this.inner[Symbol.asyncDispose]();\n }\n\n // Event delegation — no telemetry needed\n on<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void {\n this.inner.on(name, fn);\n }\n\n off<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void {\n this.inner.off(name, fn);\n }\n\n emit<Event extends TabularEventName>(\n name: Event,\n ...args: TabularEventParameters<Event, PrimaryKey, Entity>\n ): void {\n this.inner.emit(name, ...args);\n }\n\n once<Event extends TabularEventName>(\n name: Event,\n fn: TabularEventListener<Event, PrimaryKey, Entity>\n ): void {\n this.inner.once(name, fn);\n }\n\n waitOn<Event extends TabularEventName>(\n name: Event\n ): Promise<TabularEventParameters<Event, PrimaryKey, Entity>> {\n return this.inner.waitOn(name);\n }\n}\n",
16
24
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { DataPortSchemaObject } from \"@workglow/util/schema\";\nimport { EventParameters } from \"@workglow/util\";\nimport { JSONValue } from \"../tabular/ITabularStorage\";\n\n/**\n * Default schema types for simple string row data\n */\nexport const DefaultKeyValueSchema = {\n type: \"object\",\n properties: {\n key: { type: \"string\" },\n value: {},\n },\n additionalProperties: false,\n} as const satisfies DataPortSchemaObject;\nexport const DefaultKeyValueKey = [\"key\"] as const;\n\n/**\n * Type definitions for kv repository events\n */\nexport type KvEventListeners<Key, Value, Combined> = {\n put: (key: Key, value: Value) => void;\n get: (key: Key, value: Value | undefined) => void;\n getAll: (results: Combined[] | undefined) => void;\n delete: (key: unknown) => void;\n deleteall: () => void;\n};\n\nexport type KvEventName = keyof KvEventListeners<any, any, any>;\nexport type KvEventListener<Event extends KvEventName, Key, Value, Combined> = KvEventListeners<\n Key,\n Value,\n Combined\n>[Event];\n\nexport type KvEventParameters<Event extends KvEventName, Key, Value, Combined> = EventParameters<\n KvEventListeners<Key, Value, Combined>,\n Event\n>;\n\n/**\n * Interface defining the contract for kv storage repositories.\n * Provides a flexible interface for storing and retrieving data with typed\n * primary keys and values, and supports compound keys and partial key lookup.\n *\n * @typeParam Key - Type for the primary key\n * @typeParam Value - Type for the value struct re\n * @typeParam Combined - Combined type of Key & Value\n */\nexport interface IKvStorage<\n Key extends string | number = string,\n Value extends any = any,\n Combined = { key: Key; value: Value },\n> {\n // Core methods\n put(key: Key, value: Value): Promise<void>;\n putBulk(items: Array<{ key: Key; value: Value }>): Promise<void>;\n get(key: Key): Promise<Value | undefined>;\n delete(key: Key): Promise<void>;\n getAll(): Promise<Combined[] | undefined>;\n deleteAll(): Promise<void>;\n size(): Promise<number>;\n\n getObjectAsIdString(object: JSONValue): Promise<string>;\n\n // Event handling methods\n on<Event extends KvEventName>(\n name: Event,\n fn: KvEventListener<Event, Key, Value, Combined>\n ): void;\n off<Event extends KvEventName>(\n name: Event,\n fn: KvEventListener<Event, Key, Value, Combined>\n ): void;\n emit<Event extends KvEventName>(\n name: Event,\n ...args: KvEventParameters<Event, Key, Value, Combined>\n ): void;\n once<Event extends KvEventName>(\n name: Event,\n fn: KvEventListener<Event, Key, Value, Combined>\n ): void;\n waitOn<Event extends KvEventName>(\n name: Event\n ): Promise<KvEventParameters<Event, Key, Value, Combined>>;\n}\n",
17
25
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { JsonSchema } from \"@workglow/util/schema\";\nimport { createServiceToken } from \"@workglow/util\";\nimport { InMemoryTabularStorage } from \"../tabular/InMemoryTabularStorage\";\nimport { DefaultKeyValueKey, DefaultKeyValueSchema, IKvStorage } from \"./IKvStorage\";\nimport { KvViaTabularStorage } from \"./KvViaTabularStorage\";\n\nexport const MEMORY_KV_REPOSITORY = createServiceToken<IKvStorage<string, any, any>>(\n \"storage.kvRepository.inMemory\"\n);\n\n/**\n * An in-memory key-value repository implementation for fast, ephemeral storage.\n * Uses a tabular repository abstraction for in-memory persistence.\n *\n * @template Key - The type of the primary key\n * @template Value - The type of the value being stored\n * @template Combined - Combined type of Key & Value\n */\nexport class InMemoryKvStorage extends KvViaTabularStorage {\n public tabularRepository: InMemoryTabularStorage<\n typeof DefaultKeyValueSchema,\n typeof DefaultKeyValueKey\n >;\n\n /**\n * Creates a new KvStorage instance\n */\n constructor(keySchema: JsonSchema = { type: \"string\" }, valueSchema: JsonSchema = {}) {\n super(keySchema, valueSchema);\n this.tabularRepository = new InMemoryTabularStorage(DefaultKeyValueSchema, DefaultKeyValueKey);\n }\n}\n",
18
26
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { JsonSchema } from \"@workglow/util/schema\";\nimport { createServiceToken, EventEmitter, makeFingerprint } from \"@workglow/util\";\nimport { JSONValue } from \"../tabular/ITabularStorage\";\nimport {\n IKvStorage,\n KvEventListener,\n KvEventListeners,\n KvEventName,\n KvEventParameters,\n} from \"./IKvStorage\";\n\nexport const KV_REPOSITORY = createServiceToken<IKvStorage<any, any, any>>(\"storage.kvRepository\");\n\n/**\n * Abstract base class for key-value storage repositories.\n * Has a basic event emitter for listening to repository events.\n *\n * @template Key - The type of the primary key\n * @template Value - The type of the value being stored\n * @template Combined - Combined type of Key & Value\n */\nexport abstract class KvStorage<\n Key extends string = string,\n Value extends any = any,\n Combined = { key: Key; value: Value },\n> implements IKvStorage<Key, Value, Combined> {\n /** Event emitter for repository events */\n protected events = new EventEmitter<KvEventListeners<Key, Value, Combined>>();\n\n /**\n * Creates a new KvStorage instance\n */\n constructor(\n public keySchema: JsonSchema = { type: \"string\" },\n public valueSchema: JsonSchema = {}\n ) {}\n\n /**\n * Stores a row in the repository.\n * @param key - The primary key\n * @param value - The value to store\n */\n abstract put(key: Key, value: Value): Promise<void>;\n\n /**\n * Stores multiple rows in the repository in a bulk operation.\n * @param items - Array of key-value pairs to store\n */\n abstract putBulk(items: Array<{ key: Key; value: Value }>): Promise<void>;\n\n /**\n * Retrieves a value by its key.\n * This is a convenience method that automatically converts simple types to structured format if using default schema.\n *\n * @param key - Primary key to look up (basic key like default schema)\n * @returns The stored value or undefined if not found\n */\n abstract get(key: Key): Promise<Value | undefined>;\n\n /**\n * Deletes a row from the repository.\n * @param key - The primary key of the row to delete\n */\n abstract delete(key: Key): Promise<void>;\n\n /**\n * Retrieves all rows from the repository.\n * @returns An array of all rows in the repository or undefined if empty\n */\n abstract getAll(): Promise<Combined[] | undefined>;\n\n /**\n * Deletes all rows from the repository.\n */\n abstract deleteAll(): Promise<void>;\n\n /**\n * Retrieves the number of rows in the repository.\n * @returns The number of rows in the repository\n */\n abstract size(): Promise<number>;\n\n /**\n * Generates a consistent string identifier for a given key.\n *\n * @param object - Object to convert\n * @returns Promise resolving to a string fingerprint of the object for use as an id\n */\n public async getObjectAsIdString(object: JSONValue): Promise<string> {\n return await makeFingerprint(object);\n }\n\n /**\n * Adds an event listener for a specific event\n * @param name The name of the event to listen for\n * @param fn The callback function to execute when the event occurs\n */\n on<Event extends KvEventName>(name: Event, fn: KvEventListener<Event, Key, Value, Combined>) {\n this.events.on(name, fn);\n }\n\n /**\n * Removes an event listener for a specific event\n * @param name The name of the event to remove the listener from\n * @param fn The callback function to remove\n */\n off<Event extends KvEventName>(name: Event, fn: KvEventListener<Event, Key, Value, Combined>) {\n this.events.off(name, fn);\n }\n\n /**\n * Adds an event listener that will only be called once\n * @param name The name of the event to listen for\n * @param fn The callback function to execute when the event occurs\n */\n once<Event extends KvEventName>(name: Event, fn: KvEventListener<Event, Key, Value, Combined>) {\n this.events.once(name, fn);\n }\n\n /**\n * Emits an event with the specified name and arguments\n * @param name The name of the event to emit\n * @param args The arguments to pass to the event listeners\n */\n emit<Event extends KvEventName>(\n name: Event,\n ...args: KvEventParameters<Event, Key, Value, Combined>\n ) {\n this.events.emit(name, ...args);\n }\n\n /**\n * Returns when the event was emitted (promise form of once)\n * @param name The name of the event to check\n * @returns true if the event has listeners, false otherwise\n */\n waitOn<Event extends KvEventName>(\n name: Event\n ): Promise<KvEventParameters<Event, Key, Value, Combined>> {\n return this.events.waitOn(name) as Promise<KvEventParameters<Event, Key, Value, Combined>>;\n }\n}\n",
@@ -20,17 +28,19 @@
20
28
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { JSONValue } from \"../tabular/ITabularStorage\";\nimport { traced } from \"@workglow/util\";\nimport type { IKvStorage, KvEventListener, KvEventName, KvEventParameters } from \"./IKvStorage\";\n\n/**\n * Telemetry wrapper for any IKvStorage implementation.\n * Creates spans for all storage operations.\n */\nexport class TelemetryKvStorage<\n Key extends string | number = string,\n Value extends any = any,\n Combined = { key: Key; value: Value },\n> implements IKvStorage<Key, Value, Combined> {\n constructor(\n private readonly storageName: string,\n private readonly inner: IKvStorage<Key, Value, Combined>\n ) {}\n\n put(key: Key, value: Value): Promise<void> {\n return traced(\"workglow.storage.kv.put\", this.storageName, () => this.inner.put(key, value));\n }\n putBulk(items: Array<{ key: Key; value: Value }>): Promise<void> {\n return traced(\"workglow.storage.kv.putBulk\", this.storageName, () => this.inner.putBulk(items));\n }\n get(key: Key): Promise<Value | undefined> {\n return traced(\"workglow.storage.kv.get\", this.storageName, () => this.inner.get(key));\n }\n delete(key: Key): Promise<void> {\n return traced(\"workglow.storage.kv.delete\", this.storageName, () => this.inner.delete(key));\n }\n getAll(): Promise<Combined[] | undefined> {\n return traced(\"workglow.storage.kv.getAll\", this.storageName, () => this.inner.getAll());\n }\n deleteAll(): Promise<void> {\n return traced(\"workglow.storage.kv.deleteAll\", this.storageName, () => this.inner.deleteAll());\n }\n size(): Promise<number> {\n return traced(\"workglow.storage.kv.size\", this.storageName, () => this.inner.size());\n }\n getObjectAsIdString(object: JSONValue): Promise<string> {\n return this.inner.getObjectAsIdString(object);\n }\n\n // Event delegation — no telemetry needed\n on<Event extends KvEventName>(name: Event, fn: KvEventListener<Event, Key, Value, Combined>) {\n this.inner.on(name, fn);\n }\n off<Event extends KvEventName>(name: Event, fn: KvEventListener<Event, Key, Value, Combined>) {\n this.inner.off(name, fn);\n }\n emit<Event extends KvEventName>(\n name: Event,\n ...args: KvEventParameters<Event, Key, Value, Combined>\n ) {\n this.inner.emit(name, ...args);\n }\n once<Event extends KvEventName>(name: Event, fn: KvEventListener<Event, Key, Value, Combined>) {\n this.inner.once(name, fn);\n }\n waitOn<Event extends KvEventName>(\n name: Event\n ): Promise<KvEventParameters<Event, Key, Value, Combined>> {\n return this.inner.waitOn(name);\n }\n}\n",
21
29
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * Configuration options for the hybrid subscription manager\n */\nexport interface HybridManagerOptions {\n /** Default polling interval in milliseconds for backup polling */\n readonly defaultIntervalMs?: number;\n /** Backup polling interval in milliseconds (0 to disable, default: 5000) */\n readonly backupPollingIntervalMs?: number;\n /** Enable BroadcastChannel notifications (default: true) */\n readonly useBroadcastChannel?: boolean;\n /** BroadcastChannel name for cross-tab communication */\n readonly broadcastChannelName?: string;\n}\n\n// Re-use types from PollingSubscriptionManager to avoid duplication\nimport type {\n ChangeCallback,\n ChangePayloadFactory,\n ItemComparator,\n StateFetcher,\n} from \"./PollingSubscriptionManager\";\n\n/**\n * Options for subscribing to changes\n */\nexport interface HybridSubscriptionOptions {\n /** Polling interval in milliseconds (not used if BroadcastChannel is active) */\n readonly intervalMs?: number;\n}\n\n/**\n * Internal subscription record\n */\ninterface Subscription<ChangePayload> {\n readonly callback: ChangeCallback<ChangePayload>;\n readonly intervalMs: number;\n}\n\n/**\n * BroadcastChannel message types\n */\ntype BroadcastMessage =\n | { readonly type: \"CHANGE\" }\n | { readonly type: \"HEARTBEAT\"; readonly tabId: string; readonly timestamp: number };\n\n/**\n * Manages hybrid event + polling subscriptions efficiently by using BroadcastChannel\n * for instant cross-tab change notifications with optional backup polling for reliability.\n *\n * This manager combines three notification mechanisms:\n * 1. Local event notifications for same-tab changes (instant)\n * 2. BroadcastChannel for cross-tab change notifications (near-instant)\n * 3. Optional backup polling for reliability (infrequent, 5-10s)\n *\n * When BroadcastChannel is not available, falls back to local events only (assumes single tab).\n *\n * @template Item - The type of items being tracked\n * @template Key - The type of key used to identify items\n * @template ChangePayload - The type of change payload sent to subscribers\n */\nexport class HybridSubscriptionManager<Item, Key, ChangePayload> {\n /** Map of interval (ms) to interval ID and subscriber list */\n private readonly subscribers = new Set<Subscription<ChangePayload>>();\n\n /** Current known state from last fetch */\n private lastKnownState = new Map<Key, Item>();\n\n /** Whether the manager has been initialized with a state fetch */\n private initialized = false;\n\n /** BroadcastChannel for cross-tab communication */\n private channel: BroadcastChannel | null = null;\n\n /** Backup polling interval ID */\n private backupPollingIntervalId: ReturnType<typeof setInterval> | null = null;\n\n /** Function to fetch current state */\n private readonly fetchState: StateFetcher<Item, Key>;\n\n /** Function to compare items for equality */\n private readonly compareItems: ItemComparator<Item>;\n\n /** Factory for creating change payloads */\n private readonly payloadFactory: ChangePayloadFactory<Item, ChangePayload>;\n\n /** Configuration options */\n private readonly options: Required<HybridManagerOptions>;\n\n /** Whether BroadcastChannel is available */\n private readonly hasBroadcastChannel: boolean;\n\n /**\n * Creates a new HybridSubscriptionManager\n *\n * @param channelName - Name for the BroadcastChannel (should be unique per storage instance)\n * @param fetchState - Function that returns the current state as a Map\n * @param compareItems - Function that compares two items for equality\n * @param payloadFactory - Factory for creating INSERT/UPDATE/DELETE payloads\n * @param options - Configuration options\n */\n constructor(\n channelName: string,\n fetchState: StateFetcher<Item, Key>,\n compareItems: ItemComparator<Item>,\n payloadFactory: ChangePayloadFactory<Item, ChangePayload>,\n options?: HybridManagerOptions\n ) {\n this.fetchState = fetchState;\n this.compareItems = compareItems;\n this.payloadFactory = payloadFactory;\n\n this.options = {\n defaultIntervalMs: options?.defaultIntervalMs ?? 1000,\n backupPollingIntervalMs: options?.backupPollingIntervalMs ?? 5000,\n useBroadcastChannel: options?.useBroadcastChannel ?? true,\n broadcastChannelName: options?.broadcastChannelName ?? channelName,\n };\n\n this.hasBroadcastChannel =\n this.options.useBroadcastChannel && typeof BroadcastChannel !== \"undefined\";\n\n if (this.hasBroadcastChannel) {\n this.initializeBroadcastChannel();\n }\n }\n\n /**\n * Initializes the BroadcastChannel and sets up message handlers\n */\n private initializeBroadcastChannel(): void {\n try {\n this.channel = new BroadcastChannel(this.options.broadcastChannelName);\n this.channel.onmessage = (event: MessageEvent<BroadcastMessage>) => {\n this.handleBroadcastMessage(event.data);\n };\n } catch (error) {\n console.error(\"Failed to initialize BroadcastChannel:\", error);\n this.channel = null;\n }\n }\n\n /**\n * Handles incoming BroadcastChannel messages\n */\n private async handleBroadcastMessage(message: BroadcastMessage): Promise<void> {\n if (message.type === \"CHANGE\") {\n // Another tab made a change - poll for updates\n await this.pollAndNotify();\n }\n // HEARTBEAT messages could be used for tab detection in future\n }\n\n /**\n * Notifies other tabs that a local change occurred\n * Should be called after any local mutation\n */\n notifyLocalChange(): void {\n // Immediately poll and notify local subscribers\n this.pollAndNotify();\n\n // Broadcast change notification to other tabs\n if (this.channel) {\n try {\n this.channel.postMessage({ type: \"CHANGE\" } as BroadcastMessage);\n } catch (error) {\n // Ignore broadcast errors\n }\n }\n }\n\n /**\n * Subscribe to changes\n *\n * @param callback - Function called when changes are detected\n * @param options - Subscription options\n * @returns Unsubscribe function\n */\n subscribe(\n callback: ChangeCallback<ChangePayload>,\n options?: HybridSubscriptionOptions\n ): () => void {\n const interval = options?.intervalMs ?? this.options.defaultIntervalMs;\n const subscription: Subscription<ChangePayload> = {\n callback,\n intervalMs: interval,\n };\n\n const isFirstSubscriber = this.subscribers.size === 0;\n this.subscribers.add(subscription);\n\n if (isFirstSubscriber) {\n // First subscriber - initialize and set up backup polling\n if (!this.initialized) {\n this.initialized = true;\n // Don't await - let it run async to avoid blocking\n void this.initAndNotify(subscription);\n } else {\n // Send current state to new subscriber\n this.notifySubscriberOfCurrentState(subscription);\n }\n\n // Start backup polling if configured\n // When BroadcastChannel is active, use backup polling for reliability\n // When BroadcastChannel is not available/disabled, use polling as the primary mechanism\n if (this.options.backupPollingIntervalMs > 0) {\n this.startBackupPolling();\n }\n } else {\n this.notifySubscriberOfCurrentState(subscription);\n }\n\n return () => {\n this.subscribers.delete(subscription);\n\n // If no more subscribers, stop backup polling\n if (this.subscribers.size === 0) {\n this.stopBackupPolling();\n }\n };\n }\n\n /**\n * Initialize state and notify first subscriber\n */\n private async initAndNotify(subscription: Subscription<ChangePayload>): Promise<void> {\n try {\n this.lastKnownState = await this.fetchState();\n // Notify the new subscriber of initial state as INSERTs\n for (const [, item] of this.lastKnownState) {\n const payload = this.payloadFactory.insert(item);\n try {\n subscription.callback(payload);\n } catch {\n // Ignore callback errors\n }\n }\n } catch {\n // Ignore fetch errors during initialization\n }\n }\n\n /**\n * Send current state to a subscriber\n */\n private notifySubscriberOfCurrentState(subscription: Subscription<ChangePayload>): void {\n // Send current state as INSERTs to the new subscriber\n for (const [, item] of this.lastKnownState) {\n const payload = this.payloadFactory.insert(item);\n try {\n subscription.callback(payload);\n } catch {\n // Ignore callback errors\n }\n }\n }\n\n /**\n * Poll for changes and notify all subscribers\n */\n private async pollAndNotify(): Promise<void> {\n if (this.subscribers.size === 0) return;\n\n try {\n const currentState = await this.fetchState();\n const changes: ChangePayload[] = [];\n\n // Detect new and updated items\n for (const [key, item] of currentState) {\n const oldItem = this.lastKnownState.get(key);\n if (!oldItem) {\n changes.push(this.payloadFactory.insert(item));\n } else if (!this.compareItems(oldItem, item)) {\n changes.push(this.payloadFactory.update(oldItem, item));\n }\n }\n\n // Detect deleted items\n for (const [key, item] of this.lastKnownState) {\n if (!currentState.has(key)) {\n changes.push(this.payloadFactory.delete(item));\n }\n }\n\n // Update state\n this.lastKnownState = currentState;\n\n // Broadcast changes to all subscribers\n for (const change of changes) {\n for (const sub of this.subscribers) {\n try {\n sub.callback(change);\n } catch {\n // Ignore callback errors\n }\n }\n }\n } catch {\n // Ignore polling errors\n }\n }\n\n /**\n * Start backup polling\n */\n private startBackupPolling(): void {\n if (this.backupPollingIntervalId) return;\n\n this.backupPollingIntervalId = setInterval(\n () => this.pollAndNotify(),\n this.options.backupPollingIntervalMs\n );\n }\n\n /**\n * Stop backup polling\n */\n private stopBackupPolling(): void {\n if (this.backupPollingIntervalId) {\n clearInterval(this.backupPollingIntervalId);\n this.backupPollingIntervalId = null;\n }\n }\n\n /**\n * Get the number of active subscriptions\n */\n get subscriptionCount(): number {\n return this.subscribers.size;\n }\n\n /**\n * Check if there are any active subscriptions\n */\n get hasSubscriptions(): boolean {\n return this.subscribers.size > 0;\n }\n\n /**\n * Check if BroadcastChannel is available and active\n */\n get isBroadcastChannelActive(): boolean {\n return this.channel !== null;\n }\n\n /**\n * Destroy the manager and clean up all resources\n */\n destroy(): void {\n this.stopBackupPolling();\n if (this.channel) {\n this.channel.close();\n this.channel = null;\n }\n this.subscribers.clear();\n this.lastKnownState.clear();\n this.initialized = false;\n }\n}\n",
22
30
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * Configuration options for the polling subscription manager\n */\nexport interface PollingManagerOptions {\n /** Default polling interval in milliseconds */\n readonly defaultIntervalMs?: number;\n}\n\n/**\n * A callback function that is called when changes are detected\n */\nexport type ChangeCallback<T> = (change: T) => void;\n\n/**\n * A function that fetches the current state for comparison\n */\nexport type StateFetcher<Item, Key> = () => Promise<Map<Key, Item>>;\n\n/**\n * A function that compares two items for equality\n */\nexport type ItemComparator<Item> = (a: Item, b: Item) => boolean;\n\n/**\n * A factory function that creates change payloads\n */\nexport interface ChangePayloadFactory<Item, ChangePayload> {\n /** Create an INSERT change payload */\n readonly insert: (item: Item) => ChangePayload;\n /** Create an UPDATE change payload */\n readonly update: (oldItem: Item, newItem: Item) => ChangePayload;\n /** Create a DELETE change payload */\n readonly delete: (item: Item) => ChangePayload;\n}\n\n/**\n * Options for subscribing to changes\n */\nexport interface PollingSubscriptionOptions {\n /** Polling interval in milliseconds */\n readonly intervalMs?: number;\n}\n\n/**\n * Internal subscription record\n */\ninterface Subscription<ChangePayload> {\n readonly callback: ChangeCallback<ChangePayload>;\n readonly intervalMs: number;\n}\n\n/**\n * Manages polling-based subscriptions efficiently by consolidating multiple\n * subscribers into a single polling loop per interval tier.\n *\n * Instead of each subscription creating its own polling interval, this manager\n * groups subscriptions by their requested polling interval and runs a single\n * poll for each group, broadcasting changes to all subscribers in that group.\n *\n * @template Item - The type of items being tracked\n * @template Key - The type of key used to identify items\n * @template ChangePayload - The type of change payload sent to subscribers\n */\nexport class PollingSubscriptionManager<Item, Key, ChangePayload> {\n /** Map of interval (ms) to interval ID and subscriber list */\n private readonly intervals = new Map<\n number,\n {\n intervalId: ReturnType<typeof setInterval>;\n subscribers: Set<Subscription<ChangePayload>>;\n }\n >();\n\n /** Current known state from last poll */\n private lastKnownState = new Map<Key, Item>();\n\n /** Whether the manager has been initialized with a state fetch */\n private initialized = false;\n\n /** Whether initialization is currently in progress (guards against poll/init race) */\n private initializing = false;\n\n /** Function to fetch current state */\n private readonly fetchState: StateFetcher<Item, Key>;\n\n /** Function to compare items for equality */\n private readonly compareItems: ItemComparator<Item>;\n\n /** Factory for creating change payloads */\n private readonly payloadFactory: ChangePayloadFactory<Item, ChangePayload>;\n\n /** Default polling interval */\n private readonly defaultIntervalMs: number;\n\n /**\n * Creates a new PollingSubscriptionManager\n *\n * @param fetchState - Function that returns the current state as a Map\n * @param compareItems - Function that compares two items for equality\n * @param payloadFactory - Factory for creating INSERT/UPDATE/DELETE payloads\n * @param options - Configuration options\n */\n constructor(\n fetchState: StateFetcher<Item, Key>,\n compareItems: ItemComparator<Item>,\n payloadFactory: ChangePayloadFactory<Item, ChangePayload>,\n options?: PollingManagerOptions\n ) {\n this.fetchState = fetchState;\n this.compareItems = compareItems;\n this.payloadFactory = payloadFactory;\n this.defaultIntervalMs = options?.defaultIntervalMs ?? 1000;\n }\n\n /**\n * Subscribe to changes with a specific polling interval\n *\n * @param callback - Function called when changes are detected\n * @param options - Subscription options including interval\n * @returns Unsubscribe function\n */\n subscribe(\n callback: ChangeCallback<ChangePayload>,\n options?: PollingSubscriptionOptions\n ): () => void {\n const interval = options?.intervalMs ?? this.defaultIntervalMs;\n const subscription: Subscription<ChangePayload> = {\n callback,\n intervalMs: interval,\n };\n\n let intervalGroup = this.intervals.get(interval);\n if (!intervalGroup) {\n // First subscriber for this interval - create the polling loop\n const subscribers = new Set<Subscription<ChangePayload>>();\n const intervalId = setInterval(() => this.poll(subscribers), interval);\n\n intervalGroup = { intervalId, subscribers };\n this.intervals.set(interval, intervalGroup);\n\n // Run initial poll if this is the first subscriber ever\n if (!this.initialized) {\n this.initialized = true;\n this.initializing = true;\n this.initAndPoll(subscription);\n } else {\n // Run immediate poll for new subscriber\n this.pollForNewSubscriber(subscription);\n }\n } else {\n // New subscriber joining existing interval - send them current state\n this.pollForNewSubscriber(subscription);\n }\n\n intervalGroup.subscribers.add(subscription);\n\n return () => {\n const group = this.intervals.get(interval);\n if (group) {\n group.subscribers.delete(subscription);\n\n // If no more subscribers for this interval, clean up\n if (group.subscribers.size === 0) {\n clearInterval(group.intervalId);\n this.intervals.delete(interval);\n }\n }\n };\n }\n\n /**\n * Initialize state and run first poll\n */\n private async initAndPoll(newSubscription: Subscription<ChangePayload>): Promise<void> {\n try {\n this.lastKnownState = await this.fetchState();\n // Notify the new subscriber of initial state as INSERTs\n for (const [, item] of this.lastKnownState) {\n const payload = this.payloadFactory.insert(item);\n try {\n newSubscription.callback(payload);\n } catch {\n // Ignore callback errors\n }\n }\n } catch {\n // Ignore fetch errors during initialization\n } finally {\n this.initializing = false;\n }\n }\n\n /**\n * Send current state to a new subscriber\n */\n private pollForNewSubscriber(subscription: Subscription<ChangePayload>): void {\n // Send current state as INSERTs to the new subscriber\n for (const [, item] of this.lastKnownState) {\n const payload = this.payloadFactory.insert(item);\n try {\n subscription.callback(payload);\n } catch {\n // Ignore callback errors\n }\n }\n }\n\n /**\n * Poll for changes and notify all subscribers in the given set\n */\n private async poll(subscribers: Set<Subscription<ChangePayload>>): Promise<void> {\n if (subscribers.size === 0) return;\n // Skip polling while initAndPoll is still running to avoid racing on lastKnownState\n if (this.initializing) return;\n\n try {\n const currentState = await this.fetchState();\n const changes: ChangePayload[] = [];\n\n // Detect new and updated items\n for (const [key, item] of currentState) {\n const oldItem = this.lastKnownState.get(key);\n if (!oldItem) {\n changes.push(this.payloadFactory.insert(item));\n } else if (!this.compareItems(oldItem, item)) {\n changes.push(this.payloadFactory.update(oldItem, item));\n }\n }\n\n // Detect deleted items\n for (const [key, item] of this.lastKnownState) {\n if (!currentState.has(key)) {\n changes.push(this.payloadFactory.delete(item));\n }\n }\n\n // Update state\n this.lastKnownState = currentState;\n\n // Broadcast changes to all subscribers\n for (const change of changes) {\n for (const sub of subscribers) {\n try {\n sub.callback(change);\n } catch {\n // Ignore callback errors\n }\n }\n }\n } catch {\n // Ignore polling errors\n }\n }\n\n /**\n * Get the number of active subscriptions across all intervals\n */\n get subscriptionCount(): number {\n let count = 0;\n for (const group of this.intervals.values()) {\n count += group.subscribers.size;\n }\n return count;\n }\n\n /**\n * Check if there are any active subscriptions\n */\n get hasSubscriptions(): boolean {\n return this.intervals.size > 0;\n }\n\n /**\n * Destroy the manager and clean up all intervals\n */\n destroy(): void {\n for (const group of this.intervals.values()) {\n clearInterval(group.intervalId);\n }\n this.intervals.clear();\n this.lastKnownState.clear();\n this.initialized = false;\n this.initializing = false;\n }\n}\n",
31
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n type DeleteSearchCriteria,\n isSearchCondition,\n type SearchOperator,\n type ValueOptionType,\n} from \"../tabular/ITabularStorage\";\nimport type { ISqlDialect } from \"./Dialect\";\n\n/**\n * Result of {@link buildSearchWhere} — a parameterized WHERE-clause body\n * (without the leading `WHERE` keyword) and its ordered parameters.\n */\nexport interface BuiltWhereClause {\n readonly whereClause: string;\n readonly params: ValueOptionType[];\n}\n\n/**\n * Builds a parameterized AND-joined WHERE clause from a {@link DeleteSearchCriteria}.\n * Used by every SQL tabular backend so the operator handling stays consistent.\n *\n * @param dialect Identifier-quoting + placeholder rules for the target DB.\n * @param criteria Per-column equality value or {@link SearchOperator} condition.\n * @param schemaProps Schema property bag — unknown columns throw, preventing\n * callers from accidentally letting user input pick a column.\n * @param convertValue Backend-specific JS-to-SQL coercion (e.g. boolean → 0/1).\n * @param startIndex 1-based starting parameter index (defaults to 1).\n * PostgreSQL callers use this when other params have already\n * been bound; SQLite ignores it because placeholders are positional.\n */\nexport function buildSearchWhere<Entity>(\n dialect: ISqlDialect,\n criteria: DeleteSearchCriteria<Entity>,\n schemaProps: Record<string, unknown>,\n convertValue: (column: string, value: Entity[keyof Entity]) => ValueOptionType,\n startIndex: number = 1\n): BuiltWhereClause {\n const conditions: string[] = [];\n const params: ValueOptionType[] = [];\n let paramIndex = startIndex;\n\n for (const column of Object.keys(criteria) as Array<keyof Entity>) {\n if (!(column in schemaProps)) {\n throw new Error(`Schema must have a \"${String(column)}\" field to use it in search criteria`);\n }\n\n const criterion = criteria[column];\n let operator: SearchOperator = \"=\";\n let value: Entity[keyof Entity];\n\n if (isSearchCondition(criterion)) {\n operator = criterion.operator;\n value = criterion.value as Entity[keyof Entity];\n } else {\n value = criterion as Entity[keyof Entity];\n }\n\n conditions.push(\n `${dialect.quoteId(String(column))} ${operator} ${dialect.placeholder(paramIndex)}`\n );\n params.push(convertValue(column as string, value));\n paramIndex++;\n }\n\n return {\n whereClause: conditions.join(\" AND \"),\n params,\n };\n}\n",
32
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ISqlDialect } from \"./Dialect\";\n\n/**\n * Minimal column descriptor used by the prefix-DDL helpers. This mirrors the\n * `PrefixColumn` shape that backends like the job queue and rate limiter use,\n * without forcing `@workglow/storage` to take a dependency on those packages.\n *\n * Backends that have their own `PrefixColumn`-like type pass instances of it\n * directly — the structural match is enough.\n */\nexport interface ISqlPrefixColumn {\n readonly name: string;\n readonly type: \"uuid\" | \"number\";\n}\n\n/**\n * Regex for safe SQL identifiers used in prefix column DDL: must start with a\n * letter and contain only letters, digits, and underscores. Prefix names are\n * spliced into CREATE TABLE / CREATE INDEX statements without parameterisation,\n * so they MUST be validated before use.\n */\nconst SAFE_IDENTIFIER = /^[a-zA-Z][a-zA-Z0-9_]*$/;\n\n/**\n * Common SQL reserved words rejected by {@link assertPrefixesSafe}. Used as\n * unquoted identifiers these would generate hard-to-debug syntax errors,\n * even when they pass the {@link SAFE_IDENTIFIER} regex. The list isn't\n * exhaustive (full SQL has hundreds of reserved words across dialects), but\n * it covers the keywords most likely to be picked as a \"tenant\" / \"user\" /\n * \"group\" prefix name. Comparison is case-insensitive.\n */\nconst SQL_RESERVED_WORDS: ReadonlySet<string> = new Set([\n \"all\",\n \"alter\",\n \"and\",\n \"as\",\n \"asc\",\n \"between\",\n \"by\",\n \"case\",\n \"check\",\n \"column\",\n \"constraint\",\n \"create\",\n \"cross\",\n \"current\",\n \"default\",\n \"delete\",\n \"desc\",\n \"distinct\",\n \"drop\",\n \"else\",\n \"end\",\n \"exists\",\n \"false\",\n \"for\",\n \"foreign\",\n \"from\",\n \"full\",\n \"function\",\n \"grant\",\n \"group\",\n \"having\",\n \"in\",\n \"index\",\n \"inner\",\n \"insert\",\n \"into\",\n \"is\",\n \"join\",\n \"key\",\n \"left\",\n \"like\",\n \"limit\",\n \"natural\",\n \"not\",\n \"null\",\n \"offset\",\n \"on\",\n \"or\",\n \"order\",\n \"outer\",\n \"primary\",\n \"references\",\n \"returning\",\n \"right\",\n \"select\",\n \"set\",\n \"table\",\n \"then\",\n \"true\",\n \"union\",\n \"unique\",\n \"update\",\n \"user\",\n \"using\",\n \"values\",\n \"view\",\n \"when\",\n \"where\",\n \"with\",\n]);\n\n/**\n * Throws if any prefix column name would be unsafe to splice into DDL.\n *\n * Beyond shape-checking via {@link SAFE_IDENTIFIER}, this also rejects names\n * that match a common SQL reserved word ({@link SQL_RESERVED_WORDS}) since\n * they're spliced unquoted and would otherwise produce confusing syntax\n * errors at the database level. Every helper in this module that splices\n * `prefix.name` into SQL calls this first, so storage backends don't need to\n * remember to validate at construction time.\n */\nexport function assertPrefixesSafe(prefixes: readonly ISqlPrefixColumn[]): void {\n for (const p of prefixes) {\n if (!SAFE_IDENTIFIER.test(p.name)) {\n throw new Error(\n `Prefix column name must start with a letter and contain only letters, digits, and underscores, got: ${p.name}`\n );\n }\n if (SQL_RESERVED_WORDS.has(p.name.toLowerCase())) {\n throw new Error(\n `Prefix column name \"${p.name}\" is a reserved SQL keyword. Pick a different identifier (e.g. \"${p.name}_id\").`\n );\n }\n }\n}\n\n/**\n * Throws if any declared prefix column lacks a value in `prefixValues`.\n *\n * Prefix columns are declared `NOT NULL` in the DDL helpers; binding\n * `undefined` for them later yields opaque database errors (\"invalid input\n * syntax for type X\" / \"null value violates not-null constraint\"). Catch the\n * misconfiguration at the call site instead.\n */\nexport function assertPrefixValuesPresent(\n prefixes: readonly ISqlPrefixColumn[],\n prefixValues: Readonly<Record<string, string | number>>\n): void {\n for (const p of prefixes) {\n const v = prefixValues[p.name];\n if (v === undefined || v === null) {\n throw new Error(\n `Missing prefix value for column \"${p.name}\". Every prefix declared in \\`prefixes\\` ` +\n `must have a corresponding entry in \\`prefixValues\\`.`\n );\n }\n }\n}\n\n/** Returns the SQL column type for a {@link ISqlPrefixColumn} on the given dialect. */\nexport function prefixColumnType(dialect: ISqlDialect, type: ISqlPrefixColumn[\"type\"]): string {\n if (type === \"uuid\") {\n return dialect.name === \"postgres\" ? \"UUID\" : \"TEXT\";\n }\n return \"INTEGER\";\n}\n\n/**\n * Builds the prefix portion of a CREATE TABLE column list, e.g.\n * `tenant_id UUID NOT NULL,\\n project_id INTEGER NOT NULL,\\n `\n *\n * Returns an empty string when there are no prefixes so callers can splice it\n * directly into a column list without a trailing comma.\n */\nexport function buildPrefixColumnsSql(\n dialect: ISqlDialect,\n prefixes: readonly ISqlPrefixColumn[]\n): string {\n if (prefixes.length === 0) return \"\";\n assertPrefixesSafe(prefixes);\n return (\n prefixes\n .map((p) => `${p.name} ${prefixColumnType(dialect, p.type)} NOT NULL`)\n .join(\",\\n \") + \",\\n \"\n );\n}\n\n/** Returns prefix column names in declaration order. */\nexport function getPrefixColumnNames(prefixes: readonly ISqlPrefixColumn[]): string[] {\n return prefixes.map((p) => p.name);\n}\n\n/**\n * Comma-joined prefix column list with a trailing `, ` for index/insert use,\n * or an empty string when there are no prefixes.\n */\nexport function getPrefixIndexPrefix(prefixes: readonly ISqlPrefixColumn[]): string {\n if (prefixes.length === 0) return \"\";\n assertPrefixesSafe(prefixes);\n return prefixes.map((p) => p.name).join(\", \") + \", \";\n}\n\n/** Returns the index-name suffix derived from prefix columns. */\nexport function getPrefixIndexSuffix(prefixes: readonly ISqlPrefixColumn[]): string {\n if (prefixes.length === 0) return \"\";\n assertPrefixesSafe(prefixes);\n return \"_\" + prefixes.map((p) => p.name).join(\"_\");\n}\n\n/**\n * Builds an ` AND col1 = ? AND col2 = ?` (or `$N` for Postgres) suffix.\n *\n * @param startParam 1-based starting parameter index (Postgres only — SQLite\n * placeholders are positional and ignore this).\n */\nexport function buildPrefixWhereClause(\n dialect: ISqlDialect,\n prefixes: readonly ISqlPrefixColumn[],\n prefixValues: Readonly<Record<string, string | number>>,\n startParam: number = 1\n): { conditions: string; params: Array<string | number> } {\n if (prefixes.length === 0) return { conditions: \"\", params: [] };\n assertPrefixesSafe(prefixes);\n assertPrefixValuesPresent(prefixes, prefixValues);\n const conditions = prefixes\n .map((p, i) => `${p.name} = ${dialect.placeholder(startParam + i)}`)\n .join(\" AND \");\n const params = prefixes.map((p) => prefixValues[p.name]);\n return { conditions: \" AND \" + conditions, params };\n}\n\n/** Returns prefix values in column order for binding to placeholders. */\nexport function getPrefixParamValues(\n prefixes: readonly ISqlPrefixColumn[],\n prefixValues: Readonly<Record<string, string | number>>\n): Array<string | number> {\n if (prefixes.length === 0) return [];\n assertPrefixValuesPresent(prefixes, prefixValues);\n return prefixes.map((p) => prefixValues[p.name]);\n}\n\n/**\n * Builds the prefix-portion of an INSERT column list and its placeholder list,\n * starting at parameter index `startParam`. Returns empty strings when there\n * are no prefixes so callers can concatenate without a trailing comma.\n */\nexport function buildPrefixInsertFragments(\n dialect: ISqlDialect,\n prefixes: readonly ISqlPrefixColumn[],\n startParam: number = 1\n): { columns: string; placeholders: string } {\n if (prefixes.length === 0) return { columns: \"\", placeholders: \"\" };\n assertPrefixesSafe(prefixes);\n const columns = prefixes.map((p) => p.name).join(\", \") + \", \";\n const placeholders =\n prefixes.map((_, i) => dialect.placeholder(startParam + i)).join(\", \") + \", \";\n return { columns, placeholders };\n}\n",
23
33
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {\n DataPortSchemaObject,\n FromSchema,\n TypedArray,\n TypedArrayConstructor,\n TypedArraySchemaOptions,\n} from \"@workglow/util/schema\";\nimport { cosineSimilarity } from \"@workglow/util/schema\";\nimport { InMemoryTabularStorage } from \"../tabular/InMemoryTabularStorage\";\nimport type { HybridSearchOptions, IVectorStorage, VectorSearchOptions } from \"./IVectorStorage\";\nimport { getMetadataProperty, getVectorProperty } from \"./IVectorStorage\";\n\n/**\n * Check if metadata matches filter\n */\nfunction matchesFilter<Metadata>(metadata: Metadata, filter: Partial<Metadata>): boolean {\n for (const [key, value] of Object.entries(filter)) {\n if (metadata[key as keyof Metadata] !== value) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Simple full-text search scoring (keyword matching)\n */\nfunction textRelevance(text: string, query: string): number {\n const textLower = text.toLowerCase();\n const queryLower = query.toLowerCase();\n const queryWords = queryLower.split(/\\s+/).filter((w) => w.length > 0);\n if (queryWords.length === 0) {\n return 0;\n }\n let matches = 0;\n for (const word of queryWords) {\n if (textLower.includes(word)) {\n matches++;\n }\n }\n return matches / queryWords.length;\n}\n\n/**\n * In-memory document chunk vector repository implementation.\n * Extends InMemoryTabularStorage for storage.\n * Suitable for testing and small-scale browser applications.\n * Supports all vector types including quantized formats.\n *\n * @template Metadata - The metadata type for the document chunk\n * @template VectorCtor - Constructor for stored vectors (default {@link typeof Float32Array})\n */\nexport class InMemoryVectorStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n Metadata extends Record<string, unknown> = Record<string, unknown>,\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n>\n extends InMemoryTabularStorage<Schema, PrimaryKeyNames, Entity>\n implements IVectorStorage<Metadata, Schema, Entity, PrimaryKeyNames>\n{\n private vectorDimensions: number;\n private vectorPropertyName: keyof Entity;\n private metadataPropertyName: keyof Entity | undefined;\n\n /**\n * Creates a new in-memory document chunk vector repository\n * @param schema - The schema definition for the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable\n * @param dimensions - The number of dimensions of the vector\n * @param _vectorCtor - TypedArray constructor (unused, for API compatibility)\n */\n constructor(\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n dimensions: number,\n _vectorCtor: TypedArrayConstructor = Float32Array\n ) {\n super(schema, primaryKeyNames, indexes);\n\n this.vectorDimensions = dimensions;\n\n // Cache vector and metadata property names from schema\n const vectorProp = getVectorProperty(schema);\n if (!vectorProp) {\n throw new Error(\"Schema must have a property with type array and format TypedArray\");\n }\n this.vectorPropertyName = vectorProp as keyof Entity;\n this.metadataPropertyName = getMetadataProperty(schema) as keyof Entity | undefined;\n }\n\n /**\n * Get the vector dimensions\n * @returns The vector dimensions\n */\n getVectorDimensions(): number {\n return this.vectorDimensions;\n }\n\n async similaritySearch(\n query: TypedArray,\n options: VectorSearchOptions<Record<string, unknown>> = {}\n ) {\n const { topK = 10, filter, scoreThreshold = 0 } = options;\n const results: Array<Entity & { score: number }> = [];\n\n const allEntities = (await this.getAll()) || [];\n\n for (const entity of allEntities) {\n const vector = entity[this.vectorPropertyName] as TypedArray;\n const metadata = this.metadataPropertyName\n ? (entity[this.metadataPropertyName] as Metadata)\n : ({} as Metadata);\n\n // Apply filter if provided\n if (filter && !matchesFilter(metadata, filter)) {\n continue;\n }\n\n // Calculate similarity\n const score = cosineSimilarity(query, vector);\n\n // Apply threshold\n if (score < scoreThreshold) {\n continue;\n }\n\n results.push({\n ...entity,\n score,\n } as Entity & { score: number });\n }\n\n // Sort by score descending and take top K\n results.sort((a, b) => b.score - a.score);\n const topResults = results.slice(0, topK);\n\n return topResults;\n }\n\n async hybridSearch(query: TypedArray, options: HybridSearchOptions<Record<string, unknown>>) {\n const { topK = 10, filter, scoreThreshold = 0, textQuery, vectorWeight = 0.7 } = options;\n\n if (!textQuery || textQuery.trim().length === 0) {\n // Fall back to regular vector search if no text query\n return this.similaritySearch(query, { topK, filter, scoreThreshold });\n }\n\n const results: Array<Entity & { score: number }> = [];\n const allEntities = (await this.getAll()) || [];\n\n for (const entity of allEntities) {\n // In memory, vectors are stored as TypedArrays directly (not serialized)\n const vector = entity[this.vectorPropertyName] as TypedArray;\n const metadata = this.metadataPropertyName\n ? (entity[this.metadataPropertyName] as Metadata)\n : ({} as Metadata);\n\n // Apply filter if provided\n if (filter && !matchesFilter(metadata, filter)) {\n continue;\n }\n\n // Calculate vector similarity\n const vectorScore = cosineSimilarity(query, vector);\n\n // Calculate text relevance (simple keyword matching)\n const metadataText = Object.values(metadata ?? {})\n .join(\" \")\n .toLowerCase();\n const textScore = textRelevance(metadataText, textQuery);\n\n // Combine scores\n const combinedScore = vectorWeight * vectorScore + (1 - vectorWeight) * textScore;\n\n // Apply threshold\n if (combinedScore < scoreThreshold) {\n continue;\n }\n\n results.push({\n ...entity,\n score: combinedScore,\n } as Entity & { score: number });\n }\n\n // Sort by combined score descending and take top K\n results.sort((a, b) => b.score - a.score);\n const topResults = results.slice(0, topK);\n\n return topResults;\n }\n}\n",
24
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {\n DataPortSchemaObject,\n FromSchema,\n JsonSchema,\n TypedArray,\n TypedArraySchemaOptions,\n} from \"@workglow/util/schema\";\nimport type { EventParameters } from \"@workglow/util\";\nimport type {\n AutoGeneratedKeys,\n InsertEntity,\n ITabularStorage,\n SimplifyPrimaryKey,\n TabularEventListeners,\n} from \"../tabular/ITabularStorage\";\n\nexport type AnyVectorStorage = Omit<IVectorStorage<any, any, any, any>, \"queryIndex\"> & {\n queryIndex(criteria: any, options: any): Promise<any[]>;\n};\n\n/**\n * Options for vector search operations\n */\nexport interface VectorSearchOptions<\n Metadata extends Record<string, unknown> | undefined = Record<string, unknown>,\n> {\n readonly topK?: number;\n readonly filter?: Partial<Metadata>;\n readonly scoreThreshold?: number;\n}\n\n/**\n * Options for hybrid search (vector + full-text)\n */\nexport interface HybridSearchOptions<\n Metadata extends Record<string, unknown> | undefined = Record<string, unknown>,\n> extends VectorSearchOptions<Metadata> {\n readonly textQuery: string;\n readonly vectorWeight?: number;\n}\n\n/**\n * Type definitions for document chunk vector repository events\n */\nexport interface VectorEventListeners<PrimaryKey, Entity> extends TabularEventListeners<\n PrimaryKey,\n Entity\n> {\n similaritySearch: (query: TypedArray, results: (Entity & { score: number })[]) => void;\n hybridSearch: (query: TypedArray, results: (Entity & { score: number })[]) => void;\n}\n\nexport type VectorEventName = keyof VectorEventListeners<any, any>;\nexport type VectorEventListener<\n Event extends VectorEventName,\n PrimaryKey,\n Entity,\n> = VectorEventListeners<PrimaryKey, Entity>[Event];\n\nexport type VectorEventParameters<\n Event extends VectorEventName,\n PrimaryKey,\n Entity,\n> = EventParameters<VectorEventListeners<PrimaryKey, Entity>, Event>;\n\n/**\n * Interface defining the contract for vector storage repositories.\n * These repositories store vector embeddings with metadata.\n * Extends ITabularStorage to provide standard storage operations,\n * plus vector-specific similarity search capabilities.\n * Supports various vector types including quantized formats.\n *\n * @typeParam Schema - The schema definition for the entity using JSON Schema\n * @typeParam PrimaryKeyNames - Array of property names that form the primary key\n * @typeParam Entity - The entity type\n */\nexport interface IVectorStorage<\n Metadata extends Record<string, unknown> | undefined,\n Schema extends DataPortSchemaObject,\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]> = ReadonlyArray<\n keyof Schema[\"properties\"]\n >,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n InsertType = InsertEntity<Entity, AutoGeneratedKeys<Schema>>,\n> extends ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, InsertType> {\n /**\n * Get the vector dimension\n * @returns The vector dimension\n */\n getVectorDimensions(): number;\n\n /**\n * Search for similar vectors using similarity scoring\n * @param query - Query vector to compare against\n * @param options - Search options (topK, filter, scoreThreshold)\n * @returns Array of search results sorted by similarity (highest first)\n */\n similaritySearch(\n query: TypedArray,\n options?: VectorSearchOptions<Metadata>\n ): Promise<(Entity & { score: number })[]>;\n\n /**\n * Hybrid search combining vector similarity with full-text search\n * This is optional and may not be supported by all implementations\n * @param query - Query vector to compare against\n * @param options - Hybrid search options including text query\n * @returns Array of search results sorted by combined relevance\n */\n hybridSearch?(\n query: TypedArray,\n options: HybridSearchOptions<Metadata>\n ): Promise<(Entity & { score: number })[]>;\n}\n\n/**\n * TODO: Given a schema, return the vector column by searching for a property with a TypedArray format (or TypedArray:xxx format)\n */\n\nexport function getVectorProperty<Schema extends DataPortSchemaObject>(\n schema: Schema\n): keyof Schema[\"properties\"] | undefined {\n for (const [key, value] of Object.entries<JsonSchema>(schema.properties)) {\n if (\n typeof value !== \"boolean\" &&\n value.type === \"array\" &&\n (value.format === \"TypedArray\" || value.format?.startsWith(\"TypedArray:\"))\n ) {\n return key;\n }\n }\n return undefined;\n}\n\n/**\n * Given a schema, return the property which is an object with format \"metadata\"\n */\nexport function getMetadataProperty<Schema extends DataPortSchemaObject>(\n schema: Schema\n): keyof Schema[\"properties\"] | undefined {\n for (const [key, value] of Object.entries<JsonSchema>(schema.properties)) {\n if (typeof value !== \"boolean\" && value.type === \"object\" && value.format === \"metadata\") {\n return key;\n }\n }\n return undefined;\n}\n",
34
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {\n DataPortSchemaObject,\n FromSchema,\n JsonSchema,\n TypedArray,\n TypedArraySchemaOptions,\n} from \"@workglow/util/schema\";\nimport type { EventParameters } from \"@workglow/util\";\nimport type {\n AutoGeneratedKeys,\n InsertEntity,\n ITabularStorage,\n SimplifyPrimaryKey,\n TabularEventListeners,\n} from \"../tabular/ITabularStorage\";\n\nexport type AnyVectorStorage = Omit<\n IVectorStorage<any, any, any, any>,\n \"queryIndex\" | \"withTransaction\"\n> & {\n queryIndex(criteria: any, options: any): Promise<any[]>;\n withTransaction<T>(fn: (tx: any) => Promise<T>): Promise<T>;\n};\n\n/**\n * Distance metric used by vector indexes.\n *\n * - `cosine`: cosine distance (1 - cosine similarity). Default for embedding\n * models that produce normalised vectors. Recommended starting point for\n * text/RAG workloads.\n * - `l2`: euclidean distance. Use when vectors carry magnitude information.\n * - `ip`: negative inner product. Use when embeddings are pre-normalised AND\n * you want a slightly cheaper similarity computation than cosine.\n */\nexport type VectorDistanceMetric = \"cosine\" | \"l2\" | \"ip\";\n\n/**\n * HNSW index parameters. HNSW is the default approximate-nearest-neighbour\n * algorithm in pgvector and other modern vector indexes; tuning these knobs\n * trades index build cost and memory for recall and query latency.\n *\n * Defaults from pgvector when unspecified: `m = 16`, `efConstruction = 64`,\n * `efSearch = 40`.\n *\n * - `m`: number of bi-directional links per layer. Higher = better recall,\n * more memory, slower build. Typical values: 8 (low-recall), 16 (default),\n * 32 (high-recall).\n * - `efConstruction`: candidate-list size during index build. Higher = better\n * recall, much slower build. Typical values: 64 (default), 200, 400.\n * - `efSearch`: candidate-list size at query time. Higher = better recall,\n * slower queries. Typical values: 40 (default), 80, 200. This is the knob\n * you want to tune at query time once the index is built.\n */\nexport interface HnswIndexOptions {\n readonly m?: number;\n readonly efConstruction?: number;\n readonly efSearch?: number;\n}\n\n/**\n * IVFFlat index parameters (pgvector). Offers faster builds than HNSW at the\n * cost of recall, useful for very large corpora.\n *\n * - `lists`: number of inverted lists. pgvector docs recommend `rows / 1000`\n * for up to 1M rows, then `sqrt(rows)` for larger sets.\n * - `probes`: number of lists scanned at query time. Higher = better recall,\n * slower queries. Default `1`.\n */\nexport interface IvfflatIndexOptions {\n readonly lists: number;\n readonly probes?: number;\n}\n\n/**\n * Vector-index tuning options threaded into the storage constructor and\n * applied during {@link IVectorStorage} setup / migration.\n *\n * Only one of `hnsw` or `ivfflat` should be set; if both are absent the\n * backend uses a sensible default (HNSW on pgvector, COSINE-only for\n * sqlite-vec which does not yet support HNSW tuning).\n */\nexport interface VectorIndexOptions {\n readonly distance?: VectorDistanceMetric;\n readonly hnsw?: HnswIndexOptions;\n readonly ivfflat?: IvfflatIndexOptions;\n}\n\n/**\n * Options for vector search operations\n */\nexport interface VectorSearchOptions<\n Metadata extends Record<string, unknown> | undefined = Record<string, unknown>,\n> {\n readonly topK?: number;\n readonly filter?: Partial<Metadata>;\n readonly scoreThreshold?: number;\n}\n\n/**\n * Options for hybrid search (vector + full-text)\n */\nexport interface HybridSearchOptions<\n Metadata extends Record<string, unknown> | undefined = Record<string, unknown>,\n> extends VectorSearchOptions<Metadata> {\n readonly textQuery: string;\n readonly vectorWeight?: number;\n}\n\n/**\n * Type definitions for document chunk vector repository events\n */\nexport interface VectorEventListeners<PrimaryKey, Entity> extends TabularEventListeners<\n PrimaryKey,\n Entity\n> {\n similaritySearch: (query: TypedArray, results: (Entity & { score: number })[]) => void;\n hybridSearch: (query: TypedArray, results: (Entity & { score: number })[]) => void;\n}\n\nexport type VectorEventName = keyof VectorEventListeners<any, any>;\nexport type VectorEventListener<\n Event extends VectorEventName,\n PrimaryKey,\n Entity,\n> = VectorEventListeners<PrimaryKey, Entity>[Event];\n\nexport type VectorEventParameters<\n Event extends VectorEventName,\n PrimaryKey,\n Entity,\n> = EventParameters<VectorEventListeners<PrimaryKey, Entity>, Event>;\n\n/**\n * Interface defining the contract for vector storage repositories.\n * These repositories store vector embeddings with metadata.\n * Extends ITabularStorage to provide standard storage operations,\n * plus vector-specific similarity search capabilities.\n * Supports various vector types including quantized formats.\n *\n * @typeParam Schema - The schema definition for the entity using JSON Schema\n * @typeParam PrimaryKeyNames - Array of property names that form the primary key\n * @typeParam Entity - The entity type\n */\nexport interface IVectorStorage<\n Metadata extends Record<string, unknown> | undefined,\n Schema extends DataPortSchemaObject,\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]> = ReadonlyArray<\n keyof Schema[\"properties\"]\n >,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n InsertType = InsertEntity<Entity, AutoGeneratedKeys<Schema>>,\n> extends ITabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, InsertType> {\n /**\n * Get the vector dimension\n * @returns The vector dimension\n */\n getVectorDimensions(): number;\n\n /**\n * Search for similar vectors using similarity scoring\n * @param query - Query vector to compare against\n * @param options - Search options (topK, filter, scoreThreshold)\n * @returns Array of search results sorted by similarity (highest first)\n */\n similaritySearch(\n query: TypedArray,\n options?: VectorSearchOptions<Metadata>\n ): Promise<(Entity & { score: number })[]>;\n\n /**\n * Hybrid search combining vector similarity with full-text search\n * This is optional and may not be supported by all implementations\n * @param query - Query vector to compare against\n * @param options - Hybrid search options including text query\n * @returns Array of search results sorted by combined relevance\n */\n hybridSearch?(\n query: TypedArray,\n options: HybridSearchOptions<Metadata>\n ): Promise<(Entity & { score: number })[]>;\n}\n\n/**\n * TODO: Given a schema, return the vector column by searching for a property with a TypedArray format (or TypedArray:xxx format)\n */\n\nexport function getVectorProperty<Schema extends DataPortSchemaObject>(\n schema: Schema\n): keyof Schema[\"properties\"] | undefined {\n for (const [key, value] of Object.entries<JsonSchema>(schema.properties)) {\n if (\n typeof value !== \"boolean\" &&\n value.type === \"array\" &&\n (value.format === \"TypedArray\" || value.format?.startsWith(\"TypedArray:\"))\n ) {\n return key;\n }\n }\n return undefined;\n}\n\n/**\n * Given a schema, return the property which is an object with format \"metadata\"\n */\nexport function getMetadataProperty<Schema extends DataPortSchemaObject>(\n schema: Schema\n): keyof Schema[\"properties\"] | undefined {\n for (const [key, value] of Object.entries<JsonSchema>(schema.properties)) {\n if (typeof value !== \"boolean\" && value.type === \"object\" && value.format === \"metadata\") {\n return key;\n }\n }\n return undefined;\n}\n",
25
35
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {\n DataPortSchemaObject,\n FromSchema,\n TypedArray,\n TypedArraySchemaOptions,\n} from \"@workglow/util/schema\";\nimport type {\n AutoGeneratedKeys,\n InsertEntity,\n SimplifyPrimaryKey,\n} from \"../tabular/ITabularStorage\";\nimport { TelemetryTabularStorage } from \"../tabular/TelemetryTabularStorage\";\nimport { traced } from \"@workglow/util\";\nimport type { HybridSearchOptions, IVectorStorage, VectorSearchOptions } from \"./IVectorStorage\";\n\n/**\n * Telemetry wrapper for any IVectorStorage implementation.\n * Extends TelemetryTabularStorage and adds spans for vector-specific operations.\n */\nexport class TelemetryVectorStorage<\n Metadata extends Record<string, unknown> | undefined,\n Schema extends DataPortSchemaObject,\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]> = ReadonlyArray<\n keyof Schema[\"properties\"]\n >,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n InsertType = InsertEntity<Entity, AutoGeneratedKeys<Schema>>,\n>\n extends TelemetryTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, InsertType>\n implements IVectorStorage<Metadata, Schema, Entity, PrimaryKeyNames, PrimaryKey, InsertType>\n{\n private readonly vectorInner: IVectorStorage<\n Metadata,\n Schema,\n Entity,\n PrimaryKeyNames,\n PrimaryKey,\n InsertType\n >;\n\n constructor(\n storageName: string,\n inner: IVectorStorage<Metadata, Schema, Entity, PrimaryKeyNames, PrimaryKey, InsertType>\n ) {\n super(storageName, inner);\n this.vectorInner = inner;\n }\n\n getVectorDimensions(): number {\n return this.vectorInner.getVectorDimensions();\n }\n\n similaritySearch(\n query: TypedArray,\n options?: VectorSearchOptions<Metadata>\n ): Promise<(Entity & { score: number })[]> {\n return traced(\"workglow.storage.vector.similaritySearch\", this.storageName, () =>\n this.vectorInner.similaritySearch(query, options)\n );\n }\n\n hybridSearch(\n query: TypedArray,\n options: HybridSearchOptions<Metadata>\n ): Promise<(Entity & { score: number })[]> {\n if (!this.vectorInner.hybridSearch) {\n throw new Error(\"hybridSearch is not supported by the underlying storage implementation\");\n }\n return traced(\"workglow.storage.vector.hybridSearch\", this.storageName, () =>\n this.vectorInner.hybridSearch!(query, options)\n );\n }\n}\n",
26
36
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { CredentialPutOptions, ICredentialStore } from \"@workglow/util\";\nimport { decrypt, encrypt } from \"@workglow/util\";\nimport type { IKvStorage } from \"../kv/IKvStorage\";\n\n/**\n * Serialized form of a credential stored in the KV backend\n */\ninterface StoredCredential {\n readonly encrypted: string;\n readonly iv: string;\n readonly label: string | undefined;\n readonly provider: string | undefined;\n readonly createdAt: string;\n readonly updatedAt: string;\n readonly expiresAt: string | undefined;\n}\n\n/**\n * Credential store that encrypts values with AES-256-GCM before persisting\n * them to an {@link IKvStorage} backend.\n *\n * Works with any KV backend (SQLite, PostgreSQL, IndexedDB, in-memory, etc.).\n * Uses the Web Crypto API (available in Node 20+, Bun, and browsers).\n *\n * @example\n * ```ts\n * import { SqliteKvStorage } from \"@workglow/storage\";\n *\n * const kv = new SqliteKvStorage(\":memory:\");\n * const store = new EncryptedKvCredentialStore(kv, \"my-encryption-key\");\n *\n * await store.put(\"openai-api-key\", \"sk-...\", { provider: \"openai\" });\n * const key = await store.get(\"openai-api-key\"); // \"sk-...\"\n * ```\n */\nexport class EncryptedKvCredentialStore implements ICredentialStore {\n /** Per-instance cache of derived CryptoKey instances keyed by base64(salt). */\n private readonly keyCache = new Map<string, CryptoKey>();\n\n constructor(\n private readonly kv: IKvStorage<string, StoredCredential>,\n private readonly passphrase: string\n ) {\n if (!passphrase) {\n throw new Error(\"EncryptedKvCredentialStore requires a non-empty passphrase.\");\n }\n }\n\n async get(key: string): Promise<string | undefined> {\n const raw = (await this.kv.get(key)) as StoredCredential | undefined;\n if (!raw) return undefined;\n\n if (raw.expiresAt && new Date(raw.expiresAt) <= new Date()) {\n await this.kv.delete(key);\n return undefined;\n }\n\n return decrypt(raw.encrypted, raw.iv, this.passphrase, this.keyCache);\n }\n\n async put(key: string, value: string, options?: CredentialPutOptions): Promise<void> {\n const now = new Date();\n const existing = (await this.kv.get(key)) as StoredCredential | undefined;\n\n const { encrypted, iv } = await encrypt(value, this.passphrase, this.keyCache);\n\n const stored: StoredCredential = {\n encrypted,\n iv,\n label: options?.label ?? existing?.label,\n provider: options?.provider ?? existing?.provider,\n createdAt: existing?.createdAt ?? now.toISOString(),\n updatedAt: now.toISOString(),\n expiresAt: options?.expiresAt ? options.expiresAt.toISOString() : existing?.expiresAt,\n };\n\n await this.kv.put(key, stored);\n }\n\n async delete(key: string): Promise<boolean> {\n const exists = (await this.kv.get(key)) !== undefined;\n if (exists) {\n await this.kv.delete(key);\n }\n return exists;\n }\n\n async has(key: string): Promise<boolean> {\n const raw = (await this.kv.get(key)) as StoredCredential | undefined;\n if (!raw) return false;\n\n if (raw.expiresAt && new Date(raw.expiresAt) <= new Date()) {\n await this.kv.delete(key);\n return false;\n }\n return true;\n }\n\n async keys(): Promise<readonly string[]> {\n const all = await this.kv.getAll();\n if (!all) return [];\n\n const now = new Date();\n const result: string[] = [];\n for (const entry of all) {\n if (entry.value.expiresAt && new Date(entry.value.expiresAt) <= now) {\n await this.kv.delete(entry.key);\n continue;\n }\n result.push(entry.key);\n }\n return result;\n }\n\n async deleteAll(): Promise<void> {\n await this.kv.deleteAll();\n }\n}\n",
27
37
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { CredentialPutOptions, ICredentialStore } from \"@workglow/util\";\nimport type { IKvStorage } from \"../kv/IKvStorage\";\nimport { EncryptedKvCredentialStore } from \"./EncryptedKvCredentialStore\";\n\n/**\n * An {@link ICredentialStore} wrapper that starts in a locked state and defers\n * construction of the underlying {@link EncryptedKvCredentialStore} until\n * {@link unlock} is called with a passphrase.\n *\n * **Locked behavior** (before {@link unlock}):\n * - `get()` returns `undefined` (falls through in a {@link ChainedCredentialStore})\n * - `has()` returns `false`\n * - `keys()` returns `[]`\n * - `put()` throws an error\n * - `delete()` returns `false`\n * - `deleteAll()` is a no-op\n *\n * **Unlocked behavior**: all methods delegate to the inner\n * {@link EncryptedKvCredentialStore}.\n *\n * @example\n * ```ts\n * const lazy = new LazyEncryptedCredentialStore(kvStorage);\n * await lazy.get(\"key\"); // undefined (locked)\n *\n * lazy.unlock(\"my-passphrase\");\n * await lazy.get(\"key\"); // decrypted value\n *\n * lazy.lock(); // discards inner store\n * await lazy.get(\"key\"); // undefined again\n * ```\n */\nexport class LazyEncryptedCredentialStore implements ICredentialStore {\n private inner: EncryptedKvCredentialStore | undefined;\n\n constructor(private readonly kv: IKvStorage<string, unknown>) {}\n\n /**\n * Whether the store is currently unlocked and able to decrypt credentials.\n */\n get isUnlocked(): boolean {\n return this.inner !== undefined;\n }\n\n /**\n * Unlock the store by providing a passphrase. Creates the underlying\n * {@link EncryptedKvCredentialStore} using the same KV backend.\n *\n * @throws if the passphrase is empty\n */\n unlock(passphrase: string): void {\n this.inner = new EncryptedKvCredentialStore(this.kv as IKvStorage<string, any>, passphrase);\n }\n\n /**\n * Lock the store, discarding the inner {@link EncryptedKvCredentialStore}\n * and its derived key cache.\n */\n lock(): void {\n this.inner = undefined;\n }\n\n async get(key: string): Promise<string | undefined> {\n if (!this.inner) return undefined;\n return this.inner.get(key);\n }\n\n async put(key: string, value: string, options?: CredentialPutOptions): Promise<void> {\n if (!this.inner) {\n throw new Error(\"Credential store is locked. Call unlock() before storing credentials.\");\n }\n return this.inner.put(key, value, options);\n }\n\n async delete(key: string): Promise<boolean> {\n if (!this.inner) return false;\n return this.inner.delete(key);\n }\n\n async has(key: string): Promise<boolean> {\n if (!this.inner) return false;\n return this.inner.has(key);\n }\n\n async keys(): Promise<readonly string[]> {\n if (!this.inner) return [];\n return this.inner.keys();\n }\n\n async deleteAll(): Promise<void> {\n if (!this.inner) return;\n return this.inner.deleteAll();\n }\n}\n",
28
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n createServiceToken,\n deepEqual,\n getLogger,\n makeFingerprint,\n sleep,\n uuid4,\n} from \"@workglow/util\";\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport { mkdir, readdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { PollingSubscriptionManager } from \"../util/PollingSubscriptionManager\";\nimport {\n BaseTabularStorage,\n ClientProvidedKeysOption,\n KeyGenerationStrategy,\n} from \"./BaseTabularStorage\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\nimport { StorageUnsupportedError } from \"./StorageError\";\n\nexport const FS_FOLDER_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.fsFolder\"\n);\n\n/**\n * A tabular repository implementation that uses the filesystem for storage.\n * Each row is stored as a separate JSON file in the specified directory.\n *\n * @template Schema - The schema definition for the entity\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class FsFolderTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType = InsertEntity<Entity, AutoGeneratedKeys<Schema>>,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n private folderPath: string;\n /** Counter for auto-incrementing integer keys */\n private autoIncrementCounter = 0;\n /** Shared polling subscription manager */\n private pollingManager: PollingSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n > | null = null;\n\n /**\n * Creates a new FsFolderTabularStorage instance.\n *\n * @param folderPath - The directory path where the JSON files will be stored\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Note: indexes are not supported in this implementation.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n constructor(\n folderPath: string,\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\"\n ) {\n super(schema, primaryKeyNames, indexes, clientProvidedKeys);\n this.folderPath = path.join(folderPath);\n }\n\n /**\n * Sets up the directory for the repository (creates directory)\n */\n async setupDirectory(): Promise<void> {\n try {\n await mkdir(this.folderPath, { recursive: true });\n } catch (error) {\n // CI system sometimes has issues temporarily\n await new Promise((resolve) => setTimeout(resolve, 0));\n try {\n await mkdir(this.folderPath, { recursive: true });\n } catch {\n // Ignore error if directory already exists\n }\n }\n }\n\n /**\n * Generates a key value for auto-generated keys\n * @param columnName - Name of the column to generate a key for\n * @param strategy - The generation strategy to use\n * @returns The generated key value\n */\n protected override generateKeyValue(\n columnName: string,\n strategy: KeyGenerationStrategy\n ): string | number {\n if (strategy === \"autoincrement\") {\n return ++this.autoIncrementCounter;\n } else {\n return uuid4();\n }\n }\n\n /**\n * Stores a row in the repository\n * @param entity - The entity to store (may be missing auto-generated keys)\n * @returns The stored entity\n * @emits 'put' event when successful\n */\n async put(entity: InsertType): Promise<Entity> {\n let entityToStore = entity as unknown as Entity;\n\n // Handle auto-generated keys\n if (this.hasAutoGeneratedKey() && this.autoGeneratedKeyName) {\n const keyName = this.autoGeneratedKeyName as string;\n const clientProvidedValue = (entity as Record<string, unknown>)[keyName];\n const hasClientValue = clientProvidedValue !== undefined && clientProvidedValue !== null;\n\n let shouldGenerate = false;\n if (this.clientProvidedKeys === \"never\") {\n shouldGenerate = true;\n } else if (this.clientProvidedKeys === \"always\") {\n if (!hasClientValue) {\n throw new Error(\n `Auto-generated key \"${keyName}\" is required when clientProvidedKeys is \"always\"`\n );\n }\n shouldGenerate = false;\n } else {\n // \"if-missing\"\n shouldGenerate = !hasClientValue;\n }\n\n if (shouldGenerate) {\n const generatedValue = this.generateKeyValue(keyName, this.autoGeneratedKeyStrategy!);\n entityToStore = { ...entity, [keyName]: generatedValue } as Entity;\n }\n }\n\n await this.setupDirectory();\n const filePath = await this.getFilePath(entityToStore);\n try {\n await writeFile(filePath, JSON.stringify(entityToStore));\n } catch (error) {\n // CI system sometimes has issues temporarily — retry once\n await sleep(1);\n try {\n await writeFile(filePath, JSON.stringify(entityToStore));\n } catch (retryError) {\n throw new Error(\n `Failed to write file \"${filePath}\" after retry: ${retryError instanceof Error ? retryError.message : String(retryError)}`,\n { cause: retryError }\n );\n }\n }\n this.events.emit(\"put\", entityToStore);\n return entityToStore;\n }\n\n /**\n * Stores multiple rows in the repository in a bulk operation\n * @param entities - Array of entities to store (may be missing auto-generated keys)\n * @returns Array of stored entities\n * @emits 'put' event for each entity stored\n */\n async putBulk(entities: InsertType[]): Promise<Entity[]> {\n await this.setupDirectory();\n return await Promise.all(entities.map(async (entity) => this.put(entity)));\n }\n\n /**\n * Retrieves a value by its key\n * @param key - The primary key object to look up\n * @returns The value object if found, undefined otherwise\n * @emits 'get' event with the fingerprint ID and value when found\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n await this.setupDirectory();\n const filePath = await this.getFilePath(key);\n try {\n const buf = await readFile(filePath);\n const data = buf.toString(\"utf8\");\n const entity = JSON.parse(data) as Entity;\n this.events.emit(\"get\", key, entity);\n return entity;\n } catch (error) {\n this.events.emit(\"get\", key, undefined);\n return undefined; // File not found or read error\n }\n }\n\n /**\n * Deletes an entry by its key\n * @param key - The primary key object of the entry to delete\n * @emits 'delete' event with the fingerprint ID when successful\n */\n async delete(value: PrimaryKey | Entity): Promise<void> {\n await this.setupDirectory();\n const { key } = this.separateKeyValueFromCombined(value as Entity);\n const filePath = await this.getFilePath(key);\n try {\n await rm(filePath);\n } catch (error) {\n console.error(\"Error deleting file\", filePath, error);\n }\n this.events.emit(\"delete\", key as keyof Entity);\n }\n\n /**\n * Retrieves all rows stored in the repository\n * @returns Array of combined objects (rows) if found, undefined otherwise\n */\n async getAll(): Promise<Entity[] | undefined> {\n await this.setupDirectory();\n try {\n const files = await readdir(this.folderPath);\n const jsonFiles = files.filter((file) => file.endsWith(\".json\"));\n if (jsonFiles.length === 0) {\n return undefined;\n }\n const results = await Promise.allSettled(\n jsonFiles.map(async (file) => {\n const buf = await readFile(path.join(this.folderPath, file));\n const content = buf.toString(\"utf8\");\n const data = JSON.parse(content) as Entity;\n return data;\n })\n );\n\n const values = results\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value);\n\n return values.length > 0 ? values : undefined;\n } catch (error) {\n console.error(\"Error in getAll:\", error);\n throw error;\n }\n }\n\n /**\n * Removes all entries from the repository\n * @emits 'clearall' event when successful\n */\n async deleteAll(): Promise<void> {\n await this.setupDirectory();\n // Delete all files in the folder\n try {\n await rm(this.folderPath, { recursive: true, force: true });\n } catch (error) {\n console.error(\"Error deleting folder\", this.folderPath, error);\n await rm(this.folderPath, { recursive: true, force: true });\n }\n this.events.emit(\"clearall\");\n }\n\n /**\n * Returns the total number of stored rows\n * @returns Promise resolving to the count of stored items\n */\n async size(): Promise<number> {\n await this.setupDirectory();\n // Count all files in the folder ending in .json\n const files = await readdir(this.folderPath);\n const jsonFiles = files.filter((file) => file.endsWith(\".json\"));\n return jsonFiles.length;\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n await this.setupDirectory();\n const files = await readdir(this.folderPath);\n const jsonFiles = files.filter((file) => file.endsWith(\".json\"));\n\n if (jsonFiles.length === 0) {\n return undefined;\n }\n\n // Read all files in parallel, skipping corrupted files\n const results = await Promise.allSettled(\n jsonFiles.map(async (file) => {\n const filePath = path.join(this.folderPath, file);\n try {\n const content = await readFile(filePath, \"utf8\");\n return JSON.parse(content) as Entity;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to read or parse \"${filePath}\": ${message}`);\n }\n })\n );\n const allEntities: Entity[] = [];\n for (const result of results) {\n if (result.status === \"fulfilled\") {\n allEntities.push(result.value);\n } else {\n getLogger().warn(\n `Skipping corrupted file in getBulk: ${result.reason?.message ?? result.reason}`\n );\n }\n }\n\n // Sort by primary key to ensure deterministic ordering\n // TODO: rethink this, it's not efficient to sort all entities every time\n allEntities.sort((a, b) => {\n for (const key of this.primaryKeyNames) {\n const aVal = (a as Record<string, string | number>)[key as string];\n const bVal = (b as Record<string, string | number>)[key as string];\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n }\n return 0;\n });\n\n // Slice the sorted array to get the page\n const page = allEntities.slice(offset, offset + limit);\n return page.length > 0 ? page : undefined;\n }\n\n /**\n * Generates the full filesystem path for a given key.\n * @private\n */\n private async getFilePath(value: PrimaryKey | Entity): Promise<string> {\n const { key } = this.separateKeyValueFromCombined(value as Entity);\n const filename = await this.getKeyAsIdString(key);\n const fullPath = path.join(this.folderPath, `${filename}.json`);\n return fullPath;\n }\n\n /**\n * Query is not supported for filesystem repository.\n * @throws StorageUnsupportedError always\n */\n async query(\n _criteria: SearchCriteria<Entity>,\n _options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n throw new StorageUnsupportedError(\"query\", \"FsFolderTabularStorage\");\n }\n\n /**\n * queryIndex is not supported for filesystem repository.\n * @throws StorageUnsupportedError always\n */\n override async queryIndex<K extends keyof Entity & string>(\n _criteria: SearchCriteria<Entity>,\n _options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n throw new StorageUnsupportedError(\"queryIndex\", \"FsFolderTabularStorage\");\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Not supported for filesystem repository.\n *\n * @param _criteria - Object with column names as keys and values or SearchConditions\n * @throws Error always - deleteSearch is not supported for filesystem storage\n */\n async deleteSearch(_criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n throw new Error(\"deleteSearch is not supported for FsFolderTabularStorage\");\n }\n\n /**\n * Gets or creates the shared polling subscription manager.\n * This ensures all subscriptions share a single polling loop per interval.\n */\n private getPollingManager(): PollingSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n > {\n if (!this.pollingManager) {\n this.pollingManager = new PollingSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n >(\n async () => {\n // Fetch all entities and create a map keyed by entity fingerprint\n const entities = (await this.getAll()) || [];\n const map = new Map<string, Entity>();\n for (const entity of entities) {\n const { key } = this.separateKeyValueFromCombined(entity);\n const fingerprint = await makeFingerprint(key);\n map.set(fingerprint, entity);\n }\n return map;\n },\n (a, b) => deepEqual(a, b),\n {\n insert: (item) => ({ type: \"INSERT\" as const, new: item }),\n update: (oldItem, newItem) => ({ type: \"UPDATE\" as const, old: oldItem, new: newItem }),\n delete: (item) => ({ type: \"DELETE\" as const, old: item }),\n }\n );\n }\n return this.pollingManager;\n }\n\n /**\n * Subscribes to changes in the repository.\n * Uses polling since filesystem has no native change notification support.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options including polling interval\n * @returns Unsubscribe function\n */\n override subscribeToChanges(\n callback: (change: TabularChangePayload<Entity>) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n // Note: We don't await setupDirectory() here to keep the method synchronous\n // The getAll() method in the polling manager will call setupDirectory() when needed\n const intervalMs = options?.pollingIntervalMs ?? 1000;\n const manager = this.getPollingManager();\n return manager.subscribe(callback, { intervalMs });\n }\n\n /**\n * Destroys the repository and frees up resources.\n */\n override destroy(): void {\n if (this.pollingManager) {\n this.pollingManager.destroy();\n this.pollingManager = null;\n }\n super.destroy();\n }\n}\n",
38
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport {\n createServiceToken,\n deepEqual,\n getLogger,\n makeFingerprint,\n sleep,\n uuid4,\n} from \"@workglow/util\";\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport { mkdir, readdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { type ITabularMigration, type ITabularMigrationApplier } from \"../migrations\";\nimport { InMemoryTabularMigrationApplier } from \"./InMemoryTabularMigrationApplier\";\nimport path from \"node:path\";\nimport { PollingSubscriptionManager } from \"../util/PollingSubscriptionManager\";\nimport {\n BaseTabularStorage,\n ClientProvidedKeysOption,\n KeyGenerationStrategy,\n} from \"./BaseTabularStorage\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularChangePayload,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\nimport { StorageUnsupportedError } from \"./StorageError\";\n\nexport const FS_FOLDER_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.fsFolder\"\n);\n\nclass FsFolderMigrationApplier extends InMemoryTabularMigrationApplier {\n private loaded = false;\n constructor(\n storage: AnyTabularStorage,\n private readonly folderPath: string\n ) {\n super(storage, \"fsfolder\");\n }\n override async ensureBookkeeping(): Promise<void> {\n await this.load();\n }\n override async appliedVersions(component: string): Promise<Set<number>> {\n await this.load();\n return new Set(this.applied.get(component) ?? []);\n }\n private async load(): Promise<void> {\n if (this.loaded) return;\n const file = `${this.folderPath}/_storage_migrations.json`;\n try {\n const text = await readFile(file, \"utf8\");\n const parsed = JSON.parse(text) as Record<string, number[]>;\n for (const [c, vs] of Object.entries(parsed)) {\n this.applied.set(c, new Set(vs));\n }\n } catch (err: unknown) {\n if ((err as { code?: string })?.code !== \"ENOENT\") throw err;\n }\n this.loaded = true;\n }\n protected override async persist(): Promise<void> {\n const file = `${this.folderPath}/_storage_migrations.json`;\n const out: Record<string, number[]> = {};\n for (const [c, vs] of this.applied) out[c] = [...vs].sort((a, b) => a - b);\n await writeFile(file, JSON.stringify(out, null, 2));\n }\n}\n\n/**\n * A tabular repository implementation that uses the filesystem for storage.\n * Each row is stored as a separate JSON file in the specified directory.\n *\n * @template Schema - The schema definition for the entity\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class FsFolderTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType = InsertEntity<Entity, AutoGeneratedKeys<Schema>>,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n private folderPath: string;\n /** Counter for auto-incrementing integer keys */\n private autoIncrementCounter = 0;\n /** Shared polling subscription manager */\n private pollingManager: PollingSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n > | null = null;\n\n /**\n * Creates a new FsFolderTabularStorage instance.\n *\n * @param folderPath - The directory path where the JSON files will be stored\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Note: indexes are not supported in this implementation.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n * @param tabularMigrations - Optional declarative migrations to run on setup\n */\n constructor(\n folderPath: string,\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\",\n tabularMigrations?: ReadonlyArray<ITabularMigration>\n ) {\n super(schema, primaryKeyNames, indexes, clientProvidedKeys, tabularMigrations, \"fsfolder\");\n this.folderPath = path.join(folderPath);\n }\n\n /**\n * Sets up the directory for the repository (creates directory)\n */\n async setupDirectory(): Promise<void> {\n try {\n await mkdir(this.folderPath, { recursive: true });\n } catch (error) {\n // CI system sometimes has issues temporarily\n await new Promise((resolve) => setTimeout(resolve, 0));\n try {\n await mkdir(this.folderPath, { recursive: true });\n } catch {\n // Ignore error if directory already exists\n }\n }\n }\n\n override async setupDatabase(): Promise<void> {\n await this.setupDirectory();\n if (this.tabularMigrations && this.tabularMigrations.length > 0) {\n await this.applyTabularMigrations();\n }\n }\n\n public override getMigrationApplier(): ITabularMigrationApplier | null {\n return new FsFolderMigrationApplier(this as unknown as AnyTabularStorage, this.folderPath);\n }\n\n /**\n * Generates a key value for auto-generated keys\n * @param columnName - Name of the column to generate a key for\n * @param strategy - The generation strategy to use\n * @returns The generated key value\n */\n protected override generateKeyValue(\n columnName: string,\n strategy: KeyGenerationStrategy\n ): string | number {\n if (strategy === \"autoincrement\") {\n return ++this.autoIncrementCounter;\n } else {\n return uuid4();\n }\n }\n\n /**\n * Stores a row in the repository\n * @param entity - The entity to store (may be missing auto-generated keys)\n * @returns The stored entity\n * @emits 'put' event when successful\n */\n async put(entity: InsertType): Promise<Entity> {\n let entityToStore = entity as unknown as Entity;\n\n // Handle auto-generated keys\n if (this.hasAutoGeneratedKey() && this.autoGeneratedKeyName) {\n const keyName = this.autoGeneratedKeyName as string;\n const clientProvidedValue = (entity as Record<string, unknown>)[keyName];\n const hasClientValue = clientProvidedValue !== undefined && clientProvidedValue !== null;\n\n let shouldGenerate = false;\n if (this.clientProvidedKeys === \"never\") {\n shouldGenerate = true;\n } else if (this.clientProvidedKeys === \"always\") {\n if (!hasClientValue) {\n throw new Error(\n `Auto-generated key \"${keyName}\" is required when clientProvidedKeys is \"always\"`\n );\n }\n shouldGenerate = false;\n } else {\n // \"if-missing\"\n shouldGenerate = !hasClientValue;\n }\n\n if (shouldGenerate) {\n const generatedValue = this.generateKeyValue(keyName, this.autoGeneratedKeyStrategy!);\n entityToStore = { ...entity, [keyName]: generatedValue } as Entity;\n }\n }\n\n await this.setupDirectory();\n const filePath = await this.getFilePath(entityToStore);\n try {\n await writeFile(filePath, JSON.stringify(entityToStore));\n } catch (error) {\n // CI system sometimes has issues temporarily — retry once\n await sleep(1);\n try {\n await writeFile(filePath, JSON.stringify(entityToStore));\n } catch (retryError) {\n throw new Error(\n `Failed to write file \"${filePath}\" after retry: ${retryError instanceof Error ? retryError.message : String(retryError)}`,\n { cause: retryError }\n );\n }\n }\n this.events.emit(\"put\", entityToStore);\n return entityToStore;\n }\n\n /**\n * Stores multiple rows in the repository in a bulk operation\n * @param entities - Array of entities to store (may be missing auto-generated keys)\n * @returns Array of stored entities\n * @emits 'put' event for each entity stored\n */\n async putBulk(entities: InsertType[]): Promise<Entity[]> {\n await this.setupDirectory();\n return await Promise.all(entities.map(async (entity) => this.put(entity)));\n }\n\n /**\n * Retrieves a value by its key\n * @param key - The primary key object to look up\n * @returns The value object if found, undefined otherwise\n * @emits 'get' event with the fingerprint ID and value when found\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n await this.setupDirectory();\n const filePath = await this.getFilePath(key);\n try {\n const buf = await readFile(filePath);\n const data = buf.toString(\"utf8\");\n const entity = JSON.parse(data) as Entity;\n this.events.emit(\"get\", key, entity);\n return entity;\n } catch (error) {\n this.events.emit(\"get\", key, undefined);\n return undefined; // File not found or read error\n }\n }\n\n /**\n * Deletes an entry by its key\n * @param key - The primary key object of the entry to delete\n * @emits 'delete' event with the fingerprint ID when successful\n */\n async delete(value: PrimaryKey | Entity): Promise<void> {\n await this.setupDirectory();\n const { key } = this.separateKeyValueFromCombined(value as Entity);\n const filePath = await this.getFilePath(key);\n try {\n await rm(filePath);\n } catch (error) {\n console.error(\"Error deleting file\", filePath, error);\n }\n this.events.emit(\"delete\", key as keyof Entity);\n }\n\n /**\n * Retrieves all rows stored in the repository\n * @returns Array of combined objects (rows) if found, undefined otherwise\n */\n async getAll(): Promise<Entity[] | undefined> {\n await this.setupDirectory();\n try {\n const files = await readdir(this.folderPath);\n // Exclude internal bookkeeping files (prefixed with \"_\").\n const jsonFiles = files.filter((file) => file.endsWith(\".json\") && !file.startsWith(\"_\"));\n if (jsonFiles.length === 0) {\n return undefined;\n }\n const results = await Promise.allSettled(\n jsonFiles.map(async (file) => {\n const buf = await readFile(path.join(this.folderPath, file));\n const content = buf.toString(\"utf8\");\n const data = JSON.parse(content) as Entity;\n return data;\n })\n );\n\n const values = results\n .filter((result) => result.status === \"fulfilled\")\n .map((result) => result.value);\n\n return values.length > 0 ? values : undefined;\n } catch (error) {\n console.error(\"Error in getAll:\", error);\n throw error;\n }\n }\n\n /**\n * Removes all entries from the repository\n * @emits 'clearall' event when successful\n */\n async deleteAll(): Promise<void> {\n await this.setupDirectory();\n // Delete all files in the folder\n try {\n await rm(this.folderPath, { recursive: true, force: true });\n } catch (error) {\n console.error(\"Error deleting folder\", this.folderPath, error);\n await rm(this.folderPath, { recursive: true, force: true });\n }\n this.events.emit(\"clearall\");\n }\n\n /**\n * Returns the total number of stored rows\n * @returns Promise resolving to the count of stored items\n */\n async size(): Promise<number> {\n await this.setupDirectory();\n // Count data files only; exclude internal bookkeeping files (prefixed with \"_\").\n const files = await readdir(this.folderPath);\n const jsonFiles = files.filter((file) => file.endsWith(\".json\") && !file.startsWith(\"_\"));\n return jsonFiles.length;\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n await this.setupDirectory();\n const files = await readdir(this.folderPath);\n // Exclude internal bookkeeping files (prefixed with \"_\").\n const jsonFiles = files.filter((file) => file.endsWith(\".json\") && !file.startsWith(\"_\"));\n\n if (jsonFiles.length === 0) {\n return undefined;\n }\n\n // Read all files in parallel, skipping corrupted files\n const results = await Promise.allSettled(\n jsonFiles.map(async (file) => {\n const filePath = path.join(this.folderPath, file);\n try {\n const content = await readFile(filePath, \"utf8\");\n return JSON.parse(content) as Entity;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to read or parse \"${filePath}\": ${message}`);\n }\n })\n );\n const allEntities: Entity[] = [];\n for (const result of results) {\n if (result.status === \"fulfilled\") {\n allEntities.push(result.value);\n } else {\n getLogger().warn(\n `Skipping corrupted file in getBulk: ${result.reason?.message ?? result.reason}`\n );\n }\n }\n\n // Sort by primary key to ensure deterministic ordering\n // TODO: rethink this, it's not efficient to sort all entities every time\n allEntities.sort((a, b) => {\n for (const key of this.primaryKeyNames) {\n const aVal = (a as Record<string, string | number>)[key as string];\n const bVal = (b as Record<string, string | number>)[key as string];\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n }\n return 0;\n });\n\n // Slice the sorted array to get the page\n const page = allEntities.slice(offset, offset + limit);\n return page.length > 0 ? page : undefined;\n }\n\n /**\n * Generates the full filesystem path for a given key.\n * @private\n */\n private async getFilePath(value: PrimaryKey | Entity): Promise<string> {\n const { key } = this.separateKeyValueFromCombined(value as Entity);\n const filename = await this.getKeyAsIdString(key);\n const fullPath = path.join(this.folderPath, `${filename}.json`);\n return fullPath;\n }\n\n /**\n * Query is not supported for filesystem repository.\n * @throws StorageUnsupportedError always\n */\n async query(\n _criteria: SearchCriteria<Entity>,\n _options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n throw new StorageUnsupportedError(\"query\", \"FsFolderTabularStorage\");\n }\n\n /**\n * queryIndex is not supported for filesystem repository.\n * @throws StorageUnsupportedError always\n */\n override async queryIndex<K extends keyof Entity & string>(\n _criteria: SearchCriteria<Entity>,\n _options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n throw new StorageUnsupportedError(\"queryIndex\", \"FsFolderTabularStorage\");\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Not supported for filesystem repository.\n *\n * @param _criteria - Object with column names as keys and values or SearchConditions\n * @throws Error always - deleteSearch is not supported for filesystem storage\n */\n async deleteSearch(_criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n throw new Error(\"deleteSearch is not supported for FsFolderTabularStorage\");\n }\n\n /**\n * Gets or creates the shared polling subscription manager.\n * This ensures all subscriptions share a single polling loop per interval.\n */\n private getPollingManager(): PollingSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n > {\n if (!this.pollingManager) {\n this.pollingManager = new PollingSubscriptionManager<\n Entity,\n string,\n TabularChangePayload<Entity>\n >(\n async () => {\n // Fetch all entities and create a map keyed by entity fingerprint\n const entities = (await this.getAll()) || [];\n const map = new Map<string, Entity>();\n for (const entity of entities) {\n const { key } = this.separateKeyValueFromCombined(entity);\n const fingerprint = await makeFingerprint(key);\n map.set(fingerprint, entity);\n }\n return map;\n },\n (a, b) => deepEqual(a, b),\n {\n insert: (item) => ({ type: \"INSERT\" as const, new: item }),\n update: (oldItem, newItem) => ({ type: \"UPDATE\" as const, old: oldItem, new: newItem }),\n delete: (item) => ({ type: \"DELETE\" as const, old: item }),\n }\n );\n }\n return this.pollingManager;\n }\n\n /**\n * Subscribes to changes in the repository.\n * Uses polling since filesystem has no native change notification support.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options including polling interval\n * @returns Unsubscribe function\n */\n override subscribeToChanges(\n callback: (change: TabularChangePayload<Entity>) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n // Note: We don't await setupDirectory() here to keep the method synchronous\n // The getAll() method in the polling manager will call setupDirectory() when needed\n const intervalMs = options?.pollingIntervalMs ?? 1000;\n const manager = this.getPollingManager();\n return manager.subscribe(callback, { intervalMs });\n }\n\n /**\n * Destroys the repository and frees up resources.\n */\n override destroy(): void {\n if (this.pollingManager) {\n this.pollingManager.destroy();\n this.pollingManager = null;\n }\n super.destroy();\n }\n}\n",
29
39
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { JsonSchema } from \"@workglow/util/schema\";\nimport { createServiceToken } from \"@workglow/util\";\nimport { FsFolderTabularStorage } from \"../tabular/FsFolderTabularStorage\";\nimport { DefaultKeyValueKey, DefaultKeyValueSchema, IKvStorage } from \"./IKvStorage\";\nimport { KvViaTabularStorage } from \"./KvViaTabularStorage\";\n\nexport const FS_FOLDER_JSON_KV_REPOSITORY = createServiceToken<IKvStorage<string, any, any>>(\n \"storage.kvRepository.fsFolderJson\"\n);\n\n/**\n * A key-value repository implementation that stores values as JSON files in a specified folder.\n * Uses a tabular repository abstraction for file-based persistence.\n *\n * @template Key - The type of the primary key\n * @template Value - The type of the value being stored\n * @template Combined - Combined type of Key & Value\n */\nexport class FsFolderJsonKvStorage extends KvViaTabularStorage {\n public tabularRepository: FsFolderTabularStorage<\n typeof DefaultKeyValueSchema,\n typeof DefaultKeyValueKey\n >;\n\n /**\n * Creates a new KvStorage instance\n */\n constructor(\n public folderPath: string,\n keySchema: JsonSchema = { type: \"string\" },\n valueSchema: JsonSchema = {}\n ) {\n super(keySchema, valueSchema);\n this.tabularRepository = new FsFolderTabularStorage(\n folderPath,\n DefaultKeyValueSchema,\n DefaultKeyValueKey\n );\n }\n}\n",
30
40
  "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { JsonSchema } from \"@workglow/util/schema\";\nimport { createServiceToken } from \"@workglow/util\";\nimport { mkdir, readFile, rm, unlink, writeFile } from \"fs/promises\";\nimport path from \"path\";\nimport { IKvStorage } from \"./IKvStorage\";\nimport { KvStorage } from \"./KvStorage\";\n\nexport const FS_FOLDER_KV_REPOSITORY = createServiceToken<IKvStorage<string, any, any>>(\n \"storage.kvRepository.fsFolder\"\n);\n\n/**\n * A key-value repository implementation that stores each value as a file in a specified folder.\n * Uses the file system for persistence, with each key mapped to a file path.\n *\n * @template Key - The type of the primary key\n * @template Value - The type of the value being stored\n * @template Combined - Combined type of Key & Value\n */\nexport class FsFolderKvStorage<\n Key extends string = string,\n Value extends any = any,\n Combined = { key: Key; value: Value },\n> extends KvStorage<Key, Value, Combined> {\n /**\n * Creates a new KvStorage instance\n */\n constructor(\n public folderPath: string,\n public pathWriter: (key: Key) => string,\n keySchema: JsonSchema = { type: \"string\" },\n valueSchema: JsonSchema = { contentEncoding: \"blob\" }\n ) {\n super(keySchema, valueSchema);\n }\n\n /**\n * Sets up the directory for the repository (creates directory)\n */\n private async setupDirectory(): Promise<void> {\n try {\n await mkdir(this.folderPath, { recursive: true });\n } catch (error) {\n // CI system sometimes has issues temporarily\n await new Promise((resolve) => setTimeout(resolve, 0));\n try {\n await mkdir(this.folderPath, { recursive: true });\n } catch {\n // Ignore error if directory already exists\n }\n }\n }\n\n /**\n * Stores a row in the repository.\n * @param key - The primary key\n * @param value - The value to store\n */\n public async put(key: Key, value: Value): Promise<void> {\n const localPath = path.join(this.folderPath, this.pathWriter(key).replaceAll(\"..\", \"_\"));\n\n let content: string;\n const schemaType =\n typeof this.valueSchema === \"object\" &&\n this.valueSchema !== null &&\n \"type\" in this.valueSchema\n ? this.valueSchema.type\n : undefined;\n if (value === null) {\n content = \"\";\n } else if (schemaType === \"object\") {\n content = JSON.stringify(value);\n } else if (typeof value === \"object\") {\n // Handle 'json' type schema from tests\n content = JSON.stringify(value);\n } else {\n content = String(value);\n }\n\n await mkdir(path.dirname(localPath), { recursive: true });\n await writeFile(localPath, content);\n }\n\n /**\n * Stores multiple rows in the repository in a bulk operation.\n * @param items - Array of key-value pairs to store\n */\n public async putBulk(items: Array<{ key: Key; value: Value }>): Promise<void> {\n await this.setupDirectory();\n await Promise.all(items.map(async ({ key, value }) => this.put(key, value)));\n }\n\n /**\n * Retrieves a value by its key.\n * This is a convenience method that automatically converts simple types to structured format if using default schema.\n *\n * @param key - Primary key to look up (basic key like default schema)\n * @returns The stored value or undefined if not found\n */\n public async get(key: Key): Promise<Value | undefined> {\n const localPath = path.join(this.folderPath, this.pathWriter(key).replaceAll(\"..\", \"_\"));\n const typeDef = this.valueSchema;\n try {\n const encoding =\n typeof typeDef === \"object\" &&\n typeDef !== null &&\n \"contentEncoding\" in typeDef &&\n typeDef.contentEncoding === \"blob\"\n ? \"binary\"\n : \"utf-8\";\n const content = (await readFile(localPath, { encoding })).toString().trim();\n\n if (encoding === \"utf-8\") {\n const schemaType =\n typeof typeDef === \"object\" && typeDef !== null && \"type\" in typeDef\n ? typeDef.type\n : undefined;\n if (\n schemaType === \"object\" ||\n (content.startsWith(\"{\") && content.endsWith(\"}\")) ||\n (content.startsWith(\"[\") && content.endsWith(\"]\"))\n ) {\n try {\n return JSON.parse(content) as Value;\n } catch (e) {\n // If JSON parsing fails, return as string\n return content as unknown as Value;\n }\n }\n }\n\n return content as unknown as Value;\n } catch (error) {\n return undefined;\n }\n }\n\n /**\n * Deletes a row from the repository.\n * @param key - The primary key of the row to delete\n */\n public async delete(key: Key): Promise<void> {\n const localPath = path.join(this.folderPath, this.pathWriter(key).replaceAll(\"..\", \"_\"));\n await unlink(localPath);\n }\n\n /**\n * Retrieves all rows from the repository.\n * @returns An array of all rows in the repository or undefined if empty\n */\n public async getAll(): Promise<Combined[] | undefined> {\n throw new Error(\"Not implemented\");\n }\n\n /**\n * Deletes all rows from the repository.\n */\n public async deleteAll(): Promise<void> {\n const localPath = path.join(this.folderPath);\n await rm(localPath, { recursive: true });\n }\n\n /**\n * Retrieves the number of rows in the repository.\n * @returns The number of rows in the repository\n */\n public async size(): Promise<number> {\n throw new Error(\"Not implemented\");\n }\n}\n",
31
- "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport { createServiceToken } from \"@workglow/util\";\nimport { BaseTabularStorage, ClientProvidedKeysOption } from \"./BaseTabularStorage\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\nimport { InMemoryTabularStorage } from \"./InMemoryTabularStorage\";\n\nexport const SHARED_IN_MEMORY_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.sharedInMemory\"\n);\n\nconst SYNC_TIMEOUT = 1000;\nconst MAX_PENDING_MESSAGES = 1000;\n\n/**\n * Message types for BroadcastChannel communication\n */\ntype BroadcastMessage =\n | { type: \"SYNC_REQUEST\" }\n | { type: \"SYNC_RESPONSE\"; data: any[] }\n | { type: \"PUT\"; entity: any }\n | { type: \"PUT_BULK\"; entities: any[] }\n | { type: \"DELETE\"; key: any }\n | { type: \"DELETE_ALL\" }\n | { type: \"DELETE_SEARCH\"; criteria: DeleteSearchCriteria<any> };\n\n/**\n * A tabular repository implementation that shares data across browser tabs/windows\n * using BroadcastChannel API. Uses InMemoryTabularStorage internally and\n * synchronizes changes across all instances.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class SharedInMemoryTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n private channel: BroadcastChannel | null = null;\n private channelName: string;\n private inMemoryRepo: InMemoryTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>;\n private isInitialized = false;\n private syncInProgress = false;\n private pendingMessages: BroadcastMessage[] = [];\n\n /**\n * Creates a new SharedInMemoryTabularStorage instance\n * @param channelName - Unique name for the BroadcastChannel (defaults to \"tabular_store\")\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n */\n constructor(\n channelName: string = \"tabular_store\",\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\"\n ) {\n super(schema, primaryKeyNames, indexes, clientProvidedKeys);\n this.channelName = channelName;\n this.inMemoryRepo = new InMemoryTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>(\n schema,\n primaryKeyNames,\n indexes,\n clientProvidedKeys\n );\n\n this.setupEventForwarding();\n\n this.initializeBroadcastChannel();\n }\n\n /**\n * Checks if BroadcastChannel is available in the current environment\n */\n private isBroadcastChannelAvailable(): boolean {\n return typeof BroadcastChannel !== \"undefined\";\n }\n\n /**\n * Initializes the BroadcastChannel and sets up message handlers\n */\n private initializeBroadcastChannel(): void {\n if (!this.isBroadcastChannelAvailable()) {\n console.warn(\"BroadcastChannel is not available. Tab synchronization will not work.\");\n return;\n }\n\n try {\n this.channel = new BroadcastChannel(this.channelName);\n this.channel.onmessage = (event: MessageEvent<BroadcastMessage>) => {\n this.handleBroadcastMessage(event.data);\n };\n\n // Request sync from other tabs on initialization\n this.syncFromOtherTabs();\n } catch (error) {\n console.error(\"Failed to initialize BroadcastChannel:\", error);\n }\n }\n\n /**\n * Sets up event forwarding from the internal InMemoryTabularStorage\n */\n private setupEventForwarding(): void {\n this.inMemoryRepo.on(\"put\", (entity) => {\n this.events.emit(\"put\", entity);\n });\n this.inMemoryRepo.on(\"get\", (key, entity) => {\n this.events.emit(\"get\", key, entity);\n });\n this.inMemoryRepo.on(\"query\", (key, entities) => {\n this.events.emit(\"query\", key, entities);\n });\n this.inMemoryRepo.on(\"delete\", (key) => {\n this.events.emit(\"delete\", key);\n });\n this.inMemoryRepo.on(\"clearall\", () => {\n this.events.emit(\"clearall\");\n });\n }\n\n /**\n * Handles incoming BroadcastChannel messages\n */\n private async handleBroadcastMessage(message: BroadcastMessage): Promise<void> {\n if (this.syncInProgress && message.type !== \"SYNC_RESPONSE\") {\n // Queue messages during sync to replay after sync completes\n if (this.pendingMessages.length < MAX_PENDING_MESSAGES) {\n this.pendingMessages.push(message);\n }\n return;\n }\n\n switch (message.type) {\n case \"SYNC_REQUEST\":\n // Respond to sync request with current data\n const all = await this.inMemoryRepo.getAll();\n if (this.channel && all) {\n this.channel.postMessage({\n type: \"SYNC_RESPONSE\",\n data: all,\n } as BroadcastMessage);\n }\n break;\n\n case \"SYNC_RESPONSE\":\n // Copy data from the responding tab\n if (message.data && Array.isArray(message.data)) {\n await this.copyDataFromArray(message.data);\n }\n this.syncInProgress = false;\n await this.drainPendingMessages();\n break;\n\n case \"PUT\":\n // Apply put from another tab\n await this.inMemoryRepo.put(message.entity);\n break;\n\n case \"PUT_BULK\":\n // Apply bulk put from another tab\n await this.inMemoryRepo.putBulk(message.entities);\n break;\n\n case \"DELETE\":\n // Apply delete from another tab\n await this.inMemoryRepo.delete(message.key);\n break;\n\n case \"DELETE_ALL\":\n // Apply deleteAll from another tab\n await this.inMemoryRepo.deleteAll();\n break;\n\n case \"DELETE_SEARCH\":\n // Apply deleteSearch from another tab\n await this.inMemoryRepo.deleteSearch(message.criteria as DeleteSearchCriteria<Entity>);\n break;\n }\n }\n\n /**\n * Drains pending messages that were queued during sync.\n * Uses a while loop to handle messages that may be re-queued during draining\n * (e.g., if a replayed message triggers a new sync cycle).\n */\n private async drainPendingMessages(): Promise<void> {\n while (!this.syncInProgress && this.pendingMessages.length > 0) {\n const messages = this.pendingMessages;\n this.pendingMessages = [];\n for (const message of messages) {\n await this.handleBroadcastMessage(message);\n if (this.syncInProgress) {\n break;\n }\n }\n }\n }\n\n /**\n * Requests synchronization from other tabs\n */\n private syncFromOtherTabs(): void {\n if (!this.channel) return;\n\n this.syncInProgress = true;\n this.channel.postMessage({ type: \"SYNC_REQUEST\" } as BroadcastMessage);\n\n // Set a timeout to stop waiting for sync response\n setTimeout(() => {\n if (this.syncInProgress) {\n this.syncInProgress = false;\n void this.drainPendingMessages().catch((error) => {\n console.error(\"Failed to drain pending messages after sync timeout\", error);\n });\n }\n }, SYNC_TIMEOUT);\n }\n\n /**\n * Copies data from an array of entities into the repository\n */\n private async copyDataFromArray(entities: Entity[]): Promise<void> {\n if (entities.length === 0) return;\n\n // Clear existing data\n await this.inMemoryRepo.deleteAll();\n\n // Bulk insert the new data\n await this.inMemoryRepo.putBulk(entities);\n }\n\n /**\n * Broadcasts a message to other tabs\n */\n private broadcast(message: BroadcastMessage): void {\n if (this.channel) {\n this.channel.postMessage(message);\n }\n }\n\n /**\n * Sets up the database for the repository (syncs from other tabs)\n */\n public override async setupDatabase(): Promise<void> {\n if (this.isInitialized) return;\n this.isInitialized = true;\n await this.syncFromOtherTabs();\n }\n\n /**\n * Stores a key-value pair in the repository\n * @param value - The combined object to store\n * @returns The stored entity\n * @emits 'put' event with the stored entity when successful\n */\n public async put(value: InsertType): Promise<Entity> {\n const result = await this.inMemoryRepo.put(value);\n this.broadcast({ type: \"PUT\", entity: result });\n return result;\n }\n\n /**\n * Stores multiple key-value pairs in the repository in a bulk operation\n * @param values - Array of combined objects to store\n * @returns Array of stored entities\n * @emits 'put' event for each value stored\n */\n public async putBulk(values: InsertType[]): Promise<Entity[]> {\n const result = await this.inMemoryRepo.putBulk(values);\n this.broadcast({ type: \"PUT_BULK\", entities: result });\n return result;\n }\n\n /**\n * Retrieves a value by its key\n * @param key - The primary key object to look up\n * @returns The value object if found, undefined otherwise\n * @emits 'get' event with the fingerprint ID and value when found\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n return await this.inMemoryRepo.get(key);\n }\n\n /**\n * Deletes an entry by its key\n * @param value - The primary key object or entity of the entry to delete\n * @emits 'delete' event with the fingerprint ID when successful\n */\n async delete(value: PrimaryKey | Entity): Promise<void> {\n await this.inMemoryRepo.delete(value);\n const { key } = this.separateKeyValueFromCombined(value as Entity);\n this.broadcast({ type: \"DELETE\", key });\n }\n\n /**\n * Removes all entries from the repository\n * @emits 'clearall' event when successful\n */\n async deleteAll(): Promise<void> {\n await this.inMemoryRepo.deleteAll();\n this.broadcast({ type: \"DELETE_ALL\" });\n }\n\n /**\n * Returns an array of all entries in the repository, with optional ordering, offset, and limit.\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of all entries in the repository\n */\n async getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n return await this.inMemoryRepo.getAll(options);\n }\n\n /**\n * Returns the number of entries in the repository\n * @returns The total count of stored entries\n */\n async size(): Promise<number> {\n return await this.inMemoryRepo.size();\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n return await this.inMemoryRepo.getBulk(offset, limit);\n }\n\n /**\n * Queries entries matching the specified search criteria with optional ordering and limit.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering and limit options\n * @returns Array of matching entities or undefined if no matches found\n */\n async query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n return await this.inMemoryRepo.query(criteria, options);\n }\n\n override async queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n return await this.inMemoryRepo.queryIndex(criteria, options);\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n */\n async deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n await this.inMemoryRepo.deleteSearch(criteria);\n this.broadcast({\n type: \"DELETE_SEARCH\",\n criteria,\n });\n }\n\n /**\n * Subscribes to changes in the repository.\n * Delegates to the internal InMemoryTabularStorage which monitors local changes.\n * Changes from other tabs/windows are already propagated via BroadcastChannel.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options (not used for in-memory)\n * @returns Unsubscribe function\n */\n public override subscribeToChanges(\n callback: (change: any) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n return this.inMemoryRepo.subscribeToChanges(callback, options);\n }\n\n /**\n * Cleanup method to close the BroadcastChannel\n */\n public override destroy(): void {\n if (this.channel) {\n this.channel.close();\n this.channel = null;\n }\n this.inMemoryRepo.destroy();\n }\n}\n"
41
+ "/**\n * @license\n * Copyright 2025 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { DataPortSchemaObject, FromSchema, TypedArraySchemaOptions } from \"@workglow/util/schema\";\nimport { createServiceToken } from \"@workglow/util\";\nimport { BaseTabularStorage, ClientProvidedKeysOption } from \"./BaseTabularStorage\";\nimport {\n AnyTabularStorage,\n AutoGeneratedKeys,\n CoveringIndexQueryOptions,\n DeleteSearchCriteria,\n InsertEntity,\n QueryOptions,\n SearchCriteria,\n SimplifyPrimaryKey,\n TabularSubscribeOptions,\n} from \"./ITabularStorage\";\nimport { InMemoryTabularStorage } from \"./InMemoryTabularStorage\";\nimport { type ITabularMigration, type ITabularMigrationApplier } from \"../migrations\";\nimport { InMemoryTabularMigrationApplier } from \"./InMemoryTabularMigrationApplier\";\n\nexport const SHARED_IN_MEMORY_TABULAR_REPOSITORY = createServiceToken<AnyTabularStorage>(\n \"storage.tabularRepository.sharedInMemory\"\n);\n\nconst SYNC_TIMEOUT = 1000;\nconst MAX_PENDING_MESSAGES = 1000;\n\n/**\n * Message types for BroadcastChannel communication\n */\ntype BroadcastMessage =\n | { type: \"SYNC_REQUEST\" }\n | { type: \"SYNC_RESPONSE\"; data: any[] }\n | { type: \"PUT\"; entity: any }\n | { type: \"PUT_BULK\"; entities: any[] }\n | { type: \"DELETE\"; key: any }\n | { type: \"DELETE_ALL\" }\n | { type: \"DELETE_SEARCH\"; criteria: DeleteSearchCriteria<any> };\n\n/**\n * A tabular repository implementation that shares data across browser tabs/windows\n * using BroadcastChannel API. Uses InMemoryTabularStorage internally and\n * synchronizes changes across all instances.\n *\n * @template Schema - The schema definition for the entity using JSON Schema\n * @template PrimaryKeyNames - Array of property names that form the primary key\n */\nexport class SharedInMemoryTabularStorage<\n Schema extends DataPortSchemaObject,\n PrimaryKeyNames extends ReadonlyArray<keyof Schema[\"properties\"]>,\n // computed types\n Entity = FromSchema<Schema, TypedArraySchemaOptions>,\n PrimaryKey = SimplifyPrimaryKey<Entity, PrimaryKeyNames>,\n Value = Omit<Entity, PrimaryKeyNames[number] & keyof Entity>,\n InsertType extends InsertEntity<Entity, AutoGeneratedKeys<Schema>> = InsertEntity<\n Entity,\n AutoGeneratedKeys<Schema>\n >,\n> extends BaseTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey, Value, InsertType> {\n private channel: BroadcastChannel | null = null;\n private channelName: string;\n private inMemoryRepo: InMemoryTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>;\n private isInitialized = false;\n private syncInProgress = false;\n private pendingMessages: BroadcastMessage[] = [];\n\n /**\n * Creates a new SharedInMemoryTabularStorage instance\n * @param channelName - Unique name for the BroadcastChannel (defaults to \"tabular_store\")\n * @param schema - Schema defining the structure of the entity\n * @param primaryKeyNames - Array of property names that form the primary key\n * @param indexes - Array of columns or column arrays to make searchable. Each string or single column creates a single-column index,\n * while each array creates a compound index with columns in the specified order.\n * @param clientProvidedKeys - How to handle client-provided values for auto-generated keys\n * @param tabularMigrations - Optional declarative migrations to run on setup\n */\n constructor(\n channelName: string = \"tabular_store\",\n schema: Schema,\n primaryKeyNames: PrimaryKeyNames,\n indexes: readonly (keyof NoInfer<Entity> | readonly (keyof NoInfer<Entity>)[])[] = [],\n clientProvidedKeys: ClientProvidedKeysOption = \"if-missing\",\n tabularMigrations?: ReadonlyArray<ITabularMigration>\n ) {\n super(schema, primaryKeyNames, indexes, clientProvidedKeys, tabularMigrations, channelName);\n this.channelName = channelName;\n // Don't pass migrations to the inner repo — migrations are owned by the\n // outer Shared wrapper which delegates them through getMigrationApplier.\n this.inMemoryRepo = new InMemoryTabularStorage<Schema, PrimaryKeyNames, Entity, PrimaryKey>(\n schema,\n primaryKeyNames,\n indexes,\n clientProvidedKeys\n );\n\n this.setupEventForwarding();\n\n this.initializeBroadcastChannel();\n }\n\n /**\n * Checks if BroadcastChannel is available in the current environment\n */\n private isBroadcastChannelAvailable(): boolean {\n return typeof BroadcastChannel !== \"undefined\";\n }\n\n /**\n * Initializes the BroadcastChannel and sets up message handlers\n */\n private initializeBroadcastChannel(): void {\n if (!this.isBroadcastChannelAvailable()) {\n console.warn(\"BroadcastChannel is not available. Tab synchronization will not work.\");\n return;\n }\n\n try {\n this.channel = new BroadcastChannel(this.channelName);\n this.channel.onmessage = (event: MessageEvent<BroadcastMessage>) => {\n this.handleBroadcastMessage(event.data);\n };\n\n // Request sync from other tabs on initialization\n this.syncFromOtherTabs();\n } catch (error) {\n console.error(\"Failed to initialize BroadcastChannel:\", error);\n }\n }\n\n /**\n * Sets up event forwarding from the internal InMemoryTabularStorage\n */\n private setupEventForwarding(): void {\n this.inMemoryRepo.on(\"put\", (entity) => {\n this.events.emit(\"put\", entity);\n });\n this.inMemoryRepo.on(\"get\", (key, entity) => {\n this.events.emit(\"get\", key, entity);\n });\n this.inMemoryRepo.on(\"query\", (key, entities) => {\n this.events.emit(\"query\", key, entities);\n });\n this.inMemoryRepo.on(\"delete\", (key) => {\n this.events.emit(\"delete\", key);\n });\n this.inMemoryRepo.on(\"clearall\", () => {\n this.events.emit(\"clearall\");\n });\n }\n\n /**\n * Handles incoming BroadcastChannel messages\n */\n private async handleBroadcastMessage(message: BroadcastMessage): Promise<void> {\n if (this.syncInProgress && message.type !== \"SYNC_RESPONSE\") {\n // Queue messages during sync to replay after sync completes\n if (this.pendingMessages.length < MAX_PENDING_MESSAGES) {\n this.pendingMessages.push(message);\n }\n return;\n }\n\n switch (message.type) {\n case \"SYNC_REQUEST\":\n // Respond to sync request with current data\n const all = await this.inMemoryRepo.getAll();\n if (this.channel && all) {\n this.channel.postMessage({\n type: \"SYNC_RESPONSE\",\n data: all,\n } as BroadcastMessage);\n }\n break;\n\n case \"SYNC_RESPONSE\":\n // Copy data from the responding tab\n if (message.data && Array.isArray(message.data)) {\n await this.copyDataFromArray(message.data);\n }\n this.syncInProgress = false;\n await this.drainPendingMessages();\n break;\n\n case \"PUT\":\n // Apply put from another tab\n await this.inMemoryRepo.put(message.entity);\n break;\n\n case \"PUT_BULK\":\n // Apply bulk put from another tab\n await this.inMemoryRepo.putBulk(message.entities);\n break;\n\n case \"DELETE\":\n // Apply delete from another tab\n await this.inMemoryRepo.delete(message.key);\n break;\n\n case \"DELETE_ALL\":\n // Apply deleteAll from another tab\n await this.inMemoryRepo.deleteAll();\n break;\n\n case \"DELETE_SEARCH\":\n // Apply deleteSearch from another tab\n await this.inMemoryRepo.deleteSearch(message.criteria as DeleteSearchCriteria<Entity>);\n break;\n }\n }\n\n /**\n * Drains pending messages that were queued during sync.\n * Uses a while loop to handle messages that may be re-queued during draining\n * (e.g., if a replayed message triggers a new sync cycle).\n */\n private async drainPendingMessages(): Promise<void> {\n while (!this.syncInProgress && this.pendingMessages.length > 0) {\n const messages = this.pendingMessages;\n this.pendingMessages = [];\n for (const message of messages) {\n await this.handleBroadcastMessage(message);\n if (this.syncInProgress) {\n break;\n }\n }\n }\n }\n\n /**\n * Requests synchronization from other tabs\n */\n private syncFromOtherTabs(): void {\n if (!this.channel) return;\n\n this.syncInProgress = true;\n this.channel.postMessage({ type: \"SYNC_REQUEST\" } as BroadcastMessage);\n\n // Set a timeout to stop waiting for sync response\n setTimeout(() => {\n if (this.syncInProgress) {\n this.syncInProgress = false;\n void this.drainPendingMessages().catch((error) => {\n console.error(\"Failed to drain pending messages after sync timeout\", error);\n });\n }\n }, SYNC_TIMEOUT);\n }\n\n /**\n * Copies data from an array of entities into the repository\n */\n private async copyDataFromArray(entities: Entity[]): Promise<void> {\n if (entities.length === 0) return;\n\n // Clear existing data\n await this.inMemoryRepo.deleteAll();\n\n // Bulk insert the new data\n await this.inMemoryRepo.putBulk(entities);\n }\n\n /**\n * Broadcasts a message to other tabs\n */\n private broadcast(message: BroadcastMessage): void {\n if (this.channel) {\n this.channel.postMessage(message);\n }\n }\n\n /**\n * Sets up the database for the repository (syncs from other tabs)\n */\n public override async setupDatabase(): Promise<void> {\n if (this.isInitialized) return;\n this.isInitialized = true;\n await this.syncFromOtherTabs();\n if (this.tabularMigrations && this.tabularMigrations.length > 0) {\n await this.applyTabularMigrations();\n }\n }\n\n public override getMigrationApplier(): ITabularMigrationApplier | null {\n return new InMemoryTabularMigrationApplier(\n this as unknown as AnyTabularStorage,\n this.channelName\n );\n }\n\n /**\n * Stores a key-value pair in the repository\n * @param value - The combined object to store\n * @returns The stored entity\n * @emits 'put' event with the stored entity when successful\n */\n public async put(value: InsertType): Promise<Entity> {\n const result = await this.inMemoryRepo.put(value);\n this.broadcast({ type: \"PUT\", entity: result });\n return result;\n }\n\n /**\n * Stores multiple key-value pairs in the repository in a bulk operation\n * @param values - Array of combined objects to store\n * @returns Array of stored entities\n * @emits 'put' event for each value stored\n */\n public async putBulk(values: InsertType[]): Promise<Entity[]> {\n const result = await this.inMemoryRepo.putBulk(values);\n this.broadcast({ type: \"PUT_BULK\", entities: result });\n return result;\n }\n\n /**\n * Retrieves a value by its key\n * @param key - The primary key object to look up\n * @returns The value object if found, undefined otherwise\n * @emits 'get' event with the fingerprint ID and value when found\n */\n async get(key: PrimaryKey): Promise<Entity | undefined> {\n return await this.inMemoryRepo.get(key);\n }\n\n /**\n * Deletes an entry by its key\n * @param value - The primary key object or entity of the entry to delete\n * @emits 'delete' event with the fingerprint ID when successful\n */\n async delete(value: PrimaryKey | Entity): Promise<void> {\n await this.inMemoryRepo.delete(value);\n const { key } = this.separateKeyValueFromCombined(value as Entity);\n this.broadcast({ type: \"DELETE\", key });\n }\n\n /**\n * Removes all entries from the repository\n * @emits 'clearall' event when successful\n */\n async deleteAll(): Promise<void> {\n await this.inMemoryRepo.deleteAll();\n this.broadcast({ type: \"DELETE_ALL\" });\n }\n\n /**\n * Returns an array of all entries in the repository, with optional ordering, offset, and limit.\n * @param options - Optional ordering, limit, and offset options\n * @returns Array of all entries in the repository\n */\n async getAll(options?: QueryOptions<Entity>): Promise<Entity[] | undefined> {\n return await this.inMemoryRepo.getAll(options);\n }\n\n /**\n * Returns the number of entries in the repository\n * @returns The total count of stored entries\n */\n async size(): Promise<number> {\n return await this.inMemoryRepo.size();\n }\n\n /**\n * Fetches a page of records from the repository.\n * @param offset - Number of records to skip\n * @param limit - Maximum number of records to return\n * @returns Array of entities or undefined if no records found\n */\n async getBulk(offset: number, limit: number): Promise<Entity[] | undefined> {\n return await this.inMemoryRepo.getBulk(offset, limit);\n }\n\n /**\n * Queries entries matching the specified search criteria with optional ordering and limit.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n * @param options - Optional ordering and limit options\n * @returns Array of matching entities or undefined if no matches found\n */\n async query(\n criteria: SearchCriteria<Entity>,\n options?: QueryOptions<Entity>\n ): Promise<Entity[] | undefined> {\n return await this.inMemoryRepo.query(criteria, options);\n }\n\n override async queryIndex<K extends keyof Entity & string>(\n criteria: SearchCriteria<Entity>,\n options: CoveringIndexQueryOptions<Entity, K>\n ): Promise<Pick<Entity, K>[]> {\n return await this.inMemoryRepo.queryIndex(criteria, options);\n }\n\n /**\n * Deletes all entries matching the specified search criteria.\n * Supports multiple columns with optional comparison operators.\n *\n * @param criteria - Object with column names as keys and values or SearchConditions\n */\n async deleteSearch(criteria: DeleteSearchCriteria<Entity>): Promise<void> {\n await this.inMemoryRepo.deleteSearch(criteria);\n this.broadcast({\n type: \"DELETE_SEARCH\",\n criteria,\n });\n }\n\n /**\n * Subscribes to changes in the repository.\n * Delegates to the internal InMemoryTabularStorage which monitors local changes.\n * Changes from other tabs/windows are already propagated via BroadcastChannel.\n *\n * @param callback - Function called when a change occurs\n * @param options - Optional subscription options (not used for in-memory)\n * @returns Unsubscribe function\n */\n public override subscribeToChanges(\n callback: (change: any) => void,\n options?: TabularSubscribeOptions\n ): () => void {\n return this.inMemoryRepo.subscribeToChanges(callback, options);\n }\n\n /**\n * Cleanup method to close the BroadcastChannel\n */\n public override destroy(): void {\n if (this.channel) {\n this.channel.close();\n this.channel = null;\n }\n this.inMemoryRepo.destroy();\n }\n}\n"
32
42
  ],
33
- "mappings": "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",
34
- "debugId": "AFBB3D7D5939892064756E2164756E21",
43
+ "mappings": "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",
44
+ "debugId": "6C0E2096955BE84F64756E2164756E21",
35
45
  "names": []
36
46
  }