From 96512ee1be3bfecdd153cc8540f5bd98a14f21b2 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 9 Feb 2026 10:33:34 +0100 Subject: [PATCH 1/6] Add sync definition superinterface --- .changeset/neat-ears-sort.md | 11 ++ .../implementation/MongoPersistedSyncRules.ts | 5 +- .../src/api/MongoRouteAPIAdapter.ts | 2 +- .../test/src/mongo_test.test.ts | 6 +- .../PostgresPersistedSyncRulesContent.ts | 2 +- .../src/replication/replication-utils.ts | 4 +- .../test/src/validation.test.ts | 4 +- .../src/test-utils/general-utils.ts | 2 +- packages/service-core/src/api/RouteAPI.ts | 4 +- packages/service-core/src/api/diagnostics.ts | 9 +- .../src/routes/endpoints/admin.ts | 4 +- .../src/routes/endpoints/sync-rules.ts | 10 +- .../src/storage/PersistedSyncRulesContent.ts | 5 +- .../test/src/sync/BucketChecksumState.test.ts | 8 +- packages/sync-rules/src/BaseSyncConfig.ts | 131 +++++++++++++++ packages/sync-rules/src/HydratedSyncRules.ts | 9 +- packages/sync-rules/src/SqlSyncRules.ts | 159 ++++-------------- packages/sync-rules/src/compatibility.ts | 46 +++++ packages/sync-rules/src/index.ts | 3 +- .../schema-generators/DartSchemaGenerator.ts | 6 +- .../DotNetSchemaGenerator.ts | 4 +- .../JsLegacySchemaGenerator.ts | 4 +- .../KotlinSchemaGenerator.ts | 4 +- .../schema-generators/RoomSchemaGenerator.ts | 4 +- .../src/schema-generators/SchemaGenerator.ts | 6 +- .../schema-generators/SqlSchemaGenerator.ts | 4 +- .../schema-generators/SwiftSchemaGenerator.ts | 5 +- .../schema-generators/TsSchemaGenerator.ts | 5 +- .../src/sync_plan/evaluator/index.ts | 66 +++++--- packages/sync-rules/src/types.ts | 5 +- .../sync-rules/test/src/compatibility.test.ts | 20 +-- .../test/src/generate_schema.test.ts | 2 +- .../sync_plan/evaluator/output_schema.test.ts | 12 +- .../test/src/sync_plan/evaluator/utils.ts | 13 +- .../sync-rules/test/src/sync_rules.test.ts | 58 +++---- 35 files changed, 379 insertions(+), 263 deletions(-) create mode 100644 .changeset/neat-ears-sort.md create mode 100644 packages/sync-rules/src/BaseSyncConfig.ts diff --git a/.changeset/neat-ears-sort.md b/.changeset/neat-ears-sort.md new file mode 100644 index 000000000..bbea56102 --- /dev/null +++ b/.changeset/neat-ears-sort.md @@ -0,0 +1,11 @@ +--- +'@powersync/service-sync-rules': minor +'@powersync/service-module-postgres-storage': patch +'@powersync/service-module-mongodb-storage': patch +'@powersync/service-core-tests': patch +'@powersync/service-module-postgres': patch +'@powersync/service-module-mongodb': patch +'@powersync/service-core': patch +--- + +Introduce `BaseSyncConfig` to represent SQL-based sync rules and precompiled sync plans. diff --git a/modules/module-mongodb-storage/src/storage/implementation/MongoPersistedSyncRules.ts b/modules/module-mongodb-storage/src/storage/implementation/MongoPersistedSyncRules.ts index 922689b6a..a93c9057c 100644 --- a/modules/module-mongodb-storage/src/storage/implementation/MongoPersistedSyncRules.ts +++ b/modules/module-mongodb-storage/src/storage/implementation/MongoPersistedSyncRules.ts @@ -1,13 +1,14 @@ import { SqlSyncRules, HydratedSyncRules, versionedHydrationState } from '@powersync/service-sync-rules'; import { storage } from '@powersync/service-core'; +import { SyncConfigWithErrors } from '@powersync/service-sync-rules/src/BaseSyncConfig.js'; export class MongoPersistedSyncRules implements storage.PersistedSyncRules { public readonly slot_name: string; constructor( public readonly id: number, - public readonly sync_rules: SqlSyncRules, + public readonly sync_rules: SyncConfigWithErrors, public readonly checkpoint_lsn: string | null, slot_name: string | null ) { @@ -15,6 +16,6 @@ export class MongoPersistedSyncRules implements storage.PersistedSyncRules { } hydratedSyncRules(): HydratedSyncRules { - return this.sync_rules.hydrate({ hydrationState: versionedHydrationState(this.id) }); + return this.sync_rules.config.hydrate({ hydrationState: versionedHydrationState(this.id) }); } } diff --git a/modules/module-mongodb/src/api/MongoRouteAPIAdapter.ts b/modules/module-mongodb/src/api/MongoRouteAPIAdapter.ts index 9a093b2e8..b9d899218 100644 --- a/modules/module-mongodb/src/api/MongoRouteAPIAdapter.ts +++ b/modules/module-mongodb/src/api/MongoRouteAPIAdapter.ts @@ -81,7 +81,7 @@ export class MongoRouteAPIAdapter implements api.RouteAPI { async getDebugTablesInfo( tablePatterns: sync_rules.TablePattern[], - sqlSyncRules: sync_rules.SqlSyncRules + sqlSyncRules: sync_rules.BaseSyncConfig ): Promise { let result: api.PatternResult[] = []; diff --git a/modules/module-mongodb/test/src/mongo_test.test.ts b/modules/module-mongodb/test/src/mongo_test.test.ts index c27002535..2b92eb52e 100644 --- a/modules/module-mongodb/test/src/mongo_test.test.ts +++ b/modules/module-mongodb/test/src/mongo_test.test.ts @@ -410,7 +410,7 @@ describe('mongo data types', () => { await setupTable(db); await insert(collection); - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: global: @@ -457,7 +457,7 @@ bucket_definitions: await setupTable(db); await insert(collection); - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: global: @@ -505,7 +505,7 @@ bucket_definitions: await setupTable(db); await insert(collection); - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: global: diff --git a/modules/module-postgres-storage/src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts b/modules/module-postgres-storage/src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts index 0b0ec4d79..d7e98e970 100644 --- a/modules/module-postgres-storage/src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts +++ b/modules/module-postgres-storage/src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts @@ -37,7 +37,7 @@ export class PostgresPersistedSyncRulesContent implements storage.PersistedSyncR slot_name: this.slot_name, sync_rules: SqlSyncRules.fromYaml(this.sync_rules_content, options), hydratedSyncRules() { - return this.sync_rules.hydrate({ + return this.sync_rules.config.hydrate({ hydrationState: versionedHydrationState(this.id) }); } diff --git a/modules/module-postgres/src/replication/replication-utils.ts b/modules/module-postgres/src/replication/replication-utils.ts index 52373a9b0..8cc26fffd 100644 --- a/modules/module-postgres/src/replication/replication-utils.ts +++ b/modules/module-postgres/src/replication/replication-utils.ts @@ -196,7 +196,7 @@ export interface GetDebugTablesInfoOptions { publicationName: string; connectionTag: string; tablePatterns: sync_rules.TablePattern[]; - syncRules: sync_rules.SqlSyncRules; + syncRules: sync_rules.BaseSyncConfig; } export async function getDebugTablesInfo(options: GetDebugTablesInfoOptions): Promise { @@ -296,7 +296,7 @@ export interface GetDebugTableInfoOptions { connectionTag: string; tablePattern: sync_rules.TablePattern; relationId: number | null; - syncRules: sync_rules.SqlSyncRules; + syncRules: sync_rules.BaseSyncConfig; } export async function getDebugTableInfo(options: GetDebugTableInfoOptions): Promise { diff --git a/modules/module-postgres/test/src/validation.test.ts b/modules/module-postgres/test/src/validation.test.ts index 135b0fab7..c1d110fe8 100644 --- a/modules/module-postgres/test/src/validation.test.ts +++ b/modules/module-postgres/test/src/validation.test.ts @@ -21,13 +21,13 @@ bucket_definitions: const syncRules = await context.factory.updateSyncRules({ content: syncRuleContent }); - const tablePatterns = syncRules.parsed({ defaultSchema: 'public' }).sync_rules.getSourceTables(); + const tablePatterns = syncRules.parsed({ defaultSchema: 'public' }).sync_rules.config.getSourceTables(); const tableInfo = await getDebugTablesInfo({ db: pool, publicationName: context.publicationName, connectionTag: context.connectionTag, tablePatterns: tablePatterns, - syncRules: syncRules.parsed({ defaultSchema: 'public' }).sync_rules + syncRules: syncRules.parsed({ defaultSchema: 'public' }).sync_rules.config }); expect(tableInfo).toEqual([ { diff --git a/packages/service-core-tests/src/test-utils/general-utils.ts b/packages/service-core-tests/src/test-utils/general-utils.ts index fb79c5fae..0c6128909 100644 --- a/packages/service-core-tests/src/test-utils/general-utils.ts +++ b/packages/service-core-tests/src/test-utils/general-utils.ts @@ -28,7 +28,7 @@ export function testRules(content: string): storage.PersistedSyncRulesContent { sync_rules: SqlSyncRules.fromYaml(content, options), slot_name: 'test', hydratedSyncRules() { - return this.sync_rules.hydrate({ hydrationState: versionedHydrationState(1) }); + return this.sync_rules.config.hydrate({ hydrationState: versionedHydrationState(1) }); } }; }, diff --git a/packages/service-core/src/api/RouteAPI.ts b/packages/service-core/src/api/RouteAPI.ts index 4c92516a3..206470f46 100644 --- a/packages/service-core/src/api/RouteAPI.ts +++ b/packages/service-core/src/api/RouteAPI.ts @@ -1,4 +1,4 @@ -import { SqlSyncRules, TablePattern } from '@powersync/service-sync-rules'; +import { BaseSyncConfig, SqlSyncRules, TablePattern } from '@powersync/service-sync-rules'; import * as types from '@powersync/service-types'; import { ParseSyncRulesOptions, SyncRulesBucketStorage } from '../storage/storage-index.js'; @@ -41,7 +41,7 @@ export interface RouteAPI { * tables to ensure syncing should function according to the input * pattern. Debug errors and warnings are reported per table. */ - getDebugTablesInfo(tablePatterns: TablePattern[], sqlSyncRules: SqlSyncRules): Promise; + getDebugTablesInfo(tablePatterns: TablePattern[], sqlSyncRules: BaseSyncConfig): Promise; /** * @returns The replication lag: that is the amount of data which has not been diff --git a/packages/service-core/src/api/diagnostics.ts b/packages/service-core/src/api/diagnostics.ts index 3950d2761..e85ed1c85 100644 --- a/packages/service-core/src/api/diagnostics.ts +++ b/packages/service-core/src/api/diagnostics.ts @@ -1,5 +1,5 @@ import { logger } from '@powersync/lib-services-framework'; -import { DEFAULT_TAG, SourceTableInterface, SqlSyncRules } from '@powersync/service-sync-rules'; +import { DEFAULT_TAG, SourceTableInterface, SqlSyncRules, SyncConfigWithErrors } from '@powersync/service-sync-rules'; import { ReplicationError, SyncRulesStatus, TableInfo } from '@powersync/service-types'; import * as storage from '../storage/storage-index.js'; @@ -41,11 +41,11 @@ export async function getSyncRulesStatus( const check_connection = options.check_connection ?? false; const now = new Date().toISOString(); - let rules: SqlSyncRules; + let parsed: SyncConfigWithErrors; let persisted: storage.PersistedSyncRules; try { persisted = sync_rules.parsed(apiHandler.getParseSyncRulesOptions()); - rules = persisted.sync_rules; + parsed = persisted.sync_rules; } catch (e) { return { content: include_content ? sync_rules.sync_rules_content : undefined, @@ -54,6 +54,7 @@ export async function getSyncRulesStatus( }; } + const { config: rules, errors: syncRuleErrors } = parsed; const sourceConfig = await apiHandler.getSourceConfig(); // This method can run under some situations if no connection is configured yet. // It will return a default tag in such a case. This default tag is not module specific. @@ -131,7 +132,7 @@ export async function getSyncRulesStatus( }); } errors.push( - ...rules.errors.map((e) => { + ...syncRuleErrors.map((e) => { return { level: e.type, message: e.message, diff --git a/packages/service-core/src/routes/endpoints/admin.ts b/packages/service-core/src/routes/endpoints/admin.ts index 6d162a072..80b07a52c 100644 --- a/packages/service-core/src/routes/endpoints/admin.ts +++ b/packages/service-core/src/routes/endpoints/admin.ts @@ -128,7 +128,7 @@ export const reprocess = routeDefinition({ } const new_rules = await activeBucketStorage.updateSyncRules({ - content: active.sync_rules.content, + content: active.sync_rules.config.content, // These sync rules already passed validation. But if the rules are not valid anymore due // to a service change, we do want to report the error here. validate: true @@ -179,7 +179,7 @@ export const validate = routeDefinition({ schema }), hydratedSyncRules() { - return this.sync_rules.hydrate(); + return this.sync_rules.config.hydrate(); } }; }, diff --git a/packages/service-core/src/routes/endpoints/sync-rules.ts b/packages/service-core/src/routes/endpoints/sync-rules.ts index e3ed68422..9d32032e8 100644 --- a/packages/service-core/src/routes/endpoints/sync-rules.ts +++ b/packages/service-core/src/routes/endpoints/sync-rules.ts @@ -169,7 +169,7 @@ export const reprocessSyncRules = routeDefinition({ } const new_rules = await activeBucketStorage.updateSyncRules({ - content: sync_rules.sync_rules.content, + content: sync_rules.sync_rules.config.content, // These sync rules already passed validation. But if the rules are not valid anymore due // to a service change, we do want to report the error here. validate: true @@ -197,14 +197,14 @@ async function debugSyncRules(apiHandler: RouteAPI, sync_rules: string) { // No schema-based validation at this point schema: undefined }); - const source_table_patterns = rules.getSourceTables(); - const resolved_tables = await apiHandler.getDebugTablesInfo(source_table_patterns, rules); + const source_table_patterns = rules.config.getSourceTables(); + const resolved_tables = await apiHandler.getDebugTablesInfo(source_table_patterns, rules.config); return { valid: true, - bucket_definitions: rules.debugRepresentation(), + bucket_definitions: rules.config.debugRepresentation(), source_tables: resolved_tables, - data_tables: rules.debugGetOutputTables() + data_tables: rules.config.debugGetOutputTables() }; } catch (e) { if (e instanceof SyncRulesErrors) { diff --git a/packages/service-core/src/storage/PersistedSyncRulesContent.ts b/packages/service-core/src/storage/PersistedSyncRulesContent.ts index dd3922764..b26921156 100644 --- a/packages/service-core/src/storage/PersistedSyncRulesContent.ts +++ b/packages/service-core/src/storage/PersistedSyncRulesContent.ts @@ -1,5 +1,6 @@ -import { SqlSyncRules, HydratedSyncRules } from '@powersync/service-sync-rules'; +import { HydratedSyncRules } from '@powersync/service-sync-rules'; import { ReplicationLock } from './ReplicationLock.js'; +import { SyncConfigWithErrors } from '@powersync/service-sync-rules/src/BaseSyncConfig.js'; export interface ParseSyncRulesOptions { defaultSchema: string; @@ -28,7 +29,7 @@ export interface PersistedSyncRulesContent { export interface PersistedSyncRules { readonly id: number; - readonly sync_rules: SqlSyncRules; + readonly sync_rules: SyncConfigWithErrors; readonly slot_name: string; hydratedSyncRules(): HydratedSyncRules; diff --git a/packages/service-core/test/src/sync/BucketChecksumState.test.ts b/packages/service-core/test/src/sync/BucketChecksumState.test.ts index 26c986521..0f7035b86 100644 --- a/packages/service-core/test/src/sync/BucketChecksumState.test.ts +++ b/packages/service-core/test/src/sync/BucketChecksumState.test.ts @@ -26,7 +26,7 @@ bucket_definitions: data: [] `, { defaultSchema: 'public' } - ).hydrate({ hydrationState: versionedHydrationState(1) }); + ).config.hydrate({ hydrationState: versionedHydrationState(1) }); // global[1] and global[2] const SYNC_RULES_GLOBAL_TWO = SqlSyncRules.fromYaml( @@ -39,7 +39,7 @@ bucket_definitions: data: [] `, { defaultSchema: 'public' } - ).hydrate({ hydrationState: versionedHydrationState(2) }); + ).config.hydrate({ hydrationState: versionedHydrationState(2) }); // by_project[n] const SYNC_RULES_DYNAMIC = SqlSyncRules.fromYaml( @@ -50,7 +50,7 @@ bucket_definitions: data: [] `, { defaultSchema: 'public' } - ).hydrate({ hydrationState: versionedHydrationState(3) }); + ).config.hydrate({ hydrationState: versionedHydrationState(3) }); const syncContext = new SyncContext({ maxBuckets: 100, @@ -615,7 +615,7 @@ config: const rules = SqlSyncRules.fromYaml(source, { defaultSchema: 'public' - }).hydrate({ hydrationState: versionedHydrationState(1) }); + }).config.hydrate({ hydrationState: versionedHydrationState(1) }); return new BucketChecksumState({ syncContext, diff --git a/packages/sync-rules/src/BaseSyncConfig.ts b/packages/sync-rules/src/BaseSyncConfig.ts new file mode 100644 index 000000000..d3e4575b9 --- /dev/null +++ b/packages/sync-rules/src/BaseSyncConfig.ts @@ -0,0 +1,131 @@ +import { BucketDataSource, BucketSource, CreateSourceParams, ParameterIndexLookupCreator } from './BucketSource.js'; +import { CompatibilityContext, CompatibilityOption } from './compatibility.js'; +import { YamlError } from './errors.js'; +import { SqlEventDescriptor } from './events/SqlEventDescriptor.js'; +import { HydratedSyncRules } from './HydratedSyncRules.js'; +import { DEFAULT_HYDRATION_STATE } from './HydrationState.js'; +import { SourceTableInterface } from './SourceTableInterface.js'; +import { SyncPlan } from './sync_plan/plan.js'; +import { TablePattern } from './TablePattern.js'; +import { SqliteInputValue, SqliteRow, SqliteValue, SyncConfig } from './types.js'; +import { applyRowContext } from './utils.js'; + +/** + * @internal Sealed class, can only be extended by `SqlSyncRules` and `PrecompiledSyncConfig`. + */ +export abstract class BaseSyncConfig { + bucketDataSources: BucketDataSource[] = []; + bucketParameterLookupSources: ParameterIndexLookupCreator[] = []; + bucketSources: BucketSource[] = []; + compatibility: CompatibilityContext = CompatibilityContext.FULL_BACKWARDS_COMPATIBILITY; + eventDescriptors: SqlEventDescriptor[] = []; + + /** + * The (YAML-based) source contents from which these sync rules have been derived. + */ + content: string; + + constructor(content: string) { + this.content = content; + } + + // Ensure asSyncConfig is not implemented externally + protected abstract asSyncConfig(): SyncConfig & this; + + /** + * If this sync config is fully described by a serializable sync plan, returns that plan. + */ + abstract extractSyncPlan(): SyncPlan | null; + + /** + * Hydrate the sync rule definitions with persisted state into runnable sync rules. + * + * @param params.hydrationState Transforms bucket ids based on persisted state. May omit for tests. + */ + hydrate(params?: CreateSourceParams): HydratedSyncRules { + let hydrationState = params?.hydrationState; + if (hydrationState == null || !this.compatibility.isEnabled(CompatibilityOption.versionedBucketIds)) { + hydrationState = DEFAULT_HYDRATION_STATE; + } + const resolvedParams = { hydrationState }; + return new HydratedSyncRules({ + definition: this.asSyncConfig(), + createParams: resolvedParams, + bucketDataSources: this.bucketDataSources, + bucketParameterIndexLookupCreators: this.bucketParameterLookupSources, + eventDescriptors: this.eventDescriptors, + compatibility: this.compatibility + }); + } + + applyRowContext( + source: SqliteRow + ): SqliteRow { + return applyRowContext(source, this.compatibility); + } + + protected writeSourceTables(sourceTables: Map): void { + for (const bucket of this.bucketDataSources) { + for (const r of bucket.getSourceTables()) { + const key = `${r.connectionTag}.${r.schema}.${r.tablePattern}`; + sourceTables.set(key, r); + } + } + for (const bucket of this.bucketParameterLookupSources) { + for (const r of bucket.getSourceTables()) { + const key = `${r.connectionTag}.${r.schema}.${r.tablePattern}`; + sourceTables.set(key, r); + } + } + } + + getSourceTables(): TablePattern[] { + const sourceTables = new Map(); + this.writeSourceTables(sourceTables); + return [...sourceTables.values()]; + } + + getEventTables(): TablePattern[] { + const eventTables = new Map(); + + if (this.eventDescriptors) { + for (const event of this.eventDescriptors) { + for (const r of event.getSourceTables()) { + const key = `${r.connectionTag}.${r.schema}.${r.tablePattern}`; + eventTables.set(key, r); + } + } + } + + return [...eventTables.values()]; + } + + tableTriggersEvent(table: SourceTableInterface): boolean { + return this.eventDescriptors.some((bucket) => bucket.tableTriggersEvent(table)); + } + + tableSyncsData(table: SourceTableInterface): boolean { + return this.bucketDataSources.some((b) => b.tableSyncsData(table)); + } + + tableSyncsParameters(table: SourceTableInterface): boolean { + return this.bucketParameterLookupSources.some((b) => b.tableSyncsParameters(table)); + } + + debugGetOutputTables() { + let result: Record = {}; + for (let bucket of this.bucketDataSources) { + bucket.debugWriteOutputTables(result); + } + return result; + } + + debugRepresentation() { + return this.bucketSources.map((rules) => rules.debugRepresentation()); + } +} + +export interface SyncConfigWithErrors { + config: SyncConfig; + errors: YamlError[]; +} diff --git a/packages/sync-rules/src/HydratedSyncRules.ts b/packages/sync-rules/src/HydratedSyncRules.ts index 36ccb8a1f..7cabaeb37 100644 --- a/packages/sync-rules/src/HydratedSyncRules.ts +++ b/packages/sync-rules/src/HydratedSyncRules.ts @@ -1,10 +1,7 @@ -import { Scope } from 'ajv/dist/compile/codegen/scope.js'; import { BucketDataSource, CreateSourceParams, HydratedBucketSource } from './BucketSource.js'; -import { BucketDataScope, ParameterLookupScope } from './HydrationState.js'; import { ParameterIndexLookupCreator, BucketParameterQuerier, - buildBucketName, CompatibilityContext, EvaluatedParameters, EvaluatedRow, @@ -23,7 +20,7 @@ import { SqlEventDescriptor, SqliteInputValue, SqliteValue, - SqlSyncRules + SyncConfig } from './index.js'; import { SourceTableInterface } from './SourceTableInterface.js'; import { EvaluatedParametersResult, EvaluateRowOptions, EvaluationResult, SqliteRow } from './types.js'; @@ -37,13 +34,13 @@ export class HydratedSyncRules { eventDescriptors: SqlEventDescriptor[] = []; compatibility: CompatibilityContext = CompatibilityContext.FULL_BACKWARDS_COMPATIBILITY; - readonly definition: SqlSyncRules; + readonly definition: SyncConfig; private readonly innerEvaluateRow: ScopedEvaluateRow; private readonly innerEvaluateParameterRow: ScopedEvaluateParameterRow; constructor(params: { - definition: SqlSyncRules; + definition: SyncConfig; createParams: CreateSourceParams; bucketDataSources: BucketDataSource[]; bucketParameterIndexLookupCreators: ParameterIndexLookupCreator[]; diff --git a/packages/sync-rules/src/SqlSyncRules.ts b/packages/sync-rules/src/SqlSyncRules.ts index 5a6adfd24..c6cde3411 100644 --- a/packages/sync-rules/src/SqlSyncRules.ts +++ b/packages/sync-rules/src/SqlSyncRules.ts @@ -1,7 +1,6 @@ import { isScalar, LineCounter, parseDocument, Scalar, YAMLMap, YAMLSeq } from 'yaml'; import { isValidPriority } from './BucketDescription.js'; import { BucketParameterQuerier, QuerierError } from './BucketParameterQuerier.js'; -import { BucketDataSource, BucketSource, CreateSourceParams, ParameterIndexLookupCreator } from './BucketSource.js'; import { CompatibilityContext, CompatibilityEdition, @@ -10,24 +9,13 @@ import { } from './compatibility.js'; import { SqlRuleError, SyncRulesErrors, YamlError } from './errors.js'; import { SqlEventDescriptor } from './events/SqlEventDescriptor.js'; -import { HydratedSyncRules } from './HydratedSyncRules.js'; -import { DEFAULT_HYDRATION_STATE } from './HydrationState.js'; import { validateSyncRulesSchema } from './json_schema.js'; -import { SourceTableInterface } from './SourceTableInterface.js'; import { QueryParseResult, SqlBucketDescriptor } from './SqlBucketDescriptor.js'; import { syncStreamFromSql } from './streams/from_sql.js'; import { TablePattern } from './TablePattern.js'; -import { - QueryParseOptions, - RequestParameters, - SourceSchema, - SqliteInputValue, - SqliteJsonRow, - SqliteRow, - SqliteValue, - StreamParseOptions -} from './types.js'; -import { applyRowContext } from './utils.js'; +import { QueryParseOptions, RequestParameters, SourceSchema, SqliteJsonRow, StreamParseOptions } from './types.js'; +import { BaseSyncConfig, SyncConfigWithErrors } from './BaseSyncConfig.js'; +import { SyncPlan } from './sync_plan/plan.js'; const ACCEPT_POTENTIALLY_DANGEROUS_QUERIES = Symbol('ACCEPT_POTENTIALLY_DANGEROUS_QUERIES'); @@ -82,17 +70,9 @@ export interface GetBucketParameterQuerierResult { errors: QuerierError[]; } -export class SqlSyncRules { - bucketDataSources: BucketDataSource[] = []; - bucketParameterLookupSources: ParameterIndexLookupCreator[] = []; - bucketSources: BucketSource[] = []; - - eventDescriptors: SqlEventDescriptor[] = []; - compatibility: CompatibilityContext = CompatibilityContext.FULL_BACKWARDS_COMPATIBILITY; - - content: string; - - errors: YamlError[] = []; +export class SqlSyncRules extends BaseSyncConfig { + // The errors here are internal only, consumers should use SyncConfigWithErrors instead. + readonly #errors: YamlError[] = []; static validate(yaml: string, options: SyncRulesOptions): YamlError[] { try { @@ -109,7 +89,12 @@ export class SqlSyncRules { } } - static fromYaml(yaml: string, options: SyncRulesOptions) { + static fromYaml(yaml: string, options: SyncRulesOptions): SyncConfigWithErrors { + const config = this.#fromYaml(yaml, options); + return { config, errors: config.#errors }; + } + + static #fromYaml(yaml: string, options: SyncRulesOptions): SqlSyncRules { const throwOnError = options.throwOnError ?? true; const lineCounter = new LineCounter(); @@ -130,7 +115,7 @@ export class SqlSyncRules { const rules = new SqlSyncRules(yaml); if (parsed.errors.length > 0) { - rules.errors.push( + rules.#errors.push( ...parsed.errors.map((error) => { return new YamlError(error); }) @@ -167,7 +152,7 @@ export class SqlSyncRules { compatibility = new CompatibilityContext({ edition, overrides: options, maxTimeValuePrecision }); if (maxTimeValuePrecision && !compatibility.isEnabled(CompatibilityOption.timestampsIso8601)) { - rules.errors.push( + rules.#errors.push( new YamlError(new Error(`'timestamp_max_precision' requires 'timestamps_iso8601' to be enabled.`)) ); } @@ -181,7 +166,7 @@ export class SqlSyncRules { const definitionNames = new Set(); const checkUniqueName = (name: string, literal: Scalar) => { if (definitionNames.has(name)) { - rules.errors.push(this.tokenError(literal, 'Duplicate stream or bucket definition.')); + rules.#errors.push(this.tokenError(literal, 'Duplicate stream or bucket definition.')); return false; } @@ -190,7 +175,7 @@ export class SqlSyncRules { }; if (bucketMap == null && streamMap == null) { - rules.errors.push(new YamlError(new Error(`'bucket_definitions' or 'streams' is required`))); + rules.#errors.push(new YamlError(new Error(`'bucket_definitions' or 'streams' is required`))); if (throwOnError) { rules.throwOnError(); @@ -206,7 +191,7 @@ export class SqlSyncRules { } if (value == null || !(value instanceof YAMLMap)) { - rules.errors.push(this.tokenError(keyScalar, `'${key}' bucket definition must be an object`)); + rules.#errors.push(this.tokenError(keyScalar, `'${key}' bucket definition must be an object`)); continue; } @@ -240,7 +225,7 @@ export class SqlSyncRules { } if (!(dataQueries instanceof YAMLSeq)) { - rules.errors.push(this.tokenError(dataQueries ?? value, `'data' must be an array`)); + rules.#errors.push(this.tokenError(dataQueries ?? value, `'data' must be an array`)); continue; } for (let query of dataQueries.items) { @@ -285,7 +270,7 @@ export class SqlSyncRules { }; }); } else { - rules.errors.push(this.tokenError(data, 'Must be a string.')); + rules.#errors.push(this.tokenError(data, 'Must be a string.')); continue; } } @@ -295,20 +280,20 @@ export class SqlSyncRules { const { key, value } = event as { key: Scalar; value: YAMLSeq }; if (false == value instanceof YAMLMap) { - rules.errors.push(new YamlError(new Error(`Event definitions must be objects.`))); + rules.#errors.push(new YamlError(new Error(`Event definitions must be objects.`))); continue; } const payloads = value.get('payloads') as YAMLSeq; if (false == payloads instanceof YAMLSeq) { - rules.errors.push(new YamlError(new Error(`Event definition payloads must be an array.`))); + rules.#errors.push(new YamlError(new Error(`Event definition payloads must be an array.`))); continue; } const eventDescriptor = new SqlEventDescriptor(key.toString(), compatibility); for (let item of payloads.items) { if (!isScalar(item)) { - rules.errors.push(new YamlError(new Error(`Payload queries for events must be scalar.`))); + rules.#errors.push(new YamlError(new Error(`Payload queries for events must be scalar.`))); continue; } rules.withScalar(item, (q) => { @@ -323,7 +308,7 @@ export class SqlSyncRules { // Since these errors don't contain line numbers, do this last. const valid = validateSyncRulesSchema(parsed.toJSON()); if (!valid) { - rules.errors.push( + rules.#errors.push( ...validateSyncRulesSchema.errors!.map((e: any) => { return new YamlError(e); }) @@ -337,9 +322,13 @@ export class SqlSyncRules { return rules; } + protected asSyncConfig(): this { + return this; + } + throwOnError() { - if (this.errors.filter((e) => e.type != 'warning').length > 0) { - throw new SyncRulesErrors(this.errors); + if (this.#errors.filter((e) => e.type != 'warning').length > 0) { + throw new SyncRulesErrors(this.#errors); } } @@ -384,56 +373,18 @@ export class SqlSyncRules { } const pos = { start: offset, end }; - this.errors.push(new YamlError(err, pos)); + this.#errors.push(new YamlError(err, pos)); } return result; } - constructor(content: string) { - this.content = content; + extractSyncPlan(): SyncPlan | null { + // TODO: Once we use the new compiler to parse sync rules from YAML, we could store the plan here to extract it. + return null; } - /** - * Hydrate the sync rule definitions with persisted state into runnable sync rules. - * - * @param params.hydrationState Transforms bucket ids based on persisted state. May omit for tests. - */ - hydrate(params?: CreateSourceParams): HydratedSyncRules { - let hydrationState = params?.hydrationState; - if (hydrationState == null || !this.compatibility.isEnabled(CompatibilityOption.versionedBucketIds)) { - hydrationState = DEFAULT_HYDRATION_STATE; - } - const resolvedParams = { hydrationState }; - return new HydratedSyncRules({ - definition: this, - createParams: resolvedParams, - bucketDataSources: this.bucketDataSources, - bucketParameterIndexLookupCreators: this.bucketParameterLookupSources, - eventDescriptors: this.eventDescriptors, - compatibility: this.compatibility - }); - } - - applyRowContext( - source: SqliteRow - ): SqliteRow { - return applyRowContext(source, this.compatibility); - } - - getSourceTables(): TablePattern[] { - const sourceTables = new Map(); - for (const bucket of this.bucketDataSources) { - for (const r of bucket.getSourceTables()) { - const key = `${r.connectionTag}.${r.schema}.${r.tablePattern}`; - sourceTables.set(key, r); - } - } - for (const bucket of this.bucketParameterLookupSources) { - for (const r of bucket.getSourceTables()) { - const key = `${r.connectionTag}.${r.schema}.${r.tablePattern}`; - sourceTables.set(key, r); - } - } + protected writeSourceTables(sourceTables: Map): void { + super.writeSourceTables(sourceTables); for (const event of this.eventDescriptors) { for (const r of event.getSourceTables()) { @@ -441,51 +392,13 @@ export class SqlSyncRules { sourceTables.set(key, r); } } - - return [...sourceTables.values()]; - } - - getEventTables(): TablePattern[] { - const eventTables = new Map(); - - for (const event of this.eventDescriptors) { - for (const r of event.getSourceTables()) { - const key = `${r.connectionTag}.${r.schema}.${r.tablePattern}`; - eventTables.set(key, r); - } - } - return [...eventTables.values()]; - } - - tableTriggersEvent(table: SourceTableInterface): boolean { - return this.eventDescriptors.some((bucket) => bucket.tableTriggersEvent(table)); - } - - tableSyncsData(table: SourceTableInterface): boolean { - return this.bucketDataSources.some((b) => b.tableSyncsData(table)); - } - - tableSyncsParameters(table: SourceTableInterface): boolean { - return this.bucketParameterLookupSources.some((b) => b.tableSyncsParameters(table)); - } - - debugGetOutputTables() { - let result: Record = {}; - for (let bucket of this.bucketDataSources) { - bucket.debugWriteOutputTables(result); - } - return result; - } - - debugRepresentation() { - return this.bucketSources.map((rules) => rules.debugRepresentation()); } private parsePriority(value: YAMLMap) { if (value.has('priority')) { const priorityValue = value.get('priority', true)!; if (typeof priorityValue.value != 'number' || !isValidPriority(priorityValue.value)) { - this.errors.push( + this.#errors.push( SqlSyncRules.tokenError(priorityValue, 'Invalid priority, expected a number between 0 and 3 (inclusive).') ); } else { diff --git a/packages/sync-rules/src/compatibility.ts b/packages/sync-rules/src/compatibility.ts index 72b974e92..e11489ec4 100644 --- a/packages/sync-rules/src/compatibility.ts +++ b/packages/sync-rules/src/compatibility.ts @@ -105,6 +105,46 @@ export class CompatibilityContext { return this.overrides.get(option) ?? option.fixedIn <= this.edition; } + serialize(): SerializedCompatibilityContext { + const serialized: SerializedCompatibilityContext = { + edition: this.edition, + overrides: {} + }; + + this.overrides.forEach((enabled, key) => (serialized.overrides[key.name] = enabled)); + if (this.maxTimeValuePrecision) { + serialized.maxTimeValuePrecision = this.maxTimeValuePrecision.subSecondDigits; + } + + return serialized; + } + + static deserialize(serialized: SerializedCompatibilityContext): CompatibilityContext { + const overrides = new Map(); + for (const [option, enabled] of Object.entries(serialized.overrides)) { + const knownOption = CompatibilityOption.byName[option]; + if (knownOption) { + overrides.set(knownOption, enabled); + } + } + + let maxTimeValuePrecision: TimeValuePrecision | undefined; + if (serialized.maxTimeValuePrecision) { + for (const option of Object.values(TimeValuePrecision.byName)) { + if (option.subSecondDigits == serialized.maxTimeValuePrecision) { + maxTimeValuePrecision = option; + break; + } + } + } + + return new CompatibilityContext({ + edition: serialized.edition, + overrides, + maxTimeValuePrecision + }); + } + /** * A {@link CompatibilityContext} in which no fixes are applied. */ @@ -112,3 +152,9 @@ export class CompatibilityContext { edition: CompatibilityEdition.LEGACY }); } + +export interface SerializedCompatibilityContext { + edition: number; + overrides: Record; + maxTimeValuePrecision?: number; +} diff --git a/packages/sync-rules/src/index.ts b/packages/sync-rules/src/index.ts index 11caf762b..1fdc81424 100644 --- a/packages/sync-rules/src/index.ts +++ b/packages/sync-rules/src/index.ts @@ -1,3 +1,4 @@ +export * from './BaseSyncConfig.js'; export * from './BucketDescription.js'; export * from './BucketParameterQuerier.js'; export * from './BucketSource.js'; @@ -33,6 +34,6 @@ export * from './HydratedSyncRules.js'; export * from './compiler/compiler.js'; export * from './sync_plan/plan.js'; export { serializeSyncPlan, deserializeSyncPlan } from './sync_plan/serialize.js'; -export { addPrecompiledSyncPlanToRules } from './sync_plan/evaluator/index.js'; +export { PrecompiledSyncConfig } from './sync_plan/evaluator/index.js'; export { javaScriptExpressionEngine } from './sync_plan/engine/javascript.js'; export { nodeSqliteExpressionEngine } from './sync_plan/engine/sqlite.js'; diff --git a/packages/sync-rules/src/schema-generators/DartSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/DartSchemaGenerator.ts index 3079bfb4c..5b3795c18 100644 --- a/packages/sync-rules/src/schema-generators/DartSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/DartSchemaGenerator.ts @@ -1,5 +1,5 @@ +import { BaseSyncConfig } from '../BaseSyncConfig.js'; import { ColumnDefinition, ExpressionType } from '../ExpressionType.js'; -import { SqlSyncRules } from '../SqlSyncRules.js'; import { SourceSchema } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; @@ -9,7 +9,7 @@ export class DartSchemaGenerator extends SchemaGenerator { readonly mediaType = 'text/x-dart'; readonly fileName = 'schema.dart'; - generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { const tables = super.getAllTables(source, schema); return `Schema([ @@ -51,7 +51,7 @@ export class DartFlutterFlowSchemaGenerator extends SchemaGenerator { readonly mediaType = 'application/json'; readonly fileName = 'schema.json'; - generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { const serializedTables = this.getAllTables(source, schema).map((e) => this.generateTable(e.name, e.columns)); // Not all FlutterFlow apps will use the attachments queue table, but it needs to be part of the app schema if used // and does no harm otherwise. So, we just include it by default. diff --git a/packages/sync-rules/src/schema-generators/DotNetSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/DotNetSchemaGenerator.ts index b18f7844b..21983a5ae 100644 --- a/packages/sync-rules/src/schema-generators/DotNetSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/DotNetSchemaGenerator.ts @@ -1,5 +1,5 @@ +import { BaseSyncConfig } from '../BaseSyncConfig.js'; import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js'; -import { SqlSyncRules } from '../SqlSyncRules.js'; import { SourceSchema } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; @@ -9,7 +9,7 @@ export class DotNetSchemaGenerator extends SchemaGenerator { readonly mediaType = 'text/x-csharp'; readonly fileName = 'Schema.cs'; - generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { const tables = super.getAllTables(source, schema); return `using PowerSync.Common.DB.Schema; diff --git a/packages/sync-rules/src/schema-generators/JsLegacySchemaGenerator.ts b/packages/sync-rules/src/schema-generators/JsLegacySchemaGenerator.ts index b1b67f16a..f27431420 100644 --- a/packages/sync-rules/src/schema-generators/JsLegacySchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/JsLegacySchemaGenerator.ts @@ -1,5 +1,5 @@ +import { BaseSyncConfig } from '../BaseSyncConfig.js'; import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js'; -import { SqlSyncRules } from '../SqlSyncRules.js'; import { SourceSchema } from '../types.js'; import { SchemaGenerator } from './SchemaGenerator.js'; @@ -9,7 +9,7 @@ export class JsLegacySchemaGenerator extends SchemaGenerator { readonly mediaType = 'text/javascript'; readonly fileName = 'schema.js'; - generate(source: SqlSyncRules, schema: SourceSchema): string { + generate(source: BaseSyncConfig, schema: SourceSchema): string { const tables = super.getAllTables(source, schema); return `new Schema([ diff --git a/packages/sync-rules/src/schema-generators/KotlinSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/KotlinSchemaGenerator.ts index 64082f3b1..90ded76b0 100644 --- a/packages/sync-rules/src/schema-generators/KotlinSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/KotlinSchemaGenerator.ts @@ -1,5 +1,5 @@ +import { BaseSyncConfig } from '../BaseSyncConfig.js'; import { ColumnDefinition } from '../ExpressionType.js'; -import { SqlSyncRules } from '../SqlSyncRules.js'; import { SourceSchema } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; @@ -9,7 +9,7 @@ export class KotlinSchemaGenerator extends SchemaGenerator { readonly mediaType = 'text/x-kotlin'; readonly fileName = 'schema.kt'; - generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { const tables = super.getAllTables(source, schema); return `import com.powersync.db.schema.Column diff --git a/packages/sync-rules/src/schema-generators/RoomSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/RoomSchemaGenerator.ts index 082f89cf2..18c1bec9a 100644 --- a/packages/sync-rules/src/schema-generators/RoomSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/RoomSchemaGenerator.ts @@ -1,4 +1,4 @@ -import { SqlSyncRules } from '../SqlSyncRules.js'; +import { BaseSyncConfig } from '../BaseSyncConfig.js'; import { SourceSchema } from '../types.js'; import { SchemaGenerator } from './SchemaGenerator.js'; @@ -11,7 +11,7 @@ export class RoomSchemaGenerator extends SchemaGenerator { readonly mediaType = 'text/x-kotlin'; readonly fileName = 'Entities.kt'; - generate(source: SqlSyncRules, schema: SourceSchema): string { + generate(source: BaseSyncConfig, schema: SourceSchema): string { let buffer = `import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey diff --git a/packages/sync-rules/src/schema-generators/SchemaGenerator.ts b/packages/sync-rules/src/schema-generators/SchemaGenerator.ts index 38d0f9db5..c750b7974 100644 --- a/packages/sync-rules/src/schema-generators/SchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/SchemaGenerator.ts @@ -1,5 +1,5 @@ +import { BaseSyncConfig } from '../BaseSyncConfig.js'; import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js'; -import { SqlSyncRules } from '../SqlSyncRules.js'; import { SourceSchema } from '../types.js'; export interface GenerateSchemaOptions { @@ -7,7 +7,7 @@ export interface GenerateSchemaOptions { } export abstract class SchemaGenerator { - protected getAllTables(source: SqlSyncRules, schema: SourceSchema) { + protected getAllTables(source: BaseSyncConfig, schema: SourceSchema) { let tables: Record> = {}; for (let descriptor of source.bucketDataSources) { @@ -27,7 +27,7 @@ export abstract class SchemaGenerator { abstract readonly mediaType: string; abstract readonly fileName: string; - abstract generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string; + abstract generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string; /** * @param def The column definition to generate the type for. diff --git a/packages/sync-rules/src/schema-generators/SqlSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/SqlSchemaGenerator.ts index 66a826d08..5a54f90dc 100644 --- a/packages/sync-rules/src/schema-generators/SqlSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/SqlSchemaGenerator.ts @@ -1,4 +1,4 @@ -import { SqlSyncRules } from '../SqlSyncRules.js'; +import { BaseSyncConfig } from '../BaseSyncConfig.js'; import { SourceSchema } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; @@ -17,7 +17,7 @@ export class SqlSchemaGenerator extends SchemaGenerator { super(); } - generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { let buffer = '-- Note: These definitions are only used to generate typed code. PowerSync manages the database schema.\n'; const tables = super.getAllTables(source, schema); diff --git a/packages/sync-rules/src/schema-generators/SwiftSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/SwiftSchemaGenerator.ts index 8e1b7acae..ad764c6e4 100644 --- a/packages/sync-rules/src/schema-generators/SwiftSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/SwiftSchemaGenerator.ts @@ -1,6 +1,5 @@ import { ColumnDefinition } from '../ExpressionType.js'; -import { SqlSyncRules } from '../SqlSyncRules.js'; -import { SourceSchema } from '../types.js'; +import { SourceSchema, SyncConfig } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; export class SwiftSchemaGenerator extends SchemaGenerator { @@ -9,7 +8,7 @@ export class SwiftSchemaGenerator extends SchemaGenerator { readonly mediaType = 'text/x-swift'; readonly fileName = 'schema.swift'; - generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: SyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { const tables = super.getAllTables(source, schema); return `import PowerSync diff --git a/packages/sync-rules/src/schema-generators/TsSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/TsSchemaGenerator.ts index 28a5a93cb..4a6a8747d 100644 --- a/packages/sync-rules/src/schema-generators/TsSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/TsSchemaGenerator.ts @@ -1,6 +1,5 @@ import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js'; -import { SqlSyncRules } from '../SqlSyncRules.js'; -import { SourceSchema } from '../types.js'; +import { SourceSchema, SyncConfig } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; export interface TsSchemaGeneratorOptions { @@ -47,7 +46,7 @@ export class TsSchemaGenerator extends SchemaGenerator { } } - generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: SyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { const tables = super.getAllTables(source, schema); return `${this.generateImports()} diff --git a/packages/sync-rules/src/sync_plan/evaluator/index.ts b/packages/sync-rules/src/sync_plan/evaluator/index.ts index dd3951e7c..30a5babcf 100644 --- a/packages/sync-rules/src/sync_plan/evaluator/index.ts +++ b/packages/sync-rules/src/sync_plan/evaluator/index.ts @@ -1,40 +1,58 @@ -import { SqlSyncRules } from '../../SqlSyncRules.js'; import * as plan from '../plan.js'; import { PreparedStreamBucketDataSource } from './bucket_data_source.js'; import { PreparedParameterIndexLookupCreator } from './parameter_index_lookup_creator.js'; import { StreamBucketSource, StreamInput } from './bucket_source.js'; import { ScalarExpressionEngine } from '../engine/scalar_expression_engine.js'; +import { BaseSyncConfig } from '../../BaseSyncConfig.js'; export interface StreamEvaluationContext { engine: ScalarExpressionEngine; + + /** + * Source contents that were used to compile the sync plan. + * + * This is not used to evaluate rows, but required for all sync config instances. + */ + sourceText: string; } -export function addPrecompiledSyncPlanToRules( - plan: plan.SyncPlan, - rules: SqlSyncRules, - context: StreamEvaluationContext -) { - const preparedBuckets = new Map(); - const preparedLookups = new Map(); - - for (const bucket of plan.buckets) { - const prepared = new PreparedStreamBucketDataSource(bucket, context); - preparedBuckets.set(bucket, prepared); - rules.bucketDataSources.push(prepared); +export class PrecompiledSyncConfig extends BaseSyncConfig { + constructor( + private readonly plan: plan.SyncPlan, + context: StreamEvaluationContext + ) { + super(context.sourceText); + + const preparedBuckets = new Map(); + const preparedLookups = new Map(); + + for (const bucket of plan.buckets) { + const prepared = new PreparedStreamBucketDataSource(bucket, context); + preparedBuckets.set(bucket, prepared); + this.bucketDataSources.push(prepared); + } + + for (const parameter of plan.parameterIndexes) { + const prepared = new PreparedParameterIndexLookupCreator(parameter, context); + preparedLookups.set(parameter, prepared); + this.bucketParameterLookupSources.push(prepared); + } + + const streamInput: StreamInput = { + ...context, + preparedBuckets, + preparedLookups + }; + for (const stream of plan.streams) { + this.bucketSources.push(new StreamBucketSource(stream, streamInput)); + } } - for (const parameter of plan.parameterIndexes) { - const prepared = new PreparedParameterIndexLookupCreator(parameter, context); - preparedLookups.set(parameter, prepared); - rules.bucketParameterLookupSources.push(prepared); + protected asSyncConfig(): this { + return this; } - const streamInput: StreamInput = { - ...context, - preparedBuckets, - preparedLookups - }; - for (const stream of plan.streams) { - rules.bucketSources.push(new StreamBucketSource(stream, streamInput)); + extractSyncPlan(): plan.SyncPlan | null { + return this.plan; } } diff --git a/packages/sync-rules/src/types.ts b/packages/sync-rules/src/types.ts index 2967ac37f..b0b8c130c 100644 --- a/packages/sync-rules/src/types.ts +++ b/packages/sync-rules/src/types.ts @@ -5,10 +5,13 @@ import { CompatibilityContext } from './compatibility.js'; import { ColumnDefinition } from './ExpressionType.js'; import { RequestFunctionCall } from './request_functions.js'; import { SourceTableInterface } from './SourceTableInterface.js'; -import { SyncRulesOptions } from './SqlSyncRules.js'; +import { SqlSyncRules, SyncRulesOptions } from './SqlSyncRules.js'; import { TablePattern } from './TablePattern.js'; import { CustomSqliteValue } from './types/custom_sqlite_value.js'; import { toSyncRulesParameters } from './utils.js'; +import { PrecompiledSyncConfig } from './sync_plan/evaluator/index.js'; + +export type SyncConfig = SqlSyncRules | PrecompiledSyncConfig; export interface QueryParseOptions extends SyncRulesOptions { accept_potentially_dangerous_queries?: boolean; diff --git a/packages/sync-rules/test/src/compatibility.test.ts b/packages/sync-rules/test/src/compatibility.test.ts index 11068de01..bb9496349 100644 --- a/packages/sync-rules/test/src/compatibility.test.ts +++ b/packages/sync-rules/test/src/compatibility.test.ts @@ -20,7 +20,7 @@ bucket_definitions: - SELECT id, description FROM assets `, PARSE_OPTIONS - ).hydrate(); + ).config.hydrate(); expect( rules.evaluateRow({ @@ -47,7 +47,7 @@ config: timestamps_iso8601: true `, PARSE_OPTIONS - ).hydrate(); + ).config.hydrate(); expect( rules.evaluateRow({ @@ -74,7 +74,7 @@ config: edition: 2 `, PARSE_OPTIONS - ).hydrate({ hydrationState: versionedHydrationState(1) }); + ).config.hydrate({ hydrationState: versionedHydrationState(1) }); expect( rules.evaluateRow({ @@ -112,7 +112,7 @@ config: versioned_bucket_ids: false `, PARSE_OPTIONS - ).hydrate({ hydrationState: versionedHydrationState(1) }); + ).config.hydrate({ hydrationState: versionedHydrationState(1) }); expect( rules.evaluateRow({ @@ -149,7 +149,7 @@ config: versioned_bucket_ids: true `, PARSE_OPTIONS - ).hydrate({ hydrationState: versionedHydrationState(1) }); + ).config.hydrate({ hydrationState: versionedHydrationState(1) }); expect( rules.evaluateRow({ @@ -173,7 +173,7 @@ config: edition: 2 `, PARSE_OPTIONS - ).hydrate({ hydrationState: versionedHydrationState(1) }); + ).config.hydrate({ hydrationState: versionedHydrationState(1) }); expect( rules.evaluateRow({ @@ -203,7 +203,7 @@ bucket_definitions: - SELECT id, description ->> 'foo.bar' AS "desc" FROM assets `, PARSE_OPTIONS - ).hydrate(); + ).config.hydrate(); expect( rules.evaluateRow({ @@ -227,7 +227,7 @@ config: fixed_json_extract: true `, PARSE_OPTIONS - ).hydrate(); + ).config.hydrate(); expect( rules.evaluateRow({ @@ -282,7 +282,7 @@ config: `; } - const rules = SqlSyncRules.fromYaml(syncRules, PARSE_OPTIONS).hydrate({ + const rules = SqlSyncRules.fromYaml(syncRules, PARSE_OPTIONS).config.hydrate({ hydrationState: versionedHydrationState(1) }); expect( @@ -337,7 +337,7 @@ config: }); test('can set max precision', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: diff --git a/packages/sync-rules/test/src/generate_schema.test.ts b/packages/sync-rules/test/src/generate_schema.test.ts index a041f8483..6651aad59 100644 --- a/packages/sync-rules/test/src/generate_schema.test.ts +++ b/packages/sync-rules/test/src/generate_schema.test.ts @@ -40,7 +40,7 @@ describe('schema generation', () => { } ]); - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: diff --git a/packages/sync-rules/test/src/sync_plan/evaluator/output_schema.test.ts b/packages/sync-rules/test/src/sync_plan/evaluator/output_schema.test.ts index 4ef456247..981eabb66 100644 --- a/packages/sync-rules/test/src/sync_plan/evaluator/output_schema.test.ts +++ b/packages/sync-rules/test/src/sync_plan/evaluator/output_schema.test.ts @@ -1,15 +1,14 @@ import { describe, expect, test } from 'vitest'; import { - addPrecompiledSyncPlanToRules, ColumnDefinition, sqlTypeName, CompatibilityContext, DEFAULT_TAG, javaScriptExpressionEngine, SourceTableDefinition, - SqlSyncRules, StaticSchema, - TablePattern + TablePattern, + PrecompiledSyncConfig } from '../../../../src/index.js'; import { compileToSyncPlanWithoutErrors } from '../../compiler/utils.js'; @@ -37,11 +36,10 @@ describe('schema inference', () => { function generateSchema(...queries: string[]) { const plan = compileToSyncPlanWithoutErrors([{ name: 'stream', queries }]); - const rules = new SqlSyncRules(''); - - addPrecompiledSyncPlanToRules(plan, rules, { + const rules = new PrecompiledSyncConfig(plan, { // Engine isn't actually used here, but required to load sync plan - engine: javaScriptExpressionEngine(CompatibilityContext.FULL_BACKWARDS_COMPATIBILITY) + engine: javaScriptExpressionEngine(CompatibilityContext.FULL_BACKWARDS_COMPATIBILITY), + sourceText: '' }); const outputSchema: Record> = {}; diff --git a/packages/sync-rules/test/src/sync_plan/evaluator/utils.ts b/packages/sync-rules/test/src/sync_plan/evaluator/utils.ts index 6f8c8cc6b..0c1aa5021 100644 --- a/packages/sync-rules/test/src/sync_plan/evaluator/utils.ts +++ b/packages/sync-rules/test/src/sync_plan/evaluator/utils.ts @@ -1,11 +1,11 @@ import { HydratedSyncRules, - SqlSyncRules, versionedHydrationState, - addPrecompiledSyncPlanToRules, javaScriptExpressionEngine, CompatibilityContext, - CompatibilityEdition + CompatibilityEdition, + SyncConfig, + PrecompiledSyncConfig } from '../../../../src/index.js'; import { compileToSyncPlanWithoutErrors, SyncStreamInput } from '../../compiler/utils.js'; import { test } from 'vitest'; @@ -13,7 +13,7 @@ import { ScalarExpressionEngine } from '../../../../src/sync_plan/engine/scalar_ interface SyncTest { engine: ScalarExpressionEngine; - prepareWithoutHydration(inputs: SyncStreamInput[]): SqlSyncRules; + prepareWithoutHydration(inputs: SyncStreamInput[]): SyncConfig; prepareSyncStreams(inputs: SyncStreamInput[]): HydratedSyncRules; } @@ -25,10 +25,7 @@ export const syncTest = test.extend<{ sync: SyncTest }>({ engine, prepareWithoutHydration: (inputs) => { const plan = compileToSyncPlanWithoutErrors(inputs); - const rules = new SqlSyncRules(''); - - addPrecompiledSyncPlanToRules(plan, rules, { engine }); - return rules; + return new PrecompiledSyncConfig(plan, { engine, sourceText: '' }); }, prepareSyncStreams(inputs) { return this.prepareWithoutHydration(inputs).hydrate({ hydrationState: versionedHydrationState(1) }); diff --git a/packages/sync-rules/test/src/sync_rules.test.ts b/packages/sync-rules/test/src/sync_rules.test.ts index 36b21b995..331a15d57 100644 --- a/packages/sync-rules/test/src/sync_rules.test.ts +++ b/packages/sync-rules/test/src/sync_rules.test.ts @@ -19,13 +19,13 @@ describe('sync rules', () => { const hydrationParams: CreateSourceParams = { hydrationState: DEFAULT_HYDRATION_STATE }; test('parse empty sync rules', () => { - const rules = SqlSyncRules.fromYaml('bucket_definitions: {}', PARSE_OPTIONS); + const { config: rules } = SqlSyncRules.fromYaml('bucket_definitions: {}', PARSE_OPTIONS); expect(rules.bucketParameterLookupSources).toEqual([]); expect(rules.bucketDataSources).toEqual([]); }); test('parse global sync rules', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -64,7 +64,7 @@ bucket_definitions: }); test('parse global sync rules with filter', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -97,7 +97,7 @@ bucket_definitions: }); test('parse global sync rules with table filter', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -117,7 +117,7 @@ bucket_definitions: }); test('parse bucket with parameters', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -164,7 +164,7 @@ bucket_definitions: test('bucket with parameters with custom hydrationState', async () => { // "end-to-end" test with custom hydrationState. // We don't test complex details here, but do cover bucket names and parameter lookup scope. - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` config: edition: 2 @@ -232,7 +232,7 @@ bucket_definitions: }); test('parse bucket with parameters and OR condition', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -373,7 +373,7 @@ bucket_definitions: }); test('transforming things', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -411,7 +411,7 @@ bucket_definitions: test('transforming things with upper-case functions', () => { // Testing that we can use different case for the function names - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -448,7 +448,7 @@ bucket_definitions: }); test('transforming json', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -481,7 +481,7 @@ bucket_definitions: }); test('IN json', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -525,7 +525,7 @@ bucket_definitions: }); test('direct boolean param', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -604,7 +604,7 @@ bucket_definitions: }); test('some math', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -631,7 +631,7 @@ bucket_definitions: }); test('bucket with static numeric parameters', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -664,7 +664,7 @@ bucket_definitions: }); test('static parameter query with function on token_parameter', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules, errors } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -673,7 +673,7 @@ bucket_definitions: `, PARSE_OPTIONS ); - expect(rules.errors).toEqual([]); + expect(errors).toEqual([]); const hydrated = rules.hydrate(hydrationParams); expect(hydrated.getBucketParameterQuerier(normalizeQuerierOptions({ user_id: 'test' })).querier).toMatchObject({ staticBuckets: [{ bucket: 'mybucket["TEST"]', priority: 3 }], @@ -682,7 +682,7 @@ bucket_definitions: }); test('custom table and id', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -724,7 +724,7 @@ bucket_definitions: }); test('wildcard table', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -758,7 +758,7 @@ bucket_definitions: }); test('wildcard without alias', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -790,7 +790,7 @@ bucket_definitions: }); test('should filter schemas', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -881,7 +881,7 @@ bucket_definitions: }); test('dangerous query errors', () => { - const rules = SqlSyncRules.fromYaml( + const { errors } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -891,7 +891,7 @@ bucket_definitions: { schema: BASIC_SCHEMA, ...PARSE_OPTIONS } ); - expect(rules.errors).toMatchObject([ + expect(errors).toMatchObject([ { message: "Potentially dangerous query based on parameters set by the client. The client can send any value for these parameters so it's not a good place to do authorization.", @@ -901,7 +901,7 @@ bucket_definitions: }); test('dangerous query errors - ignored', () => { - const rules = SqlSyncRules.fromYaml( + const { errors } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: @@ -912,11 +912,11 @@ bucket_definitions: { schema: BASIC_SCHEMA, ...PARSE_OPTIONS } ); - expect(rules.errors).toEqual([]); + expect(errors).toEqual([]); }); test('priorities on queries', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules, errors } = SqlSyncRules.fromYaml( ` bucket_definitions: highprio: @@ -930,7 +930,7 @@ bucket_definitions: { schema: BASIC_SCHEMA, ...PARSE_OPTIONS } ); - expect(rules.errors).toEqual([]); + expect(errors).toEqual([]); const hydrated = rules.hydrate(hydrationParams); expect(hydrated.getBucketParameterQuerier(normalizeQuerierOptions({})).querier).toMatchObject({ @@ -942,7 +942,7 @@ bucket_definitions: }); test('priorities on bucket', () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules, errors } = SqlSyncRules.fromYaml( ` bucket_definitions: highprio: @@ -956,7 +956,7 @@ bucket_definitions: { schema: BASIC_SCHEMA, ...PARSE_OPTIONS } ); - expect(rules.errors).toEqual([]); + expect(errors).toEqual([]); const hydrated = rules.hydrate(hydrationParams); expect(hydrated.getBucketParameterQuerier(normalizeQuerierOptions({})).querier).toMatchObject({ @@ -999,7 +999,7 @@ bucket_definitions: }); test('dynamic bucket definitions list', async () => { - const rules = SqlSyncRules.fromYaml( + const { config: rules } = SqlSyncRules.fromYaml( ` bucket_definitions: mybucket: From 947c04cbf5b0d6959c37bac0ab4fa6eb232add06 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 9 Feb 2026 10:43:07 +0100 Subject: [PATCH 2/6] Remove some unintentional changes --- packages/sync-rules/src/SqlSyncRules.ts | 3 +- packages/sync-rules/src/compatibility.ts | 40 ------------------------ 2 files changed, 1 insertion(+), 42 deletions(-) diff --git a/packages/sync-rules/src/SqlSyncRules.ts b/packages/sync-rules/src/SqlSyncRules.ts index c6cde3411..43d014fbf 100644 --- a/packages/sync-rules/src/SqlSyncRules.ts +++ b/packages/sync-rules/src/SqlSyncRules.ts @@ -378,8 +378,7 @@ export class SqlSyncRules extends BaseSyncConfig { return result; } - extractSyncPlan(): SyncPlan | null { - // TODO: Once we use the new compiler to parse sync rules from YAML, we could store the plan here to extract it. + extractSyncPlan(): null { return null; } diff --git a/packages/sync-rules/src/compatibility.ts b/packages/sync-rules/src/compatibility.ts index e11489ec4..e372d60cd 100644 --- a/packages/sync-rules/src/compatibility.ts +++ b/packages/sync-rules/src/compatibility.ts @@ -105,46 +105,6 @@ export class CompatibilityContext { return this.overrides.get(option) ?? option.fixedIn <= this.edition; } - serialize(): SerializedCompatibilityContext { - const serialized: SerializedCompatibilityContext = { - edition: this.edition, - overrides: {} - }; - - this.overrides.forEach((enabled, key) => (serialized.overrides[key.name] = enabled)); - if (this.maxTimeValuePrecision) { - serialized.maxTimeValuePrecision = this.maxTimeValuePrecision.subSecondDigits; - } - - return serialized; - } - - static deserialize(serialized: SerializedCompatibilityContext): CompatibilityContext { - const overrides = new Map(); - for (const [option, enabled] of Object.entries(serialized.overrides)) { - const knownOption = CompatibilityOption.byName[option]; - if (knownOption) { - overrides.set(knownOption, enabled); - } - } - - let maxTimeValuePrecision: TimeValuePrecision | undefined; - if (serialized.maxTimeValuePrecision) { - for (const option of Object.values(TimeValuePrecision.byName)) { - if (option.subSecondDigits == serialized.maxTimeValuePrecision) { - maxTimeValuePrecision = option; - break; - } - } - } - - return new CompatibilityContext({ - edition: serialized.edition, - overrides, - maxTimeValuePrecision - }); - } - /** * A {@link CompatibilityContext} in which no fixes are applied. */ From 59b0f49da0b16467224a16d744b2daf1d92c1a31 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 9 Feb 2026 10:49:30 +0100 Subject: [PATCH 3/6] Don't expose BaseSyncConfig --- .../implementation/MongoPersistedSyncRules.ts | 3 +-- .../module-mongodb/src/api/MongoRouteAPIAdapter.ts | 2 +- .../src/replication/replication-utils.ts | 4 ++-- packages/service-core/src/api/RouteAPI.ts | 4 ++-- .../src/storage/PersistedSyncRulesContent.ts | 3 +-- packages/sync-rules/src/BaseSyncConfig.ts | 5 ----- packages/sync-rules/src/SqlSyncRules.ts | 12 +++++++++--- packages/sync-rules/src/index.ts | 1 - packages/sync-rules/src/types.ts | 6 ++++++ 9 files changed, 22 insertions(+), 18 deletions(-) diff --git a/modules/module-mongodb-storage/src/storage/implementation/MongoPersistedSyncRules.ts b/modules/module-mongodb-storage/src/storage/implementation/MongoPersistedSyncRules.ts index a93c9057c..dc0fc5237 100644 --- a/modules/module-mongodb-storage/src/storage/implementation/MongoPersistedSyncRules.ts +++ b/modules/module-mongodb-storage/src/storage/implementation/MongoPersistedSyncRules.ts @@ -1,7 +1,6 @@ -import { SqlSyncRules, HydratedSyncRules, versionedHydrationState } from '@powersync/service-sync-rules'; +import { SyncConfigWithErrors, HydratedSyncRules, versionedHydrationState } from '@powersync/service-sync-rules'; import { storage } from '@powersync/service-core'; -import { SyncConfigWithErrors } from '@powersync/service-sync-rules/src/BaseSyncConfig.js'; export class MongoPersistedSyncRules implements storage.PersistedSyncRules { public readonly slot_name: string; diff --git a/modules/module-mongodb/src/api/MongoRouteAPIAdapter.ts b/modules/module-mongodb/src/api/MongoRouteAPIAdapter.ts index b9d899218..6e9215775 100644 --- a/modules/module-mongodb/src/api/MongoRouteAPIAdapter.ts +++ b/modules/module-mongodb/src/api/MongoRouteAPIAdapter.ts @@ -81,7 +81,7 @@ export class MongoRouteAPIAdapter implements api.RouteAPI { async getDebugTablesInfo( tablePatterns: sync_rules.TablePattern[], - sqlSyncRules: sync_rules.BaseSyncConfig + sqlSyncRules: sync_rules.SyncConfig ): Promise { let result: api.PatternResult[] = []; diff --git a/modules/module-postgres/src/replication/replication-utils.ts b/modules/module-postgres/src/replication/replication-utils.ts index 8cc26fffd..91f80017f 100644 --- a/modules/module-postgres/src/replication/replication-utils.ts +++ b/modules/module-postgres/src/replication/replication-utils.ts @@ -196,7 +196,7 @@ export interface GetDebugTablesInfoOptions { publicationName: string; connectionTag: string; tablePatterns: sync_rules.TablePattern[]; - syncRules: sync_rules.BaseSyncConfig; + syncRules: sync_rules.SyncConfig; } export async function getDebugTablesInfo(options: GetDebugTablesInfoOptions): Promise { @@ -296,7 +296,7 @@ export interface GetDebugTableInfoOptions { connectionTag: string; tablePattern: sync_rules.TablePattern; relationId: number | null; - syncRules: sync_rules.BaseSyncConfig; + syncRules: sync_rules.SyncConfig; } export async function getDebugTableInfo(options: GetDebugTableInfoOptions): Promise { diff --git a/packages/service-core/src/api/RouteAPI.ts b/packages/service-core/src/api/RouteAPI.ts index 206470f46..d98b7747c 100644 --- a/packages/service-core/src/api/RouteAPI.ts +++ b/packages/service-core/src/api/RouteAPI.ts @@ -1,4 +1,4 @@ -import { BaseSyncConfig, SqlSyncRules, TablePattern } from '@powersync/service-sync-rules'; +import { SyncConfig, TablePattern } from '@powersync/service-sync-rules'; import * as types from '@powersync/service-types'; import { ParseSyncRulesOptions, SyncRulesBucketStorage } from '../storage/storage-index.js'; @@ -41,7 +41,7 @@ export interface RouteAPI { * tables to ensure syncing should function according to the input * pattern. Debug errors and warnings are reported per table. */ - getDebugTablesInfo(tablePatterns: TablePattern[], sqlSyncRules: BaseSyncConfig): Promise; + getDebugTablesInfo(tablePatterns: TablePattern[], sqlSyncRules: SyncConfig): Promise; /** * @returns The replication lag: that is the amount of data which has not been diff --git a/packages/service-core/src/storage/PersistedSyncRulesContent.ts b/packages/service-core/src/storage/PersistedSyncRulesContent.ts index b26921156..b8b40f7f9 100644 --- a/packages/service-core/src/storage/PersistedSyncRulesContent.ts +++ b/packages/service-core/src/storage/PersistedSyncRulesContent.ts @@ -1,6 +1,5 @@ -import { HydratedSyncRules } from '@powersync/service-sync-rules'; +import { HydratedSyncRules, SyncConfig, SyncConfigWithErrors } from '@powersync/service-sync-rules'; import { ReplicationLock } from './ReplicationLock.js'; -import { SyncConfigWithErrors } from '@powersync/service-sync-rules/src/BaseSyncConfig.js'; export interface ParseSyncRulesOptions { defaultSchema: string; diff --git a/packages/sync-rules/src/BaseSyncConfig.ts b/packages/sync-rules/src/BaseSyncConfig.ts index d3e4575b9..d63ab4acd 100644 --- a/packages/sync-rules/src/BaseSyncConfig.ts +++ b/packages/sync-rules/src/BaseSyncConfig.ts @@ -124,8 +124,3 @@ export abstract class BaseSyncConfig { return this.bucketSources.map((rules) => rules.debugRepresentation()); } } - -export interface SyncConfigWithErrors { - config: SyncConfig; - errors: YamlError[]; -} diff --git a/packages/sync-rules/src/SqlSyncRules.ts b/packages/sync-rules/src/SqlSyncRules.ts index 43d014fbf..d084c5973 100644 --- a/packages/sync-rules/src/SqlSyncRules.ts +++ b/packages/sync-rules/src/SqlSyncRules.ts @@ -13,9 +13,15 @@ import { validateSyncRulesSchema } from './json_schema.js'; import { QueryParseResult, SqlBucketDescriptor } from './SqlBucketDescriptor.js'; import { syncStreamFromSql } from './streams/from_sql.js'; import { TablePattern } from './TablePattern.js'; -import { QueryParseOptions, RequestParameters, SourceSchema, SqliteJsonRow, StreamParseOptions } from './types.js'; -import { BaseSyncConfig, SyncConfigWithErrors } from './BaseSyncConfig.js'; -import { SyncPlan } from './sync_plan/plan.js'; +import { + QueryParseOptions, + RequestParameters, + SourceSchema, + SqliteJsonRow, + StreamParseOptions, + SyncConfigWithErrors +} from './types.js'; +import { BaseSyncConfig } from './BaseSyncConfig.js'; const ACCEPT_POTENTIALLY_DANGEROUS_QUERIES = Symbol('ACCEPT_POTENTIALLY_DANGEROUS_QUERIES'); diff --git a/packages/sync-rules/src/index.ts b/packages/sync-rules/src/index.ts index 1fdc81424..c6f65d91f 100644 --- a/packages/sync-rules/src/index.ts +++ b/packages/sync-rules/src/index.ts @@ -1,4 +1,3 @@ -export * from './BaseSyncConfig.js'; export * from './BucketDescription.js'; export * from './BucketParameterQuerier.js'; export * from './BucketSource.js'; diff --git a/packages/sync-rules/src/types.ts b/packages/sync-rules/src/types.ts index b0b8c130c..e41bf2b16 100644 --- a/packages/sync-rules/src/types.ts +++ b/packages/sync-rules/src/types.ts @@ -10,9 +10,15 @@ import { TablePattern } from './TablePattern.js'; import { CustomSqliteValue } from './types/custom_sqlite_value.js'; import { toSyncRulesParameters } from './utils.js'; import { PrecompiledSyncConfig } from './sync_plan/evaluator/index.js'; +import { YamlError } from './errors.js'; export type SyncConfig = SqlSyncRules | PrecompiledSyncConfig; +export interface SyncConfigWithErrors { + config: SyncConfig; + errors: YamlError[]; +} + export interface QueryParseOptions extends SyncRulesOptions { accept_potentially_dangerous_queries?: boolean; priority?: BucketPriority; From 1d53ea73c08daa24acd5cdc977edfdab864895d1 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 9 Feb 2026 13:50:36 +0100 Subject: [PATCH 4/6] BaseSyncConfig -> SyncConfig --- packages/sync-rules/src/SqlSyncRules.ts | 17 +++-------------- .../src/{BaseSyncConfig.ts => SyncConfig.ts} | 16 +++++++++------- packages/sync-rules/src/index.ts | 1 + .../schema-generators/DartSchemaGenerator.ts | 6 +++--- .../schema-generators/DotNetSchemaGenerator.ts | 4 ++-- .../JsLegacySchemaGenerator.ts | 4 ++-- .../schema-generators/KotlinSchemaGenerator.ts | 4 ++-- .../schema-generators/RoomSchemaGenerator.ts | 4 ++-- .../src/schema-generators/SchemaGenerator.ts | 6 +++--- .../src/schema-generators/SqlSchemaGenerator.ts | 4 ++-- .../schema-generators/SwiftSchemaGenerator.ts | 3 ++- .../src/schema-generators/TsSchemaGenerator.ts | 3 ++- .../sync-rules/src/sync_plan/evaluator/index.ts | 4 ++-- packages/sync-rules/src/types.ts | 8 -------- 14 files changed, 35 insertions(+), 49 deletions(-) rename packages/sync-rules/src/{BaseSyncConfig.ts => SyncConfig.ts} (91%) diff --git a/packages/sync-rules/src/SqlSyncRules.ts b/packages/sync-rules/src/SqlSyncRules.ts index d084c5973..56f2aba24 100644 --- a/packages/sync-rules/src/SqlSyncRules.ts +++ b/packages/sync-rules/src/SqlSyncRules.ts @@ -13,15 +13,8 @@ import { validateSyncRulesSchema } from './json_schema.js'; import { QueryParseResult, SqlBucketDescriptor } from './SqlBucketDescriptor.js'; import { syncStreamFromSql } from './streams/from_sql.js'; import { TablePattern } from './TablePattern.js'; -import { - QueryParseOptions, - RequestParameters, - SourceSchema, - SqliteJsonRow, - StreamParseOptions, - SyncConfigWithErrors -} from './types.js'; -import { BaseSyncConfig } from './BaseSyncConfig.js'; +import { QueryParseOptions, RequestParameters, SourceSchema, SqliteJsonRow, StreamParseOptions } from './types.js'; +import { SyncConfig, SyncConfigWithErrors } from './SyncConfig.js'; const ACCEPT_POTENTIALLY_DANGEROUS_QUERIES = Symbol('ACCEPT_POTENTIALLY_DANGEROUS_QUERIES'); @@ -76,7 +69,7 @@ export interface GetBucketParameterQuerierResult { errors: QuerierError[]; } -export class SqlSyncRules extends BaseSyncConfig { +export class SqlSyncRules extends SyncConfig { // The errors here are internal only, consumers should use SyncConfigWithErrors instead. readonly #errors: YamlError[] = []; @@ -328,10 +321,6 @@ export class SqlSyncRules extends BaseSyncConfig { return rules; } - protected asSyncConfig(): this { - return this; - } - throwOnError() { if (this.#errors.filter((e) => e.type != 'warning').length > 0) { throw new SyncRulesErrors(this.#errors); diff --git a/packages/sync-rules/src/BaseSyncConfig.ts b/packages/sync-rules/src/SyncConfig.ts similarity index 91% rename from packages/sync-rules/src/BaseSyncConfig.ts rename to packages/sync-rules/src/SyncConfig.ts index d63ab4acd..7be098ea2 100644 --- a/packages/sync-rules/src/BaseSyncConfig.ts +++ b/packages/sync-rules/src/SyncConfig.ts @@ -7,13 +7,14 @@ import { DEFAULT_HYDRATION_STATE } from './HydrationState.js'; import { SourceTableInterface } from './SourceTableInterface.js'; import { SyncPlan } from './sync_plan/plan.js'; import { TablePattern } from './TablePattern.js'; -import { SqliteInputValue, SqliteRow, SqliteValue, SyncConfig } from './types.js'; +import { SqliteInputValue, SqliteRow, SqliteValue } from './types.js'; import { applyRowContext } from './utils.js'; /** - * @internal Sealed class, can only be extended by `SqlSyncRules` and `PrecompiledSyncConfig`. + * A class describing how the sync process has been configured (i.e. which buckets and parameters to create and how to + * resolve buckets for connections). */ -export abstract class BaseSyncConfig { +export abstract class SyncConfig { bucketDataSources: BucketDataSource[] = []; bucketParameterLookupSources: ParameterIndexLookupCreator[] = []; bucketSources: BucketSource[] = []; @@ -29,9 +30,6 @@ export abstract class BaseSyncConfig { this.content = content; } - // Ensure asSyncConfig is not implemented externally - protected abstract asSyncConfig(): SyncConfig & this; - /** * If this sync config is fully described by a serializable sync plan, returns that plan. */ @@ -49,7 +47,7 @@ export abstract class BaseSyncConfig { } const resolvedParams = { hydrationState }; return new HydratedSyncRules({ - definition: this.asSyncConfig(), + definition: this, createParams: resolvedParams, bucketDataSources: this.bucketDataSources, bucketParameterIndexLookupCreators: this.bucketParameterLookupSources, @@ -124,3 +122,7 @@ export abstract class BaseSyncConfig { return this.bucketSources.map((rules) => rules.debugRepresentation()); } } +export interface SyncConfigWithErrors { + config: SyncConfig; + errors: YamlError[]; +} diff --git a/packages/sync-rules/src/index.ts b/packages/sync-rules/src/index.ts index c6f65d91f..7e37329e6 100644 --- a/packages/sync-rules/src/index.ts +++ b/packages/sync-rules/src/index.ts @@ -22,6 +22,7 @@ export * from './StaticSchema.js'; export { SyncStream } from './streams/stream.js'; export { STREAM_FUNCTIONS } from './streams/functions.js'; export { syncStreamFromSql } from './streams/from_sql.js'; +export { SyncConfig, SyncConfigWithErrors } from './SyncConfig.js'; export * from './TablePattern.js'; export * from './types.js'; export * from './types/custom_sqlite_value.js'; diff --git a/packages/sync-rules/src/schema-generators/DartSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/DartSchemaGenerator.ts index 5b3795c18..e664d9569 100644 --- a/packages/sync-rules/src/schema-generators/DartSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/DartSchemaGenerator.ts @@ -1,4 +1,4 @@ -import { BaseSyncConfig } from '../BaseSyncConfig.js'; +import { SyncConfig } from '../SyncConfig.js'; import { ColumnDefinition, ExpressionType } from '../ExpressionType.js'; import { SourceSchema } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; @@ -9,7 +9,7 @@ export class DartSchemaGenerator extends SchemaGenerator { readonly mediaType = 'text/x-dart'; readonly fileName = 'schema.dart'; - generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: SyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { const tables = super.getAllTables(source, schema); return `Schema([ @@ -51,7 +51,7 @@ export class DartFlutterFlowSchemaGenerator extends SchemaGenerator { readonly mediaType = 'application/json'; readonly fileName = 'schema.json'; - generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: SyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { const serializedTables = this.getAllTables(source, schema).map((e) => this.generateTable(e.name, e.columns)); // Not all FlutterFlow apps will use the attachments queue table, but it needs to be part of the app schema if used // and does no harm otherwise. So, we just include it by default. diff --git a/packages/sync-rules/src/schema-generators/DotNetSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/DotNetSchemaGenerator.ts index 21983a5ae..e508ce02f 100644 --- a/packages/sync-rules/src/schema-generators/DotNetSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/DotNetSchemaGenerator.ts @@ -1,4 +1,4 @@ -import { BaseSyncConfig } from '../BaseSyncConfig.js'; +import { SyncConfig } from '../SyncConfig.js'; import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js'; import { SourceSchema } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; @@ -9,7 +9,7 @@ export class DotNetSchemaGenerator extends SchemaGenerator { readonly mediaType = 'text/x-csharp'; readonly fileName = 'Schema.cs'; - generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: SyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { const tables = super.getAllTables(source, schema); return `using PowerSync.Common.DB.Schema; diff --git a/packages/sync-rules/src/schema-generators/JsLegacySchemaGenerator.ts b/packages/sync-rules/src/schema-generators/JsLegacySchemaGenerator.ts index f27431420..2838db5bc 100644 --- a/packages/sync-rules/src/schema-generators/JsLegacySchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/JsLegacySchemaGenerator.ts @@ -1,4 +1,4 @@ -import { BaseSyncConfig } from '../BaseSyncConfig.js'; +import { SyncConfig } from '../SyncConfig.js'; import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js'; import { SourceSchema } from '../types.js'; import { SchemaGenerator } from './SchemaGenerator.js'; @@ -9,7 +9,7 @@ export class JsLegacySchemaGenerator extends SchemaGenerator { readonly mediaType = 'text/javascript'; readonly fileName = 'schema.js'; - generate(source: BaseSyncConfig, schema: SourceSchema): string { + generate(source: SyncConfig, schema: SourceSchema): string { const tables = super.getAllTables(source, schema); return `new Schema([ diff --git a/packages/sync-rules/src/schema-generators/KotlinSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/KotlinSchemaGenerator.ts index 90ded76b0..52bd764d6 100644 --- a/packages/sync-rules/src/schema-generators/KotlinSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/KotlinSchemaGenerator.ts @@ -1,4 +1,4 @@ -import { BaseSyncConfig } from '../BaseSyncConfig.js'; +import { SyncConfig } from '../SyncConfig.js'; import { ColumnDefinition } from '../ExpressionType.js'; import { SourceSchema } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; @@ -9,7 +9,7 @@ export class KotlinSchemaGenerator extends SchemaGenerator { readonly mediaType = 'text/x-kotlin'; readonly fileName = 'schema.kt'; - generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: SyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { const tables = super.getAllTables(source, schema); return `import com.powersync.db.schema.Column diff --git a/packages/sync-rules/src/schema-generators/RoomSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/RoomSchemaGenerator.ts index 18c1bec9a..adfcb62ed 100644 --- a/packages/sync-rules/src/schema-generators/RoomSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/RoomSchemaGenerator.ts @@ -1,4 +1,4 @@ -import { BaseSyncConfig } from '../BaseSyncConfig.js'; +import { SyncConfig } from '../SyncConfig.js'; import { SourceSchema } from '../types.js'; import { SchemaGenerator } from './SchemaGenerator.js'; @@ -11,7 +11,7 @@ export class RoomSchemaGenerator extends SchemaGenerator { readonly mediaType = 'text/x-kotlin'; readonly fileName = 'Entities.kt'; - generate(source: BaseSyncConfig, schema: SourceSchema): string { + generate(source: SyncConfig, schema: SourceSchema): string { let buffer = `import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey diff --git a/packages/sync-rules/src/schema-generators/SchemaGenerator.ts b/packages/sync-rules/src/schema-generators/SchemaGenerator.ts index c750b7974..d60fcacc4 100644 --- a/packages/sync-rules/src/schema-generators/SchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/SchemaGenerator.ts @@ -1,4 +1,4 @@ -import { BaseSyncConfig } from '../BaseSyncConfig.js'; +import { SyncConfig } from '../SyncConfig.js'; import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js'; import { SourceSchema } from '../types.js'; @@ -7,7 +7,7 @@ export interface GenerateSchemaOptions { } export abstract class SchemaGenerator { - protected getAllTables(source: BaseSyncConfig, schema: SourceSchema) { + protected getAllTables(source: SyncConfig, schema: SourceSchema) { let tables: Record> = {}; for (let descriptor of source.bucketDataSources) { @@ -27,7 +27,7 @@ export abstract class SchemaGenerator { abstract readonly mediaType: string; abstract readonly fileName: string; - abstract generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string; + abstract generate(source: SyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string; /** * @param def The column definition to generate the type for. diff --git a/packages/sync-rules/src/schema-generators/SqlSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/SqlSchemaGenerator.ts index 5a54f90dc..c41ac5f2f 100644 --- a/packages/sync-rules/src/schema-generators/SqlSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/SqlSchemaGenerator.ts @@ -1,4 +1,4 @@ -import { BaseSyncConfig } from '../BaseSyncConfig.js'; +import { SyncConfig } from '../SyncConfig.js'; import { SourceSchema } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; @@ -17,7 +17,7 @@ export class SqlSchemaGenerator extends SchemaGenerator { super(); } - generate(source: BaseSyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { + generate(source: SyncConfig, schema: SourceSchema, options?: GenerateSchemaOptions): string { let buffer = '-- Note: These definitions are only used to generate typed code. PowerSync manages the database schema.\n'; const tables = super.getAllTables(source, schema); diff --git a/packages/sync-rules/src/schema-generators/SwiftSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/SwiftSchemaGenerator.ts index ad764c6e4..1da5ce583 100644 --- a/packages/sync-rules/src/schema-generators/SwiftSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/SwiftSchemaGenerator.ts @@ -1,5 +1,6 @@ import { ColumnDefinition } from '../ExpressionType.js'; -import { SourceSchema, SyncConfig } from '../types.js'; +import { SyncConfig } from '../SyncConfig.js'; +import { SourceSchema } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; export class SwiftSchemaGenerator extends SchemaGenerator { diff --git a/packages/sync-rules/src/schema-generators/TsSchemaGenerator.ts b/packages/sync-rules/src/schema-generators/TsSchemaGenerator.ts index 4a6a8747d..175cbc942 100644 --- a/packages/sync-rules/src/schema-generators/TsSchemaGenerator.ts +++ b/packages/sync-rules/src/schema-generators/TsSchemaGenerator.ts @@ -1,5 +1,6 @@ +import { SyncConfig } from '../SyncConfig.js'; import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js'; -import { SourceSchema, SyncConfig } from '../types.js'; +import { SourceSchema } from '../types.js'; import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js'; export interface TsSchemaGeneratorOptions { diff --git a/packages/sync-rules/src/sync_plan/evaluator/index.ts b/packages/sync-rules/src/sync_plan/evaluator/index.ts index 30a5babcf..341f3d6f3 100644 --- a/packages/sync-rules/src/sync_plan/evaluator/index.ts +++ b/packages/sync-rules/src/sync_plan/evaluator/index.ts @@ -3,7 +3,7 @@ import { PreparedStreamBucketDataSource } from './bucket_data_source.js'; import { PreparedParameterIndexLookupCreator } from './parameter_index_lookup_creator.js'; import { StreamBucketSource, StreamInput } from './bucket_source.js'; import { ScalarExpressionEngine } from '../engine/scalar_expression_engine.js'; -import { BaseSyncConfig } from '../../BaseSyncConfig.js'; +import { SyncConfig } from '../../SyncConfig.js'; export interface StreamEvaluationContext { engine: ScalarExpressionEngine; @@ -16,7 +16,7 @@ export interface StreamEvaluationContext { sourceText: string; } -export class PrecompiledSyncConfig extends BaseSyncConfig { +export class PrecompiledSyncConfig extends SyncConfig { constructor( private readonly plan: plan.SyncPlan, context: StreamEvaluationContext diff --git a/packages/sync-rules/src/types.ts b/packages/sync-rules/src/types.ts index e41bf2b16..8fae35b97 100644 --- a/packages/sync-rules/src/types.ts +++ b/packages/sync-rules/src/types.ts @@ -10,14 +10,6 @@ import { TablePattern } from './TablePattern.js'; import { CustomSqliteValue } from './types/custom_sqlite_value.js'; import { toSyncRulesParameters } from './utils.js'; import { PrecompiledSyncConfig } from './sync_plan/evaluator/index.js'; -import { YamlError } from './errors.js'; - -export type SyncConfig = SqlSyncRules | PrecompiledSyncConfig; - -export interface SyncConfigWithErrors { - config: SyncConfig; - errors: YamlError[]; -} export interface QueryParseOptions extends SyncRulesOptions { accept_potentially_dangerous_queries?: boolean; From 612ab42991d88a0e7df940d67a99605e7181cfb1 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 9 Feb 2026 13:53:07 +0100 Subject: [PATCH 5/6] Remove extractSyncPlan --- packages/sync-rules/src/SqlSyncRules.ts | 4 ---- packages/sync-rules/src/SyncConfig.ts | 5 ----- packages/sync-rules/src/sync_plan/evaluator/index.ts | 6 +----- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/sync-rules/src/SqlSyncRules.ts b/packages/sync-rules/src/SqlSyncRules.ts index 56f2aba24..06323513b 100644 --- a/packages/sync-rules/src/SqlSyncRules.ts +++ b/packages/sync-rules/src/SqlSyncRules.ts @@ -373,10 +373,6 @@ export class SqlSyncRules extends SyncConfig { return result; } - extractSyncPlan(): null { - return null; - } - protected writeSourceTables(sourceTables: Map): void { super.writeSourceTables(sourceTables); diff --git a/packages/sync-rules/src/SyncConfig.ts b/packages/sync-rules/src/SyncConfig.ts index 7be098ea2..8572ec184 100644 --- a/packages/sync-rules/src/SyncConfig.ts +++ b/packages/sync-rules/src/SyncConfig.ts @@ -30,11 +30,6 @@ export abstract class SyncConfig { this.content = content; } - /** - * If this sync config is fully described by a serializable sync plan, returns that plan. - */ - abstract extractSyncPlan(): SyncPlan | null; - /** * Hydrate the sync rule definitions with persisted state into runnable sync rules. * diff --git a/packages/sync-rules/src/sync_plan/evaluator/index.ts b/packages/sync-rules/src/sync_plan/evaluator/index.ts index 341f3d6f3..a3a129d20 100644 --- a/packages/sync-rules/src/sync_plan/evaluator/index.ts +++ b/packages/sync-rules/src/sync_plan/evaluator/index.ts @@ -18,7 +18,7 @@ export interface StreamEvaluationContext { export class PrecompiledSyncConfig extends SyncConfig { constructor( - private readonly plan: plan.SyncPlan, + readonly plan: plan.SyncPlan, context: StreamEvaluationContext ) { super(context.sourceText); @@ -51,8 +51,4 @@ export class PrecompiledSyncConfig extends SyncConfig { protected asSyncConfig(): this { return this; } - - extractSyncPlan(): plan.SyncPlan | null { - return this.plan; - } } From ef22766a6e5727fc056143e255bdf14c9da76b1a Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 9 Feb 2026 16:09:05 +0100 Subject: [PATCH 6/6] Remove superfluous method --- packages/sync-rules/src/sync_plan/evaluator/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/sync-rules/src/sync_plan/evaluator/index.ts b/packages/sync-rules/src/sync_plan/evaluator/index.ts index a3a129d20..80e54be59 100644 --- a/packages/sync-rules/src/sync_plan/evaluator/index.ts +++ b/packages/sync-rules/src/sync_plan/evaluator/index.ts @@ -47,8 +47,4 @@ export class PrecompiledSyncConfig extends SyncConfig { this.bucketSources.push(new StreamBucketSource(stream, streamInput)); } } - - protected asSyncConfig(): this { - return this; - } }