@powersync/service-core 0.0.0-dev-20250507132759 → 0.0.0-dev-20250507151436
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 +24 -4
- package/dist/entry/commands/compact-action.js +4 -1
- package/dist/entry/commands/compact-action.js.map +1 -1
- package/dist/entry/commands/migrate-action.js +4 -1
- package/dist/entry/commands/migrate-action.js.map +1 -1
- package/dist/entry/commands/test-connection-action.js +4 -1
- package/dist/entry/commands/test-connection-action.js.map +1 -1
- package/dist/routes/RouterEngine.d.ts +2 -0
- package/dist/routes/RouterEngine.js +15 -10
- package/dist/routes/RouterEngine.js.map +1 -1
- package/dist/routes/configure-fastify.js +0 -4
- package/dist/routes/configure-fastify.js.map +1 -1
- package/dist/routes/configure-rsocket.js +0 -3
- package/dist/routes/configure-rsocket.js.map +1 -1
- package/dist/routes/endpoints/admin.js.map +1 -1
- package/dist/routes/endpoints/checkpointing.js.map +1 -1
- package/dist/routes/endpoints/route-endpoints-index.d.ts +1 -0
- package/dist/routes/endpoints/route-endpoints-index.js +1 -0
- package/dist/routes/endpoints/route-endpoints-index.js.map +1 -1
- package/dist/routes/endpoints/socket-route.js.map +1 -1
- package/dist/routes/endpoints/sync-rules.js.map +1 -1
- package/dist/routes/endpoints/sync-stream.js.map +1 -1
- package/dist/routes/router.d.ts +1 -4
- package/dist/routes/router.js.map +1 -1
- package/dist/runner/teardown.js +4 -1
- package/dist/runner/teardown.js.map +1 -1
- package/dist/system/ServiceContext.d.ts +19 -4
- package/dist/system/ServiceContext.js +20 -8
- package/dist/system/ServiceContext.js.map +1 -1
- package/dist/util/config/collectors/config-collector.js +7 -30
- package/dist/util/config/collectors/config-collector.js.map +1 -1
- package/dist/util/config/collectors/impl/yaml-env.d.ts +7 -0
- package/dist/util/config/collectors/impl/yaml-env.js +59 -0
- package/dist/util/config/collectors/impl/yaml-env.js.map +1 -0
- package/dist/util/config/compound-config-collector.js +18 -1
- package/dist/util/config/compound-config-collector.js.map +1 -1
- package/dist/util/config/types.d.ts +11 -0
- package/package.json +4 -4
- package/src/entry/commands/compact-action.ts +4 -1
- package/src/entry/commands/migrate-action.ts +4 -1
- package/src/entry/commands/test-connection-action.ts +4 -1
- package/src/routes/RouterEngine.ts +21 -11
- package/src/routes/configure-fastify.ts +2 -7
- package/src/routes/configure-rsocket.ts +0 -4
- package/src/routes/endpoints/admin.ts +5 -5
- package/src/routes/endpoints/checkpointing.ts +2 -2
- package/src/routes/endpoints/route-endpoints-index.ts +1 -0
- package/src/routes/endpoints/socket-route.ts +3 -3
- package/src/routes/endpoints/sync-rules.ts +4 -4
- package/src/routes/endpoints/sync-stream.ts +3 -3
- package/src/routes/router.ts +2 -1
- package/src/runner/teardown.ts +5 -1
- package/src/system/ServiceContext.ts +31 -12
- package/src/util/config/collectors/config-collector.ts +9 -36
- package/src/util/config/collectors/impl/yaml-env.ts +67 -0
- package/src/util/config/compound-config-collector.ts +22 -5
- package/src/util/config/types.ts +13 -0
- package/test/src/config.test.ts +72 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,29 +1,22 @@
|
|
|
1
|
+
import { configFile } from '@powersync/service-types';
|
|
2
|
+
import * as t from 'ts-codec';
|
|
1
3
|
import * as yaml from 'yaml';
|
|
2
4
|
|
|
3
5
|
import { schema } from '@powersync/lib-services-framework';
|
|
4
|
-
import { configFile } from '@powersync/service-types';
|
|
5
|
-
|
|
6
6
|
import { RunnerConfig } from '../types.js';
|
|
7
|
+
import { YamlEnvTag } from './impl/yaml-env.js';
|
|
7
8
|
|
|
8
9
|
export enum ConfigFileFormat {
|
|
9
10
|
YAML = 'yaml',
|
|
10
11
|
JSON = 'json'
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
/**
|
|
14
|
-
* Environment variables can be substituted into the YAML config
|
|
15
|
-
* when parsing if the environment variable name starts with this prefix.
|
|
16
|
-
* Attempting to substitute any other environment variable will throw an exception.
|
|
17
|
-
*
|
|
18
|
-
* Example of substitution:
|
|
19
|
-
* storage:
|
|
20
|
-
* type: mongodb
|
|
21
|
-
* uri: !env PS_MONGO_URI
|
|
22
|
-
*/
|
|
23
|
-
const YAML_ENV_PREFIX = 'PS_';
|
|
24
|
-
|
|
25
14
|
// ts-codec itself doesn't give great validation errors, so we use json schema for that
|
|
26
|
-
const configSchemaValidator = schema
|
|
15
|
+
const configSchemaValidator = schema
|
|
16
|
+
.parseJSONSchema(
|
|
17
|
+
t.generateJSONSchema(configFile.powerSyncConfig, { allowAdditional: true, parsers: [configFile.portParser] })
|
|
18
|
+
)
|
|
19
|
+
.validator();
|
|
27
20
|
|
|
28
21
|
export abstract class ConfigCollector {
|
|
29
22
|
abstract get name(): string;
|
|
@@ -100,27 +93,7 @@ export abstract class ConfigCollector {
|
|
|
100
93
|
schema: 'core',
|
|
101
94
|
keepSourceTokens: true,
|
|
102
95
|
lineCounter,
|
|
103
|
-
customTags: [
|
|
104
|
-
{
|
|
105
|
-
tag: '!env',
|
|
106
|
-
resolve(envName: string, onError: (error: string) => void) {
|
|
107
|
-
if (!envName.startsWith(YAML_ENV_PREFIX)) {
|
|
108
|
-
onError(
|
|
109
|
-
`Attempting to substitute environment variable ${envName} is not allowed. Variables must start with "${YAML_ENV_PREFIX}"`
|
|
110
|
-
);
|
|
111
|
-
return envName;
|
|
112
|
-
}
|
|
113
|
-
const value = process.env[envName];
|
|
114
|
-
if (typeof value == 'undefined') {
|
|
115
|
-
onError(
|
|
116
|
-
`Attempted to substitute environment variable "${envName}" which is undefined. Set this variable on the environment.`
|
|
117
|
-
);
|
|
118
|
-
return envName;
|
|
119
|
-
}
|
|
120
|
-
return value;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
]
|
|
96
|
+
customTags: [YamlEnvTag]
|
|
124
97
|
});
|
|
125
98
|
|
|
126
99
|
if (parsed.errors.length) {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as yaml from 'yaml';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Environment variables can be substituted into the YAML config
|
|
5
|
+
* when parsing if the environment variable name starts with this prefix.
|
|
6
|
+
* Attempting to substitute any other environment variable will throw an exception.
|
|
7
|
+
*
|
|
8
|
+
* Example of substitution:
|
|
9
|
+
* storage:
|
|
10
|
+
* type: mongodb
|
|
11
|
+
* uri: !env PS_MONGO_URI
|
|
12
|
+
*/
|
|
13
|
+
const YAML_ENV_PREFIX = 'PS_';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Custom YAML tag which performs string environment variable substitution
|
|
17
|
+
* Allows for type casting string environment variables to boolean or number
|
|
18
|
+
* by using the syntax !env PS_MONGO_PORT::number or !env PS_USE_SUPABASE::boolean
|
|
19
|
+
*/
|
|
20
|
+
export const YamlEnvTag: yaml.ScalarTag = {
|
|
21
|
+
tag: '!env',
|
|
22
|
+
resolve(envName: string, onError: (error: string) => void) {
|
|
23
|
+
if (!envName.startsWith(YAML_ENV_PREFIX)) {
|
|
24
|
+
onError(
|
|
25
|
+
`Attempting to substitute environment variable ${envName} is not allowed. Variables must start with "${YAML_ENV_PREFIX}"`
|
|
26
|
+
);
|
|
27
|
+
return envName;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// allow type casting if the envName contains a type suffix
|
|
31
|
+
// e.g. PS_MONGO_PORT::number or PS_USE_SUPABASE::boolean
|
|
32
|
+
const [name, type = 'string'] = envName.split('::');
|
|
33
|
+
|
|
34
|
+
let value = process.env[name];
|
|
35
|
+
|
|
36
|
+
if (typeof value == 'undefined') {
|
|
37
|
+
onError(
|
|
38
|
+
`Attempted to substitute environment variable "${envName}" which is undefined. Set this variable on the environment.`
|
|
39
|
+
);
|
|
40
|
+
return envName;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
switch (type) {
|
|
44
|
+
case 'string':
|
|
45
|
+
return value;
|
|
46
|
+
case 'number':
|
|
47
|
+
const numberValue = Number(value);
|
|
48
|
+
if (Number.isNaN(numberValue)) {
|
|
49
|
+
onError(`Environment variable "${envName}" is not a valid number. Got: "${value}".`);
|
|
50
|
+
return envName;
|
|
51
|
+
}
|
|
52
|
+
return numberValue;
|
|
53
|
+
case 'boolean':
|
|
54
|
+
if (value?.toLowerCase() == 'true') {
|
|
55
|
+
return true;
|
|
56
|
+
} else if (value?.toLowerCase() == 'false') {
|
|
57
|
+
return false;
|
|
58
|
+
} else {
|
|
59
|
+
onError(`Environment variable "${envName}" is not a boolean. Expected "true" or "false", got "${value}".`);
|
|
60
|
+
return envName;
|
|
61
|
+
}
|
|
62
|
+
default:
|
|
63
|
+
onError(`Environment variable "${envName}" has an invalid type suffix "${type}".`);
|
|
64
|
+
return envName;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
@@ -5,11 +5,6 @@ import { ConfigCollector } from './collectors/config-collector.js';
|
|
|
5
5
|
import { Base64ConfigCollector } from './collectors/impl/base64-config-collector.js';
|
|
6
6
|
import { FallbackConfigCollector } from './collectors/impl/fallback-config-collector.js';
|
|
7
7
|
import { FileSystemConfigCollector } from './collectors/impl/filesystem-config-collector.js';
|
|
8
|
-
import { Base64SyncRulesCollector } from './sync-rules/impl/base64-sync-rules-collector.js';
|
|
9
|
-
import { FileSystemSyncRulesCollector } from './sync-rules/impl/filesystem-sync-rules-collector.js';
|
|
10
|
-
import { InlineSyncRulesCollector } from './sync-rules/impl/inline-sync-rules-collector.js';
|
|
11
|
-
import { SyncRulesCollector } from './sync-rules/sync-collector.js';
|
|
12
|
-
import { ResolvedPowerSyncConfig, RunnerConfig, SyncRulesConfig } from './types.js';
|
|
13
8
|
import {
|
|
14
9
|
DEFAULT_MAX_BUCKETS_PER_CONNECTION,
|
|
15
10
|
DEFAULT_MAX_CONCURRENT_CONNECTIONS,
|
|
@@ -17,6 +12,11 @@ import {
|
|
|
17
12
|
DEFAULT_MAX_PARAMETER_QUERY_RESULTS,
|
|
18
13
|
DEFAULT_MAX_POOL_SIZE
|
|
19
14
|
} from './defaults.js';
|
|
15
|
+
import { Base64SyncRulesCollector } from './sync-rules/impl/base64-sync-rules-collector.js';
|
|
16
|
+
import { FileSystemSyncRulesCollector } from './sync-rules/impl/filesystem-sync-rules-collector.js';
|
|
17
|
+
import { InlineSyncRulesCollector } from './sync-rules/impl/inline-sync-rules-collector.js';
|
|
18
|
+
import { SyncRulesCollector } from './sync-rules/sync-collector.js';
|
|
19
|
+
import { ResolvedPowerSyncConfig, RunnerConfig, SyncRulesConfig } from './types.js';
|
|
20
20
|
|
|
21
21
|
export type CompoundConfigCollectorOptions = {
|
|
22
22
|
/**
|
|
@@ -159,6 +159,23 @@ export class CompoundConfigCollector {
|
|
|
159
159
|
internal_service_endpoint:
|
|
160
160
|
baseConfig.telemetry?.internal_service_endpoint ?? 'https://pulse.journeyapps.com/v1/metrics'
|
|
161
161
|
},
|
|
162
|
+
healthcheck: {
|
|
163
|
+
/**
|
|
164
|
+
* Default to legacy mode if no probes config is provided.
|
|
165
|
+
* If users provide a config, all options require explicit opt-in.
|
|
166
|
+
*/
|
|
167
|
+
probes: baseConfig.healthcheck?.probes
|
|
168
|
+
? {
|
|
169
|
+
use_filesystem: baseConfig.healthcheck.probes.use_filesystem ?? false,
|
|
170
|
+
use_http: baseConfig.healthcheck.probes.use_http ?? false,
|
|
171
|
+
use_legacy: baseConfig.healthcheck.probes.use_legacy ?? false
|
|
172
|
+
}
|
|
173
|
+
: {
|
|
174
|
+
use_filesystem: false,
|
|
175
|
+
use_http: false,
|
|
176
|
+
use_legacy: true
|
|
177
|
+
}
|
|
178
|
+
},
|
|
162
179
|
api_parameters: {
|
|
163
180
|
max_buckets_per_connection:
|
|
164
181
|
baseConfig.api?.parameters?.max_buckets_per_connection ?? DEFAULT_MAX_BUCKETS_PER_CONNECTION,
|
package/src/util/config/types.ts
CHANGED
|
@@ -69,5 +69,18 @@ export type ResolvedPowerSyncConfig = {
|
|
|
69
69
|
|
|
70
70
|
/** Prefix for postgres replication slot names. May eventually be connection-specific. */
|
|
71
71
|
slot_name_prefix: string;
|
|
72
|
+
|
|
73
|
+
healthcheck: {
|
|
74
|
+
probes: {
|
|
75
|
+
use_filesystem: boolean;
|
|
76
|
+
use_http: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* @deprecated This maintains backwards compatibility with the legacy default.
|
|
79
|
+
* Explicit probe configuration should be used instead.
|
|
80
|
+
*/
|
|
81
|
+
use_legacy: boolean;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
72
85
|
parameters: Record<string, number | string | boolean | null>;
|
|
73
86
|
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Vitest Unit Tests
|
|
2
|
+
import { CompoundConfigCollector } from '@/index.js';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('Config', () => {
|
|
6
|
+
it('should substitute env variables in YAML config', {}, async () => {
|
|
7
|
+
const type = 'mongodb';
|
|
8
|
+
vi.stubEnv('PS_MONGO_TYPE', type);
|
|
9
|
+
|
|
10
|
+
const yamlConfig = /* yaml */ `
|
|
11
|
+
# PowerSync config
|
|
12
|
+
replication:
|
|
13
|
+
connections: []
|
|
14
|
+
storage:
|
|
15
|
+
type: !env PS_MONGO_TYPE
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const collector = new CompoundConfigCollector();
|
|
19
|
+
|
|
20
|
+
const config = await collector.collectConfig({
|
|
21
|
+
config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(config.storage.type).toBe(type);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should substitute boolean env variables in YAML config', {}, async () => {
|
|
28
|
+
vi.stubEnv('PS_MONGO_HEALTHCHECK', 'true');
|
|
29
|
+
|
|
30
|
+
const yamlConfig = /* yaml */ `
|
|
31
|
+
# PowerSync config
|
|
32
|
+
replication:
|
|
33
|
+
connections: []
|
|
34
|
+
storage:
|
|
35
|
+
type: mongodb
|
|
36
|
+
healthcheck:
|
|
37
|
+
probes:
|
|
38
|
+
use_http: !env PS_MONGO_HEALTHCHECK::boolean
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
const collector = new CompoundConfigCollector();
|
|
42
|
+
|
|
43
|
+
const config = await collector.collectConfig({
|
|
44
|
+
config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(config.healthcheck.probes.use_http).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should substitute number env variables in YAML config', {}, async () => {
|
|
51
|
+
vi.stubEnv('PS_MAX_BUCKETS', '1');
|
|
52
|
+
|
|
53
|
+
const yamlConfig = /* yaml */ `
|
|
54
|
+
# PowerSync config
|
|
55
|
+
replication:
|
|
56
|
+
connections: []
|
|
57
|
+
storage:
|
|
58
|
+
type: mongodb
|
|
59
|
+
api:
|
|
60
|
+
parameters:
|
|
61
|
+
max_buckets_per_connection: !env PS_MAX_BUCKETS::number
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
const collector = new CompoundConfigCollector();
|
|
65
|
+
|
|
66
|
+
const config = await collector.collectConfig({
|
|
67
|
+
config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(config.api_parameters.max_buckets_per_connection).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
});
|