@powersync/service-types 0.10.0 → 0.12.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.
@@ -1,13 +1,24 @@
1
+ import dedent from 'dedent';
1
2
  import * as t from 'ts-codec';
2
3
 
4
+ /**
5
+ * The meta tags here are used in the generated JSON schema.
6
+ * The JSON schema can be used to help self hosted users edit the YAML config file.
7
+ */
8
+
3
9
  /**
4
10
  * Users might specify ports as strings if using YAML custom tag environment substitutions
5
11
  */
6
- export const portCodec = t.codec<number, number | string>(
7
- 'Port',
8
- (value) => value,
9
- (value) => (typeof value == 'number' ? value : parseInt(value))
10
- );
12
+ export const portCodec = t
13
+ .codec<number, number | string>(
14
+ 'Port',
15
+ (value) => value,
16
+ (value) => (typeof value == 'number' ? value : parseInt(value))
17
+ )
18
+ .meta({
19
+ description:
20
+ 'A network port value that can be specified as either a number or a string that will be parsed to a number.'
21
+ });
11
22
 
12
23
  /**
13
24
  * This gets used whenever generating a JSON schema
@@ -19,18 +30,36 @@ export const portParser = {
19
30
  })
20
31
  };
21
32
 
22
- export const DataSourceConfig = t.object({
23
- // Unique string identifier for the data source
24
- type: t.string,
25
- /** Unique identifier for the connection - optional when a single connection is present. */
26
- id: t.string.optional(),
27
- /** Additional meta tag for connection */
28
- tag: t.string.optional(),
29
- /**
30
- * Allows for debug query execution
31
- */
32
- debug_api: t.boolean.optional()
33
- });
33
+ export const DataSourceConfig = t
34
+ .object({
35
+ // Unique string identifier for the data source
36
+ type: t.string.meta({
37
+ description: 'Unique string identifier for the data source type (e.g., "postgresql", "mysql", etc.).'
38
+ }),
39
+ /** Unique identifier for the connection - optional when a single connection is present. */
40
+ id: t.string
41
+ .meta({
42
+ description: 'Unique identifier for the connection. Optional when only a single connection is present.'
43
+ })
44
+ .optional(),
45
+ /** Additional meta tag for connection */
46
+ tag: t.string
47
+ .meta({
48
+ description: 'Additional meta tag for the connection, used for categorization or grouping.'
49
+ })
50
+ .optional(),
51
+ /**
52
+ * Allows for debug query execution
53
+ */
54
+ debug_api: t.boolean
55
+ .meta({
56
+ description: 'When enabled, allows query execution.'
57
+ })
58
+ .optional()
59
+ })
60
+ .meta({
61
+ description: 'Base configuration for a replication data source connection.'
62
+ });
34
63
 
35
64
  export type DataSourceConfig = t.Decoded<typeof DataSourceConfig>;
36
65
 
@@ -53,61 +82,158 @@ export type ResolvedDataSourceConfig = t.Decoded<typeof ResolvedDataSourceConfig
53
82
  export const genericDataSourceConfig = DataSourceConfig.and(t.record(t.any));
54
83
  export type GenericDataSourceConfig = t.Decoded<typeof genericDataSourceConfig>;
55
84
 
