@powersync/service-core 0.0.0-dev-20250117095455 → 0.0.0-dev-20250214100224

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CHANGELOG.md +48 -8
  2. package/dist/api/RouteAPI.d.ts +8 -0
  3. package/dist/auth/CachedKeyCollector.js +26 -25
  4. package/dist/auth/CachedKeyCollector.js.map +1 -1
  5. package/dist/auth/CompoundKeyCollector.js +1 -0
  6. package/dist/auth/CompoundKeyCollector.js.map +1 -1
  7. package/dist/auth/KeySpec.js +3 -0
  8. package/dist/auth/KeySpec.js.map +1 -1
  9. package/dist/auth/KeyStore.js +4 -0
  10. package/dist/auth/KeyStore.js.map +1 -1
  11. package/dist/auth/LeakyBucket.js +5 -0
  12. package/dist/auth/LeakyBucket.js.map +1 -1
  13. package/dist/auth/RemoteJWKSCollector.d.ts +3 -0
  14. package/dist/auth/RemoteJWKSCollector.js +12 -5
  15. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  16. package/dist/auth/StaticKeyCollector.js +1 -0
  17. package/dist/auth/StaticKeyCollector.js.map +1 -1
  18. package/dist/auth/StaticSupabaseKeyCollector.js +1 -0
  19. package/dist/auth/StaticSupabaseKeyCollector.js.map +1 -1
  20. package/dist/entry/cli-entry.js +2 -0
  21. package/dist/entry/cli-entry.js.map +1 -1
  22. package/dist/entry/commands/teardown-action.js +2 -1
  23. package/dist/entry/commands/teardown-action.js.map +1 -1
  24. package/dist/entry/commands/test-connection-action.d.ts +2 -0
  25. package/dist/entry/commands/test-connection-action.js +32 -0
  26. package/dist/entry/commands/test-connection-action.js.map +1 -0
  27. package/dist/metrics/Metrics.js +37 -3
  28. package/dist/metrics/Metrics.js.map +1 -1
  29. package/dist/modules/AbstractModule.js +2 -0
  30. package/dist/modules/AbstractModule.js.map +1 -1
  31. package/dist/modules/ModuleManager.js +1 -3
  32. package/dist/modules/ModuleManager.js.map +1 -1
  33. package/dist/replication/AbstractReplicationJob.js +4 -2
  34. package/dist/replication/AbstractReplicationJob.js.map +1 -1
  35. package/dist/replication/AbstractReplicator.d.ts +2 -0
  36. package/dist/replication/AbstractReplicator.js +18 -12
  37. package/dist/replication/AbstractReplicator.js.map +1 -1
  38. package/dist/replication/ReplicationEngine.d.ts +2 -0
  39. package/dist/replication/ReplicationEngine.js +4 -3
  40. package/dist/replication/ReplicationEngine.js.map +1 -1
  41. package/dist/replication/ReplicationModule.d.ts +8 -2
  42. package/dist/replication/ReplicationModule.js +9 -11
  43. package/dist/replication/ReplicationModule.js.map +1 -1
  44. package/dist/routes/RouterEngine.js +8 -0
  45. package/dist/routes/RouterEngine.js.map +1 -1
  46. package/dist/routes/configure-fastify.d.ts +3 -3
  47. package/dist/routes/endpoints/admin.d.ts +6 -6
  48. package/dist/routes/endpoints/admin.js +7 -4
  49. package/dist/routes/endpoints/admin.js.map +1 -1
  50. package/dist/routes/endpoints/checkpointing.js +14 -84
  51. package/dist/routes/endpoints/checkpointing.js.map +1 -1
  52. package/dist/routes/endpoints/socket-route.js +5 -5
  53. package/dist/routes/endpoints/socket-route.js.map +1 -1
  54. package/dist/routes/endpoints/sync-rules.js +16 -11
  55. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  56. package/dist/routes/endpoints/sync-stream.js +5 -5
  57. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  58. package/dist/routes/route-register.js +4 -4
  59. package/dist/routes/route-register.js.map +1 -1
  60. package/dist/runner/teardown.js +2 -2
  61. package/dist/runner/teardown.js.map +1 -1
  62. package/dist/storage/BucketStorage.d.ts +32 -5
  63. package/dist/storage/BucketStorage.js +3 -0
  64. package/dist/storage/BucketStorage.js.map +1 -1
  65. package/dist/storage/ChecksumCache.js +12 -7
  66. package/dist/storage/ChecksumCache.js.map +1 -1
  67. package/dist/storage/SourceTable.js +32 -25
  68. package/dist/storage/SourceTable.js.map +1 -1
  69. package/dist/storage/StorageEngine.js +4 -3
  70. package/dist/storage/StorageEngine.js.map +1 -1
  71. package/dist/storage/bson.d.ts +5 -3
  72. package/dist/storage/bson.js.map +1 -1
  73. package/dist/sync/BroadcastIterable.js +4 -3
  74. package/dist/sync/BroadcastIterable.js.map +1 -1
  75. package/dist/sync/LastValueSink.js +2 -0
  76. package/dist/sync/LastValueSink.js.map +1 -1
  77. package/dist/sync/RequestTracker.js +2 -4
  78. package/dist/sync/RequestTracker.js.map +1 -1
  79. package/dist/sync/merge.js +4 -0
  80. package/dist/sync/merge.js.map +1 -1
  81. package/dist/sync/sync.js +2 -2
  82. package/dist/sync/sync.js.map +1 -1
  83. package/dist/sync/util.js +2 -2
  84. package/dist/sync/util.js.map +1 -1
  85. package/dist/system/ServiceContext.js +3 -0
  86. package/dist/system/ServiceContext.js.map +1 -1
  87. package/dist/util/Mutex.js +5 -0
  88. package/dist/util/Mutex.js.map +1 -1
  89. package/dist/util/checkpointing.d.ts +13 -0
  90. package/dist/util/checkpointing.js +92 -0
  91. package/dist/util/checkpointing.js.map +1 -0
  92. package/dist/util/config/compound-config-collector.js +3 -1
  93. package/dist/util/config/compound-config-collector.js.map +1 -1
  94. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js +1 -0
  95. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
  96. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js +1 -0
  97. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
  98. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js +1 -0
  99. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -1
  100. package/dist/util/config/sync-rules/sync-rules-provider.d.ts +2 -0
  101. package/dist/util/config/sync-rules/sync-rules-provider.js +4 -0
  102. package/dist/util/config/sync-rules/sync-rules-provider.js.map +1 -1
  103. package/dist/util/config/types.d.ts +1 -0
  104. package/dist/util/memory-tracking.js +1 -1
  105. package/dist/util/memory-tracking.js.map +1 -1
  106. package/dist/util/util-index.d.ts +1 -0
  107. package/dist/util/util-index.js +1 -0
  108. package/dist/util/util-index.js.map +1 -1
  109. package/dist/util/utils.d.ts +0 -1
  110. package/dist/util/utils.js +2 -10
  111. package/dist/util/utils.js.map +1 -1
  112. package/package.json +5 -5
  113. package/src/api/RouteAPI.ts +10 -0
  114. package/src/auth/RemoteJWKSCollector.ts +18 -5
  115. package/src/entry/cli-entry.ts +2 -0
  116. package/src/entry/commands/teardown-action.ts +2 -1
  117. package/src/entry/commands/test-connection-action.ts +41 -0
  118. package/src/metrics/Metrics.ts +2 -2
  119. package/src/replication/AbstractReplicator.ts +12 -3
  120. package/src/replication/ReplicationEngine.ts +5 -0
  121. package/src/replication/ReplicationModule.ts +15 -13
  122. package/src/routes/endpoints/admin.ts +7 -4
  123. package/src/routes/endpoints/checkpointing.ts +8 -19
  124. package/src/routes/endpoints/socket-route.ts +5 -5
  125. package/src/routes/endpoints/sync-rules.ts +16 -11
  126. package/src/routes/endpoints/sync-stream.ts +5 -5
  127. package/src/routes/route-register.ts +4 -4
  128. package/src/storage/BucketStorage.ts +39 -4
  129. package/src/storage/bson.ts +9 -7
  130. package/src/util/checkpointing.ts +43 -0
  131. package/src/util/config/compound-config-collector.ts +2 -1
  132. package/src/util/config/sync-rules/impl/base64-sync-rules-collector.ts +1 -0
  133. package/src/util/config/sync-rules/impl/filesystem-sync-rules-collector.ts +1 -0
  134. package/src/util/config/sync-rules/impl/inline-sync-rules-collector.ts +1 -0
  135. package/src/util/config/sync-rules/sync-rules-provider.ts +6 -0
  136. package/src/util/config/types.ts +1 -0
  137. package/src/util/memory-tracking.ts +2 -2
  138. package/src/util/util-index.ts +1 -0
  139. package/src/util/utils.ts +2 -11
  140. package/tsconfig.tsbuildinfo +1 -1
