@powersync/service-core 0.2.2 → 0.4.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.
Files changed (262) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/api/diagnostics.js +2 -2
  3. package/dist/api/diagnostics.js.map +1 -1
  4. package/dist/api/schema.js.map +1 -1
  5. package/dist/auth/CachedKeyCollector.js.map +1 -1
  6. package/dist/auth/JwtPayload.d.ts +6 -2
  7. package/dist/auth/KeySpec.js.map +1 -1
  8. package/dist/auth/KeyStore.js +3 -9
  9. package/dist/auth/KeyStore.js.map +1 -1
  10. package/dist/auth/LeakyBucket.js.map +1 -1
  11. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  12. package/dist/auth/SupabaseKeyCollector.js.map +1 -1
  13. package/dist/db/mongo.js.map +1 -1
  14. package/dist/entry/cli-entry.js +2 -2
  15. package/dist/entry/cli-entry.js.map +1 -1
  16. package/dist/entry/commands/config-command.js.map +1 -1
  17. package/dist/entry/commands/migrate-action.js +12 -4
  18. package/dist/entry/commands/migrate-action.js.map +1 -1
  19. package/dist/entry/commands/start-action.js.map +1 -1
  20. package/dist/entry/commands/teardown-action.js.map +1 -1
  21. package/dist/index.d.ts +3 -2
  22. package/dist/index.js +4 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/locks/LockManager.d.ts +10 -0
  25. package/dist/locks/LockManager.js +7 -0
  26. package/dist/locks/LockManager.js.map +1 -0
  27. package/dist/locks/MongoLocks.d.ts +36 -0
  28. package/dist/locks/MongoLocks.js +81 -0
  29. package/dist/locks/MongoLocks.js.map +1 -0
  30. package/dist/locks/locks-index.d.ts +2 -0
  31. package/dist/locks/locks-index.js +3 -0
  32. package/dist/locks/locks-index.js.map +1 -0
  33. package/dist/metrics/Metrics.js +6 -6
  34. package/dist/metrics/Metrics.js.map +1 -1
  35. package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -1
  36. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
  37. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -1
  38. package/dist/migrations/definitions.d.ts +18 -0
  39. package/dist/migrations/definitions.js +6 -0
  40. package/dist/migrations/definitions.js.map +1 -0
  41. package/dist/migrations/executor.d.ts +16 -0
  42. package/dist/migrations/executor.js +64 -0
  43. package/dist/migrations/executor.js.map +1 -0
  44. package/dist/migrations/migrations-index.d.ts +3 -0
  45. package/dist/migrations/migrations-index.js +4 -0
  46. package/dist/migrations/migrations-index.js.map +1 -0
  47. package/dist/migrations/migrations.d.ts +1 -1
  48. package/dist/migrations/migrations.js +12 -8
  49. package/dist/migrations/migrations.js.map +1 -1
  50. package/dist/migrations/store/migration-store.d.ts +11 -0
  51. package/dist/migrations/store/migration-store.js +46 -0
  52. package/dist/migrations/store/migration-store.js.map +1 -0
  53. package/dist/replication/ErrorRateLimiter.js.map +1 -1
  54. package/dist/replication/PgRelation.js.map +1 -1
  55. package/dist/replication/WalConnection.js.map +1 -1
  56. package/dist/replication/WalStream.d.ts +0 -1
  57. package/dist/replication/WalStream.js +21 -25
  58. package/dist/replication/WalStream.js.map +1 -1
  59. package/dist/replication/WalStreamManager.js +12 -13
  60. package/dist/replication/WalStreamManager.js.map +1 -1
  61. package/dist/replication/WalStreamRunner.js +8 -8
  62. package/dist/replication/WalStreamRunner.js.map +1 -1
  63. package/dist/replication/util.js.map +1 -1
  64. package/dist/routes/auth.d.ts +8 -10
  65. package/dist/routes/auth.js.map +1 -1
  66. package/dist/routes/endpoints/admin.d.ts +1011 -0
  67. package/dist/routes/{admin.js → endpoints/admin.js} +33 -18
  68. package/dist/routes/endpoints/admin.js.map +1 -0
  69. package/dist/routes/endpoints/checkpointing.d.ts +76 -0
  70. package/dist/routes/endpoints/checkpointing.js +36 -0
  71. package/dist/routes/endpoints/checkpointing.js.map +1 -0
  72. package/dist/routes/endpoints/dev.d.ts +312 -0
  73. package/dist/routes/{dev.js → endpoints/dev.js} +25 -16
  74. package/dist/routes/endpoints/dev.js.map +1 -0
  75. package/dist/routes/endpoints/route-endpoints-index.d.ts +6 -0
  76. package/dist/routes/endpoints/route-endpoints-index.js +7 -0
  77. package/dist/routes/endpoints/route-endpoints-index.js.map +1 -0
  78. package/dist/routes/endpoints/socket-route.d.ts +2 -0
  79. package/dist/routes/{socket-route.js → endpoints/socket-route.js} +12 -12
  80. package/dist/routes/endpoints/socket-route.js.map +1 -0
  81. package/dist/routes/endpoints/sync-rules.d.ts +174 -0
  82. package/dist/routes/{sync-rules.js → endpoints/sync-rules.js} +44 -24
  83. package/dist/routes/endpoints/sync-rules.js.map +1 -0
  84. package/dist/routes/endpoints/sync-stream.d.ts +132 -0
  85. package/dist/routes/{sync-stream.js → endpoints/sync-stream.js} +28 -19
  86. package/dist/routes/endpoints/sync-stream.js.map +1 -0
  87. package/dist/routes/hooks.d.ts +10 -0
  88. package/dist/routes/hooks.js +31 -0
  89. package/dist/routes/hooks.js.map +1 -0
  90. package/dist/routes/route-register.d.ts +10 -0
  91. package/dist/routes/route-register.js +87 -0
  92. package/dist/routes/route-register.js.map +1 -0
  93. package/dist/routes/router.d.ts +16 -4
  94. package/dist/routes/router.js +6 -1
  95. package/dist/routes/router.js.map +1 -1
  96. package/dist/routes/routes-index.d.ts +5 -3
  97. package/dist/routes/routes-index.js +5 -3
  98. package/dist/routes/routes-index.js.map +1 -1
  99. package/dist/runner/teardown.js +9 -9
  100. package/dist/runner/teardown.js.map +1 -1
  101. package/dist/storage/BucketStorage.d.ts +3 -0
  102. package/dist/storage/BucketStorage.js.map +1 -1
  103. package/dist/storage/ChecksumCache.js.map +1 -1
  104. package/dist/storage/MongoBucketStorage.js +5 -5
  105. package/dist/storage/MongoBucketStorage.js.map +1 -1
  106. package/dist/storage/SourceTable.js.map +1 -1
  107. package/dist/storage/mongo/MongoBucketBatch.js +23 -18
  108. package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
  109. package/dist/storage/mongo/MongoIdSequence.js.map +1 -1
  110. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
  111. package/dist/storage/mongo/MongoSyncRulesLock.js +3 -3
  112. package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -1
  113. package/dist/storage/mongo/OperationBatch.js.map +1 -1
  114. package/dist/storage/mongo/PersistedBatch.js +2 -2
  115. package/dist/storage/mongo/PersistedBatch.js.map +1 -1
  116. package/dist/storage/mongo/db.d.ts +2 -2
  117. package/dist/storage/mongo/db.js.map +1 -1
  118. package/dist/storage/mongo/util.js.map +1 -1
  119. package/dist/sync/BroadcastIterable.js.map +1 -1
  120. package/dist/sync/LastValueSink.js.map +1 -1
  121. package/dist/sync/merge.js.map +1 -1
  122. package/dist/sync/safeRace.js.map +1 -1
  123. package/dist/sync/sync.d.ts +2 -2
  124. package/dist/sync/sync.js +5 -5
  125. package/dist/sync/sync.js.map +1 -1
  126. package/dist/sync/util.js.map +1 -1
  127. package/dist/system/CorePowerSyncSystem.d.ts +12 -7
  128. package/dist/system/CorePowerSyncSystem.js +26 -2
  129. package/dist/system/CorePowerSyncSystem.js.map +1 -1
  130. package/dist/system/system-index.d.ts +1 -0
  131. package/dist/system/system-index.js +2 -0
  132. package/dist/system/system-index.js.map +1 -0
  133. package/dist/util/Mutex.js.map +1 -1
  134. package/dist/util/PgManager.js.map +1 -1
  135. package/dist/util/alerting.d.ts +0 -2
  136. package/dist/util/alerting.js +0 -6
  137. package/dist/util/alerting.js.map +1 -1
  138. package/dist/util/config/collectors/config-collector.js +3 -3
  139. package/dist/util/config/collectors/config-collector.js.map +1 -1
  140. package/dist/util/config/collectors/impl/base64-config-collector.js.map +1 -1
  141. package/dist/util/config/collectors/impl/filesystem-config-collector.js +7 -5
  142. package/dist/util/config/collectors/impl/filesystem-config-collector.js.map +1 -1
  143. package/dist/util/config/compound-config-collector.js +4 -4
  144. package/dist/util/config/compound-config-collector.js.map +1 -1
  145. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
  146. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
  147. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -1
  148. package/dist/util/config.js.map +1 -1
  149. package/dist/util/env.d.ts +1 -2
  150. package/dist/util/env.js +3 -2
  151. package/dist/util/env.js.map +1 -1
  152. package/dist/util/memory-tracking.js +2 -2
  153. package/dist/util/memory-tracking.js.map +1 -1
  154. package/dist/util/migration_lib.js.map +1 -1
  155. package/dist/util/pgwire_utils.js +2 -2
  156. package/dist/util/pgwire_utils.js.map +1 -1
  157. package/dist/util/populate_test_data.js.map +1 -1
  158. package/dist/util/secs.js.map +1 -1
  159. package/dist/util/utils.js +4 -4
  160. package/dist/util/utils.js.map +1 -1
  161. package/package.json +13 -10
  162. package/src/api/diagnostics.ts +5 -5
  163. package/src/api/schema.ts +1 -1
  164. package/src/auth/JwtPayload.ts +6 -2
  165. package/src/auth/KeyStore.ts +3 -9
  166. package/src/entry/cli-entry.ts +3 -4
  167. package/src/entry/commands/config-command.ts +1 -1
  168. package/src/entry/commands/migrate-action.ts +14 -6
  169. package/src/entry/commands/start-action.ts +1 -1
  170. package/src/entry/commands/teardown-action.ts +1 -1
  171. package/src/index.ts +5 -2
  172. package/src/locks/LockManager.ts +16 -0
  173. package/src/locks/MongoLocks.ts +142 -0
  174. package/src/locks/locks-index.ts +2 -0
  175. package/src/metrics/Metrics.ts +8 -8
  176. package/src/migrations/db/migrations/1684951997326-init.ts +3 -3
  177. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +3 -3
  178. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +2 -2
  179. package/src/migrations/definitions.ts +21 -0
  180. package/src/migrations/executor.ts +87 -0
  181. package/src/migrations/migrations-index.ts +3 -0
  182. package/src/migrations/migrations.ts +15 -11
  183. package/src/migrations/store/migration-store.ts +63 -0
  184. package/src/replication/WalConnection.ts +2 -2
  185. package/src/replication/WalStream.ts +24 -29
  186. package/src/replication/WalStreamManager.ts +14 -15
  187. package/src/replication/WalStreamRunner.ts +10 -10
  188. package/src/replication/util.ts +1 -1
  189. package/src/routes/auth.ts +22 -16
  190. package/src/routes/endpoints/admin.ts +237 -0
  191. package/src/routes/endpoints/checkpointing.ts +41 -0
  192. package/src/routes/endpoints/dev.ts +199 -0
  193. package/src/routes/endpoints/route-endpoints-index.ts +6 -0
  194. package/src/routes/{socket-route.ts → endpoints/socket-route.ts} +13 -16
  195. package/src/routes/endpoints/sync-rules.ts +227 -0
  196. package/src/routes/endpoints/sync-stream.ts +98 -0
  197. package/src/routes/hooks.ts +45 -0
  198. package/src/routes/route-register.ts +104 -0
  199. package/src/routes/router.ts +34 -6
  200. package/src/routes/routes-index.ts +5 -4
  201. package/src/runner/teardown.ts +9 -9
  202. package/src/storage/BucketStorage.ts +7 -2
  203. package/src/storage/ChecksumCache.ts +2 -2
  204. package/src/storage/MongoBucketStorage.ts +8 -8
  205. package/src/storage/SourceTable.ts +2 -2
  206. package/src/storage/mongo/MongoBucketBatch.ts +29 -22
  207. package/src/storage/mongo/MongoSyncBucketStorage.ts +3 -3
  208. package/src/storage/mongo/MongoSyncRulesLock.ts +3 -3
  209. package/src/storage/mongo/OperationBatch.ts +1 -1
  210. package/src/storage/mongo/PersistedBatch.ts +3 -3
  211. package/src/storage/mongo/db.ts +3 -4
  212. package/src/sync/sync.ts +11 -11
  213. package/src/sync/util.ts +2 -2
  214. package/src/system/CorePowerSyncSystem.ts +31 -10
  215. package/src/system/system-index.ts +1 -0
  216. package/src/util/alerting.ts +0 -8
  217. package/src/util/config/collectors/config-collector.ts +5 -3
  218. package/src/util/config/collectors/impl/filesystem-config-collector.ts +8 -6
  219. package/src/util/config/compound-config-collector.ts +4 -4
  220. package/src/util/env.ts +4 -2
  221. package/src/util/memory-tracking.ts +2 -2
  222. package/src/util/pgwire_utils.ts +3 -3
  223. package/src/util/utils.ts +5 -5
  224. package/test/src/auth.test.ts +4 -2
  225. package/test/src/data_storage.test.ts +181 -19
  226. package/test/src/env.ts +6 -6
  227. package/test/src/setup.ts +7 -0
  228. package/test/src/slow_tests.test.ts +45 -6
  229. package/test/src/sync.test.ts +6 -5
  230. package/test/tsconfig.json +1 -1
  231. package/tsconfig.json +5 -6
  232. package/tsconfig.tsbuildinfo +1 -1
  233. package/vitest.config.ts +1 -3
  234. package/dist/migrations/db/store.d.ts +0 -3
  235. package/dist/migrations/db/store.js +0 -10
  236. package/dist/migrations/db/store.js.map +0 -1
  237. package/dist/routes/admin.d.ts +0 -7
  238. package/dist/routes/admin.js.map +0 -1
  239. package/dist/routes/checkpointing.d.ts +0 -3
  240. package/dist/routes/checkpointing.js +0 -30
  241. package/dist/routes/checkpointing.js.map +0 -1
  242. package/dist/routes/dev.d.ts +0 -6
  243. package/dist/routes/dev.js.map +0 -1
  244. package/dist/routes/route-generators.d.ts +0 -15
  245. package/dist/routes/route-generators.js +0 -32
  246. package/dist/routes/route-generators.js.map +0 -1
  247. package/dist/routes/socket-route.d.ts +0 -2
  248. package/dist/routes/socket-route.js.map +0 -1
  249. package/dist/routes/sync-rules.d.ts +0 -6
  250. package/dist/routes/sync-rules.js.map +0 -1
  251. package/dist/routes/sync-stream.d.ts +0 -5
  252. package/dist/routes/sync-stream.js.map +0 -1
  253. package/src/migrations/db/store.ts +0 -11
  254. package/src/routes/admin.ts +0 -229
  255. package/src/routes/checkpointing.ts +0 -38
  256. package/src/routes/dev.ts +0 -194
  257. package/src/routes/route-generators.ts +0 -39
  258. package/src/routes/sync-rules.ts +0 -210
  259. package/src/routes/sync-stream.ts +0 -95
  260. package/test/src/sql_functions.test.ts +0 -254
  261. package/test/src/sql_operators.test.ts +0 -132
  262. package/test/src/sync_rules.test.ts +0 -1053
