@syncular/testkit 0.0.0 → 0.0.2-136
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/dist/assertions.d.ts +61 -0
- package/dist/assertions.d.ts.map +1 -0
- package/dist/assertions.js +239 -0
- package/dist/assertions.js.map +1 -0
- package/dist/faults.d.ts +40 -0
- package/dist/faults.d.ts.map +1 -0
- package/dist/faults.js +136 -0
- package/dist/faults.js.map +1 -0
- package/dist/fixtures.d.ts +100 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +541 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/hono-node-server.d.ts +10 -0
- package/dist/hono-node-server.d.ts.map +1 -0
- package/dist/hono-node-server.js +63 -0
- package/dist/hono-node-server.js.map +1 -0
- package/dist/http-fixtures.d.ts +57 -0
- package/dist/http-fixtures.d.ts.map +1 -0
- package/dist/http-fixtures.js +107 -0
- package/dist/http-fixtures.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/project-scoped-tasks.d.ts +40 -0
- package/dist/project-scoped-tasks.d.ts.map +1 -0
- package/dist/project-scoped-tasks.js +245 -0
- package/dist/project-scoped-tasks.js.map +1 -0
- package/dist/runtime-process.d.ts +11 -0
- package/dist/runtime-process.d.ts.map +1 -0
- package/dist/runtime-process.js +92 -0
- package/dist/runtime-process.js.map +1 -0
- package/dist/sync-http.d.ts +48 -0
- package/dist/sync-http.d.ts.map +1 -0
- package/dist/sync-http.js +30 -0
- package/dist/sync-http.js.map +1 -0
- package/dist/sync-response.d.ts +7 -0
- package/dist/sync-response.d.ts.map +1 -0
- package/dist/sync-response.js +19 -0
- package/dist/sync-response.js.map +1 -0
- package/package.json +12 -12
- package/src/faults.ts +0 -3
- package/src/fixtures.ts +0 -3
- package/src/index.ts +3 -0
- package/src/project-scoped-tasks.ts +51 -1
- package/src/runtime-process.ts +133 -0
- package/src/sync-http.ts +100 -0
- package/src/sync-response.ts +45 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SyncCombinedRequest, SyncCombinedResponse, SyncPullRequest, SyncPullResponse, SyncPushRequest, SyncPushResponse } from '@syncular/core';
|
|
2
|
+
export interface JsonActorHeadersOptions {
|
|
3
|
+
actorId: string;
|
|
4
|
+
actorHeader?: string;
|
|
5
|
+
extraHeaders?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export declare function createJsonActorHeaders(options: JsonActorHeadersOptions): Record<string, string>;
|
|
8
|
+
export interface PostJsonWithActorOptions<TBody> {
|
|
9
|
+
fetch: typeof globalThis.fetch;
|
|
10
|
+
url: string;
|
|
11
|
+
actorId: string;
|
|
12
|
+
actorHeader?: string;
|
|
13
|
+
extraHeaders?: Record<string, string>;
|
|
14
|
+
body: TBody;
|
|
15
|
+
}
|
|
16
|
+
export interface PostJsonWithActorResult<TResponse> {
|
|
17
|
+
response: Response;
|
|
18
|
+
json: TResponse;
|
|
19
|
+
}
|
|
20
|
+
export declare function postJsonWithActor<TBody, TResponse>(options: PostJsonWithActorOptions<TBody>): Promise<PostJsonWithActorResult<TResponse>>;
|
|
21
|
+
export interface PostSyncCombinedRequestOptions {
|
|
22
|
+
fetch: typeof globalThis.fetch;
|
|
23
|
+
url: string;
|
|
24
|
+
actorId: string;
|
|
25
|
+
actorHeader?: string;
|
|
26
|
+
extraHeaders?: Record<string, string>;
|
|
27
|
+
body: SyncCombinedRequest;
|
|
28
|
+
}
|
|
29
|
+
export declare function postSyncCombinedRequest(options: PostSyncCombinedRequestOptions): Promise<PostJsonWithActorResult<SyncCombinedResponse>>;
|
|
30
|
+
export interface PostSyncPushRequestOptions {
|
|
31
|
+
fetch: typeof globalThis.fetch;
|
|
32
|
+
url: string;
|
|
33
|
+
actorId: string;
|
|
34
|
+
actorHeader?: string;
|
|
35
|
+
extraHeaders?: Record<string, string>;
|
|
36
|
+
body: SyncPushRequest;
|
|
37
|
+
}
|
|
38
|
+
export declare function postSyncPushRequest(options: PostSyncPushRequestOptions): Promise<PostJsonWithActorResult<SyncPushResponse>>;
|
|
39
|
+
export interface PostSyncPullRequestOptions {
|
|
40
|
+
fetch: typeof globalThis.fetch;
|
|
41
|
+
url: string;
|
|
42
|
+
actorId: string;
|
|
43
|
+
actorHeader?: string;
|
|
44
|
+
extraHeaders?: Record<string, string>;
|
|
45
|
+
body: SyncPullRequest;
|
|
46
|
+
}
|
|
47
|
+
export declare function postSyncPullRequest(options: PostSyncPullRequestOptions): Promise<PostJsonWithActorResult<SyncPullResponse>>;
|
|
48
|
+
//# sourceMappingURL=sync-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-http.d.ts","sourceRoot":"","sources":["../src/sync-http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxB;AAED,MAAM,WAAW,wBAAwB,CAAC,KAAK;IAC7C,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,IAAI,EAAE,KAAK,CAAC;CACb;AAED,MAAM,WAAW,uBAAuB,CAAC,SAAS;IAChD,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,SAAS,EACtD,OAAO,EAAE,wBAAwB,CAAC,KAAK,CAAC,GACvC,OAAO,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAa7C;AAED,MAAM,WAAW,8BAA8B;IAC7C,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,IAAI,EAAE,mBAAmB,CAAC;CAC3B;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC,uBAAuB,CAAC,oBAAoB,CAAC,CAAC,CAExD;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,IAAI,EAAE,eAAe,CAAC;CACvB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,uBAAuB,CAAC,gBAAgB,CAAC,CAAC,CAEpD;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,IAAI,EAAE,eAAe,CAAC;CACvB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,uBAAuB,CAAC,gBAAgB,CAAC,CAAC,CAEpD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function createJsonActorHeaders(options) {
|
|
2
|
+
return {
|
|
3
|
+
'content-type': 'application/json',
|
|
4
|
+
[options.actorHeader ?? 'x-actor-id']: options.actorId,
|
|
5
|
+
...options.extraHeaders,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export async function postJsonWithActor(options) {
|
|
9
|
+
const response = await options.fetch(options.url, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: createJsonActorHeaders({
|
|
12
|
+
actorId: options.actorId,
|
|
13
|
+
actorHeader: options.actorHeader,
|
|
14
|
+
extraHeaders: options.extraHeaders,
|
|
15
|
+
}),
|
|
16
|
+
body: JSON.stringify(options.body),
|
|
17
|
+
});
|
|
18
|
+
const json = (await response.json());
|
|
19
|
+
return { response, json };
|
|
20
|
+
}
|
|
21
|
+
export async function postSyncCombinedRequest(options) {
|
|
22
|
+
return postJsonWithActor(options);
|
|
23
|
+
}
|
|
24
|
+
export async function postSyncPushRequest(options) {
|
|
25
|
+
return postJsonWithActor(options);
|
|
26
|
+
}
|
|
27
|
+
export async function postSyncPullRequest(options) {
|
|
28
|
+
return postJsonWithActor(options);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=sync-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-http.js","sourceRoot":"","sources":["../src/sync-http.ts"],"names":[],"mappings":"AAeA,MAAM,UAAU,sBAAsB,CACpC,OAAgC,EACR;IACxB,OAAO;QACL,cAAc,EAAE,kBAAkB;QAClC,CAAC,OAAO,CAAC,WAAW,IAAI,YAAY,CAAC,EAAE,OAAO,CAAC,OAAO;QACtD,GAAG,OAAO,CAAC,YAAY;KACxB,CAAC;AAAA,CACH;AAgBD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAwC,EACK;IAC7C,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QAChD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,sBAAsB,CAAC;YAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,YAAY,EAAE,OAAO,CAAC,YAAY;SACnC,CAAC;QACF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;KACnC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAc,CAAC;IAClD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAAA,CAC3B;AAWD,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAAuC,EACiB;IACxD,OAAO,iBAAiB,CAA4C,OAAO,CAAC,CAAC;AAAA,CAC9E;AAWD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAmC,EACiB;IACpD,OAAO,iBAAiB,CAAoC,OAAO,CAAC,CAAC;AAAA,CACtE;AAWD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAmC,EACiB;IACpD,OAAO,iBAAiB,CAAoC,OAAO,CAAC,CAAC;AAAA,CACtE"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type SyncChange, type SyncPullResponse } from '@syncular/core';
|
|
2
|
+
export type SyncChangeRecord = SyncChange;
|
|
3
|
+
export type SyncSubscriptionRecord = Pick<SyncPullResponse['subscriptions'][number], 'id' | 'commits'>;
|
|
4
|
+
export declare function subscriptionChanges(subscriptions: SyncSubscriptionRecord[] | undefined, subscriptionId: string): SyncChangeRecord[];
|
|
5
|
+
export declare function findSubscriptionChange(subscriptions: SyncSubscriptionRecord[] | undefined, subscriptionId: string, rowId: string): SyncChangeRecord | undefined;
|
|
6
|
+
export declare function subscriptionChangeRow(change: SyncChangeRecord | undefined): Record<string, unknown> | undefined;
|
|
7
|
+
//# sourceMappingURL=sync-response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-response.d.ts","sourceRoot":"","sources":["../src/sync-response.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAE1C,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,gBAAgB,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,EACzC,IAAI,GAAG,SAAS,CACjB,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,sBAAsB,EAAE,GAAG,SAAS,EACnD,cAAc,EAAE,MAAM,GACrB,gBAAgB,EAAE,CASpB;AAED,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,sBAAsB,EAAE,GAAG,SAAS,EACnD,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,MAAM,GACZ,gBAAgB,GAAG,SAAS,CAG9B;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GACnC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAMrC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isRecord, } from '@syncular/core';
|
|
2
|
+
export function subscriptionChanges(subscriptions, subscriptionId) {
|
|
3
|
+
const subscription = subscriptions?.find((item) => item.id === subscriptionId);
|
|
4
|
+
if (!subscription) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
return subscription.commits?.flatMap((commit) => commit.changes) ?? [];
|
|
8
|
+
}
|
|
9
|
+
export function findSubscriptionChange(subscriptions, subscriptionId, rowId) {
|
|
10
|
+
const changes = subscriptionChanges(subscriptions, subscriptionId);
|
|
11
|
+
return changes.find((change) => change.row_id === rowId);
|
|
12
|
+
}
|
|
13
|
+
export function subscriptionChangeRow(change) {
|
|
14
|
+
if (!change) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return isRecord(change.row_json) ? change.row_json : undefined;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=sync-response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-response.js","sourceRoot":"","sources":["../src/sync-response.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,GAGT,MAAM,gBAAgB,CAAC;AASxB,MAAM,UAAU,mBAAmB,CACjC,aAAmD,EACnD,cAAsB,EACF;IACpB,MAAM,YAAY,GAAG,aAAa,EAAE,IAAI,CACtC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,cAAc,CACrC,CAAC;IACF,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;AAAA,CACxE;AAED,MAAM,UAAU,sBAAsB,CACpC,aAAmD,EACnD,cAAsB,EACtB,KAAa,EACiB;IAC9B,MAAM,OAAO,GAAG,mBAAmB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IACnE,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;AAAA,CAC1D;AAED,MAAM,UAAU,qBAAqB,CACnC,MAAoC,EACC;IACrC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAChE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/testkit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-136",
|
|
4
4
|
"description": "Testing fixtures and utilities for Syncular",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -41,17 +41,17 @@
|
|
|
41
41
|
"release": "bunx syncular-publish"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@syncular/client": "0.0.
|
|
45
|
-
"@syncular/core": "0.0.
|
|
46
|
-
"@syncular/dialect-bun-sqlite": "0.0.
|
|
47
|
-
"@syncular/dialect-libsql": "0.0.
|
|
48
|
-
"@syncular/dialect-pglite": "0.0.
|
|
49
|
-
"@syncular/dialect-sqlite3": "0.0.
|
|
50
|
-
"@syncular/server": "0.0.
|
|
51
|
-
"@syncular/server-dialect-postgres": "0.0.
|
|
52
|
-
"@syncular/server-dialect-sqlite": "0.0.
|
|
53
|
-
"@syncular/server-hono": "0.0.
|
|
54
|
-
"@syncular/transport-http": "0.0.
|
|
44
|
+
"@syncular/client": "0.0.2-136",
|
|
45
|
+
"@syncular/core": "0.0.2-136",
|
|
46
|
+
"@syncular/dialect-bun-sqlite": "0.0.2-136",
|
|
47
|
+
"@syncular/dialect-libsql": "0.0.2-136",
|
|
48
|
+
"@syncular/dialect-pglite": "0.0.2-136",
|
|
49
|
+
"@syncular/dialect-sqlite3": "0.0.2-136",
|
|
50
|
+
"@syncular/server": "0.0.2-136",
|
|
51
|
+
"@syncular/server-dialect-postgres": "0.0.2-136",
|
|
52
|
+
"@syncular/server-dialect-sqlite": "0.0.2-136",
|
|
53
|
+
"@syncular/server-hono": "0.0.2-136",
|
|
54
|
+
"@syncular/transport-http": "0.0.2-136",
|
|
55
55
|
"hono": "^4.11.9"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
package/src/faults.ts
CHANGED
package/src/fixtures.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
isRecord,
|
|
3
|
+
type SyncOperation,
|
|
4
|
+
type SyncSubscriptionRequest,
|
|
5
|
+
} from '@syncular/core';
|
|
2
6
|
import type {
|
|
3
7
|
ApplyOperationResult,
|
|
4
8
|
EmittedChange,
|
|
@@ -297,3 +301,49 @@ export function createProjectScopedTasksHandler<
|
|
|
297
301
|
},
|
|
298
302
|
};
|
|
299
303
|
}
|
|
304
|
+
|
|
305
|
+
export interface CreateProjectScopedTasksSubscriptionOptions {
|
|
306
|
+
id?: string;
|
|
307
|
+
userId: string;
|
|
308
|
+
projectId?: string;
|
|
309
|
+
cursor?: number;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function createProjectScopedTasksSubscription(
|
|
313
|
+
options: CreateProjectScopedTasksSubscriptionOptions
|
|
314
|
+
): SyncSubscriptionRequest {
|
|
315
|
+
return {
|
|
316
|
+
id: options.id ?? 'sub-tasks',
|
|
317
|
+
table: 'tasks',
|
|
318
|
+
scopes: {
|
|
319
|
+
user_id: options.userId,
|
|
320
|
+
project_id: options.projectId ?? 'p0',
|
|
321
|
+
},
|
|
322
|
+
cursor: options.cursor ?? 0,
|
|
323
|
+
bootstrapState: null,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export interface CreateProjectScopedTaskUpsertOperationOptions {
|
|
328
|
+
taskId: string;
|
|
329
|
+
title: string;
|
|
330
|
+
completed?: number;
|
|
331
|
+
projectId?: string;
|
|
332
|
+
baseVersion?: number | null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function createProjectScopedTaskUpsertOperation(
|
|
336
|
+
options: CreateProjectScopedTaskUpsertOperationOptions
|
|
337
|
+
): SyncOperation {
|
|
338
|
+
return {
|
|
339
|
+
table: 'tasks',
|
|
340
|
+
row_id: options.taskId,
|
|
341
|
+
op: 'upsert',
|
|
342
|
+
payload: {
|
|
343
|
+
title: options.title,
|
|
344
|
+
completed: options.completed ?? 0,
|
|
345
|
+
project_id: options.projectId ?? 'p0',
|
|
346
|
+
},
|
|
347
|
+
base_version: options.baseVersion ?? null,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export interface WaitForJsonPortOptions {
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
processName?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function waitForJsonPortFromStdout(
|
|
9
|
+
process: ChildProcess,
|
|
10
|
+
options: WaitForJsonPortOptions = {}
|
|
11
|
+
): Promise<number> {
|
|
12
|
+
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
13
|
+
const processName = options.processName ?? 'Child process';
|
|
14
|
+
|
|
15
|
+
return new Promise<number>((resolve, reject) => {
|
|
16
|
+
const stdout = process.stdout;
|
|
17
|
+
const stderr = process.stderr;
|
|
18
|
+
|
|
19
|
+
if (!stdout || !stderr) {
|
|
20
|
+
reject(new Error(`${processName} has no stdout/stderr pipes`));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let stdoutBuffer = '';
|
|
25
|
+
let stderrBuffer = '';
|
|
26
|
+
|
|
27
|
+
const cleanup = () => {
|
|
28
|
+
clearTimeout(timeout);
|
|
29
|
+
stdout.off('data', onStdoutData);
|
|
30
|
+
stderr.off('data', onStderrData);
|
|
31
|
+
process.off('exit', onExit);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const resolvePort = (port: number) => {
|
|
35
|
+
cleanup();
|
|
36
|
+
resolve(port);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const rejectWith = (error: Error) => {
|
|
40
|
+
cleanup();
|
|
41
|
+
reject(error);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const onStdoutData = (chunk: Buffer | string) => {
|
|
45
|
+
stdoutBuffer += chunk.toString();
|
|
46
|
+
|
|
47
|
+
const lines = stdoutBuffer.split('\n');
|
|
48
|
+
stdoutBuffer = lines.pop() ?? '';
|
|
49
|
+
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
const trimmed = line.trim();
|
|
52
|
+
if (!trimmed) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(trimmed) as { port?: number };
|
|
58
|
+
if (parsed.port && parsed.port > 0) {
|
|
59
|
+
resolvePort(parsed.port);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
// keep reading until a valid JSON line is emitted
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const onStderrData = (chunk: Buffer | string) => {
|
|
69
|
+
stderrBuffer += chunk.toString();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const onExit = (code: number | null, signal: NodeJS.Signals | null) => {
|
|
73
|
+
rejectWith(
|
|
74
|
+
new Error(
|
|
75
|
+
`${processName} exited before reporting port (code=${String(code)} signal=${String(signal)})\nstderr: ${stderrBuffer}`
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const timeout = setTimeout(() => {
|
|
81
|
+
rejectWith(
|
|
82
|
+
new Error(
|
|
83
|
+
`${processName} startup timed out after ${timeoutMs}ms\nstderr: ${stderrBuffer}`
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
}, timeoutMs);
|
|
87
|
+
|
|
88
|
+
stdout.on('data', onStdoutData);
|
|
89
|
+
stderr.on('data', onStderrData);
|
|
90
|
+
process.on('exit', onExit);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface StopChildProcessOptions {
|
|
95
|
+
gracePeriodMs?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function stopChildProcess(
|
|
99
|
+
process: ChildProcess,
|
|
100
|
+
options: StopChildProcessOptions = {}
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
if (process.exitCode != null) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const gracePeriodMs = options.gracePeriodMs ?? 5000;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
process.kill('SIGTERM');
|
|
110
|
+
} catch {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await new Promise<void>((resolve) => {
|
|
115
|
+
const onExit = () => {
|
|
116
|
+
clearTimeout(timeout);
|
|
117
|
+
process.off('exit', onExit);
|
|
118
|
+
resolve();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const timeout = setTimeout(() => {
|
|
122
|
+
try {
|
|
123
|
+
process.kill('SIGKILL');
|
|
124
|
+
} catch {
|
|
125
|
+
// ignore
|
|
126
|
+
}
|
|
127
|
+
process.off('exit', onExit);
|
|
128
|
+
resolve();
|
|
129
|
+
}, gracePeriodMs);
|
|
130
|
+
|
|
131
|
+
process.on('exit', onExit);
|
|
132
|
+
});
|
|
133
|
+
}
|
package/src/sync-http.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SyncCombinedRequest,
|
|
3
|
+
SyncCombinedResponse,
|
|
4
|
+
SyncPullRequest,
|
|
5
|
+
SyncPullResponse,
|
|
6
|
+
SyncPushRequest,
|
|
7
|
+
SyncPushResponse,
|
|
8
|
+
} from '@syncular/core';
|
|
9
|
+
|
|
10
|
+
export interface JsonActorHeadersOptions {
|
|
11
|
+
actorId: string;
|
|
12
|
+
actorHeader?: string;
|
|
13
|
+
extraHeaders?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createJsonActorHeaders(
|
|
17
|
+
options: JsonActorHeadersOptions
|
|
18
|
+
): Record<string, string> {
|
|
19
|
+
return {
|
|
20
|
+
'content-type': 'application/json',
|
|
21
|
+
[options.actorHeader ?? 'x-actor-id']: options.actorId,
|
|
22
|
+
...options.extraHeaders,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PostJsonWithActorOptions<TBody> {
|
|
27
|
+
fetch: typeof globalThis.fetch;
|
|
28
|
+
url: string;
|
|
29
|
+
actorId: string;
|
|
30
|
+
actorHeader?: string;
|
|
31
|
+
extraHeaders?: Record<string, string>;
|
|
32
|
+
body: TBody;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PostJsonWithActorResult<TResponse> {
|
|
36
|
+
response: Response;
|
|
37
|
+
json: TResponse;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function postJsonWithActor<TBody, TResponse>(
|
|
41
|
+
options: PostJsonWithActorOptions<TBody>
|
|
42
|
+
): Promise<PostJsonWithActorResult<TResponse>> {
|
|
43
|
+
const response = await options.fetch(options.url, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: createJsonActorHeaders({
|
|
46
|
+
actorId: options.actorId,
|
|
47
|
+
actorHeader: options.actorHeader,
|
|
48
|
+
extraHeaders: options.extraHeaders,
|
|
49
|
+
}),
|
|
50
|
+
body: JSON.stringify(options.body),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const json = (await response.json()) as TResponse;
|
|
54
|
+
return { response, json };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface PostSyncCombinedRequestOptions {
|
|
58
|
+
fetch: typeof globalThis.fetch;
|
|
59
|
+
url: string;
|
|
60
|
+
actorId: string;
|
|
61
|
+
actorHeader?: string;
|
|
62
|
+
extraHeaders?: Record<string, string>;
|
|
63
|
+
body: SyncCombinedRequest;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function postSyncCombinedRequest(
|
|
67
|
+
options: PostSyncCombinedRequestOptions
|
|
68
|
+
): Promise<PostJsonWithActorResult<SyncCombinedResponse>> {
|
|
69
|
+
return postJsonWithActor<SyncCombinedRequest, SyncCombinedResponse>(options);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface PostSyncPushRequestOptions {
|
|
73
|
+
fetch: typeof globalThis.fetch;
|
|
74
|
+
url: string;
|
|
75
|
+
actorId: string;
|
|
76
|
+
actorHeader?: string;
|
|
77
|
+
extraHeaders?: Record<string, string>;
|
|
78
|
+
body: SyncPushRequest;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function postSyncPushRequest(
|
|
82
|
+
options: PostSyncPushRequestOptions
|
|
83
|
+
): Promise<PostJsonWithActorResult<SyncPushResponse>> {
|
|
84
|
+
return postJsonWithActor<SyncPushRequest, SyncPushResponse>(options);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface PostSyncPullRequestOptions {
|
|
88
|
+
fetch: typeof globalThis.fetch;
|
|
89
|
+
url: string;
|
|
90
|
+
actorId: string;
|
|
91
|
+
actorHeader?: string;
|
|
92
|
+
extraHeaders?: Record<string, string>;
|
|
93
|
+
body: SyncPullRequest;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function postSyncPullRequest(
|
|
97
|
+
options: PostSyncPullRequestOptions
|
|
98
|
+
): Promise<PostJsonWithActorResult<SyncPullResponse>> {
|
|
99
|
+
return postJsonWithActor<SyncPullRequest, SyncPullResponse>(options);
|
|
100
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isRecord,
|
|
3
|
+
type SyncChange,
|
|
4
|
+
type SyncPullResponse,
|
|
5
|
+
} from '@syncular/core';
|
|
6
|
+
|
|
7
|
+
export type SyncChangeRecord = SyncChange;
|
|
8
|
+
|
|
9
|
+
export type SyncSubscriptionRecord = Pick<
|
|
10
|
+
SyncPullResponse['subscriptions'][number],
|
|
11
|
+
'id' | 'commits'
|
|
12
|
+
>;
|
|
13
|
+
|
|
14
|
+
export function subscriptionChanges(
|
|
15
|
+
subscriptions: SyncSubscriptionRecord[] | undefined,
|
|
16
|
+
subscriptionId: string
|
|
17
|
+
): SyncChangeRecord[] {
|
|
18
|
+
const subscription = subscriptions?.find(
|
|
19
|
+
(item) => item.id === subscriptionId
|
|
20
|
+
);
|
|
21
|
+
if (!subscription) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return subscription.commits?.flatMap((commit) => commit.changes) ?? [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function findSubscriptionChange(
|
|
29
|
+
subscriptions: SyncSubscriptionRecord[] | undefined,
|
|
30
|
+
subscriptionId: string,
|
|
31
|
+
rowId: string
|
|
32
|
+
): SyncChangeRecord | undefined {
|
|
33
|
+
const changes = subscriptionChanges(subscriptions, subscriptionId);
|
|
34
|
+
return changes.find((change) => change.row_id === rowId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function subscriptionChangeRow(
|
|
38
|
+
change: SyncChangeRecord | undefined
|
|
39
|
+
): Record<string, unknown> | undefined {
|
|
40
|
+
if (!change) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return isRecord(change.row_json) ? change.row_json : undefined;
|
|
45
|
+
}
|