@@ -3,6 +3,8 @@ import * as bson from 'bson';
3
3
  import { SqliteJsonValue } from '@powersync/service-sync-rules';
4
4
  import { ReplicaId } from './BucketStorage.js';
5
5
 
6
+ type NodeBuffer = Buffer<ArrayBuffer>;
7
+
6
8
  export const BSON_DESERIALIZE_OPTIONS: bson.DeserializeOptions = {
7
9
  // use bigint instead of Long
8
10
  useBigInt64: true
@@ -12,7 +14,7 @@ export const BSON_DESERIALIZE_OPTIONS: bson.DeserializeOptions = {
12
14
  * Lookup serialization must be number-agnostic. I.e. normalize numbers, instead of preserving numbers.
13
15
  * @param lookup
14
16
  */
15
- export const serializeLookupBuffer = (lookup: SqliteJsonValue[]): Buffer => {
17
+ export const serializeLookupBuffer = (lookup: SqliteJsonValue[]): NodeBuffer => {
16
18
  const normalized = lookup.map((value) => {
17
19
  if (typeof value == 'number' && Number.isInteger(value)) {
18
20
  return BigInt(value);
@@ -20,7 +22,7 @@ export const serializeLookupBuffer = (lookup: SqliteJsonValue[]): Buffer => {
20
22
  return value;
21
23
  }
22
24
  });
23
- return bson.serialize({ l: normalized }) as Buffer;
25
+ return bson.serialize({ l: normalized }) as NodeBuffer;
24
26
  };
25
27
 
26
28
  export const serializeLookup = (lookup: SqliteJsonValue[]) => {
@@ -40,8 +42,8 @@ export const isUUID = (value: any): value is bson.UUID => {
40
42
  return uuid._bsontype == 'Binary' && uuid.sub_type == bson.Binary.SUBTYPE_UUID;
41
43
  };
42
44
 
43
- export const serializeReplicaId = (id: ReplicaId): Buffer => {
44
- return bson.serialize({ id }) as Buffer;
45
+ export const serializeReplicaId = (id: ReplicaId): NodeBuffer => {
46
+ return bson.serialize({ id }) as NodeBuffer;
45
47
  };
46
48
 
47
49
  export const deserializeReplicaId = (id: Buffer): ReplicaId => {
@@ -53,8 +55,8 @@ export const deserializeBson = (buffer: Buffer) => {
53
55
  return bson.deserialize(buffer, BSON_DESERIALIZE_OPTIONS);
54
56
  };
55
57
 
56
- export const serializeBson = (document: any): Buffer => {
57
- return bson.serialize(document) as Buffer;
58
+ export const serializeBson = (document: any): NodeBuffer => {
59
+ return bson.serialize(document) as NodeBuffer;
58
60
  };
59
61
 
60
62
  /**
@@ -73,6 +75,6 @@ export const replicaIdEquals = (a: ReplicaId, b: ReplicaId) => {
73
75
  return false;
74
76
  } else {
75
77
  // There are many possible primitive values, this covers them all
76
- return serializeReplicaId(a).equals(serializeReplicaId(b) as ArrayBuffer as Uint8Array);
78
+ return serializeReplicaId(a).equals(serializeReplicaId(b));
77
79
  }
78
80
  };
@@ -0,0 +1,43 @@
1
+ import { ErrorCode, logger, ServiceError } from '@powersync/lib-services-framework';
2
+ import { RouteAPI } from '../api/RouteAPI.js';
3
+ import { BucketStorageFactory } from '../storage/BucketStorage.js';
4
+
5
+ export interface CreateWriteCheckpointOptions {
6
+ userId: string | undefined;
7
+ clientId: string | undefined;
8
+ api: RouteAPI;
9
+ storage: BucketStorageFactory;
10
+ }
11
+ export async function createWriteCheckpoint(options: CreateWriteCheckpointOptions) {
12
+ const full_user_id = checkpointUserId(options.userId, options.clientId);
13
+
14
+ const activeSyncRules = await options.storage.getActiveSyncRulesContent();
15
+ if (!activeSyncRules) {
16
+ throw new ServiceError(ErrorCode.PSYNC_S2302, `Cannot create Write Checkpoint since no sync rules are active.`);
17
+ }
18
+
19
+ using syncBucketStorage = options.storage.getInstance(activeSyncRules);
20
+
21
+ const { writeCheckpoint, currentCheckpoint } = await options.api.createReplicationHead(async (currentCheckpoint) => {
22
+ const writeCheckpoint = await syncBucketStorage.createManagedWriteCheckpoint({
23
+ user_id: full_user_id,
24
+ heads: { '1': currentCheckpoint }
25
+ });
26
+ return { writeCheckpoint, currentCheckpoint };
27
+ });
28
+
29
+ return {
30
+ writeCheckpoint: String(writeCheckpoint),
31
+ replicationHead: currentCheckpoint
32
+ };
33
+ }
34
+
35
+ export function checkpointUserId(user_id: string | undefined, client_id: string | undefined) {
36
+ if (user_id == null) {
37
+ throw new Error('user_id is required');
38
+ }
39
+ if (client_id == null) {
40
+ return user_id;
41
+ }
42
+ return `${user_id}/${client_id}`;
43
+ }
@@ -196,7 +196,8 @@ export class CompoundConfigCollector {
196
196
  }
197
197
  }
198
198
  return {
199
- present: false
199
+ present: false,
200
+ exit_on_error: true
200
201
  };
201
202
  }
202
203
  }
@@ -15,6 +15,7 @@ export class Base64SyncRulesCollector extends SyncRulesCollector {
15
15
 
16
16
  return {
17
17
  present: true,
18
+ exit_on_error: baseConfig.sync_rules?.exit_on_error ?? true,
18
19
  content: Buffer.from(sync_rules_base64, 'base64').toString()
19
20
  };
20
21
  }
@@ -20,6 +20,7 @@ export class FileSystemSyncRulesCollector extends SyncRulesCollector {
20
20
  // Only persist the path here, and load on demand using `loadSyncRules()`.
21
21
  return {
22
22
  present: true,
23
+ exit_on_error: baseConfig.sync_rules?.exit_on_error ?? true,
23
24
  path: config_path ? path.resolve(path.dirname(config_path), sync_path) : sync_path
24
25
  };
25
26
  }
@@ -15,6 +15,7 @@ export class InlineSyncRulesCollector extends SyncRulesCollector {
15
15
 
16
16
  return {
17
17
  present: true,
18
+ exit_on_error: true,
18
19
  ...baseConfig.sync_rules
19
20
  };
20
21
  }
@@ -3,6 +3,8 @@ import fs from 'fs/promises';
3
3
 
4
4
  export interface SyncRulesProvider {
5
5
  get(): Promise<string | undefined>;
6
+
7
+ readonly exitOnError: boolean;
6
8
  }
7
9
 
8
10
  export class ConfigurationFileSyncRulesProvider implements SyncRulesProvider {
@@ -15,4 +17,8 @@ export class ConfigurationFileSyncRulesProvider implements SyncRulesProvider {
15
17
  return await fs.readFile(this.config.path, 'utf-8');
16
18
  }
17
19
  }
20
+
21
+ get exitOnError() {
22
+ return this.config.exit_on_error;
23
+ }
18
24
  }
@@ -26,6 +26,7 @@ export type SyncRulesConfig = {
26
26
  present: boolean;
27
27
  content?: string;
28
28
  path?: string;
29
+ exit_on_error: boolean;
29
30
  };
30
31
 
31
32
  export type ResolvedPowerSyncConfig = {
@@ -25,8 +25,8 @@ export function trackMemoryUsage() {
25
25
  for (let key of Object.keys(bufferMemory)) {
26
26
  const typedKey = key as keyof typeof bufferMemory;
27
27
  const originalFunction = Buffer[typedKey] as (...args: any[]) => Buffer;
28
- Buffer[typedKey] = function (...args: any[]) {
29
- const buffer = originalFunction.apply(this, args);
28
+ Buffer[typedKey] = function <TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>(...args: any[]) {
29
+ const buffer = originalFunction.apply(this, args) as Buffer<TArrayBuffer>;
30
30
  bufferMemory[typedKey] += buffer.byteLength;
31
31
  bufferRegistry.register(buffer, [typedKey, buffer.byteLength]);
32
32
  return buffer;
@@ -5,6 +5,7 @@ export * from './Mutex.js';
5
5
  export * from './protocol-types.js';
6
6
  export * from './secs.js';
7
7
  export * from './utils.js';
8
+ export * from './checkpointing.js';
8
9
 
9
10
  export * from './config.js';
10
11
  export * from './config/compound-config-collector.js';
package/src/util/utils.ts CHANGED
@@ -7,6 +7,7 @@ import { BucketChecksum, OpId, OplogEntry } from './protocol-types.js';
7
7
  import * as storage from '../storage/storage-index.js';
8
8
 
9
9
  import { PartialChecksum } from '../storage/ChecksumCache.js';
10
+ import { ServiceAssertionError } from '@powersync/lib-services-framework';
10
11
 
11
12
  export type ChecksumMap = Map<string, BucketChecksum>;
12
13
 
@@ -34,7 +35,7 @@ export function timestampToOpId(ts: bigint): OpId {
34
35
  // Dynamic values are passed in in some cases, so we make extra sure that the
35
36
  // number is a bigint and not number or Long.
36
37
  if (typeof ts != 'bigint') {
37
- throw new Error(`bigint expected, got: ${ts} (${typeof ts})`);
38
+ throw new ServiceAssertionError(`bigint expected, got: ${ts} (${typeof ts})`);
38
39
  }
39
40
  return ts.toString(10);
40
41
  }
@@ -144,16 +145,6 @@ export function isCompleteRow(storeData: boolean, row: sync_rules.ToastableSqlit
144
145
  return !hasToastedValues(row);
145
146
  }
146
147
 
147
- export function checkpointUserId(user_id: string | undefined, client_id: string | undefined) {
148
- if (user_id == null) {
149
- throw new Error('user_id is required');
150
- }
151
- if (client_id == null) {
152
- return user_id;
153
- }
154
- return `${user_id}/${client_id}`;
155
- }
156
-
157
148
  /**
158
149
  * Reduce a bucket to the final state as stored on the client.
159
150
  *