@tanstack/powersync-db-collection 0.1.37 → 0.1.38

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.
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.cjs","sources":["../../src/definitions.ts"],"sourcesContent":["import type { AbstractPowerSyncDatabase, Table } from '@powersync/common'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n BaseCollectionConfig,\n CollectionConfig,\n InferSchemaOutput,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n OptionalExtractedTable,\n PowerSyncRecord,\n} from './helpers'\n\n/**\n * Small helper which determines the output type if:\n * - Standard SQLite types are to be used OR\n * - If the provided schema should be used.\n */\nexport type InferPowerSyncOutputType<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<PowerSyncRecord> = never,\n> = TSchema extends never ? ExtractedTable<TTable> : InferSchemaOutput<TSchema>\n\n/**\n * A mapping type for custom serialization of object properties to SQLite-compatible values.\n *\n * This type allows you to override, for keys in the input object (`TOutput`), a function that transforms\n * the value to the corresponding SQLite type (`TSQLite`). Keys not specified will use the default SQLite serialization.\n *\n * ## Generics\n * - `TOutput`: The input object type, representing the row data to be serialized.\n * - `TSQLite`: The target SQLite-compatible type for each property, typically inferred from the table schema.\n *\n * ## Usage\n * Use this type to define a map of serialization functions for specific keys when you need custom handling\n * (e.g., converting complex objects, formatting dates, or handling enums).\n *\n * Example:\n * ```ts\n * const serializer: CustomSQLiteSerializer<MyRowType, MySQLiteType> = {\n * createdAt: (date) => date.toISOString(),\n * status: (status) => status ? 1 : 0,\n * meta: (meta) => JSON.stringify(meta),\n * };\n * ```\n *\n * ## Behavior\n * - Each key maps to a function that receives the value and returns the SQLite-compatible value.\n * - Used by `serializeForSQLite` to override default serialization for specific columns.\n */\nexport type CustomSQLiteSerializer<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = Partial<{\n [Key in keyof TOutput]: (\n value: TOutput[Key],\n ) => Key extends keyof TSQLite ? TSQLite[Key] : never\n}>\n\nexport type SerializerConfig<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = {\n /**\n * Optional partial serializer object for customizing how individual columns are serialized for SQLite.\n *\n * This should be a partial map of column keys to serialization functions, following the\n * {@link CustomSQLiteSerializer} type. Each function receives the column value and returns a value\n * compatible with SQLite storage.\n *\n * If not provided for a column, the default behavior is used:\n * - `TEXT`: Strings are stored as-is; Dates are converted to ISO strings; other types are JSON-stringified.\n * - `INTEGER`/`REAL`: Numbers are stored as-is; booleans are mapped to 1/0.\n *\n * Use this option to override serialization for specific columns, such as formatting dates, handling enums,\n * or serializing complex objects.\n *\n * Example:\n * ```typescript\n * serializer: {\n * createdAt: (date) => date.getTime(), // Store as timestamp\n * meta: (meta) => JSON.stringify(meta), // Custom object serialization\n * }\n * ```\n */\n serializer?: CustomSQLiteSerializer<TOutput, TSQLite>\n\n /**\n * Application logic should ensure that incoming synced data is always valid.\n * Failing to deserialize and apply incoming changes results in data inconsistency - which is a fatal error.\n * Use this callback to react to deserialization errors.\n */\n onDeserializationError: (error: StandardSchemaV1.FailureResult) => void\n}\n\n/**\n * Config for when TInput and TOutput are both the SQLite types.\n */\nexport type ConfigWithSQLiteTypes = {}\n\n/**\n * Config where TInput is the SQLite types while TOutput can be defined by TSchema.\n * We can use the same schema to validate TInput and incoming SQLite changes.\n */\nexport type ConfigWithSQLiteInputType<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types.\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n}\n\n/**\n * Config where TInput and TOutput have arbitrarily typed values.\n * The keys of the types need to equal the SQLite types.\n * Since TInput is not the SQLite types, we require a schema in order to deserialize incoming SQLite updates. The schema should validate from SQLite to TOutput.\n */\nexport type ConfigWithArbitraryCollectionTypes<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // The input and output must have the same keys, the value types can be arbitrary\n AnyTableColumnType<TTable>,\n AnyTableColumnType<TTable>\n >,\n> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n /**\n * Schema for deserializing and validating input data from the sync stream.\n *\n * This schema defines how to transform and validate data coming from SQLite types (as stored in the database)\n * into the desired output types (`TOutput`) expected by your application or validation logic.\n *\n * The generic parameters allow for arbitrary input and output types, so you can specify custom conversion rules\n * for each column. This is especially useful when your application expects richer types (e.g., Date, enums, objects)\n * than what SQLite natively supports.\n *\n * Use this to ensure that incoming data from the sync stream is properly converted and validated before use.\n *\n * Example:\n * ```typescript\n * deserializationSchema: z.object({\n * createdAt: z.preprocess((val) => new Date(val as string), z.date()),\n * meta: z.preprocess((val) => JSON.parse(val as string), z.object({ ... })),\n * })\n * ```\n *\n * This enables robust type safety and validation for incoming data, bridging the gap between SQLite storage\n * and your application's expected types.\n */\n deserializationSchema: StandardSchemaV1<\n ExtractedTable<TTable>,\n StandardSchemaV1.InferOutput<TSchema>\n >\n}\nexport type BasePowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1 = never,\n> = Omit<\n BaseCollectionConfig<ExtractedTable<TTable>, string, TSchema>,\n `onInsert` | `onUpdate` | `onDelete` | `getKey`\n> & {\n /** The PowerSync schema Table definition */\n table: TTable\n /** The PowerSync database instance */\n database: AbstractPowerSyncDatabase\n /**\n * The maximum number of documents to read from the SQLite table\n * in a single batch during the initial sync between PowerSync and the\n * in-memory TanStack DB collection.\n *\n * @remarks\n * - Defaults to {@link DEFAULT_BATCH_SIZE} if not specified.\n * - Larger values reduce the number of round trips to the storage\n * engine but increase memory usage per batch.\n * - Smaller values may lower memory usage and allow earlier\n * streaming of initial results, at the cost of more query calls.\n */\n syncBatchSize?: number\n}\n\n/**\n * Configuration interface for PowerSync collection options.\n * @template TTable - The PowerSync table schema definition\n * @template TSchema - The validation schema type\n */\n/**\n * Configuration options for creating a PowerSync collection.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\n *\n * const db = new PowerSyncDatabase({\n * database: {\n * dbFilename: \"test.sqlite\",\n * },\n * schema: APP_SCHEMA,\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents\n * })\n * )\n * ```\n */\nexport type PowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<any> = never,\n> = BasePowerSyncCollectionConfig<TTable, TSchema> &\n (\n | ConfigWithSQLiteTypes\n | ConfigWithSQLiteInputType<TTable, TSchema>\n | ConfigWithArbitraryCollectionTypes<TTable, TSchema>\n )\n\n/**\n * Metadata for the PowerSync Collection.\n */\nexport type PowerSyncCollectionMeta<TTable extends Table = Table> = {\n /**\n * The SQLite table representing the collection.\n */\n tableName: string\n /**\n * The internal table used to track diffs for the collection.\n */\n trackedTableName: string\n\n /**\n * Serializes a collection value to the SQLite type\n */\n serializeValue: (value: any) => ExtractedTable<TTable>\n\n /**\n * Whether the PowerSync table tracks metadata.\n */\n metadataIsTracked: boolean\n}\n\n/**\n * A CollectionConfig which includes utilities for PowerSync.\n */\nexport type EnhancedPowerSyncCollectionConfig<\n TTable extends Table,\n OutputType extends Record<string, unknown> = Record<string, unknown>,\n TSchema extends StandardSchemaV1 = never,\n> = CollectionConfig<\n OutputType,\n string,\n TSchema,\n PowerSyncCollectionUtils<TTable>\n> & {\n id?: string\n utils: PowerSyncCollectionUtils<TTable>\n schema?: TSchema\n}\n\n/**\n * Collection-level utilities for PowerSync.\n */\nexport type PowerSyncCollectionUtils<TTable extends Table = Table> = {\n getMeta: () => PowerSyncCollectionMeta<TTable>\n}\n\n/**\n * Default value for {@link PowerSyncCollectionConfig#syncBatchSize}.\n */\nexport const DEFAULT_BATCH_SIZE = 1000\n"],"names":[],"mappings":";;AA2RO,MAAM,qBAAqB;;"}
1
+ {"version":3,"file":"definitions.cjs","sources":["../../src/definitions.ts"],"sourcesContent":["import type { AbstractPowerSyncDatabase, Table } from '@powersync/common'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type {\n BaseCollectionConfig,\n CleanupFn,\n CollectionConfig,\n InferSchemaOutput,\n LoadSubsetOptions,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n OptionalExtractedTable,\n PowerSyncRecord,\n} from './helpers'\n\n/**\n * Small helper which determines the output type if:\n * - Standard SQLite types are to be used OR\n * - If the provided schema should be used.\n */\nexport type InferPowerSyncOutputType<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<PowerSyncRecord> = never,\n> = TSchema extends never ? ExtractedTable<TTable> : InferSchemaOutput<TSchema>\n\n/**\n * A mapping type for custom serialization of object properties to SQLite-compatible values.\n *\n * This type allows you to override, for keys in the input object (`TOutput`), a function that transforms\n * the value to the corresponding SQLite type (`TSQLite`). Keys not specified will use the default SQLite serialization.\n *\n * ## Generics\n * - `TOutput`: The input object type, representing the row data to be serialized.\n * - `TSQLite`: The target SQLite-compatible type for each property, typically inferred from the table schema.\n *\n * ## Usage\n * Use this type to define a map of serialization functions for specific keys when you need custom handling\n * (e.g., converting complex objects, formatting dates, or handling enums).\n *\n * Example:\n * ```ts\n * const serializer: CustomSQLiteSerializer<MyRowType, MySQLiteType> = {\n * createdAt: (date) => date.toISOString(),\n * status: (status) => status ? 1 : 0,\n * meta: (meta) => JSON.stringify(meta),\n * };\n * ```\n *\n * ## Behavior\n * - Each key maps to a function that receives the value and returns the SQLite-compatible value.\n * - Used by `serializeForSQLite` to override default serialization for specific columns.\n */\nexport type CustomSQLiteSerializer<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = Partial<{\n [Key in keyof TOutput]: (\n value: TOutput[Key],\n ) => Key extends keyof TSQLite ? TSQLite[Key] : never\n}>\n\nexport type SerializerConfig<\n TOutput extends Record<string, unknown>,\n TSQLite extends Record<string, unknown>,\n> = {\n /**\n * Optional partial serializer object for customizing how individual columns are serialized for SQLite.\n *\n * This should be a partial map of column keys to serialization functions, following the\n * {@link CustomSQLiteSerializer} type. Each function receives the column value and returns a value\n * compatible with SQLite storage.\n *\n * If not provided for a column, the default behavior is used:\n * - `TEXT`: Strings are stored as-is; Dates are converted to ISO strings; other types are JSON-stringified.\n * - `INTEGER`/`REAL`: Numbers are stored as-is; booleans are mapped to 1/0.\n *\n * Use this option to override serialization for specific columns, such as formatting dates, handling enums,\n * or serializing complex objects.\n *\n * Example:\n * ```typescript\n * serializer: {\n * createdAt: (date) => date.getTime(), // Store as timestamp\n * meta: (meta) => JSON.stringify(meta), // Custom object serialization\n * }\n * ```\n */\n serializer?: CustomSQLiteSerializer<TOutput, TSQLite>\n\n /**\n * Application logic should ensure that incoming synced data is always valid.\n * Failing to deserialize and apply incoming changes results in data inconsistency - which is a fatal error.\n * Use this callback to react to deserialization errors.\n */\n onDeserializationError: (error: StandardSchemaV1.FailureResult) => void\n}\n\n/**\n * Config for when TInput and TOutput are both the SQLite types.\n */\nexport type ConfigWithSQLiteTypes = {}\n\n/**\n * Config where TInput is the SQLite types while TOutput can be defined by TSchema.\n * We can use the same schema to validate TInput and incoming SQLite changes.\n */\nexport type ConfigWithSQLiteInputType<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types.\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n}\n\n/**\n * Config where TInput and TOutput have arbitrarily typed values.\n * The keys of the types need to equal the SQLite types.\n * Since TInput is not the SQLite types, we require a schema in order to deserialize incoming SQLite updates. The schema should validate from SQLite to TOutput.\n */\nexport type ConfigWithArbitraryCollectionTypes<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // The input and output must have the same keys, the value types can be arbitrary\n AnyTableColumnType<TTable>,\n AnyTableColumnType<TTable>\n >,\n> = SerializerConfig<\n StandardSchemaV1.InferOutput<TSchema>,\n ExtractedTable<TTable>\n> & {\n schema: TSchema\n /**\n * Schema for deserializing and validating input data from the sync stream.\n *\n * This schema defines how to transform and validate data coming from SQLite types (as stored in the database)\n * into the desired output types (`TOutput`) expected by your application or validation logic.\n *\n * The generic parameters allow for arbitrary input and output types, so you can specify custom conversion rules\n * for each column. This is especially useful when your application expects richer types (e.g., Date, enums, objects)\n * than what SQLite natively supports.\n *\n * Use this to ensure that incoming data from the sync stream is properly converted and validated before use.\n *\n * Example:\n * ```typescript\n * deserializationSchema: z.object({\n * createdAt: z.preprocess((val) => new Date(val as string), z.date()),\n * meta: z.preprocess((val) => JSON.parse(val as string), z.object({ ... })),\n * })\n * ```\n *\n * This enables robust type safety and validation for incoming data, bridging the gap between SQLite storage\n * and your application's expected types.\n */\n deserializationSchema: StandardSchemaV1<\n ExtractedTable<TTable>,\n StandardSchemaV1.InferOutput<TSchema>\n >\n}\n/**\n * Eager sync mode hooks.\n * Called once when the collection sync starts and stops.\n */\nexport type EagerSyncHooks = {\n syncMode?: 'eager'\n /**\n * Called when the collection sync starts.\n * Use this to set up external data sources (e.g. subscribing to a sync stream).\n *\n * @returns A cleanup function that is called when the collection sync is cleaned up.\n */\n onLoad?: () => CleanupFn | void | Promise<CleanupFn | void>\n onLoadSubset?: never\n}\n\n/**\n * On-demand sync mode hooks.\n * Called each time a subset is loaded or unloaded in response to live query changes.\n */\nexport type OnDemandSyncHooks = {\n syncMode: 'on-demand'\n onLoad?: never\n /**\n * Called when a subset of data is requested by a live query.\n * Use this to set up external data sources for the requested subset\n * (e.g. subscribing to a sync stream with parameters derived from the query predicate).\n *\n * @returns A cleanup function that is called when the subset is unloaded.\n */\n\n onLoadSubset?: (\n options: LoadSubsetOptions,\n ) => CleanupFn | void | Promise<CleanupFn | void>\n}\n\nexport type BasePowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1 = never,\n> = Omit<\n BaseCollectionConfig<ExtractedTable<TTable>, string, TSchema>,\n `onInsert` | `onUpdate` | `onDelete` | `getKey` | `syncMode`\n> & {\n /** The PowerSync schema Table definition */\n table: TTable\n /** The PowerSync database instance */\n database: AbstractPowerSyncDatabase\n /**\n * The maximum number of documents to read from the SQLite table\n * in a single batch during the initial sync between PowerSync and the\n * in-memory TanStack DB collection.\n *\n * @remarks\n * - Defaults to {@link DEFAULT_BATCH_SIZE} if not specified.\n * - Larger values reduce the number of round trips to the storage\n * engine but increase memory usage per batch.\n * - Smaller values may lower memory usage and allow earlier\n * streaming of initial results, at the cost of more query calls.\n */\n syncBatchSize?: number\n} & (EagerSyncHooks | OnDemandSyncHooks)\n\n/**\n * Configuration interface for PowerSync collection options.\n * @template TTable - The PowerSync table schema definition\n * @template TSchema - The validation schema type\n */\n/**\n * Configuration options for creating a PowerSync collection.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\n *\n * const db = new PowerSyncDatabase({\n * database: {\n * dbFilename: \"test.sqlite\",\n * },\n * schema: APP_SCHEMA,\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents\n * })\n * )\n * ```\n */\nexport type PowerSyncCollectionConfig<\n TTable extends Table = Table,\n TSchema extends StandardSchemaV1<any> = never,\n> = BasePowerSyncCollectionConfig<TTable, TSchema> &\n (\n | ConfigWithSQLiteTypes\n | ConfigWithSQLiteInputType<TTable, TSchema>\n | ConfigWithArbitraryCollectionTypes<TTable, TSchema>\n )\n\n/**\n * Metadata for the PowerSync Collection.\n */\nexport type PowerSyncCollectionMeta<TTable extends Table = Table> = {\n /**\n * The SQLite table representing the collection.\n */\n tableName: string\n /**\n * The internal table used to track diffs for the collection.\n */\n trackedTableName: string\n\n /**\n * Serializes a collection value to the SQLite type\n */\n serializeValue: (value: any) => ExtractedTable<TTable>\n\n /**\n * Whether the PowerSync table tracks metadata.\n */\n metadataIsTracked: boolean\n}\n\n/**\n * A CollectionConfig which includes utilities for PowerSync.\n */\nexport type EnhancedPowerSyncCollectionConfig<\n TTable extends Table,\n OutputType extends Record<string, unknown> = Record<string, unknown>,\n TSchema extends StandardSchemaV1 = never,\n> = CollectionConfig<\n OutputType,\n string,\n TSchema,\n PowerSyncCollectionUtils<TTable>\n> & {\n id?: string\n utils: PowerSyncCollectionUtils<TTable>\n schema?: TSchema\n}\n\n/**\n * Collection-level utilities for PowerSync.\n */\nexport type PowerSyncCollectionUtils<TTable extends Table = Table> = {\n getMeta: () => PowerSyncCollectionMeta<TTable>\n}\n\n/**\n * Default value for {@link PowerSyncCollectionConfig#syncBatchSize}.\n */\nexport const DEFAULT_BATCH_SIZE = 1000\n"],"names":[],"mappings":";;AAiUO,MAAM,qBAAqB;;"}
@@ -1,6 +1,6 @@
1
1
  import { AbstractPowerSyncDatabase, Table } from '@powersync/common';
