@powersync/service-core 1.19.2 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/api/diagnostics.js +11 -4
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/entry/commands/compact-action.js +13 -2
- package/dist/entry/commands/compact-action.js.map +1 -1
- package/dist/entry/commands/config-command.js +2 -2
- package/dist/entry/commands/config-command.js.map +1 -1
- package/dist/replication/AbstractReplicator.js +2 -5
- package/dist/replication/AbstractReplicator.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +84 -0
- package/dist/routes/endpoints/admin.d.ts +168 -0
- package/dist/routes/endpoints/admin.js +33 -20
- package/dist/routes/endpoints/admin.js.map +1 -1
- package/dist/routes/endpoints/sync-rules.js +6 -9
- package/dist/routes/endpoints/sync-rules.js.map +1 -1
- package/dist/storage/BucketStorageFactory.d.ts +43 -15
- package/dist/storage/BucketStorageFactory.js +70 -1
- package/dist/storage/BucketStorageFactory.js.map +1 -1
- package/dist/storage/PersistedSyncRulesContent.d.ts +28 -2
- package/dist/storage/PersistedSyncRulesContent.js +79 -1
- package/dist/storage/PersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/StorageVersionConfig.d.ts +20 -0
- package/dist/storage/StorageVersionConfig.js +20 -0
- package/dist/storage/StorageVersionConfig.js.map +1 -0
- package/dist/storage/SyncRulesBucketStorage.d.ts +2 -1
- package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
- package/dist/storage/storage-index.d.ts +1 -0
- package/dist/storage/storage-index.js +1 -0
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/sync/BucketChecksumState.d.ts +6 -2
- package/dist/sync/BucketChecksumState.js +85 -10
- package/dist/sync/BucketChecksumState.js.map +1 -1
- package/dist/util/config/collectors/config-collector.js +13 -0
- package/dist/util/config/collectors/config-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.d.ts +1 -1
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js +4 -4
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.d.ts +1 -1
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js +2 -2
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.d.ts +1 -1
- package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js +3 -3
- package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -1
- package/dist/util/config/types.d.ts +1 -1
- package/dist/util/config/types.js.map +1 -1
- package/dist/util/env.d.ts +1 -0
- package/dist/util/env.js +5 -0
- package/dist/util/env.js.map +1 -1
- package/package.json +6 -6
- package/src/api/diagnostics.ts +12 -4
- package/src/entry/commands/compact-action.ts +15 -2
- package/src/entry/commands/config-command.ts +3 -3
- package/src/replication/AbstractReplicator.ts +3 -5
- package/src/routes/endpoints/admin.ts +42 -25
- package/src/routes/endpoints/sync-rules.ts +14 -13
- package/src/storage/BucketStorageFactory.ts +110 -19
- package/src/storage/PersistedSyncRulesContent.ts +114 -4
- package/src/storage/StorageVersionConfig.ts +30 -0
- package/src/storage/SyncRulesBucketStorage.ts +2 -1
- package/src/storage/storage-index.ts +1 -0
- package/src/sync/BucketChecksumState.ts +129 -16
- package/src/util/config/collectors/config-collector.ts +16 -0
- package/src/util/config/sync-rules/impl/base64-sync-rules-collector.ts +5 -5
- package/src/util/config/sync-rules/impl/filesystem-sync-rules-collector.ts +3 -3
- package/src/util/config/sync-rules/impl/inline-sync-rules-collector.ts +4 -4
- package/src/util/config/types.ts +1 -2
- package/src/util/env.ts +5 -0
- package/test/src/config.test.ts +115 -0
- package/test/src/routes/admin.test.ts +48 -0
- package/test/src/routes/mocks.ts +22 -1
- package/test/src/routes/stream.test.ts +3 -2
- package/test/src/sync/BucketChecksumState.test.ts +285 -78
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseObserver } from '@powersync/lib-services-framework';
|
|
2
2
|
import { ParseSyncRulesOptions, PersistedSyncRules, PersistedSyncRulesContent } from './PersistedSyncRulesContent.js';
|
|
3
3
|
import { ReplicationEventPayload } from './ReplicationEventPayload.js';
|
|
4
4
|
import { ReplicationLock } from './ReplicationLock.js';
|
|
5
5
|
import { SyncRulesBucketStorage } from './SyncRulesBucketStorage.js';
|
|
6
6
|
import { ReportStorage } from './ReportStorage.js';
|
|
7
|
+
import { SerializedCompatibilityContext, SyncConfig } from '@powersync/service-sync-rules';
|
|
7
8
|
/**
|
|
8
9
|
* Represents a configured storage provider.
|
|
9
10
|
*
|
|
@@ -12,7 +13,7 @@ import { ReportStorage } from './ReportStorage.js';
|
|
|
12
13
|
*
|
|
13
14
|
* Storage APIs for a specific copy of sync rules are provided by the `SyncRulesBucketStorage` instances.
|
|
14
15
|
*/
|
|
15
|
-
export
|
|
16
|
+
export declare abstract class BucketStorageFactory extends BaseObserver<BucketStorageFactoryListener> implements AsyncDisposable {
|
|
16
17
|
/**
|
|
17
18
|
* Update sync rules from configuration, if changed.
|
|
18
19
|
*/
|
|
@@ -24,11 +25,11 @@ export interface BucketStorageFactory extends ObserverClient<BucketStorageFactor
|
|
|
24
25
|
/**
|
|
25
26
|
* Get a storage instance to query sync data for specific sync rules.
|
|
26
27
|
*/
|
|
27
|
-
getInstance(syncRules: PersistedSyncRulesContent, options?: GetIntanceOptions): SyncRulesBucketStorage;
|
|
28
|
+
abstract getInstance(syncRules: PersistedSyncRulesContent, options?: GetIntanceOptions): SyncRulesBucketStorage;
|
|
28
29
|
/**
|
|
29
30
|
* Deploy new sync rules.
|
|
30
31
|
*/
|
|
31
|
-
updateSyncRules(options: UpdateSyncRulesOptions): Promise<PersistedSyncRulesContent>;
|
|
32
|
+
abstract updateSyncRules(options: UpdateSyncRulesOptions): Promise<PersistedSyncRulesContent>;
|
|
32
33
|
/**
|
|
33
34
|
* Indicate that a slot was removed, and we should re-sync by creating
|
|
34
35
|
* a new sync rules instance.
|
|
@@ -39,7 +40,7 @@ export interface BucketStorageFactory extends ObserverClient<BucketStorageFactor
|
|
|
39
40
|
*
|
|
40
41
|
* Replication should be restarted after this.
|
|
41
42
|
*/
|
|
42
|
-
restartReplication(sync_rules_group_id: number): Promise<void>;
|
|
43
|
+
abstract restartReplication(sync_rules_group_id: number): Promise<void>;
|
|
43
44
|
/**
|
|
44
45
|
* Get the sync rules used for querying.
|
|
45
46
|
*/
|
|
@@ -47,7 +48,7 @@ export interface BucketStorageFactory extends ObserverClient<BucketStorageFactor
|
|
|
47
48
|
/**
|
|
48
49
|
* Get the sync rules used for querying.
|
|
49
50
|
*/
|
|
50
|
-
getActiveSyncRulesContent(): Promise<PersistedSyncRulesContent | null>;
|
|
51
|
+
abstract getActiveSyncRulesContent(): Promise<PersistedSyncRulesContent | null>;
|
|
51
52
|
/**
|
|
52
53
|
* Get the sync rules that will be active next once done with initial replicatino.
|
|
53
54
|
*/
|
|
@@ -55,31 +56,32 @@ export interface BucketStorageFactory extends ObserverClient<BucketStorageFactor
|
|
|
55
56
|
/**
|
|
56
57
|
* Get the sync rules that will be active next once done with initial replicatino.
|
|
57
58
|
*/
|
|
58
|
-
getNextSyncRulesContent(): Promise<PersistedSyncRulesContent | null>;
|
|
59
|
+
abstract getNextSyncRulesContent(): Promise<PersistedSyncRulesContent | null>;
|
|
59
60
|
/**
|
|
60
61
|
* Get all sync rules currently replicating. Typically this is the "active" and "next" sync rules.
|
|
61
62
|
*/
|
|
62
|
-
getReplicatingSyncRules(): Promise<PersistedSyncRulesContent[]>;
|
|
63
|
+
abstract getReplicatingSyncRules(): Promise<PersistedSyncRulesContent[]>;
|
|
63
64
|
/**
|
|
64
65
|
* Get all sync rules stopped but not terminated yet.
|
|
65
66
|
*/
|
|
66
|
-
getStoppedSyncRules(): Promise<PersistedSyncRulesContent[]>;
|
|
67
|
+
abstract getStoppedSyncRules(): Promise<PersistedSyncRulesContent[]>;
|
|
67
68
|
/**
|
|
68
69
|
* Get the active storage instance.
|
|
69
70
|
*/
|
|
70
|
-
getActiveStorage(): Promise<SyncRulesBucketStorage | null>;
|
|
71
|
+
abstract getActiveStorage(): Promise<SyncRulesBucketStorage | null>;
|
|
71
72
|
/**
|
|
72
73
|
* Get storage size of active sync rules.
|
|
73
74
|
*/
|
|
74
|
-
getStorageMetrics(): Promise<StorageMetrics>;
|
|
75
|
+
abstract getStorageMetrics(): Promise<StorageMetrics>;
|
|
75
76
|
/**
|
|
76
77
|
* Get the unique identifier for this instance of Powersync
|
|
77
78
|
*/
|
|
78
|
-
getPowerSyncInstanceId(): Promise<string>;
|
|
79
|
+
abstract getPowerSyncInstanceId(): Promise<string>;
|
|
79
80
|
/**
|
|
80
81
|
* Get a unique identifier for the system used for storage.
|
|
81
82
|
*/
|
|
82
|
-
getSystemIdentifier(): Promise<BucketStorageSystemIdentifier>;
|
|
83
|
+
abstract getSystemIdentifier(): Promise<BucketStorageSystemIdentifier>;
|
|
84
|
+
abstract [Symbol.asyncDispose](): PromiseLike<void>;
|
|
83
85
|
}
|
|
84
86
|
export interface BucketStorageFactoryListener {
|
|
85
87
|
syncStorageCreated: (storage: SyncRulesBucketStorage) => void;
|
|
@@ -102,10 +104,36 @@ export interface StorageMetrics {
|
|
|
102
104
|
replication_size_bytes: number;
|
|
103
105
|
}
|
|
104
106
|
export interface UpdateSyncRulesOptions {
|
|
105
|
-
|
|
107
|
+
config: {
|
|
108
|
+
yaml: string;
|
|
109
|
+
/**
|
|
110
|
+
* The serialized sync plan for the sync configuration, or `null` for configurations not using the sync stream
|
|
111
|
+
* compiler.
|
|
112
|
+
*/
|
|
113
|
+
plan: SerializedSyncPlan | null;
|
|
114
|
+
};
|
|
106
115
|
lock?: boolean;
|
|
107
|
-
|
|
116
|
+
storageVersion?: number;
|
|
117
|
+
}
|
|
118
|
+
export interface SerializedSyncPlan {
|
|
119
|
+
/**
|
|
120
|
+
* The serialized plan, from {@link serializeSyncPlan}.
|
|
121
|
+
*/
|
|
122
|
+
plan: unknown;
|
|
123
|
+
compatibility: SerializedCompatibilityContext;
|
|
124
|
+
/**
|
|
125
|
+
* Event descriptors are not currently represented in the sync plan because they don't use the sync streams compiler
|
|
126
|
+
* yet.
|
|
127
|
+
*
|
|
128
|
+
* We might revisit that in the future, but for now we store SQL text of their definitions here to be able to restore
|
|
129
|
+
* them.
|
|
130
|
+
*/
|
|
131
|
+
eventDescriptors: Record<string, string[]>;
|
|
108
132
|
}
|
|
133
|
+
export declare function updateSyncRulesFromYaml(content: string, options?: Omit<UpdateSyncRulesOptions, 'config'> & {
|
|
134
|
+
validate?: boolean;
|
|
135
|
+
}): UpdateSyncRulesOptions;
|
|
136
|
+
export declare function updateSyncRulesFromConfig(parsed: SyncConfig, options?: Omit<UpdateSyncRulesOptions, 'config'>): UpdateSyncRulesOptions;
|
|
109
137
|
export interface GetIntanceOptions {
|
|
110
138
|
/**
|
|
111
139
|
* Set to true to skip trigger any events for creating the instance.
|
|
@@ -1,2 +1,71 @@
|
|
|
1
|
-
|
|
1
|
+
import { BaseObserver, logger } from '@powersync/lib-services-framework';
|
|
2
|
+
import { PrecompiledSyncConfig, serializeSyncPlan, SqlSyncRules } from '@powersync/service-sync-rules';
|
|
3
|
+
/**
|
|
4
|
+
* Represents a configured storage provider.
|
|
5
|
+
*
|
|
6
|
+
* The provider can handle multiple copies of sync rules concurrently, each with their own storage.
|
|
7
|
+
* This is to handle replication of a new version of sync rules, while the old version is still active.
|
|
8
|
+
*
|
|
9
|
+
* Storage APIs for a specific copy of sync rules are provided by the `SyncRulesBucketStorage` instances.
|
|
10
|
+
*/
|
|
11
|
+
export class BucketStorageFactory extends BaseObserver {
|
|
12
|
+
/**
|
|
13
|
+
* Update sync rules from configuration, if changed.
|
|
14
|
+
*/
|
|
15
|
+
async configureSyncRules(options) {
|
|
16
|
+
const next = await this.getNextSyncRulesContent();
|
|
17
|
+
const active = await this.getActiveSyncRulesContent();
|
|
18
|
+
if (next?.sync_rules_content == options.config.yaml) {
|
|
19
|
+
logger.info('Sync rules from configuration unchanged');
|
|
20
|
+
return { updated: false };
|
|
21
|
+
}
|
|
22
|
+
else if (next == null && active?.sync_rules_content == options.config.yaml) {
|
|
23
|
+
logger.info('Sync rules from configuration unchanged');
|
|
24
|
+
return { updated: false };
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
logger.info('Sync rules updated from configuration');
|
|
28
|
+
const persisted_sync_rules = await this.updateSyncRules(options);
|
|
29
|
+
return { updated: true, persisted_sync_rules, lock: persisted_sync_rules.current_lock ?? undefined };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get the sync rules used for querying.
|
|
34
|
+
*/
|
|
35
|
+
async getActiveSyncRules(options) {
|
|
36
|
+
const content = await this.getActiveSyncRulesContent();
|
|
37
|
+
return content?.parsed(options) ?? null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the sync rules that will be active next once done with initial replicatino.
|
|
41
|
+
*/
|
|
42
|
+
async getNextSyncRules(options) {
|
|
43
|
+
const content = await this.getNextSyncRulesContent();
|
|
44
|
+
return content?.parsed(options) ?? null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function updateSyncRulesFromYaml(content, options) {
|
|
48
|
+
const { config } = SqlSyncRules.fromYaml(content, {
|
|
49
|
+
// No schema-based validation at this point
|
|
50
|
+
schema: undefined,
|
|
51
|
+
defaultSchema: 'not_applicable', // Not needed for validation
|
|
52
|
+
throwOnError: options?.validate ?? false
|
|
53
|
+
});
|
|
54
|
+
return updateSyncRulesFromConfig(config, options);
|
|
55
|
+
}
|
|
56
|
+
export function updateSyncRulesFromConfig(parsed, options) {
|
|
57
|
+
let plan = null;
|
|
58
|
+
if (parsed instanceof PrecompiledSyncConfig) {
|
|
59
|
+
const eventDescriptors = {};
|
|
60
|
+
for (const event of parsed.eventDescriptors) {
|
|
61
|
+
eventDescriptors[event.name] = event.sourceQueries.map((q) => q.sql);
|
|
62
|
+
}
|
|
63
|
+
plan = {
|
|
64
|
+
compatibility: parsed.compatibility.serialize(),
|
|
65
|
+
plan: serializeSyncPlan(parsed.plan),
|
|
66
|
+
eventDescriptors
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return { config: { yaml: parsed.content, plan }, ...options };
|
|
70
|
+
}
|
|
2
71
|
//# sourceMappingURL=BucketStorageFactory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BucketStorageFactory.js","sourceRoot":"","sources":["../../src/storage/BucketStorageFactory.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"BucketStorageFactory.js","sourceRoot":"","sources":["../../src/storage/BucketStorageFactory.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,23 +1,49 @@
|
|
|
1
1
|
import { HydratedSyncRules, SyncConfigWithErrors } from '@powersync/service-sync-rules';
|
|
2
2
|
import { ReplicationLock } from './ReplicationLock.js';
|
|
3
|
+
import { StorageVersionConfig } from './StorageVersionConfig.js';
|
|
4
|
+
import { SerializedSyncPlan, UpdateSyncRulesOptions } from './BucketStorageFactory.js';
|
|
3
5
|
export interface ParseSyncRulesOptions {
|
|
4
6
|
defaultSchema: string;
|
|
5
7
|
}
|
|
6
|
-
export interface
|
|
8
|
+
export interface PersistedSyncRulesContentData {
|
|
7
9
|
readonly id: number;
|
|
8
10
|
readonly sync_rules_content: string;
|
|
11
|
+
readonly compiled_plan: SerializedSyncPlan | null;
|
|
9
12
|
readonly slot_name: string;
|
|
10
13
|
/**
|
|
11
14
|
* True if this is the "active" copy of the sync rules.
|
|
12
15
|
*/
|
|
13
16
|
readonly active: boolean;
|
|
17
|
+
readonly storageVersion: number;
|
|
14
18
|
readonly last_checkpoint_lsn: string | null;
|
|
15
19
|
readonly last_fatal_error?: string | null;
|
|
16
20
|
readonly last_fatal_error_ts?: Date | null;
|
|
17
21
|
readonly last_keepalive_ts?: Date | null;
|
|
18
22
|
readonly last_checkpoint_ts?: Date | null;
|
|
23
|
+
}
|
|
24
|
+
export declare abstract class PersistedSyncRulesContent implements PersistedSyncRulesContentData {
|
|
25
|
+
readonly id: number;
|
|
26
|
+
readonly sync_rules_content: string;
|
|
27
|
+
readonly compiled_plan: SerializedSyncPlan | null;
|
|
28
|
+
readonly slot_name: string;
|
|
29
|
+
readonly active: boolean;
|
|
30
|
+
readonly storageVersion: number;
|
|
31
|
+
readonly last_checkpoint_lsn: string | null;
|
|
32
|
+
readonly last_fatal_error?: string | null;
|
|
33
|
+
readonly last_fatal_error_ts?: Date | null;
|
|
34
|
+
readonly last_keepalive_ts?: Date | null;
|
|
35
|
+
readonly last_checkpoint_ts?: Date | null;
|
|
36
|
+
abstract readonly current_lock: ReplicationLock | null;
|
|
37
|
+
constructor(data: PersistedSyncRulesContentData);
|
|
38
|
+
/**
|
|
39
|
+
* Load the storage config.
|
|
40
|
+
*
|
|
41
|
+
* This may throw if the persisted storage version is not supported.
|
|
42
|
+
*/
|
|
43
|
+
getStorageConfig(): StorageVersionConfig;
|
|
19
44
|
parsed(options: ParseSyncRulesOptions): PersistedSyncRules;
|
|
20
|
-
|
|
45
|
+
asUpdateOptions(options?: Omit<UpdateSyncRulesOptions, 'config'>): UpdateSyncRulesOptions;
|
|
46
|
+
abstract lock(): Promise<ReplicationLock>;
|
|
21
47
|
}
|
|
22
48
|
export interface PersistedSyncRules {
|
|
23
49
|
readonly id: number;
|
|
@@ -1,2 +1,80 @@
|
|
|
1
|
-
|
|
1
|
+
import { CompatibilityContext, CompatibilityOption, DEFAULT_HYDRATION_STATE, deserializeSyncPlan, javaScriptExpressionEngine, PrecompiledSyncConfig, SqlEventDescriptor, SqlSyncRules, versionedHydrationState } from '@powersync/service-sync-rules';
|
|
2
|
+
import { STORAGE_VERSION_CONFIG } from './StorageVersionConfig.js';
|
|
3
|
+
import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
|
|
4
|
+
export class PersistedSyncRulesContent {
|
|
5
|
+
id;
|
|
6
|
+
sync_rules_content;
|
|
7
|
+
compiled_plan;
|
|
8
|
+
slot_name;
|
|
9
|
+
active;
|
|
10
|
+
storageVersion;
|
|
11
|
+
last_checkpoint_lsn;
|
|
12
|
+
last_fatal_error;
|
|
13
|
+
last_fatal_error_ts;
|
|
14
|
+
last_keepalive_ts;
|
|
15
|
+
last_checkpoint_ts;
|
|
16
|
+
constructor(data) {
|
|
17
|
+
Object.assign(this, data);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load the storage config.
|
|
21
|
+
*
|
|
22
|
+
* This may throw if the persisted storage version is not supported.
|
|
23
|
+
*/
|
|
24
|
+
getStorageConfig() {
|
|
25
|
+
const storageConfig = STORAGE_VERSION_CONFIG[this.storageVersion];
|
|
26
|
+
if (storageConfig == null) {
|
|
27
|
+
throw new ServiceError(ErrorCode.PSYNC_S1005, `Unsupported storage version ${this.storageVersion} for sync rules ${this.id}`);
|
|
28
|
+
}
|
|
29
|
+
return storageConfig;
|
|
30
|
+
}
|
|
31
|
+
parsed(options) {
|
|
32
|
+
let hydrationState;
|
|
33
|
+
// Do we have a compiled sync plan? If so, restore from there instead of parsing everything again.
|
|
34
|
+
let config;
|
|
35
|
+
if (this.compiled_plan != null) {
|
|
36
|
+
const plan = deserializeSyncPlan(this.compiled_plan.plan);
|
|
37
|
+
const compatibility = CompatibilityContext.deserialize(this.compiled_plan.compatibility);
|
|
38
|
+
const eventDefinitions = [];
|
|
39
|
+
for (const [name, queries] of Object.entries(this.compiled_plan.eventDescriptors)) {
|
|
40
|
+
const descriptor = new SqlEventDescriptor(name, compatibility);
|
|
41
|
+
for (const query of queries) {
|
|
42
|
+
descriptor.addSourceQuery(query, options);
|
|
43
|
+
}
|
|
44
|
+
eventDefinitions.push(descriptor);
|
|
45
|
+
}
|
|
46
|
+
const precompiled = new PrecompiledSyncConfig(plan, compatibility, eventDefinitions, {
|
|
47
|
+
defaultSchema: options.defaultSchema,
|
|
48
|
+
engine: javaScriptExpressionEngine(compatibility),
|
|
49
|
+
sourceText: this.sync_rules_content
|
|
50
|
+
});
|
|
51
|
+
config = { config: precompiled, errors: [] };
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
config = SqlSyncRules.fromYaml(this.sync_rules_content, options);
|
|
55
|
+
}
|
|
56
|
+
const storageConfig = this.getStorageConfig();
|
|
57
|
+
if (storageConfig.versionedBuckets ||
|
|
58
|
+
config.config.compatibility.isEnabled(CompatibilityOption.versionedBucketIds)) {
|
|
59
|
+
hydrationState = versionedHydrationState(this.id);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
hydrationState = DEFAULT_HYDRATION_STATE;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
id: this.id,
|
|
66
|
+
slot_name: this.slot_name,
|
|
67
|
+
sync_rules: config,
|
|
68
|
+
hydratedSyncRules: () => {
|
|
69
|
+
return config.config.hydrate({ hydrationState });
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
asUpdateOptions(options) {
|
|
74
|
+
return {
|
|
75
|
+
config: { yaml: this.sync_rules_content, plan: this.compiled_plan },
|
|
76
|
+
...options
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
2
80
|
//# sourceMappingURL=PersistedSyncRulesContent.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PersistedSyncRulesContent.js","sourceRoot":"","sources":["../../src/storage/PersistedSyncRulesContent.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"PersistedSyncRulesContent.js","sourceRoot":"","sources":["../../src/storage/PersistedSyncRulesContent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface StorageVersionConfig {
|
|
2
|
+
/**
|
|
3
|
+
* Whether versioned bucket names are automatically enabled.
|
|
4
|
+
*
|
|
5
|
+
* If this is false, bucket names may still be versioned depending on the sync config.
|
|
6
|
+
*/
|
|
7
|
+
versionedBuckets: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Oldest supported storage version.
|
|
11
|
+
*/
|
|
12
|
+
export declare const LEGACY_STORAGE_VERSION = 1;
|
|
13
|
+
/**
|
|
14
|
+
* Default storage version for newly persisted sync rules.
|
|
15
|
+
*/
|
|
16
|
+
export declare const CURRENT_STORAGE_VERSION = 2;
|
|
17
|
+
/**
|
|
18
|
+
* Shared storage-version behavior across storage implementations.
|
|
19
|
+
*/
|
|
20
|
+
export declare const STORAGE_VERSION_CONFIG: Record<number, StorageVersionConfig | undefined>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oldest supported storage version.
|
|
3
|
+
*/
|
|
4
|
+
export const LEGACY_STORAGE_VERSION = 1;
|
|
5
|
+
/**
|
|
6
|
+
* Default storage version for newly persisted sync rules.
|
|
7
|
+
*/
|
|
8
|
+
export const CURRENT_STORAGE_VERSION = 2;
|
|
9
|
+
/**
|
|
10
|
+
* Shared storage-version behavior across storage implementations.
|
|
11
|
+
*/
|
|
12
|
+
export const STORAGE_VERSION_CONFIG = {
|
|
13
|
+
[LEGACY_STORAGE_VERSION]: {
|
|
14
|
+
versionedBuckets: false
|
|
15
|
+
},
|
|
16
|
+
[CURRENT_STORAGE_VERSION]: {
|
|
17
|
+
versionedBuckets: true
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=StorageVersionConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageVersionConfig.js","sourceRoot":"","sources":["../../src/storage/StorageVersionConfig.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAExC;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AAEzC;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAqD;IACtF,CAAC,sBAAsB,CAAC,EAAE;QACxB,gBAAgB,EAAE,KAAK;KACxB;IACD,CAAC,uBAAuB,CAAC,EAAE;QACzB,gBAAgB,EAAE,IAAI;KACvB;CACF,CAAC"}
|
|
@@ -163,7 +163,8 @@ export interface CompactOptions {
|
|
|
163
163
|
*
|
|
164
164
|
* If not specified, compacts all buckets.
|
|
165
165
|
*
|
|
166
|
-
* These
|
|
166
|
+
* These must be full bucket names (e.g., "global[]", "mybucket[\"user1\"]").
|
|
167
|
+
* Bucket definition names (e.g., "global") are not supported.
|
|
167
168
|
*/
|
|
168
169
|
compactBuckets?: string[];
|
|
169
170
|
compactParameterData?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncRulesBucketStorage.js","sourceRoot":"","sources":["../../src/storage/SyncRulesBucketStorage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SyncRulesBucketStorage.js","sourceRoot":"","sources":["../../src/storage/SyncRulesBucketStorage.ts"],"names":[],"mappings":"AA8UA,MAAM,CAAC,MAAM,yBAAyB,GAAsB;IAC1D,kBAAkB,EAAE,IAAI,GAAG,EAAU;IACrC,qBAAqB,EAAE,IAAI;IAC3B,uBAAuB,EAAE,IAAI,GAAG,EAAU;IAC1C,0BAA0B,EAAE,IAAI;CACjC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage-index.js","sourceRoot":"","sources":["../../src/storage/storage-index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"storage-index.js","sourceRoot":"","sources":["../../src/storage/storage-index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { BucketDescription, BucketSource, HydratedSyncRules, RequestParameters, ResolvedBucket } from '@powersync/service-sync-rules';
|
|
1
|
+
import { BucketDescription, BucketSource, HydratedSyncRules, QuerierError, RequestParameters, ResolvedBucket } from '@powersync/service-sync-rules';
|
|
2
2
|
import * as storage from '../storage/storage-index.js';
|
|
3
3
|
import * as util from '../util/util-index.js';
|
|
4
4
|
import { Logger } from '@powersync/lib-services-framework';
|
|
5
|
-
import { QuerierError } from '@powersync/service-sync-rules/src/BucketParameterQuerier.js';
|
|
6
5
|
import { JwtPayload } from '../auth/JwtPayload.js';
|
|
7
6
|
import { SyncContext } from './SyncContext.js';
|
|
8
7
|
export interface BucketChecksumStateOptions {
|
|
@@ -80,6 +79,11 @@ export interface CheckpointUpdate {
|
|
|
80
79
|
* If null, assume that any bucket in `buckets` may have been updated.
|
|
81
80
|
*/
|
|
82
81
|
updatedBuckets: Set<string> | typeof INVALIDATE_ALL_BUCKETS;
|
|
82
|
+
/**
|
|
83
|
+
* Number of parameter query results per sync stream definition (before deduplication).
|
|
84
|
+
* Map from definition name to count.
|
|
85
|
+
*/
|
|
86
|
+
parameterQueryResultsByDefinition?: Map<string, number>;
|
|
83
87
|
}
|
|
84
88
|
export declare class BucketParameterState {
|
|
85
89
|
private readonly context;
|
|
@@ -66,7 +66,7 @@ export class BucketChecksumState {
|
|
|
66
66
|
const userIdForLogs = this.parameterState.syncParams.userId;
|
|
67
67
|
const storage = this.bucketStorage;
|
|
68
68
|
const update = await this.parameterState.getCheckpointUpdate(next);
|
|
69
|
-
const { buckets: allBuckets, updatedBuckets } = update;
|
|
69
|
+
const { buckets: allBuckets, updatedBuckets, parameterQueryResultsByDefinition } = update;
|
|
70
70
|
/** Set of all buckets in this checkpoint. */
|
|
71
71
|
const bucketDescriptionMap = new Map(allBuckets.map((b) => [b.bucket, b]));
|
|
72
72
|
if (bucketDescriptionMap.size > this.context.maxBuckets) {
|
|
@@ -145,18 +145,22 @@ export class BucketChecksumState {
|
|
|
145
145
|
};
|
|
146
146
|
});
|
|
147
147
|
deferredLog = () => {
|
|
148
|
+
const totalParamResults = computeTotalParamResults(parameterQueryResultsByDefinition);
|
|
148
149
|
let message = `Updated checkpoint: ${base.checkpoint} | `;
|
|
149
150
|
message += `write: ${writeCheckpoint} | `;
|
|
150
151
|
message += `buckets: ${allBuckets.length} | `;
|
|
152
|
+
if (totalParamResults !== undefined) {
|
|
153
|
+
message += `param_results: ${totalParamResults} | `;
|
|
154
|
+
}
|
|
151
155
|
message += `updated: ${limitedBuckets(diff.updatedBuckets, 20)} | `;
|
|
152
156
|
message += `removed: ${limitedBuckets(diff.removedBuckets, 20)}`;
|
|
153
|
-
this.logger
|
|
157
|
+
logCheckpoint(this.logger, message, {
|
|
154
158
|
checkpoint: base.checkpoint,
|
|
155
159
|
user_id: userIdForLogs,
|
|
156
160
|
buckets: allBuckets.length,
|
|
157
161
|
updated: diff.updatedBuckets.length,
|
|
158
162
|
removed: diff.removedBuckets.length
|
|
159
|
-
});
|
|
163
|
+
}, totalParamResults);
|
|
160
164
|
};
|
|
161
165
|
checkpointLine = {
|
|
162
166
|
checkpoint_diff: {
|
|
@@ -169,9 +173,18 @@ export class BucketChecksumState {
|
|
|
169
173
|
}
|
|
170
174
|
else {
|
|
171
175
|
deferredLog = () => {
|
|
176
|
+
const totalParamResults = computeTotalParamResults(parameterQueryResultsByDefinition);
|
|
172
177
|
let message = `New checkpoint: ${base.checkpoint} | write: ${writeCheckpoint} | `;
|
|
173
|
-
message += `buckets: ${allBuckets.length}
|
|
174
|
-
|
|
178
|
+
message += `buckets: ${allBuckets.length}`;
|
|
179
|
+
if (totalParamResults !== undefined) {
|
|
180
|
+
message += ` | param_results: ${totalParamResults}`;
|
|
181
|
+
}
|
|
182
|
+
message += ` ${limitedBuckets(allBuckets, 20)}`;
|
|
183
|
+
logCheckpoint(this.logger, message, {
|
|
184
|
+
checkpoint: base.checkpoint,
|
|
185
|
+
user_id: userIdForLogs,
|
|
186
|
+
buckets: allBuckets.length
|
|
187
|
+
}, totalParamResults);
|
|
175
188
|
};
|
|
176
189
|
bucketsToFetch = allBuckets.map((b) => ({ bucket: b.bucket, priority: b.priority }));
|
|
177
190
|
const subscriptions = [];
|
|
@@ -388,11 +401,18 @@ export class BucketParameterState {
|
|
|
388
401
|
// TODO: Limit number of results even before we get to this point
|
|
389
402
|
// This limit applies _before_ we get the unique set
|
|
390
403
|
const error = new ServiceError(ErrorCode.PSYNC_S2305, `Too many parameter query results: ${update.buckets.length} (limit of ${this.context.maxParameterQueryResults})`);
|
|
391
|
-
|
|
404
|
+
let errorMessage = error.message;
|
|
405
|
+
const logData = {
|
|
392
406
|
checkpoint: checkpoint,
|
|
393
407
|
user_id: this.syncParams.userId,
|
|
394
|
-
|
|
395
|
-
}
|
|
408
|
+
parameter_query_results: update.buckets.length
|
|
409
|
+
};
|
|
410
|
+
if (update.parameterQueryResultsByDefinition && update.parameterQueryResultsByDefinition.size > 0) {
|
|
411
|
+
const breakdown = formatParameterQueryBreakdown(update.parameterQueryResultsByDefinition);
|
|
412
|
+
errorMessage += breakdown.message;
|
|
413
|
+
logData.parameter_query_results_by_definition = breakdown.countsByDefinition;
|
|
414
|
+
}
|
|
415
|
+
this.logger.error(errorMessage, logData);
|
|
396
416
|
throw error;
|
|
397
417
|
}
|
|
398
418
|
return update;
|
|
@@ -440,6 +460,7 @@ export class BucketParameterState {
|
|
|
440
460
|
}
|
|
441
461
|
}
|
|
442
462
|
let dynamicBuckets;
|
|
463
|
+
let parameterQueryResultsByDefinition;
|
|
443
464
|
if (hasParameterChange || this.cachedDynamicBuckets == null || this.cachedDynamicBucketSet == null) {
|
|
444
465
|
const recordedLookups = new Set();
|
|
445
466
|
dynamicBuckets = await querier.queryDynamicBucketDescriptions({
|
|
@@ -450,6 +471,12 @@ export class BucketParameterState {
|
|
|
450
471
|
return checkpoint.base.getParameterSets(lookups);
|
|
451
472
|
}
|
|
452
473
|
});
|
|
474
|
+
// Count parameter query results per definition (before deduplication)
|
|
475
|
+
parameterQueryResultsByDefinition = new Map();
|
|
476
|
+
for (const bucket of dynamicBuckets) {
|
|
477
|
+
const count = parameterQueryResultsByDefinition.get(bucket.definition) ?? 0;
|
|
478
|
+
parameterQueryResultsByDefinition.set(bucket.definition, count + 1);
|
|
479
|
+
}
|
|
453
480
|
this.cachedDynamicBuckets = dynamicBuckets;
|
|
454
481
|
this.cachedDynamicBucketSet = new Set(dynamicBuckets.map((b) => b.bucket));
|
|
455
482
|
this.lookupsFromPreviousCheckpoint = recordedLookups;
|
|
@@ -471,17 +498,65 @@ export class BucketParameterState {
|
|
|
471
498
|
return {
|
|
472
499
|
buckets: allBuckets,
|
|
473
500
|
// We cannot track individual bucket updates for dynamic lookups yet
|
|
474
|
-
updatedBuckets: INVALIDATE_ALL_BUCKETS
|
|
501
|
+
updatedBuckets: INVALIDATE_ALL_BUCKETS,
|
|
502
|
+
parameterQueryResultsByDefinition
|
|
475
503
|
};
|
|
476
504
|
}
|
|
477
505
|
else {
|
|
478
506
|
return {
|
|
479
507
|
buckets: allBuckets,
|
|
480
|
-
updatedBuckets: updatedBuckets
|
|
508
|
+
updatedBuckets: updatedBuckets,
|
|
509
|
+
parameterQueryResultsByDefinition
|
|
481
510
|
};
|
|
482
511
|
}
|
|
483
512
|
}
|
|
484
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Compute the total number of parameter query results across all definitions.
|
|
516
|
+
*/
|
|
517
|
+
function computeTotalParamResults(parameterQueryResultsByDefinition) {
|
|
518
|
+
if (!parameterQueryResultsByDefinition) {
|
|
519
|
+
return undefined;
|
|
520
|
+
}
|
|
521
|
+
return Array.from(parameterQueryResultsByDefinition.values()).reduce((sum, count) => sum + count, 0);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Log a checkpoint message, enriching it with parameter query result counts if available.
|
|
525
|
+
*
|
|
526
|
+
* @param logger The logger instance to use
|
|
527
|
+
* @param message The base message string (param_results will NOT be appended — caller includes it if needed)
|
|
528
|
+
* @param logData The base log data object
|
|
529
|
+
* @param totalParamResults The total parameter query results count, or undefined if not applicable
|
|
530
|
+
*/
|
|
531
|
+
function logCheckpoint(logger, message, logData, totalParamResults) {
|
|
532
|
+
if (totalParamResults !== undefined) {
|
|
533
|
+
logData.parameter_query_results = totalParamResults;
|
|
534
|
+
}
|
|
535
|
+
logger.info(message, logData);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Format a breakdown of parameter query results by sync rule definition.
|
|
539
|
+
*
|
|
540
|
+
* Sorts definitions by count (descending), includes the top 10, and returns both the
|
|
541
|
+
* formatted message string and the counts record suitable for structured log data.
|
|
542
|
+
*/
|
|
543
|
+
function formatParameterQueryBreakdown(parameterQueryResultsByDefinition) {
|
|
544
|
+
// Sort definitions by count (descending) and take top 10
|
|
545
|
+
const allSorted = Array.from(parameterQueryResultsByDefinition.entries()).sort((a, b) => b[1] - a[1]);
|
|
546
|
+
const sortedDefinitions = allSorted.slice(0, 10);
|
|
547
|
+
let message = '\nParameter query results by definition:';
|
|
548
|
+
const countsByDefinition = {};
|
|
549
|
+
for (const [definition, count] of sortedDefinitions) {
|
|
550
|
+
message += `\n ${definition}: ${count}`;
|
|
551
|
+
countsByDefinition[definition] = count;
|
|
552
|
+
}
|
|
553
|
+
if (allSorted.length > 10) {
|
|
554
|
+
const remainingResults = allSorted.slice(10).reduce((sum, [, count]) => sum + count, 0);
|
|
555
|
+
const remainingDefinitions = allSorted.length - 10;
|
|
556
|
+
message += `\n ... and ${remainingResults} more results from ${remainingDefinitions} definitions`;
|
|
557
|
+
}
|
|
558
|
+
return { message, countsByDefinition };
|
|
559
|
+
}
|
|
485
560
|
function limitedBuckets(buckets, limit) {
|
|
486
561
|
buckets = buckets.map((b) => {
|
|
487
562
|
if (typeof b != 'string') {
|