56
- export const jwkRSA = t.object({
57
- kty: t.literal('RSA'),
58
- kid: t.string,
59
- n: t.string,
60
- e: t.string,
61
- alg: t.literal('RS256').or(t.literal('RS384')).or(t.literal('RS512')).optional(),
62
- use: t.string.optional()
63
- });
64
-
65
- export const jwkHmac = t.object({
66
- kty: t.literal('oct'),
67
- /**
68
- * undefined kid indicates it can match any JWT, with or without a kid.
69
- * Use a kid wherever possible.
70
- */
71
- kid: t.string.optional(),
72
- k: t.string,
73
- alg: t.literal('HS256').or(t.literal('HS384')).or(t.literal('HS512')),
74
- use: t.string.optional()
75
- });
85
+ export const jwkRSA = t
86
+ .object({
87
+ kty: t.literal('RSA').meta({
88
+ description: 'Key type identifier, must be "RSA" for RSA keys.'
89
+ }),
90
+ kid: t.string.meta({
91
+ description: 'Key ID, a unique identifier for the key.'
92
+ }),
93
+ n: t.string.meta({
94
+ description: 'RSA modulus, Base64 URL encoded.'
95
+ }),
96
+ e: t.string.meta({
97
+ description: 'RSA exponent, Base64 URL encoded.'
98
+ }),
99
+ alg: t
100
+ .literal('RS256')
101
+ .or(t.literal('RS384'))
102
+ .or(t.literal('RS512'))
103
+ .meta({
104
+ description: 'The algorithm intended for use with this key (RS256, RS384, or RS512).'
105
+ })
106
+ .optional(),
107
+ use: t.string
108
+ .meta({
109
+ description: 'The intended use of the key (e.g., "sig" for signature).'
110
+ })
111
+ .optional()
112
+ })
113
+ .meta({
114
+ description: 'JSON Web Key (JWK) representation of an RSA key.'
115
+ });
76
116
 
77
- export const jwkOKP = t.object({
78
- kty: t.literal('OKP'),
79
- kid: t.string.optional(),
80
- /** Other curves have security issues so only these two are supported. */
81
- crv: t.literal('Ed25519').or(t.literal('Ed448')),
82
- x: t.string,
83
- alg: t.literal('EdDSA'),
84
- use: t.string.optional()
85
- });
117
+ export const jwkHmac = t
118
+ .object({
119
+ kty: t.literal('oct').meta({
120
+ description: 'Key type identifier, must be "oct" for HMAC keys.'
121
+ }),
122
+ kid: t.string
123
+ .meta({
124
+ description:
125
+ 'Key ID. Undefined kid indicates it can match any JWT, with or without a kid. Use a kid wherever possible.'
126
+ })
127
+ .optional(),
128
+ k: t.string.meta({
129
+ description: 'The HMAC key value, Base64 URL encoded.'
130
+ }),
131
+ alg: t.literal('HS256').or(t.literal('HS384')).or(t.literal('HS512')).meta({
132
+ description: 'The algorithm intended for use with this key (HS256, HS384, or HS512).'
133
+ }),
134
+ use: t.string
135
+ .meta({
136
+ description: 'The intended use of the key (e.g., "sig" for signature).'
137
+ })
138
+ .optional()
139
+ })
140
+ .meta({
141
+ description: 'JSON Web Key (JWK) representation of an HMAC key.'
142
+ });
86
143
 
87
- export const jwkEC = t.object({
88
- kty: t.literal('EC'),
89
- kid: t.string.optional(),
90
- crv: t.literal('P-256').or(t.literal('P-384')).or(t.literal('P-512')),
91
- x: t.string,
92
- y: t.string,
93
- alg: t.literal('ES256').or(t.literal('ES384')).or(t.literal('ES512')),
94
- use: t.string.optional()
95
- });
144
+ export const jwkOKP = t
145
+ .object({
146
+ kty: t.literal('OKP').meta({
147
+ description: 'Key type identifier, must be "OKP" for Octet Key Pair keys.'
148
+ }),
149
+ kid: t.string
150
+ .meta({
151
+ description: 'Key ID, a unique identifier for the key.'
152
+ })
153
+ .optional(),
154
+ /** Other curves have security issues so only these two are supported. */
155
+ crv: t.literal('Ed25519').or(t.literal('Ed448')).meta({
156
+ description: 'The cryptographic curve used with this key. Only Ed25519 and Ed448 are supported.'
157
+ }),
158
+ x: t.string.meta({
159
+ description: 'The public key, Base64 URL encoded.'
160
+ }),
161
+ alg: t.literal('EdDSA').meta({
162
+ description: 'The algorithm intended for use with this key (EdDSA).'
163
+ }),
164
+ use: t.string
165
+ .meta({
166
+ description: 'The intended use of the key (e.g., "sig" for signature).'
167
+ })
168
+ .optional()
169
+ })
170
+ .meta({
171
+ description:
172
+ 'JSON Web Key (JWK) representation of an Octet Key Pair (OKP) key used with Edwards-curve Digital Signature Algorithm.'
173
+ });
96
174
 
