@syncular/server 0.15.26 → 0.15.27
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/README.md +31 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/readiness.d.ts +30 -0
- package/dist/readiness.js +43 -0
- package/dist/storage.d.ts +5 -0
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/readiness.ts +66 -0
- package/src/storage.ts +5 -0
package/README.md
CHANGED
|
@@ -57,6 +57,37 @@ done by a binding of the core or by in-database fanout — a relay would add a
|
|
|
57
57
|
hop, a second protocol surface, and a managed dependency for zero capability
|
|
58
58
|
the core lacks.
|
|
59
59
|
|
|
60
|
+
## Startup schema readiness
|
|
61
|
+
|
|
62
|
+
Fail startup before accepting traffic when the generated schema cannot compile
|
|
63
|
+
or the storage projection cannot migrate:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import {
|
|
67
|
+
ensureSyncServerReady,
|
|
68
|
+
SqliteServerStorage,
|
|
69
|
+
type SyncServerConfig,
|
|
70
|
+
} from '@syncular/server';
|
|
71
|
+
|
|
72
|
+
const config: SyncServerConfig = {
|
|
73
|
+
schema,
|
|
74
|
+
storage: new SqliteServerStorage('./sync.db'),
|
|
75
|
+
segments,
|
|
76
|
+
resolveScopes,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
await ensureSyncServerReady(config);
|
|
80
|
+
Bun.serve({ fetch: app.fetch });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The helper accepts the generated `ServerSchema`, compiles it, and calls the
|
|
84
|
+
storage backend's low-level `ensureSchema(CompiledSchema)`. A failure is a
|
|
85
|
+
`SyncServerReadinessError` with stable code `sync.schema_not_ready`, a `phase`
|
|
86
|
+
of `schema_compile` or `storage_migration`, and the generated schema version.
|
|
87
|
+
Log the cause for operators and stop startup. Do not catch readiness errors in
|
|
88
|
+
authentication or convert them to a 401; request-time schema checks are only a
|
|
89
|
+
defensive fallback.
|
|
90
|
+
|
|
60
91
|
## Write validators and recovery metadata
|
|
61
92
|
|
|
62
93
|
`validators` is the server-authoritative seam for row business rules that
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export * from './postgres-storage.js';
|
|
|
26
26
|
export * from './prune.js';
|
|
27
27
|
export * from './pull.js';
|
|
28
28
|
export * from './push.js';
|
|
29
|
+
export * from './readiness.js';
|
|
29
30
|
export * from './realtime.js';
|
|
30
31
|
export * from './relational-rows.js';
|
|
31
32
|
export * from './s3-blob-store.js';
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ export * from './postgres-storage.js';
|
|
|
30
30
|
export * from './prune.js';
|
|
31
31
|
export * from './pull.js';
|
|
32
32
|
export * from './push.js';
|
|
33
|
+
export * from './readiness.js';
|
|
33
34
|
export * from './realtime.js';
|
|
34
35
|
export * from './relational-rows.js';
|
|
35
36
|
export * from './s3-blob-store.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit server startup readiness.
|
|
3
|
+
*
|
|
4
|
+
* Protocol handlers ensure storage lazily for embeddability, but a production
|
|
5
|
+
* host must prove schema compatibility before it binds an HTTP/WebSocket port.
|
|
6
|
+
* This helper accepts the generated ServerSchema, compiles it once, and exposes
|
|
7
|
+
* a structured failure that cannot be confused with request authentication.
|
|
8
|
+
*/
|
|
9
|
+
import type { SyncServerConfig } from './context.js';
|
|
10
|
+
export declare const SYNC_SERVER_READINESS_ERROR_CODE: "sync.schema_not_ready";
|
|
11
|
+
export type SyncServerReadinessPhase = 'schema_compile' | 'storage_migration';
|
|
12
|
+
export declare class SyncServerReadinessError extends Error {
|
|
13
|
+
readonly name = "SyncServerReadinessError";
|
|
14
|
+
readonly code: "sync.schema_not_ready";
|
|
15
|
+
readonly phase: SyncServerReadinessPhase;
|
|
16
|
+
readonly schemaVersion: number;
|
|
17
|
+
constructor(options: {
|
|
18
|
+
readonly phase: SyncServerReadinessPhase;
|
|
19
|
+
readonly schemaVersion: number;
|
|
20
|
+
readonly cause: unknown;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compile and migrate the configured server storage before listening.
|
|
25
|
+
*
|
|
26
|
+
* Pass the same canonical config used by HTTP and realtime. The thrown error
|
|
27
|
+
* exposes only a stable code, phase, and schema version; operators can inspect
|
|
28
|
+
* `cause` locally for the table/column or storage diagnostic.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ensureSyncServerReady(config: Pick<SyncServerConfig, 'schema' | 'storage'>): Promise<void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { compileSchema } from './schema.js';
|
|
2
|
+
export const SYNC_SERVER_READINESS_ERROR_CODE = 'sync.schema_not_ready';
|
|
3
|
+
export class SyncServerReadinessError extends Error {
|
|
4
|
+
name = 'SyncServerReadinessError';
|
|
5
|
+
code = SYNC_SERVER_READINESS_ERROR_CODE;
|
|
6
|
+
phase;
|
|
7
|
+
schemaVersion;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super(`Syncular server schema ${options.schemaVersion} is not ready (${options.phase})`, { cause: options.cause });
|
|
10
|
+
this.phase = options.phase;
|
|
11
|
+
this.schemaVersion = options.schemaVersion;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Compile and migrate the configured server storage before listening.
|
|
16
|
+
*
|
|
17
|
+
* Pass the same canonical config used by HTTP and realtime. The thrown error
|
|
18
|
+
* exposes only a stable code, phase, and schema version; operators can inspect
|
|
19
|
+
* `cause` locally for the table/column or storage diagnostic.
|
|
20
|
+
*/
|
|
21
|
+
export async function ensureSyncServerReady(config) {
|
|
22
|
+
let compiled;
|
|
23
|
+
try {
|
|
24
|
+
compiled = compileSchema(config.schema);
|
|
25
|
+
}
|
|
26
|
+
catch (cause) {
|
|
27
|
+
throw new SyncServerReadinessError({
|
|
28
|
+
phase: 'schema_compile',
|
|
29
|
+
schemaVersion: config.schema.version,
|
|
30
|
+
cause,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
await config.storage.ensureSchema(compiled);
|
|
35
|
+
}
|
|
36
|
+
catch (cause) {
|
|
37
|
+
throw new SyncServerReadinessError({
|
|
38
|
+
phase: 'storage_migration',
|
|
39
|
+
schemaVersion: config.schema.version,
|
|
40
|
+
cause,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
package/dist/storage.d.ts
CHANGED
|
@@ -237,6 +237,11 @@ export interface ServerStorage {
|
|
|
237
237
|
* INDEX). The handler calls this before serving; hosts that drive
|
|
238
238
|
* storage directly (tests, admin tooling) call it once up front. Row
|
|
239
239
|
* operations for tables not covered by an `ensureSchema` call throw.
|
|
240
|
+
*
|
|
241
|
+
* This low-level storage seam accepts a `CompiledSchema`. Application hosts
|
|
242
|
+
* should call `ensureSyncServerReady(config)` with their generated schema
|
|
243
|
+
* before binding a public port; protocol handlers still call this method
|
|
244
|
+
* lazily as a defensive backstop.
|
|
240
245
|
*/
|
|
241
246
|
ensureSchema(schema: CompiledSchema): Promise<void>;
|
|
242
247
|
begin(partition: string): Promise<StorageTransaction>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/server",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.27",
|
|
4
4
|
"description": "Syncular server: handleSyncRequest + storage/auth interfaces for the sync protocol",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"!dist/**/*.test.d.ts"
|
|
54
54
|
],
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@syncular/core": "0.15.
|
|
56
|
+
"@syncular/core": "0.15.27"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@electric-sql/pglite": "^0.5.4"
|
package/src/index.ts
CHANGED
package/src/readiness.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit server startup readiness.
|
|
3
|
+
*
|
|
4
|
+
* Protocol handlers ensure storage lazily for embeddability, but a production
|
|
5
|
+
* host must prove schema compatibility before it binds an HTTP/WebSocket port.
|
|
6
|
+
* This helper accepts the generated ServerSchema, compiles it once, and exposes
|
|
7
|
+
* a structured failure that cannot be confused with request authentication.
|
|
8
|
+
*/
|
|
9
|
+
import type { SyncServerConfig } from './context';
|
|
10
|
+
import { type CompiledSchema, compileSchema } from './schema';
|
|
11
|
+
|
|
12
|
+
export const SYNC_SERVER_READINESS_ERROR_CODE =
|
|
13
|
+
'sync.schema_not_ready' as const;
|
|
14
|
+
|
|
15
|
+
export type SyncServerReadinessPhase = 'schema_compile' | 'storage_migration';
|
|
16
|
+
|
|
17
|
+
export class SyncServerReadinessError extends Error {
|
|
18
|
+
override readonly name = 'SyncServerReadinessError';
|
|
19
|
+
readonly code = SYNC_SERVER_READINESS_ERROR_CODE;
|
|
20
|
+
readonly phase: SyncServerReadinessPhase;
|
|
21
|
+
readonly schemaVersion: number;
|
|
22
|
+
|
|
23
|
+
constructor(options: {
|
|
24
|
+
readonly phase: SyncServerReadinessPhase;
|
|
25
|
+
readonly schemaVersion: number;
|
|
26
|
+
readonly cause: unknown;
|
|
27
|
+
}) {
|
|
28
|
+
super(
|
|
29
|
+
`Syncular server schema ${options.schemaVersion} is not ready (${options.phase})`,
|
|
30
|
+
{ cause: options.cause },
|
|
31
|
+
);
|
|
32
|
+
this.phase = options.phase;
|
|
33
|
+
this.schemaVersion = options.schemaVersion;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Compile and migrate the configured server storage before listening.
|
|
39
|
+
*
|
|
40
|
+
* Pass the same canonical config used by HTTP and realtime. The thrown error
|
|
41
|
+
* exposes only a stable code, phase, and schema version; operators can inspect
|
|
42
|
+
* `cause` locally for the table/column or storage diagnostic.
|
|
43
|
+
*/
|
|
44
|
+
export async function ensureSyncServerReady(
|
|
45
|
+
config: Pick<SyncServerConfig, 'schema' | 'storage'>,
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
let compiled: CompiledSchema;
|
|
48
|
+
try {
|
|
49
|
+
compiled = compileSchema(config.schema);
|
|
50
|
+
} catch (cause) {
|
|
51
|
+
throw new SyncServerReadinessError({
|
|
52
|
+
phase: 'schema_compile',
|
|
53
|
+
schemaVersion: config.schema.version,
|
|
54
|
+
cause,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
await config.storage.ensureSchema(compiled);
|
|
59
|
+
} catch (cause) {
|
|
60
|
+
throw new SyncServerReadinessError({
|
|
61
|
+
phase: 'storage_migration',
|
|
62
|
+
schemaVersion: config.schema.version,
|
|
63
|
+
cause,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/storage.ts
CHANGED
|
@@ -269,6 +269,11 @@ export interface ServerStorage {
|
|
|
269
269
|
* INDEX). The handler calls this before serving; hosts that drive
|
|
270
270
|
* storage directly (tests, admin tooling) call it once up front. Row
|
|
271
271
|
* operations for tables not covered by an `ensureSchema` call throw.
|
|
272
|
+
*
|
|
273
|
+
* This low-level storage seam accepts a `CompiledSchema`. Application hosts
|
|
274
|
+
* should call `ensureSyncServerReady(config)` with their generated schema
|
|
275
|
+
* before binding a public port; protocol handlers still call this method
|
|
276
|
+
* lazily as a defensive backstop.
|
|
272
277
|
*/
|
|
273
278
|
ensureSchema(schema: CompiledSchema): Promise<void>;
|
|
274
279
|
|