package/src/routes/dev.ts DELETED
@@ -1,194 +0,0 @@
1
- import * as t from 'ts-codec';
2
- import * as micro from '@journeyapps-platform/micro';
3
- import * as pgwire from '@powersync/service-jpgwire';
4
-
5
- import * as util from '@/util/util-index.js';
6
- import { authDevUser, authUser, endpoint, issueDevToken, issueLegacyDevToken, issuePowerSyncToken } from './auth.js';
7
- import { RouteGenerator } from './router.js';
8
-
9
- const AuthParams = t.object({
10
- user: t.string,
11
- password: t.string
12
- });
13
-
14
- // For legacy web client only. Remove soon.
15
- export const auth: RouteGenerator = (router) =>
16
- router.post('/auth.json', {
17
- validator: micro.schema.createTsCodecValidator(AuthParams, { allowAdditional: true }),
18
- handler: async (payload) => {
19
- const { user, password } = payload.params;
20
- const config = payload.context.system.config;
21
-
22
- if (config.dev.demo_auth == false || config.dev.demo_password == null) {
23
- throw new micro.errors.AuthorizationError(['Demo auth disabled']);
24
- }
25
-
26
- if (password == config.dev.demo_password) {
27
- const token = await issueLegacyDevToken(payload.request, user, payload.context.system.config);
28
- return { token, user_id: user, endpoint: endpoint(payload.request) };
29
- } else {
30
- throw new micro.errors.AuthorizationError(['Authentication failed']);
31
- }
32
- }
33
- });
34
-
35
- export const auth2: RouteGenerator = (router) =>
36
- router.post('/dev/auth.json', {
37
- validator: micro.schema.createTsCodecValidator(AuthParams, { allowAdditional: true }),
38
- handler: async (payload) => {
39
- const { user, password } = payload.params;
40
- const config = payload.context.system.config;
41
-
42
- if (config.dev.demo_auth == false || config.dev.demo_password == null) {
43
- throw new micro.errors.AuthorizationError(['Demo auth disabled']);
44
- }
45
-
46
- if (password == config.dev.demo_password) {
47
- const token = await issueDevToken(payload.request, user, payload.context.system.config);
48
- return { token, user_id: user };
49
- } else {
50
- throw new micro.errors.AuthorizationError(['Authentication failed']);
51
- }
52
- }
53
- });
54
-
55
- const TokenParams = t.object({});
56
-
57
- export const token: RouteGenerator = (router) =>
58
- router.post('/dev/token.json', {
59
- validator: micro.schema.createTsCodecValidator(TokenParams, { allowAdditional: true }),
60
- authorize: authDevUser,
61
- handler: async (payload) => {
62
- const { user_id } = payload.context;
63
- const outToken = await issuePowerSyncToken(payload.request, user_id!, payload.context.system.config);
64
- return { token: outToken, user_id: user_id, endpoint: endpoint(payload.request) };
65
- }
66
- });
67
-
68
- const OpType = {
69
- PUT: 'PUT',
70
- PATCH: 'PATCH',
71
- DELETE: 'DELETE'
72
- };
73
-
74
- const CrudEntry = t.object({
75
- op: t.Enum(OpType),
76
- type: t.string,
77
- id: t.string,
78
- op_id: t.number.optional(),
79
- data: t.any.optional()
80
- });
81
-
82
- const CrudRequest = t.object({
83
- data: t.array(CrudEntry),
84
- write_checkpoint: t.boolean.optional()
85
- });
86
- export const crud: RouteGenerator = (router) =>
87
- router.post('/crud.json', {
88
- validator: micro.schema.createTsCodecValidator(CrudRequest, { allowAdditional: true }),
89
- authorize: authUser,
90
-
91
- handler: async (payload) => {
92
- const { user_id, system } = payload.context;
93
-
94
- const pool = system.requirePgPool();
95
-
96
- if (!system.config.dev.crud_api) {
97
- throw new Error('CRUD api disabled');
98
- }
99
-
100
- const params = payload.params;
101
-
102
- let statements: pgwire.Statement[] = [];
103
-
104
- // Implementation note:
105
- // Postgres does automatic "assigment cast" for query literals,
106
- // e.g. a string literal to uuid. However, the same doesn't apply
107
- // to query parameters.
108
- // To handle those automatically, we use `json_populate_record`
109
- // to automatically cast to the correct types.
110
-
111
- for (let op of params.data) {
112
- const table = util.escapeIdentifier(op.type);
113
- if (op.op == 'PUT') {
114
- const data = op.data as Record<string, any>;
115
- const with_id = { ...data, id: op.id };
116
-
117
- const columnsEscaped = Object.keys(with_id).map(util.escapeIdentifier);
118
- const columnsJoined = columnsEscaped.join(', ');
119
-
120
- let updateClauses: string[] = [];
121
-
122
- for (let key of Object.keys(data)) {
123
- updateClauses.push(`${util.escapeIdentifier(key)} = EXCLUDED.${util.escapeIdentifier(key)}`);
124
- }
125
-
126
- const updateClause = updateClauses.length > 0 ? `DO UPDATE SET ${updateClauses.join(', ')}` : `DO NOTHING`;
127
-
128
- const statement = `
129
- WITH data_row AS (
130
- SELECT (json_populate_record(null::${table}, $1::json)).*
131
- )
132
- INSERT INTO ${table} (${columnsJoined})
133
- SELECT ${columnsJoined} FROM data_row
134
- ON CONFLICT(id) ${updateClause}`;
135
-
136
- statements.push({
137
- statement: statement,
138
- params: [{ type: 'varchar', value: JSON.stringify(with_id) }]
139
- });
140
- } else if (op.op == 'PATCH') {
141
- const data = op.data as Record<string, any>;
142
- const with_id = { ...data, id: op.id };
143
-
144
- let updateClauses: string[] = [];
145
-
146
- for (let key of Object.keys(data)) {
147
- updateClauses.push(`${util.escapeIdentifier(key)} = data_row.${util.escapeIdentifier(key)}`);
148
- }
149
-
150
- const statement = `
151
- WITH data_row AS (
152
- SELECT (json_populate_record(null::${table}, $1::json)).*
153
- )
154
- UPDATE ${table}
155
- SET ${updateClauses.join(', ')}
156
- FROM data_row
157
- WHERE ${table}.id = data_row.id`;
158
-
159
- statements.push({
160
- statement: statement,
161
- params: [{ type: 'varchar', value: JSON.stringify(with_id) }]
162
- });
163
- } else if (op.op == 'DELETE') {
164
- statements.push({
165
- statement: `
166
- WITH data_row AS (
167
- SELECT (json_populate_record(null::${table}, $1::json)).*
168
- )
169
- DELETE FROM ${table}
170
- USING data_row
171
- WHERE ${table}.id = data_row.id`,
172
- params: [{ type: 'varchar', value: JSON.stringify({ id: op.id }) }]
173
- });
174
- }
175
- }
176
- await pool.query(...statements);
177
-
178
- const storage = system.storage;
179
- if (payload.params.write_checkpoint === true) {
180
- const write_checkpoint = await util.createWriteCheckpoint(pool, storage, payload.context.user_id!);
181
- return { write_checkpoint: String(write_checkpoint) };
182
- } else if (payload.params.write_checkpoint === false) {
183
- return {};
184
- } else {
185
- // Legacy
186
- const checkpoint = await util.getClientCheckpoint(pool, storage);
187
- return {
188
- checkpoint
189
- };
190
- }
191
- }
192
- });
193
-
194
- export const dev_routes = [auth, auth2, token, crud];
@@ -1,39 +0,0 @@
1
- import * as micro from '@journeyapps-platform/micro';
2
-
3
- import { Context, RouteGenerator } from './router.js';
4
- import { admin_routes } from './admin.js';
5
- import { writeCheckpoint, writeCheckpoint2 } from './checkpointing.js';
6
- import { dev_routes } from './dev.js';
7
- import { syncRulesRoutes } from './sync-rules.js';
8
- import { IReactiveStream, ReactiveSocketRouter } from '@powersync/service-rsocket-router';
9
- import { sync_stream_reactive } from './socket-route.js';
10
- import { syncStreamed } from './sync-stream.js';
11
-
12
- /**
13
- * Generates router endpoints using the specified router instance
14
- */
15
- export const generateHTTPRoutes = (router: micro.fastify.FastifyRouter<Context>): micro.router.Route[] => {
16
- const generators: RouteGenerator[] = [
17
- ...dev_routes,
18
- writeCheckpoint,
19
- writeCheckpoint2,
20
- ...syncRulesRoutes,
21
- ...admin_routes
22
- ];
23
-
24
- return generators.map((generateRoute) => generateRoute(router));
25
- };
26
-
27
- /**
28
- * Generates route endpoint for HTTP sync streaming
29
- */
30
- export const generateHTTPStreamRoutes = (router: micro.fastify.FastifyRouter<Context>): micro.router.Route[] => {
31
- return [syncStreamed].map((generateRoute) => generateRoute(router));
32
- };
33
-
34
- /**
35
- * Generates socket router endpoints using the specified router instance
36
- */
37
- export const generateSocketRoutes = (router: ReactiveSocketRouter<Context>): IReactiveStream[] => {
38
- return [sync_stream_reactive].map((generateRoute) => generateRoute(router));
39
- };
@@ -1,210 +0,0 @@
1
- import * as t from 'ts-codec';
2
- import { FastifyPluginAsync, FastifyReply } from 'fastify';
3
- import * as micro from '@journeyapps-platform/micro';
4
- import * as pgwire from '@powersync/service-jpgwire';
5
- import { SqlSyncRules, SyncRulesErrors } from '@powersync/service-sync-rules';
6
-
7
- import * as replication from '@/replication/replication-index.js';
8
- import { authApi } from './auth.js';
9
- import { RouteGenerator } from './router.js';
10
-
11
- const DeploySyncRulesRequest = t.object({
12
- content: t.string
13
- });
14
-
15
- const yamlPlugin: FastifyPluginAsync = async (fastify) => {
16
- fastify.addContentTypeParser('application/yaml', async (request, payload, _d) => {
17
- const data = await micro.streaming.drain(payload);
18
-
19
- request.params = { content: Buffer.concat(data).toString('utf8') };
20
- });
21
- };
22
-
23
- export const deploySyncRules: RouteGenerator = (router) =>
24
- router.post('/api/sync-rules/v1/deploy', {
25
- authorize: authApi,
26
- parse: true,
27
- plugins: [yamlPlugin],
28
- validator: micro.schema.createTsCodecValidator(DeploySyncRulesRequest, { allowAdditional: true }),
29
- handler: async (payload) => {
30
- if (payload.context.system.config.sync_rules.present) {
31
- // If sync rules are configured via the config, disable deploy via the API.
32
- throw new micro.errors.JourneyError({
33
- status: 422,
34
- code: 'API_DISABLED',
35
- description: 'Sync rules API disabled',
36
- details: 'Use the management API to deploy sync rules'
37
- });
38
- }
39
- const content = payload.params.content;
40
-
41
- try {
42
- SqlSyncRules.fromYaml(payload.params.content);
43
- } catch (e) {
44
- throw new micro.errors.JourneyError({
45
- status: 422,
46
- code: 'INVALID_SYNC_RULES',
47
- description: 'Sync rules parsing failed',
48
- details: e.message
49
- });
50
- }
51
-
52
- const sync_rules = await payload.context.system.storage.updateSyncRules({
53
- content: content
54
- });
55
-
56
- return {
57
- slot_name: sync_rules.slot_name
58
- };
59
- }
60
- });
61
-
62
- const ValidateSyncRulesRequest = t.object({
63
- content: t.string
64
- });
65
-
66
- export const validateSyncRules: RouteGenerator = (router) =>
67
- router.post('/api/sync-rules/v1/validate', {
68
- authorize: authApi,
69
- parse: true,
70
- plugins: [yamlPlugin],
71
- validator: micro.schema.createTsCodecValidator(ValidateSyncRulesRequest, { allowAdditional: true }),
72
- handler: async (payload) => {
73
- const content = payload.params.content;
74
-
75
- const info = await debugSyncRules(payload.context.system.requirePgPool(), content);
76
-
77
- replyPrettyJson(payload.reply, info);
78
- }
79
- });
80
-
81
- export const currentSyncRules: RouteGenerator = (router) =>
82
- router.get('/api/sync-rules/v1/current', {
83
- authorize: authApi,
84
- handler: async (payload) => {
85
- const storage = payload.context.system.storage;
86
- const sync_rules = await storage.getActiveSyncRulesContent();
87
- if (!sync_rules) {
88
- throw new micro.errors.JourneyError({
89
- status: 422,
90
- code: 'NO_SYNC_RULES',
91
- description: 'No active sync rules'
92
- });
93
- }
94
- const info = await debugSyncRules(payload.context.system.requirePgPool(), sync_rules.sync_rules_content);
95
- const next = await storage.getNextSyncRulesContent();
96
-
97
- const next_info = next
98
- ? await debugSyncRules(payload.context.system.requirePgPool(), next.sync_rules_content)
99
- : null;
100
-
101
- const response = {
102
- current: {
103
- slot_name: sync_rules.slot_name,
104
- content: sync_rules.sync_rules_content,
105
- ...info
106
- },
107
- next:
108
- next == null
109
- ? null
110
- : {
111
- slot_name: next.slot_name,
112
- content: next.sync_rules_content,
113
- ...next_info
114
- }
115
- };
116
- replyPrettyJson(payload.reply, { data: response });
117
- }
118
- });
119
-
120
- const ReprocessSyncRulesRequest = t.object({});
121
-
122
- export const reprocessSyncRules: RouteGenerator = (router) =>
123
- router.post('/api/sync-rules/v1/reprocess', {
124
- authorize: authApi,
125
- validator: micro.schema.createTsCodecValidator(ReprocessSyncRulesRequest),
126
- handler: async (payload) => {
127
- const storage = payload.context.system.storage;
128
- const sync_rules = await storage.getActiveSyncRules();
129
- if (sync_rules == null) {
130
- throw new micro.errors.JourneyError({
131
- status: 422,
132
- code: 'NO_SYNC_RULES',
133
- description: 'No active sync rules'
134
- });
135
- }
136
-
137
- const new_rules = await storage.updateSyncRules({
138
- content: sync_rules.sync_rules.content
139
- });
140
- return {
141
- slot_name: new_rules.slot_name
142
- };
143
- }
144
- });
145
-
146
- export const syncRulesRoutes = [validateSyncRules, deploySyncRules, reprocessSyncRules, currentSyncRules];
147
-
148
- function replyPrettyJson(reply: FastifyReply, payload: any) {
149
- reply
150
- .status(200)
151
- .header('Content-Type', 'application/json')
152
- .send(JSON.stringify(payload, null, 2) + '\n');
153
- }
154
-
155
- async function debugSyncRules(db: pgwire.PgClient, sync_rules: string) {
156
- try {
157
- const rules = SqlSyncRules.fromYaml(sync_rules);
158
- const source_table_patterns = rules.getSourceTables();
159
- const wc = new replication.WalConnection({
160
- db: db,
161
- sync_rules: rules
162
- });
163
- const resolved_tables = await wc.getDebugTablesInfo(source_table_patterns);
164
-
165
- return {
166
- valid: true,
167
- bucket_definitions: rules.bucket_descriptors.map((d) => {
168
- let all_parameter_queries = [...d.parameter_queries.values()].flat();
169
- let all_data_queries = [...d.data_queries.values()].flat();
170
- return {
171
- name: d.name,
172
- bucket_parameters: d.bucket_parameters,
173
- global_parameter_queries: d.global_parameter_queries.map((q) => {
174
- return {
175
- sql: q.sql
176
- };
177
- }),
178
- parameter_queries: all_parameter_queries.map((q) => {
179
- return {
180
- sql: q.sql,
181
- table: q.sourceTable,
182
- input_parameters: q.input_parameters
183
- };
184
- }),
185
-
186
- data_queries: all_data_queries.map((q) => {
187
- return {
188
- sql: q.sql,
189
- table: q.sourceTable,
190
- columns: q.columnOutputNames()
191
- };
192
- })
193
- };
194
- }),
195
- source_tables: resolved_tables,
196
- data_tables: rules.debugGetOutputTables()
197
- };
198
- } catch (e) {
199
- if (e instanceof SyncRulesErrors) {
200
- return {
201
- valid: false,
202
- errors: e.errors.map((e) => e.message)
203
- };
204
- }
205
- return {
206
- valid: false,
207
- errors: [e.message]
208
- };
209
- }
210
- }
@@ -1,95 +0,0 @@
1
- import { Readable } from 'stream';
2
- import * as micro from '@journeyapps-platform/micro';
3
- import { SyncParameters, normalizeTokenParameters } from '@powersync/service-sync-rules';
4
-
5
- import * as sync from '@/sync/sync-index.js';
6
- import * as util from '@/util/util-index.js';
7
-
8
- import { authUser } from './auth.js';
9
- import { RouteGenerator } from './router.js';
10
- import { Metrics } from '@/metrics/Metrics.js';
11
-
12
- export enum SyncRoutes {
13
- STREAM = '/sync/stream'
14
- }
15
-
16
- export const syncStreamed: RouteGenerator = (router) =>
17
- router.post(SyncRoutes.STREAM, {
18
- authorize: authUser,
19
- validator: micro.schema.createTsCodecValidator(util.StreamingSyncRequest, { allowAdditional: true }),
20
- handler: async (payload) => {
21
- const userId = payload.context.user_id!;
22
- const system = payload.context.system;
23
-
24
- if (system.closed) {
25
- throw new micro.errors.JourneyError({
26
- status: 503,
27
- code: 'SERVICE_UNAVAILABLE',
28
- description: 'Service temporarily unavailable'
29
- });
30
- }
31
-
32
- const params: util.StreamingSyncRequest = payload.params;
33
- const syncParams: SyncParameters = normalizeTokenParameters(
34
- payload.context.token_payload!.parameters ?? {},
35
- payload.params.parameters ?? {}
36
- );
37
-
38
- const storage = system.storage;
39
- // Sanity check before we start the stream
40
- const cp = await storage.getActiveCheckpoint();
41
- if (!cp.hasSyncRules()) {
42
- throw new micro.errors.JourneyError({
43
- status: 500,
44
- code: 'NO_SYNC_RULES',
45
- description: 'No sync rules available'
46
- });
47
- }
48
-
49
- const res = payload.reply;
50
-
51
- res.status(200).header('Content-Type', 'application/x-ndjson');
52
-
53
- const controller = new AbortController();
54
- try {
55
- Metrics.getInstance().concurrent_connections.add(1);
56
- const stream = Readable.from(
57
- sync.transformToBytesTracked(
58
- sync.ndjson(
59
- sync.streamResponse({
60
- storage,
61
- params,
62
- syncParams,
63
- token: payload.context.token_payload!,
64
- signal: controller.signal
65
- })
66
- )
67
- ),
68
- { objectMode: false, highWaterMark: 16 * 1024 }
69
- );
70
-
71
- const deregister = system.addStopHandler(() => {
72
- // This error is not currently propagated to the client
73
- controller.abort();
74
- stream.destroy(new Error('Shutting down system'));
75
- });
76
- stream.on('close', () => {
77
- deregister();
78
- });
79
-
80
- stream.on('error', (error) => {
81
- controller.abort();
82
- // Note: This appears as a 200 response in the logs.
83
- if (error.message != 'Shutting down system') {
84
- micro.logger.error('Streaming sync request failed', error);
85
- }
86
- });
87
- await res.send(stream);
88
- } finally {
89
- controller.abort();
90
- Metrics.getInstance().concurrent_connections.add(-1);
91
- // Prevent double-send
92
- res.hijack();
93
- }
94
- }
95
- });