97
- const jwk = t.union(t.union(t.union(jwkRSA, jwkHmac), jwkOKP), jwkEC);
175
+ export const jwkEC = t
176
+ .object({
177
+ kty: t.literal('EC').meta({
178
+ description: 'Key type identifier, must be "EC" for Elliptic Curve keys.'
179
+ }),
180
+ kid: t.string
181
+ .meta({
182
+ description: 'Key ID, a unique identifier for the key.'
183
+ })
184
+ .optional(),
185
+ crv: t.literal('P-256').or(t.literal('P-384')).or(t.literal('P-512')).meta({
186
+ description: 'The cryptographic curve used with this key (P-256, P-384, or P-512).'
187
+ }),
188
+ x: t.string.meta({
189
+ description: 'The x coordinate for the Elliptic Curve point, Base64 URL encoded.'
190
+ }),
191
+ y: t.string.meta({
192
+ description: 'The y coordinate for the Elliptic Curve point, Base64 URL encoded.'
193
+ }),
194
+ alg: t.literal('ES256').or(t.literal('ES384')).or(t.literal('ES512')).meta({
195
+ description: 'The algorithm intended for use with this key (ES256, ES384, or ES512).'
196
+ }),
197
+ use: t.string
198
+ .meta({
199
+ description: 'The intended use of the key (e.g., "sig" for signature).'
200
+ })
201
+ .optional()
202
+ })
203
+ .meta({
204
+ description: 'JSON Web Key (JWK) representation of an Elliptic Curve key.'
205
+ });
98
206
 