2
2
  import { StandardSchemaV1 } from '@standard-schema/spec';
3
- import { BaseCollectionConfig, CollectionConfig, InferSchemaOutput } from '@tanstack/db';
3
+ import { BaseCollectionConfig, CleanupFn, CollectionConfig, InferSchemaOutput, LoadSubsetOptions } from '@tanstack/db';
4
4
  import { AnyTableColumnType, ExtractedTable, OptionalExtractedTable, PowerSyncRecord } from './helpers.cjs';
5
5
  /**
6
6
  * Small helper which determines the output type if:
@@ -112,7 +112,38 @@ export type ConfigWithArbitraryCollectionTypes<TTable extends Table, TSchema ext
112
112
  */
113
113
  deserializationSchema: StandardSchemaV1<ExtractedTable<TTable>, StandardSchemaV1.InferOutput<TSchema>>;
114
114
  };
115
- export type BasePowerSyncCollectionConfig<TTable extends Table = Table, TSchema extends StandardSchemaV1 = never> = Omit<BaseCollectionConfig<ExtractedTable<TTable>, string, TSchema>, `onInsert` | `onUpdate` | `onDelete` | `getKey`> & {
115
+ /**
116
+ * Eager sync mode hooks.
117
+ * Called once when the collection sync starts and stops.
118
+ */
119
+ export type EagerSyncHooks = {
120
+ syncMode?: 'eager';
121
+ /**
122
+ * Called when the collection sync starts.
123
+ * Use this to set up external data sources (e.g. subscribing to a sync stream).
124
+ *
125
+ * @returns A cleanup function that is called when the collection sync is cleaned up.
126
+ */
127
+ onLoad?: () => CleanupFn | void | Promise<CleanupFn | void>;
128
+ onLoadSubset?: never;
129
+ };
130
+ /**
131
+ * On-demand sync mode hooks.
132
+ * Called each time a subset is loaded or unloaded in response to live query changes.
133
+ */
134
+ export type OnDemandSyncHooks = {
135
+ syncMode: 'on-demand';
136
+ onLoad?: never;
137
+ /**
138
+ * Called when a subset of data is requested by a live query.
139
+ * Use this to set up external data sources for the requested subset
140
+ * (e.g. subscribing to a sync stream with parameters derived from the query predicate).
141
+ *
142
+ * @returns A cleanup function that is called when the subset is unloaded.
143
+ */
144
+ onLoadSubset?: (options: LoadSubsetOptions) => CleanupFn | void | Promise<CleanupFn | void>;
145
+ };
146
+ export type BasePowerSyncCollectionConfig<TTable extends Table = Table, TSchema extends StandardSchemaV1 = never> = Omit<BaseCollectionConfig<ExtractedTable<TTable>, string, TSchema>, `onInsert` | `onUpdate` | `onDelete` | `getKey` | `syncMode`> & {
116
147
  /** The PowerSync schema Table definition */
117
148
  table: TTable;
118
149
  /** The PowerSync database instance */
@@ -130,7 +161,7 @@ export type BasePowerSyncCollectionConfig<TTable extends Table = Table, TSchema
130
161
  * streaming of initial results, at the cost of more query calls.
131
162
  */
132
163
  syncBatchSize?: number;
133
- };
164
+ } & (EagerSyncHooks | OnDemandSyncHooks);
134
165
  /**
135
166
  * Configuration interface for PowerSync collection options.
136
167
  * @template TTable - The PowerSync table schema definition
@@ -3,7 +3,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const definitions = require("./definitions.cjs");
4
4
  const powersync = require("./powersync.cjs");
5
5
  const PowerSyncTransactor = require("./PowerSyncTransactor.cjs");
6
+ const sqliteCompiler = require("./sqlite-compiler.cjs");
6
7
  exports.DEFAULT_BATCH_SIZE = definitions.DEFAULT_BATCH_SIZE;
7
8
  exports.powerSyncCollectionOptions = powersync.powerSyncCollectionOptions;
8
9
  exports.PowerSyncTransactor = PowerSyncTransactor.PowerSyncTransactor;
10
+ exports.compileSQLite = sqliteCompiler.compileSQLite;
9
11
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
@@ -1,3 +1,4 @@
1
1
  export * from './definitions.cjs';
2
2
  export * from './powersync.cjs';
3
3
  export * from './PowerSyncTransactor.cjs';
4
+ export * from './sqlite-compiler.cjs';
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const common = require("@powersync/common");
4
+ const db = require("@tanstack/db");
5
+ const sqliteCompiler = require("./sqlite-compiler.cjs");
4
6
  const PendingOperationStore = require("./PendingOperationStore.cjs");
5
7
  const PowerSyncTransactor = require("./PowerSyncTransactor.cjs");
6
8
  const definitions = require("./definitions.cjs");
@@ -13,6 +15,7 @@ function powerSyncCollectionOptions(config) {
13
15
  table,
14
16
  schema: inputSchema,
15
17
  syncBatchSize = definitions.DEFAULT_BATCH_SIZE,
18
+ syncMode = "eager",
16
19
  ...restConfig
17
20
  } = config;
18
21
  const deserializationSchema = `deserializationSchema` in config ? config.deserializationSchema : null;
@@ -46,119 +49,270 @@ function powerSyncCollectionOptions(config) {
46
49
  });
47
50
  const sync = {
48
51
  sync: (params) => {
49
- const { begin, write, commit, markReady } = params;
52
+ const { begin, write, collection, commit, markReady } = params;
50
53
  const abortController = new AbortController();
51
- async function start() {
52
- database.logger.info(
53
- `Sync is starting for ${viewName} into ${trackedTableName}`
54
- );
55
- database.onChangeWithCallback(
56
- {
57
- onChange: async () => {
58
- await database.writeTransaction(async (context) => {
59
- begin();
60
- const operations = await context.getAll(
61
- `SELECT * FROM ${trackedTableName} ORDER BY timestamp ASC`
62
- );
63
- const pendingOperations = [];
64
- for (const op of operations) {
65
- const { id, operation, timestamp, value } = op;
66
- const parsedValue = deserializeSyncRow({
67
- id,
68
- ...JSON.parse(value)
69
- });
70
- const parsedPreviousValue = op.operation == common.DiffTriggerOperation.UPDATE ? deserializeSyncRow({
71
- id,
72
- ...JSON.parse(op.previous_value)
73
- }) : void 0;
74
- write({
75
- type: helpers.mapOperation(operation),
76
- value: parsedValue,
77
- previousValue: parsedPreviousValue
78
- });
79
- pendingOperations.push({
80
- id,
81
- operation,
82
- timestamp,
83
- tableName: viewName
84
- });
85
- }
86
- await context.execute(`DELETE FROM ${trackedTableName}`);
87
- commit();
88
- pendingOperationStore.resolvePendingFor(pendingOperations);
89
- }).catch((error) => {
90
- database.logger.error(
91
- `An error has been detected in the sync handler`,
92
- error
93
- );
94
- });
95
- }
96
- },
97
- {
98
- signal: abortController.signal,
99
- triggerImmediate: false,
100
- tables: [trackedTableName]
101
- }
102
- );
103
- const disposeTracking = await database.triggers.createDiffTrigger({
54
+ let disposeTracking = null;
55
+ if (syncMode === `eager`) {
56
+ return runEagerSync();
57
+ } else {
58
+ return runOnDemandSync();
59
+ }
60
+ async function createDiffTrigger(options) {
61
+ const { setupContext, when, writeType, batchQuery, onReady } = options;
62
+ return await database.triggers.createDiffTrigger({
104
63
  source: viewName,
105
64
  destination: trackedTableName,
106
- when: {
107
- [common.DiffTriggerOperation.INSERT]: `TRUE`,
108
- [common.DiffTriggerOperation.UPDATE]: `TRUE`,
109
- [common.DiffTriggerOperation.DELETE]: `TRUE`
110
- },
65
+ setupContext,
66
+ when,
111
67
  hooks: {
112
68
  beforeCreate: async (context) => {
113
69
  let currentBatchCount = syncBatchSize;
114
70
  let cursor = 0;
115
71
  while (currentBatchCount == syncBatchSize) {
116
72
  begin();
117
- const batchItems = await context.getAll(
118
- common.sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,
119
- [syncBatchSize, cursor]
73
+ const batchItems = await batchQuery(
74
+ context,
75
+ syncBatchSize,
76
+ cursor
120
77
  );
121
78
  currentBatchCount = batchItems.length;
122
79
  cursor += currentBatchCount;
123
80
  for (const row of batchItems) {
124
81
  write({
125
- type: `insert`,
82
+ type: writeType(row.id),
126
83
  value: deserializeSyncRow(row)
127
84
  });
128
85
  }
129
86
  commit();
130
87
  }
131
- markReady();
88
+ onReady();
132
89
  database.logger.info(
133
90
  `Sync is ready for ${viewName} into ${trackedTableName}`
134
91
  );
135
92
  }
136
93
  }
137
94
  });
95
+ }
96
+ async function flushDiffRecords() {
97
+ await database.writeTransaction(async (context) => {
98
+ await flushDiffRecordsWithContext(context);
99
+ }).catch((error) => {
100
+ database.logger.error(
101
+ `An error has been detected in the sync handler`,
102
+ error
103
+ );
104
+ });
105
+ }
106
+ async function flushDiffRecordsWithContext(context) {
107
+ try {
108
+ begin();
109
+ const operations = await context.getAll(
110
+ `SELECT * FROM ${trackedTableName} ORDER BY operation_id ASC`
111
+ );
112
+ const pendingOperations = [];
113
+ for (const op of operations) {
114
+ const { id, operation, timestamp, value } = op;
115
+ const parsedValue = deserializeSyncRow({
116
+ id,
117
+ ...JSON.parse(value)
118
+ });
119
+ const parsedPreviousValue = op.operation == common.DiffTriggerOperation.UPDATE ? deserializeSyncRow({
120
+ id,
121
+ ...JSON.parse(op.previous_value)
122
+ }) : void 0;
123
+ write({
124
+ type: helpers.mapOperation(operation),
125
+ value: parsedValue,
126
+ previousValue: parsedPreviousValue
127
+ });
128
+ pendingOperations.push({
129
+ id,
130
+ operation,
131
+ timestamp,
132
+ tableName: viewName
133
+ });
134
+ }
135
+ await context.execute(`DELETE FROM ${trackedTableName}`);
136
+ commit();
137
+ pendingOperationStore.resolvePendingFor(pendingOperations);
138
+ } catch (error) {
139
+ database.logger.error(
140
+ `An error has been detected in the sync handler`,
141
+ error
142
+ );
143
+ }
144
+ }
145
+ async function start(afterOnChangeRegistered) {
146
+ database.logger.info(
147
+ `Sync is starting for ${viewName} into ${trackedTableName}`
148
+ );
149
+ database.onChangeWithCallback(
150
+ {
151
+ onChange: async () => {
152
+ await flushDiffRecords();
153
+ }
154
+ },
155
+ {
156
+ signal: abortController.signal,
157
+ triggerImmediate: false,
158
+ tables: [trackedTableName]
159
+ }
160
+ );
161
+ await afterOnChangeRegistered?.();
138
162
  if (abortController.signal.aborted) {
139
- await disposeTracking();
163
+ await disposeTracking?.();
140
164
  } else {
141
165
  abortController.signal.addEventListener(
142
166
  `abort`,
143
- () => {
144
- disposeTracking();
167
+ async () => {
168
+ await disposeTracking?.();
145
169
  },
146
170
  { once: true }
147
171
  );
148
172
  }
149
173
  }
150
- start().catch(
151
- (error) => database.logger.error(
152
- `Could not start syncing process for ${viewName} into ${trackedTableName}`,
153
- error
154
- )
155
- );
156
- return () => {
157
- database.logger.info(
158
- `Sync has been stopped for ${viewName} into ${trackedTableName}`
174
+ function runEagerSync() {
175
+ let onUnload = null;
176
+ start(async () => {
177
+ onUnload = await restConfig.onLoad?.();
178
+ disposeTracking = await createDiffTrigger({
179
+ when: {
180
+ [common.DiffTriggerOperation.INSERT]: `TRUE`,
181
+ [common.DiffTriggerOperation.UPDATE]: `TRUE`,
182
+ [common.DiffTriggerOperation.DELETE]: `TRUE`
183
+ },
184
+ writeType: (_rowId) => `insert`,
185
+ batchQuery: (lockContext, batchSize, cursor) => lockContext.getAll(
186
+ common.sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,
187
+ [batchSize, cursor]
188
+ ),
189
+ onReady: () => markReady()
190
+ });
191
+ }).catch(
192
+ (error) => database.logger.error(
193
+ `Could not start syncing process for ${viewName} into ${trackedTableName}`,
194
+ error
195
+ )
159
196
  );
160
- abortController.abort();
161
- };
197
+ return () => {
198
+ database.logger.info(
199
+ `Sync has been stopped for ${viewName} into ${trackedTableName}`
200
+ );
201
+ abortController.abort();
202
+ onUnload?.();
203
+ };
204
+ }
205
+ function runOnDemandSync() {
206
+ let onUnloadSubset = null;
207
+ start().catch(
208
+ (error) => database.logger.error(
209
+ `Could not start syncing process for ${viewName} into ${trackedTableName}`,
210
+ error
211
+ )
212
+ );
213
+ const activeWhereExpressions = [];
214
+ const loadSubset = async (options) => {
215
+ if (options) {
216
+ activeWhereExpressions.push(options.where);
217
+ onUnloadSubset = await restConfig.onLoadSubset?.(options);
218
+ }
219
+ if (activeWhereExpressions.length === 0) {
220
+ await database.writeLock(async (ctx) => {
221
+ await flushDiffRecordsWithContext(ctx);
222
+ await disposeTracking?.({ context: ctx });
223
+ });
224
+ return;
225
+ }
226
+ const combinedWhere = activeWhereExpressions.length === 1 ? activeWhereExpressions[0] : db.or(
227
+ activeWhereExpressions[0],
228
+ activeWhereExpressions[1],
229
+ ...activeWhereExpressions.slice(2)
230
+ );
231
+ const compiledNewData = sqliteCompiler.compileSQLite(
232
+ { where: combinedWhere },
233
+ { jsonColumn: "NEW.data" }
234
+ );
235
+ const compiledOldData = sqliteCompiler.compileSQLite(
236
+ { where: combinedWhere },
237
+ { jsonColumn: "OLD.data" }
238
+ );
239
+ const compiledView = sqliteCompiler.compileSQLite({ where: combinedWhere });
240
+ const newDataWhenClause = toInlinedWhereClause(compiledNewData);
241
+ const oldDataWhenClause = toInlinedWhereClause(compiledOldData);
242
+ const viewWhereClause = toInlinedWhereClause(compiledView);
243
+ await database.writeLock(async (ctx) => {
244
+ await flushDiffRecordsWithContext(ctx);
245
+ await disposeTracking?.({ context: ctx });
246
+ disposeTracking = await createDiffTrigger({
247
+ setupContext: ctx,
248
+ when: {
249
+ [common.DiffTriggerOperation.INSERT]: newDataWhenClause,
250
+ [common.DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,
251
+ [common.DiffTriggerOperation.DELETE]: oldDataWhenClause
252
+ },
253
+ writeType: (rowId) => collection.has(rowId) ? `update` : `insert`,
254
+ batchQuery: (lockContext, batchSize, cursor) => lockContext.getAll(
255
+ `SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,
256
+ [batchSize, cursor]
257
+ ),
258
+ onReady: () => {
259
+ }
260
+ });
261
+ });
262
+ };
263
+ const toInlinedWhereClause = (compiled) => {
264
+ if (!compiled.where) return "TRUE";
265
+ const sqlParts = compiled.where.split("?");
266
+ return common.sanitizeSQL(
267
+ sqlParts,
268
+ ...compiled.params
269
+ );
270
+ };
271
+ const unloadSubset = async (options) => {
272
+ onUnloadSubset?.();
273
+ const idx = activeWhereExpressions.indexOf(options.where);
274
+ if (idx !== -1) {
275
+ activeWhereExpressions.splice(idx, 1);
276
+ }
277
+ const compiledDeparting = sqliteCompiler.compileSQLite({ where: options.where });
278
+ const departingWhereSQL = toInlinedWhereClause(compiledDeparting);
279
+ let evictionSQL;
280
+ if (activeWhereExpressions.length === 0) {
281
+ evictionSQL = `SELECT id FROM ${viewName} WHERE ${departingWhereSQL}`;
282
+ } else {
283
+ const combinedRemaining = activeWhereExpressions.length === 1 ? activeWhereExpressions[0] : db.or(
284
+ activeWhereExpressions[0],
285
+ activeWhereExpressions[1],
286
+ ...activeWhereExpressions.slice(2)
287
+ );
288
+ const compiledRemaining = sqliteCompiler.compileSQLite({
289
+ where: combinedRemaining
290
+ });
291
+ const remainingWhereSQL = toInlinedWhereClause(compiledRemaining);
292
+ evictionSQL = `SELECT id FROM ${viewName} WHERE (${departingWhereSQL}) AND NOT (${remainingWhereSQL})`;
293
+ }
294
+ const rowsToEvict = await database.getAll(evictionSQL);
295
+ if (rowsToEvict.length > 0) {
296
+ begin();
297
+ for (const { id } of rowsToEvict) {
298
+ write({ type: `delete`, key: id });
299
+ }
300
+ commit();
301
+ }
302
+ await loadSubset();
303
+ };
304
+ markReady();
305
+ return {
306
+ cleanup: () => {
307
+ database.logger.info(
308
+ `Sync has been stopped for ${viewName} into ${trackedTableName}`
309
+ );
310
+ abortController.abort();
311
+ },
312
+ loadSubset: (options) => loadSubset(options),
313
+ unloadSubset: (options) => unloadSubset(options)
314
+ };
315
+ }
162
316
  },
163
317
  // Expose the getSyncMetadata function
164
318
  getSyncMetadata: void 0
@@ -170,6 +324,7 @@ function powerSyncCollectionOptions(config) {
170
324
  getKey,
171
325
  // Syncing should start immediately since we need to monitor the changes for mutations
172
326
  startSync: true,
327
+ syncMode,
173
328
  sync,
174
329
  onInsert: async (params) => {
175
330
  return await transactor.applyTransaction(params.transaction);
@@ -1 +1 @@
1
- {"version":3,"file":"powersync.cjs","sources":["../../src/powersync.ts"],"sourcesContent":["import { DiffTriggerOperation, sanitizeSQL } from '@powersync/common'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport { PowerSyncTransactor } from './PowerSyncTransactor'\nimport { DEFAULT_BATCH_SIZE } from './definitions'\nimport { asPowerSyncRecord, mapOperation } from './helpers'\nimport { convertTableToSchema } from './schema'\nimport { serializeForSQLite } from './serialization'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n ExtractedTableColumns,\n MapBaseColumnType,\n OptionalExtractedTable,\n} from './helpers'\nimport type {\n BasePowerSyncCollectionConfig,\n ConfigWithArbitraryCollectionTypes,\n ConfigWithSQLiteInputType,\n ConfigWithSQLiteTypes,\n CustomSQLiteSerializer,\n EnhancedPowerSyncCollectionConfig,\n InferPowerSyncOutputType,\n PowerSyncCollectionConfig,\n PowerSyncCollectionUtils,\n} from './definitions'\nimport type { PendingOperation } from './PendingOperationStore'\nimport type { SyncConfig } from '@tanstack/db'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { Table, TriggerDiffRecord } from '@powersync/common'\n\n/**\n * Creates PowerSync collection options for use with a standard Collection.\n *\n * @template TTable - The SQLite-based typing\n * @template TSchema - The validation schema type (optionally supports a custom input type)\n * @param config - Configuration options for the PowerSync collection\n * @returns Collection options with utilities\n */\n\n// Overload 1: No schema is provided\n\n/**\n * Creates a PowerSync collection configuration with basic default validation.\n * Input and Output types are the SQLite column types.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\n *\n * type Document = (typeof APP_SCHEMA)[\"types\"][\"documents\"]\n *\n * const db = new PowerSyncDatabase({\n * database: {\n * dbFilename: \"test.sqlite\",\n * },\n * schema: APP_SCHEMA,\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<TTable extends Table = Table>(\n config: BasePowerSyncCollectionConfig<TTable, never> & ConfigWithSQLiteTypes,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n OptionalExtractedTable<TTable>,\n never\n>\n\n// Overload 2: Schema is provided and the TInput matches SQLite types.\n\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types satisfy the SQLite column types.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Dates are stored as ISO date strings in SQLite\n * created_at: column.text\n * }),\n * })\n *\n * // Advanced Zod validations. The output type of this schema\n * // is constrained to the SQLite schema of APP_SCHEMA\n * const schema = z.object({\n * id: z.string(),\n * // Notice that `name` is not nullable (is required) here and it has additional validation\n * name: z.string().min(3, { message: \"Should be at least 3 characters\" }).nullable(),\n * // The input type is still the SQLite string type. While collections will output smart Date instances.\n * created_at: z.string().transform(val => new Date(val))\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * serializer: {\n * // The default is toISOString, this is just to demonstrate custom overrides\n * created_at: (outputValue) => outputValue.toISOString(),\n * },\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types. We can use the supplied schema to validate sync input\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithSQLiteInputType<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n schema: TSchema\n}\n\n// Overload 3: Schema is provided with arbitrary TInput and TOutput\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types are not linked to the internal SQLite table types. This can\n * give greater flexibility, e.g. by accepting rich types as input for `insert` or `update` operations.\n * An additional `deserializationSchema` is required in order to process incoming SQLite updates to the output type.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Booleans are represented as integers in SQLite\n * is_active: column.integer\n * }),\n * })\n *\n * // Advanced Zod validations.\n * // We accept boolean values as input for operations and expose Booleans in query results\n * const schema = z.object({\n * id: z.string(),\n * isActive: z.boolean(), // TInput and TOutput are boolean\n * })\n *\n * // The deserializationSchema converts the SQLite synced INTEGER (0/1) values to booleans.\n * const deserializationSchema = z.object({\n * id: z.string(),\n * isActive: z.number().nullable().transform((val) => val == null ? true : val > 0),\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * deserializationSchema,\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // The input and output must have the same keys, the value types can be arbitrary\n AnyTableColumnType<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithArbitraryCollectionTypes<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n utils: PowerSyncCollectionUtils<TTable>\n schema: TSchema\n}\n\n/**\n * Implementation of powerSyncCollectionOptions that handles both schema and non-schema configurations.\n */\n\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(config: PowerSyncCollectionConfig<TTable, TSchema>) {\n const {\n database,\n table,\n schema: inputSchema,\n syncBatchSize = DEFAULT_BATCH_SIZE,\n ...restConfig\n } = config\n\n const deserializationSchema =\n `deserializationSchema` in config ? config.deserializationSchema : null\n const serializer = `serializer` in config ? config.serializer : undefined\n const onDeserializationError =\n `onDeserializationError` in config\n ? config.onDeserializationError\n : undefined\n\n // The SQLite table type\n type TableType = ExtractedTable<TTable>\n\n // The collection output type\n type OutputType = InferPowerSyncOutputType<TTable, TSchema>\n\n const { viewName, trackMetadata: metadataIsTracked } = table\n\n /**\n * Deserializes data from the incoming sync stream\n */\n const deserializeSyncRow = (value: TableType): OutputType => {\n const validationSchema = deserializationSchema || schema\n const validation = validationSchema[`~standard`].validate(value)\n if (`value` in validation) {\n return validation.value\n } else if (`issues` in validation) {\n const issueMessage = `Failed to validate incoming data for ${viewName}. Issues: ${validation.issues.map((issue) => `${issue.path} - ${issue.message}`)}`\n database.logger.error(issueMessage)\n onDeserializationError!(validation)\n throw new Error(issueMessage)\n } else {\n const unknownErrorMessage = `Unknown deserialization error for ${viewName}`\n database.logger.error(unknownErrorMessage)\n onDeserializationError!({ issues: [{ message: unknownErrorMessage }] })\n throw new Error(unknownErrorMessage)\n }\n }\n\n // We can do basic runtime validations for columns if not explicit schema has been provided\n const schema = inputSchema ?? (convertTableToSchema(table) as TSchema)\n /**\n * The onInsert, onUpdate, and onDelete handlers should only return\n * after we have written the changes to TanStack DB.\n * We currently only write to TanStack DB from a diff trigger.\n * We wait for the diff trigger to observe the change,\n * and only then return from the on[X] handlers.\n * This ensures that when the transaction is reported as\n * complete to the caller, the in-memory state is already\n * consistent with the database.\n */\n const pendingOperationStore = PendingOperationStore.GLOBAL\n // Keep the tracked table unique in case of multiple tabs.\n const trackedTableName = `__${viewName}_tracking_${Math.floor(\n Math.random() * 0xffffffff,\n )\n .toString(16)\n .padStart(8, `0`)}`\n\n const transactor = new PowerSyncTransactor({\n database,\n })\n\n /**\n * \"sync\"\n * Notice that this describes the Sync between the local SQLite table\n * and the in-memory tanstack-db collection.\n */\n const sync: SyncConfig<OutputType, string> = {\n sync: (params) => {\n const { begin, write, commit, markReady } = params\n const abortController = new AbortController()\n\n // The sync function needs to be synchronous\n async function start() {\n database.logger.info(\n `Sync is starting for ${viewName} into ${trackedTableName}`,\n )\n database.onChangeWithCallback(\n {\n onChange: async () => {\n await database\n .writeTransaction(async (context) => {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY timestamp ASC`,\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n commit()\n pendingOperationStore.resolvePendingFor(pendingOperations)\n })\n .catch((error) => {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n })\n },\n },\n {\n signal: abortController.signal,\n triggerImmediate: false,\n tables: [trackedTableName],\n },\n )\n\n const disposeTracking = await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin()\n const batchItems = await context.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [syncBatchSize, cursor],\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: `insert`,\n value: deserializeSyncRow(row),\n })\n }\n commit()\n }\n markReady()\n database.logger.info(\n `Sync is ready for ${viewName} into ${trackedTableName}`,\n )\n },\n },\n })\n\n // If the abort controller was aborted while processing the request above\n if (abortController.signal.aborted) {\n await disposeTracking()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n () => {\n disposeTracking()\n },\n { once: true },\n )\n }\n }\n\n start().catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n ),\n )\n\n return () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n }\n },\n // Expose the getSyncMetadata function\n getSyncMetadata: undefined,\n }\n\n const getKey = (record: OutputType) => asPowerSyncRecord(record).id\n\n const outputConfig: EnhancedPowerSyncCollectionConfig<\n TTable,\n OutputType,\n TSchema\n > = {\n ...restConfig,\n schema,\n getKey,\n // Syncing should start immediately since we need to monitor the changes for mutations\n startSync: true,\n sync,\n onInsert: async (params) => {\n // The transaction here should only ever contain a single insert mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onUpdate: async (params) => {\n // The transaction here should only ever contain a single update mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onDelete: async (params) => {\n // The transaction here should only ever contain a single delete mutation\n return await transactor.applyTransaction(params.transaction)\n },\n utils: {\n getMeta: () => ({\n tableName: viewName,\n trackedTableName,\n metadataIsTracked,\n serializeValue: (value) =>\n serializeForSQLite(\n value,\n // This is required by the input generic\n table as Table<\n MapBaseColumnType<InferPowerSyncOutputType<TTable, TSchema>>\n >,\n // Coerce serializer to the shape that corresponds to the Table constructed from OutputType\n serializer as CustomSQLiteSerializer<\n OutputType,\n ExtractedTableColumns<Table<MapBaseColumnType<OutputType>>>\n >,\n ),\n }),\n },\n }\n return outputConfig\n}\n"],"names":["DEFAULT_BATCH_SIZE","schema","convertTableToSchema","PendingOperationStore","PowerSyncTransactor","DiffTriggerOperation","mapOperation","sanitizeSQL","asPowerSyncRecord","serializeForSQLite"],"mappings":"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"}
1
+ {"version":3,"file":"powersync.cjs","sources":["../../src/powersync.ts"],"sourcesContent":["import { DiffTriggerOperation, sanitizeSQL } from '@powersync/common'\nimport { or } from '@tanstack/db'\nimport { compileSQLite } from './sqlite-compiler'\nimport { PendingOperationStore } from './PendingOperationStore'\nimport { PowerSyncTransactor } from './PowerSyncTransactor'\nimport { DEFAULT_BATCH_SIZE } from './definitions'\nimport { asPowerSyncRecord, mapOperation } from './helpers'\nimport { convertTableToSchema } from './schema'\nimport { serializeForSQLite } from './serialization'\nimport type {\n CleanupFn,\n LoadSubsetOptions,\n OperationType,\n SyncConfig,\n} from '@tanstack/db'\nimport type {\n AnyTableColumnType,\n ExtractedTable,\n ExtractedTableColumns,\n MapBaseColumnType,\n OptionalExtractedTable,\n} from './helpers'\nimport type {\n BasePowerSyncCollectionConfig,\n ConfigWithArbitraryCollectionTypes,\n ConfigWithSQLiteInputType,\n ConfigWithSQLiteTypes,\n CustomSQLiteSerializer,\n EnhancedPowerSyncCollectionConfig,\n InferPowerSyncOutputType,\n PowerSyncCollectionConfig,\n PowerSyncCollectionUtils,\n} from './definitions'\nimport type { PendingOperation } from './PendingOperationStore'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { LockContext, Table, TriggerDiffRecord } from '@powersync/common'\n\n/**\n * Creates PowerSync collection options for use with a standard Collection.\n *\n * @template TTable - The SQLite-based typing\n * @template TSchema - The validation schema type (optionally supports a custom input type)\n * @param config - Configuration options for the PowerSync collection\n * @returns Collection options with utilities\n */\n\n// Overload 1: No schema is provided\n\n/**\n * Creates a PowerSync collection configuration with basic default validation.\n * Input and Output types are the SQLite column types.\n *\n * @example\n * ```typescript\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * }),\n * })\n *\n * type Document = (typeof APP_SCHEMA)[\"types\"][\"documents\"]\n *\n * const db = new PowerSyncDatabase({\n * database: {\n * dbFilename: \"test.sqlite\",\n * },\n * schema: APP_SCHEMA,\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<TTable extends Table = Table>(\n config: BasePowerSyncCollectionConfig<TTable, never> & ConfigWithSQLiteTypes,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n OptionalExtractedTable<TTable>,\n never\n>\n\n// Overload 2: Schema is provided and the TInput matches SQLite types.\n\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types satisfy the SQLite column types.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Dates are stored as ISO date strings in SQLite\n * created_at: column.text\n * }),\n * })\n *\n * // Advanced Zod validations. The output type of this schema\n * // is constrained to the SQLite schema of APP_SCHEMA\n * const schema = z.object({\n * id: z.string(),\n * // Notice that `name` is not nullable (is required) here and it has additional validation\n * name: z.string().min(3, { message: \"Should be at least 3 characters\" }).nullable(),\n * // The input type is still the SQLite string type. While collections will output smart Date instances.\n * created_at: z.string().transform(val => new Date(val))\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * serializer: {\n * // The default is toISOString, this is just to demonstrate custom overrides\n * created_at: (outputValue) => outputValue.toISOString(),\n * },\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // TInput is the SQLite types. We can use the supplied schema to validate sync input\n OptionalExtractedTable<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithSQLiteInputType<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n schema: TSchema\n}\n\n// Overload 3: Schema is provided with arbitrary TInput and TOutput\n/**\n * Creates a PowerSync collection configuration with schema validation.\n *\n * The input types are not linked to the internal SQLite table types. This can\n * give greater flexibility, e.g. by accepting rich types as input for `insert` or `update` operations.\n * An additional `deserializationSchema` is required in order to process incoming SQLite updates to the output type.\n *\n * The output types are defined by the provided schema. This schema can enforce additional\n * validation or type transforms.\n * Arbitrary output typed mutations are encoded to SQLite for persistence. We provide a basic standard\n * serialization implementation to serialize column values. Custom or advanced types require providing additional\n * serializer specifications. Partial column overrides can be supplied to `serializer`.\n *\n * @example\n * ```typescript\n * import { z } from \"zod\"\n *\n * // The PowerSync SQLite schema\n * const APP_SCHEMA = new Schema({\n * documents: new Table({\n * name: column.text,\n * // Booleans are represented as integers in SQLite\n * is_active: column.integer\n * }),\n * })\n *\n * // Advanced Zod validations.\n * // We accept boolean values as input for operations and expose Booleans in query results\n * const schema = z.object({\n * id: z.string(),\n * isActive: z.boolean(), // TInput and TOutput are boolean\n * })\n *\n * // The deserializationSchema converts the SQLite synced INTEGER (0/1) values to booleans.\n * const deserializationSchema = z.object({\n * id: z.string(),\n * isActive: z.number().nullable().transform((val) => val == null ? true : val > 0),\n * })\n *\n * const collection = createCollection(\n * powerSyncCollectionOptions({\n * database: db,\n * table: APP_SCHEMA.props.documents,\n * schema,\n * deserializationSchema,\n * })\n * )\n * ```\n */\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<\n // The input and output must have the same keys, the value types can be arbitrary\n AnyTableColumnType<TTable>,\n AnyTableColumnType<TTable>\n >,\n>(\n config: BasePowerSyncCollectionConfig<TTable, TSchema> &\n ConfigWithArbitraryCollectionTypes<TTable, TSchema>,\n): EnhancedPowerSyncCollectionConfig<\n TTable,\n InferPowerSyncOutputType<TTable, TSchema>,\n TSchema\n> & {\n utils: PowerSyncCollectionUtils<TTable>\n schema: TSchema\n}\n\n/**\n * Implementation of powerSyncCollectionOptions that handles both schema and non-schema configurations.\n */\n\nexport function powerSyncCollectionOptions<\n TTable extends Table,\n TSchema extends StandardSchemaV1<any> = never,\n>(config: PowerSyncCollectionConfig<TTable, TSchema>) {\n const {\n database,\n table,\n schema: inputSchema,\n syncBatchSize = DEFAULT_BATCH_SIZE,\n syncMode = 'eager',\n ...restConfig\n } = config\n\n const deserializationSchema =\n `deserializationSchema` in config ? config.deserializationSchema : null\n const serializer = `serializer` in config ? config.serializer : undefined\n const onDeserializationError =\n `onDeserializationError` in config\n ? config.onDeserializationError\n : undefined\n\n // The SQLite table type\n type TableType = ExtractedTable<TTable>\n\n // The collection output type\n type OutputType = InferPowerSyncOutputType<TTable, TSchema>\n\n const { viewName, trackMetadata: metadataIsTracked } = table\n\n /**\n * Deserializes data from the incoming sync stream\n */\n const deserializeSyncRow = (value: TableType): OutputType => {\n const validationSchema = deserializationSchema || schema\n const validation = validationSchema[`~standard`].validate(value)\n if (`value` in validation) {\n return validation.value\n } else if (`issues` in validation) {\n const issueMessage = `Failed to validate incoming data for ${viewName}. Issues: ${validation.issues.map((issue) => `${issue.path} - ${issue.message}`)}`\n database.logger.error(issueMessage)\n onDeserializationError!(validation)\n throw new Error(issueMessage)\n } else {\n const unknownErrorMessage = `Unknown deserialization error for ${viewName}`\n database.logger.error(unknownErrorMessage)\n onDeserializationError!({ issues: [{ message: unknownErrorMessage }] })\n throw new Error(unknownErrorMessage)\n }\n }\n\n // We can do basic runtime validations for columns if not explicit schema has been provided\n const schema = inputSchema ?? (convertTableToSchema(table) as TSchema)\n /**\n * The onInsert, onUpdate, and onDelete handlers should only return\n * after we have written the changes to TanStack DB.\n * We currently only write to TanStack DB from a diff trigger.\n * We wait for the diff trigger to observe the change,\n * and only then return from the on[X] handlers.\n * This ensures that when the transaction is reported as\n * complete to the caller, the in-memory state is already\n * consistent with the database.\n */\n const pendingOperationStore = PendingOperationStore.GLOBAL\n // Keep the tracked table unique in case of multiple tabs.\n const trackedTableName = `__${viewName}_tracking_${Math.floor(\n Math.random() * 0xffffffff,\n )\n .toString(16)\n .padStart(8, `0`)}`\n\n const transactor = new PowerSyncTransactor({\n database,\n })\n\n /**\n * \"sync\"\n * Notice that this describes the Sync between the local SQLite table\n * and the in-memory tanstack-db collection.\n */\n const sync: SyncConfig<OutputType, string> = {\n sync: (params) => {\n const { begin, write, collection, commit, markReady } = params\n const abortController = new AbortController()\n\n let disposeTracking:\n | ((options?: { context?: LockContext }) => Promise<void>)\n | null = null\n\n if (syncMode === `eager`) {\n return runEagerSync()\n } else {\n return runOnDemandSync()\n }\n\n async function createDiffTrigger(options: {\n setupContext?: LockContext\n when: Record<DiffTriggerOperation, string>\n writeType: (rowId: string) => OperationType\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) => Promise<Array<TableType>>\n onReady: () => void\n }) {\n const { setupContext, when, writeType, batchQuery, onReady } = options\n\n return await database.triggers.createDiffTrigger({\n source: viewName,\n destination: trackedTableName,\n setupContext,\n when,\n hooks: {\n beforeCreate: async (context) => {\n let currentBatchCount = syncBatchSize\n let cursor = 0\n while (currentBatchCount == syncBatchSize) {\n begin()\n\n const batchItems = await batchQuery(\n context,\n syncBatchSize,\n cursor,\n )\n currentBatchCount = batchItems.length\n cursor += currentBatchCount\n for (const row of batchItems) {\n write({\n type: writeType(row.id),\n value: deserializeSyncRow(row),\n })\n }\n commit()\n }\n onReady()\n database.logger.info(\n `Sync is ready for ${viewName} into ${trackedTableName}`,\n )\n },\n },\n })\n }\n\n async function flushDiffRecords(): Promise<void> {\n await database\n .writeTransaction(async (context) => {\n await flushDiffRecordsWithContext(context)\n })\n .catch((error) => {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n })\n }\n\n // We can use this directly if we want to pair a flush with dispose+recreate diff trigger.\n async function flushDiffRecordsWithContext(\n context: LockContext,\n ): Promise<void> {\n try {\n begin()\n const operations = await context.getAll<TriggerDiffRecord>(\n `SELECT * FROM ${trackedTableName} ORDER BY operation_id ASC`,\n )\n const pendingOperations: Array<PendingOperation> = []\n\n for (const op of operations) {\n const { id, operation, timestamp, value } = op\n const parsedValue = deserializeSyncRow({\n id,\n ...JSON.parse(value),\n })\n const parsedPreviousValue =\n op.operation == DiffTriggerOperation.UPDATE\n ? deserializeSyncRow({\n id,\n ...JSON.parse(op.previous_value),\n })\n : undefined\n write({\n type: mapOperation(operation),\n value: parsedValue,\n previousValue: parsedPreviousValue,\n })\n pendingOperations.push({\n id,\n operation,\n timestamp,\n tableName: viewName,\n })\n }\n\n // clear the current operations\n await context.execute(`DELETE FROM ${trackedTableName}`)\n\n commit()\n pendingOperationStore.resolvePendingFor(pendingOperations)\n } catch (error) {\n database.logger.error(\n `An error has been detected in the sync handler`,\n error,\n )\n }\n }\n\n // The sync function needs to be synchronous.\n async function start(afterOnChangeRegistered?: () => Promise<void>) {\n database.logger.info(\n `Sync is starting for ${viewName} into ${trackedTableName}`,\n )\n database.onChangeWithCallback(\n {\n onChange: async () => {\n await flushDiffRecords()\n },\n },\n {\n signal: abortController.signal,\n triggerImmediate: false,\n tables: [trackedTableName],\n },\n )\n\n await afterOnChangeRegistered?.()\n\n // If the abort controller was aborted while processing the request above\n if (abortController.signal.aborted) {\n await disposeTracking?.()\n } else {\n abortController.signal.addEventListener(\n `abort`,\n async () => {\n await disposeTracking?.()\n },\n { once: true },\n )\n }\n }\n\n // Eager mode.\n // Registers a diff trigger for the entire table.\n function runEagerSync() {\n let onUnload: CleanupFn | void | null = null\n\n start(async () => {\n onUnload = await restConfig.onLoad?.()\n\n disposeTracking = await createDiffTrigger({\n when: {\n [DiffTriggerOperation.INSERT]: `TRUE`,\n [DiffTriggerOperation.UPDATE]: `TRUE`,\n [DiffTriggerOperation.DELETE]: `TRUE`,\n },\n writeType: (_rowId: string) => `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n sanitizeSQL`SELECT * FROM ${viewName} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n onReady: () => markReady(),\n })\n }).catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n ),\n )\n\n return () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n onUnload?.()\n }\n }\n\n // On-demand mode.\n // Registers a diff trigger for the active WHERE expressions.\n function runOnDemandSync() {\n let onUnloadSubset: CleanupFn | void | null = null\n\n start().catch((error) =>\n database.logger.error(\n `Could not start syncing process for ${viewName} into ${trackedTableName}`,\n error,\n ),\n )\n\n // Tracks all active WHERE expressions for on-demand sync filtering.\n // Each loadSubset call pushes its predicate; unloadSubset removes it.\n const activeWhereExpressions: Array<LoadSubsetOptions['where']> = []\n\n const loadSubset = async (\n options?: LoadSubsetOptions,\n ): Promise<void> => {\n if (options) {\n activeWhereExpressions.push(options.where)\n onUnloadSubset = await restConfig.onLoadSubset?.(options)\n }\n\n if (activeWhereExpressions.length === 0) {\n await database.writeLock(async (ctx) => {\n await flushDiffRecordsWithContext(ctx)\n await disposeTracking?.({ context: ctx })\n })\n return\n }\n\n const combinedWhere =\n activeWhereExpressions.length === 1\n ? activeWhereExpressions[0]\n : or(\n activeWhereExpressions[0],\n activeWhereExpressions[1],\n ...activeWhereExpressions.slice(2),\n )\n\n const compiledNewData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'NEW.data' },\n )\n\n const compiledOldData = compileSQLite(\n { where: combinedWhere },\n { jsonColumn: 'OLD.data' },\n )\n\n const compiledView = compileSQLite({ where: combinedWhere })\n\n const newDataWhenClause = toInlinedWhereClause(compiledNewData)\n const oldDataWhenClause = toInlinedWhereClause(compiledOldData)\n const viewWhereClause = toInlinedWhereClause(compiledView)\n\n await database.writeLock(async (ctx) => {\n await flushDiffRecordsWithContext(ctx)\n await disposeTracking?.({ context: ctx })\n\n disposeTracking = await createDiffTrigger({\n setupContext: ctx,\n when: {\n [DiffTriggerOperation.INSERT]: newDataWhenClause,\n [DiffTriggerOperation.UPDATE]: `(${newDataWhenClause}) OR (${oldDataWhenClause})`,\n [DiffTriggerOperation.DELETE]: oldDataWhenClause,\n },\n writeType: (rowId: string) =>\n collection.has(rowId) ? `update` : `insert`,\n batchQuery: (\n lockContext: LockContext,\n batchSize: number,\n cursor: number,\n ) =>\n lockContext.getAll<TableType>(\n `SELECT * FROM ${viewName} WHERE ${viewWhereClause} LIMIT ? OFFSET ?`,\n [batchSize, cursor],\n ),\n onReady: () => {},\n })\n })\n }\n\n const toInlinedWhereClause = (compiled: {\n where?: string\n params: Array<unknown>\n }): string => {\n if (!compiled.where) return 'TRUE'\n const sqlParts = compiled.where.split('?')\n return sanitizeSQL(\n sqlParts as unknown as TemplateStringsArray,\n ...compiled.params,\n )\n }\n\n const unloadSubset = async (options: LoadSubsetOptions) => {\n onUnloadSubset?.()\n\n const idx = activeWhereExpressions.indexOf(options.where)\n if (idx !== -1) {\n activeWhereExpressions.splice(idx, 1)\n }\n\n // Evict rows that were exclusively loaded by the departing predicate.\n // These are rows matching the departing WHERE that are no longer covered\n // by any remaining active predicate.\n const compiledDeparting = compileSQLite({ where: options.where })\n const departingWhereSQL = toInlinedWhereClause(compiledDeparting)\n\n let evictionSQL: string\n if (activeWhereExpressions.length === 0) {\n evictionSQL = `SELECT id FROM ${viewName} WHERE ${departingWhereSQL}`\n } else {\n const combinedRemaining =\n activeWhereExpressions.length === 1\n ? activeWhereExpressions[0]!\n : or(\n activeWhereExpressions[0],\n activeWhereExpressions[1],\n ...activeWhereExpressions.slice(2),\n )\n const compiledRemaining = compileSQLite({\n where: combinedRemaining,\n })\n const remainingWhereSQL = toInlinedWhereClause(compiledRemaining)\n evictionSQL = `SELECT id FROM ${viewName} WHERE (${departingWhereSQL}) AND NOT (${remainingWhereSQL})`\n }\n\n const rowsToEvict = await database.getAll<{ id: string }>(evictionSQL)\n if (rowsToEvict.length > 0) {\n begin()\n for (const { id } of rowsToEvict) {\n write({ type: `delete`, key: id })\n }\n commit()\n }\n\n // Recreate the diff trigger for the remaining active WHERE expressions.\n await loadSubset()\n }\n\n markReady()\n\n return {\n cleanup: () => {\n database.logger.info(\n `Sync has been stopped for ${viewName} into ${trackedTableName}`,\n )\n abortController.abort()\n },\n loadSubset: (options: LoadSubsetOptions) => loadSubset(options),\n unloadSubset: (options: LoadSubsetOptions) => unloadSubset(options),\n }\n }\n },\n // Expose the getSyncMetadata function\n getSyncMetadata: undefined,\n }\n\n const getKey = (record: OutputType) => asPowerSyncRecord(record).id\n\n const outputConfig: EnhancedPowerSyncCollectionConfig<\n TTable,\n OutputType,\n TSchema\n > = {\n ...restConfig,\n schema,\n getKey,\n // Syncing should start immediately since we need to monitor the changes for mutations\n startSync: true,\n syncMode,\n sync,\n onInsert: async (params) => {\n // The transaction here should only ever contain a single insert mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onUpdate: async (params) => {\n // The transaction here should only ever contain a single update mutation\n return await transactor.applyTransaction(params.transaction)\n },\n onDelete: async (params) => {\n // The transaction here should only ever contain a single delete mutation\n return await transactor.applyTransaction(params.transaction)\n },\n utils: {\n getMeta: () => ({\n tableName: viewName,\n trackedTableName,\n metadataIsTracked,\n serializeValue: (value) =>\n serializeForSQLite(\n value,\n // This is required by the input generic\n table as Table<\n MapBaseColumnType<InferPowerSyncOutputType<TTable, TSchema>>\n >,\n // Coerce serializer to the shape that corresponds to the Table constructed from OutputType\n serializer as CustomSQLiteSerializer<\n OutputType,\n ExtractedTableColumns<Table<MapBaseColumnType<OutputType>>>\n >,\n ),\n }),\n },\n }\n return outputConfig\n}\n"],"names":["DEFAULT_BATCH_SIZE","schema","convertTableToSchema","PendingOperationStore","PowerSyncTransactor","DiffTriggerOperation","mapOperation","sanitizeSQL","or","compileSQLite","asPowerSyncRecord","serializeForSQLite"],"mappings":"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"}