99
- export const strictJwks = t.object({
100
- keys: t.array(jwk)
207
+ const jwk = t.union(t.union(t.union(jwkRSA, jwkHmac), jwkOKP), jwkEC).meta({
208
+ description: 'A JSON Web Key (JWK) representing a cryptographic key. Can be RSA, HMAC, OKP, or EC key types.'
101
209
  });
102
210
 
211
+ export const strictJwks = t
212
+ .object({
213
+ keys: t.array(jwk).meta({
214
+ description: 'An array of JSON Web Keys (JWKs).'
215
+ })
216
+ })
217
+ .meta({
218
+ description: 'A JSON Web Key Set (JWKS) containing a collection of JWKs.'
219
+ });
220
+
103
221
  export type StrictJwk = t.Decoded<typeof jwk>;
104
222
 
105
- export const BaseStorageConfig = t.object({
106
- type: t.string,
107
- // Maximum number of connections to the storage database, per process.
108
- // Defaults to 8.
109
- max_pool_size: t.number.optional()
110
- });
223
+ export const BaseStorageConfig = t
224
+ .object({
225
+ type: t.string.meta({
226
+ description: 'The type of storage backend to use (e.g., "postgresql", "mongodb").'
227
+ }),
228
+ max_pool_size: t.number
229
+ .meta({
230
+ description: 'Maximum number of connections to the storage database, per process. Defaults to 8.'
231
+ })
232
+ .optional()
233
+ })
234
+ .meta({
235
+ description: 'Base configuration for storage connections.'
236
+ });
111
237
 
112
238
  /**
113
239
  * Base configuration for Bucket storage connections.
@@ -120,95 +246,280 @@ export type BaseStorageConfig = t.Encoded<typeof BaseStorageConfig>;
120
246
  export const GenericStorageConfig = BaseStorageConfig.and(t.record(t.any));
121
247
  export type GenericStorageConfig = t.Encoded<typeof GenericStorageConfig>;
122
248
 
123
- export const powerSyncConfig = t.object({
124
- replication: t
125
- .object({
126
- // This uses the generic config which may have additional fields
127
- connections: t.array(genericDataSourceConfig).optional()
128
- })
129
- .optional(),
130
-
131
- dev: t
132
- .object({
133
- demo_auth: t.boolean.optional(),
134
- /** @deprecated */
135
- demo_password: t.string.optional(),
136
- /** @deprecated */
137
- crud_api: t.boolean.optional(),
138
- /** @deprecated */
139
- demo_client: t.boolean.optional()
140
- })
141
- .optional(),
142
-
143
- client_auth: t
144
- .object({
145
- jwks_uri: t.string.or(t.array(t.string)).optional(),
146
- block_local_jwks: t.boolean.optional(),
147
- jwks_reject_ip_ranges: t.array(t.string).optional(),
148
- jwks: strictJwks.optional(),
149
- supabase: t.boolean.optional(),
150
- supabase_jwt_secret: t.string.optional(),
151
- audience: t.array(t.string).optional()
152
- })
153
- .optional(),
154
-
155
- api: t
156
- .object({
157
- tokens: t.array(t.string).optional(),
158
- parameters: t
159
- .object({
160
- // Maximum number of connections (http streams or websockets) per API process.
161
- // Default of 200.
162
- max_concurrent_connections: t.number.optional(),
163
- // This should not be siginificantly more than storage.max_pool_size, otherwise it would block on the
164
- // pool. Increasing this can significantly increase memory usage in some cases.
165
- // Default of 10.
166
- max_data_fetch_concurrency: t.number.optional(),
167
- // Maximum number of buckets for each connection.
168
- // More buckets increase latency and memory usage. While the actual number is controlled by sync rules,
169
- // having a hard limit ensures that the service errors instead of crashing when a sync rule is misconfigured.
170
- // Default of 1000.
171
- max_buckets_per_connection: t.number.optional(),
172
-
173
- // Related to max_buckets_per_connection, but this limit applies directly on the parameter
174
- // query results, _before_ we convert it into an unique set.
175
- // Default of 1000.
176
- max_parameter_query_results: t.number.optional()
177
- })
178
- .optional()
179
- })
180
- .optional(),
249
+ export const powerSyncConfig = t
250
+ .object({
251
+ replication: t
252
+ .object({
253
+ // This uses the generic config which may have additional fields
254
+ connections: t
255
+ .array(genericDataSourceConfig)
256
+ .meta({
257
+ description: 'Array of data source connections used for replication.'
258
+ })
259
+ .optional()
260
+ })
261
+ .meta({
262
+ description: 'Configuration for data replication services.'
263
+ })
264
+ .optional(),
181
265
 
182
- storage: GenericStorageConfig,
266
+ dev: t
267
+ .object({
268
+ demo_auth: t.boolean
269
+ .meta({
270
+ description: 'Enables demo authentication for development purposes.'
271
+ })
272
+ .optional(),
273
+ /** @deprecated */
274
+ demo_password: t.string
275
+ .meta({
276
+ description: 'Deprecated. Demo password for development authentication.'
277
+ })
278
+ .optional(),
279
+ /** @deprecated */
280
+ crud_api: t.boolean
281
+ .meta({
282
+ description: 'Deprecated. Enables CRUD API for development.'
283
+ })
284
+ .optional(),
285
+ /** @deprecated */
286
+ demo_client: t.boolean
287
+ .meta({
288
+ description: 'Deprecated. Enables demo client for development.'
289
+ })
290
+ .optional()
291
+ })
292
+ .meta({
293
+ description: 'Development-specific configuration options.'
294
+ })
295
+ .optional(),
183
296
 
184
- port: portCodec.optional(),
185
- sync_rules: t
186
- .object({
187
- path: t.string.optional(),
188
- content: t.string.optional(),
189
- exit_on_error: t.boolean.optional()
190
- })
191
- .optional(),
297
+ client_auth: t
298
+ .object({
299
+ jwks_uri: t.string
300
+ .or(t.array(t.string))
301
+ .meta({
302
+ description: 'URI or array of URIs pointing to JWKS endpoints for client authentication.'
303
+ })
304
+ .optional(),
305
+ block_local_jwks: t.boolean
306
+ .meta({
307
+ description: 'When true, blocks JWKS URIs that resolve to local network addresses.'
308
+ })
309
+ .optional(),
310
+ jwks_reject_ip_ranges: t
311
+ .array(t.string)
312
+ .meta({
313
+ description: 'IP ranges to reject when validating JWKS URIs.'
314
+ })
315
+ .optional(),
316
+ jwks: strictJwks
317
+ .meta({
318
+ description: 'Inline JWKS configuration for client authentication.'
319
+ })
320
+ .optional(),
321
+ supabase: t.boolean
322
+ .meta({
323
+ description: 'Enables Supabase authentication integration.'
324
+ })
325
+ .optional(),
326
+ supabase_jwt_secret: t.string
327
+ .meta({
328
+ description: 'JWT secret for Supabase authentication.'
329
+ })
330
+ .optional(),
331
+ audience: t
332
+ .array(t.string)
333
+ .meta({
334
+ description: 'Valid audiences for JWT validation.'
335
+ })
336
+ .optional()
337
+ })
338
+ .meta({
339
+ description: 'Configuration for client authentication mechanisms.'
340
+ })
341
+ .optional(),
192
342
 
193
- metadata: t.record(t.string).optional(),
343
+ api: t
344
+ .object({
345
+ tokens: t
346
+ .array(t.string)
347
+ .meta({
348
+ description: 'API access tokens for administrative operations.'
349
+ })
350
+ .optional(),
351
+ parameters: t
352
+ .object({
353
+ max_concurrent_connections: t.number
354
+ .meta({
355
+ description: dedent`
356
+ Maximum number of connections (http streams or websockets) per API process.
357
+ Default of 200.
358
+ `
359
+ })
360
+ .optional(),
194
361
 
195
- migrations: t
196
- .object({
197
- disable_auto_migration: t.boolean.optional()
198
- })
199
- .optional(),
200
-
201
- telemetry: t
202
- .object({
203
- // When set, metrics will be available on this port for scraping by Prometheus.
204
- prometheus_port: portCodec.optional(),
205
- disable_telemetry_sharing: t.boolean,
206
- internal_service_endpoint: t.string.optional()
207
- })
208
- .optional(),
362
+ max_data_fetch_concurrency: t.number
363
+ .meta({
364
+ description: dedent`
365
+ This should not be siginificantly more than storage.max_pool_size, otherwise it would block on the
366
+ pool. Increasing this can significantly increase memory usage in some cases.
367
+ Default of 10.
368
+ `
369
+ })
370
+ .optional(),
209
371
 
210
- parameters: t.record(t.number.or(t.string).or(t.boolean).or(t.Null)).optional()
211
- });
372
+ max_buckets_per_connection: t.number
373
+ .meta({
374
+ description: dedent`
375
+ Maximum number of buckets for each connection.
376
+ More buckets increase latency and memory usage. While the actual number is controlled by sync rules,
377
+ having a hard limit ensures that the service errors instead of crashing when a sync rule is misconfigured.
378
+ Default of 1000.
379
+ `
380
+ })
381
+ .optional(),
382
+
383
+ max_parameter_query_results: t.number
384
+ .meta({
385
+ description: dedent`
386
+ Related to max_buckets_per_connection, but this limit applies directly on the parameter
387
+ query results, _before_ we convert it into an unique set.
388
+ Default of 1000.
389
+ `
390
+ })
391
+ .optional()
392
+ })
393
+ .meta({
394
+ description: 'Performance and safety parameters for the API service.'
395
+ })
396
+ .optional()
397
+ })
398
+ .meta({
399
+ description: 'API service configuration and parameters.'
400
+ })
401
+ .optional(),
402
+
403
+ storage: GenericStorageConfig.meta({
404
+ description: 'Configuration for the storage backend.'
405
+ }),
406
+
407
+ port: portCodec
408
+ .meta({
409
+ description:
410
+ 'The port on which the service will listen for connections. Can be specified as a number or string.'
411
+ })
412
+ .optional(),
413
+
414
+ sync_rules: t
415
+ .object({
416
+ path: t.string
417
+ .meta({
418
+ description: 'Path to the sync rules YAML file.'
419
+ })
420
+ .optional(),
421
+ content: t.string
422
+ .meta({
423
+ description: 'Inline sync rules content as a string.'
424
+ })
425
+ .optional(),
426
+ exit_on_error: t.boolean
427
+ .meta({
428
+ description: 'Whether to exit the process if there is an error parsing sync rules.'
429
+ })
430
+ .optional()
431
+ })
432
+ .meta({
433
+ description: 'Configuration for synchronization rules that define data access patterns.'
434
+ })
435
+ .optional(),
436
+
437
+ metadata: t
438
+ .record(t.string)
439
+ .meta({
440
+ description: 'Custom metadata key-value pairs for the service.'
441
+ })
442
+ .optional(),
443
+
444
+ migrations: t
445
+ .object({
446
+ disable_auto_migration: t.boolean
447
+ .meta({
448
+ description: 'When true, disables automatic storage database schema migrations.'
449
+ })
450
+ .optional()
451
+ })
452
+ .meta({
453
+ description: 'Configuration for database schema migrations.'
454
+ })
455
+ .optional(),
456
+
457
+ telemetry: t
458
+ .object({
459
+ // When set, metrics will be available on this port for scraping by Prometheus.
460
+ prometheus_port: portCodec
461
+ .meta({
462
+ description:
463
+ 'Port on which Prometheus metrics will be exposed. When set, metrics will be available on this port for scraping.'
464
+ })
465
+ .optional(),
466
+ disable_telemetry_sharing: t.boolean.meta({
467
+ description: 'When true, disables sharing of anonymized telemetry data.'
468
+ }),
469
+ internal_service_endpoint: t.string
470
+ .meta({
471
+ description: 'Internal endpoint for telemetry services.'
472
+ })
473
+ .optional()
474
+ })
475
+ .meta({
476
+ description: 'Configuration for service telemetry and monitoring.'
477
+ })
478
+ .optional(),
479
+
480
+ healthcheck: t
481
+ .object({
482
+ probes: t
483
+ .object({
484
+ use_filesystem: t.boolean
485
+ .meta({
486
+ description: `Enables exposing healthcheck status via filesystem files.`
487
+ })
488
+ .optional(),
489
+ use_http: t.boolean
490
+ .meta({
491
+ description: `Enables exposing healthcheck status via HTTP endpoints.`
492
+ })
493
+ .optional(),
494
+ use_legacy: t.boolean
495
+ .meta({
496
+ description: dedent`
497
+ Deprecated.
498
+ Enables HTTP probes for both API and UNIFIED service modes. FileSystem probes are always enabled.
499
+ `
500
+ })
501
+ .optional()
502
+ })
503
+ .meta({ description: 'Mechanisms for exposing health check data.' })
504
+ .optional()
505
+ })
506
+ .optional(),
507
+
508
+ parameters: t
509
+ .record(t.number.or(t.string).or(t.boolean).or(t.Null))
510
+ .meta({
511
+ description: 'Global parameters that can be referenced in sync rules and other configurations.'
512
+ })
513
+ .optional()
514
+ })
515
+ .meta({
516
+ description: 'Root configuration object for PowerSync service.'
517
+ });
212
518
 
213
519
  export type PowerSyncConfig = t.Decoded<typeof powerSyncConfig>;
214
520
  export type SerializedPowerSyncConfig = t.Encoded<typeof powerSyncConfig>;
521
+
522
+ export const PowerSyncConfigJSONSchema = t.generateJSONSchema(powerSyncConfig, {
523
+ allowAdditional: true,
524
+ parsers: [portParser]
525
+ });
package/src/metrics.ts CHANGED
@@ -15,7 +15,10 @@ export enum ReplicationMetric {
15
15
  // Total number of replicated transactions. Not used for pricing.
16
16
  TRANSACTIONS_REPLICATED = 'powersync_transactions_replicated_total',
17
17
  // Total number of replication chunks. Not used for pricing.
18
- CHUNKS_REPLICATED = 'powersync_chunks_replicated_total'
18
+ CHUNKS_REPLICATED = 'powersync_chunks_replicated_total',
19
+ // Replication lag between the source database and PowerSync instance (estimated).
20
+ // This is estimated, and may have delays in reporting.
21
+ REPLICATION_LAG_SECONDS = 'powersync_replication_lag_seconds'
19
22
  }
20
23
 
21
24
  export enum StorageMetric {