@powersync/service-core 0.8.6 → 0.8.8
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 +12 -0
- package/dist/routes/configure-fastify.d.ts +16 -0
- package/dist/routes/configure-fastify.js +2 -1
- package/dist/routes/configure-fastify.js.map +1 -1
- package/dist/routes/endpoints/probes.d.ts +74 -0
- package/dist/routes/endpoints/probes.js +51 -0
- package/dist/routes/endpoints/probes.js.map +1 -0
- package/dist/routes/router.d.ts +3 -3
- package/package.json +3 -3
- package/src/routes/configure-fastify.ts +2 -1
- package/src/routes/endpoints/probes.ts +58 -0
- package/src/routes/router.ts +3 -3
- package/test/src/routes/probes.integration.test.ts +231 -0
- package/test/src/routes/probes.test.ts +153 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @powersync/service-core
|
|
2
2
|
|
|
3
|
+
## 0.8.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 21de621: Add probe endpoints which can be used for system health checks.
|
|
8
|
+
|
|
9
|
+
## 0.8.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 6b72e6c: Improved Postgres connection port restrictions. Connections are now supported on ports >= 1024.
|
|
14
|
+
|
|
3
15
|
## 0.8.6
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -566,6 +566,22 @@ export declare const DEFAULT_ROUTE_OPTIONS: {
|
|
|
566
566
|
}, import("./router.js").Context, import("./router.js").BasicRouterRequest>) => Promise<{
|
|
567
567
|
write_checkpoint: string;
|
|
568
568
|
}>;
|
|
569
|
+
}) | (import("@powersync/lib-services-framework").Endpoint<unknown, import("@powersync/lib-services-framework").RouterResponse<{
|
|
570
|
+
ready: boolean;
|
|
571
|
+
started: boolean;
|
|
572
|
+
touched_at: Date;
|
|
573
|
+
}>, import("./router.js").Context, import("./router.js").RequestEndpointHandlerPayload<unknown, import("./router.js").Context, import("./router.js").BasicRouterRequest>, import("@powersync/lib-services-framework").EndpointHandler<import("./router.js").RequestEndpointHandlerPayload<unknown, import("./router.js").Context, import("./router.js").BasicRouterRequest>, import("@powersync/lib-services-framework").RouterResponse<{
|
|
574
|
+
ready: boolean;
|
|
575
|
+
started: boolean;
|
|
576
|
+
touched_at: Date;
|
|
577
|
+
}>>> & {
|
|
578
|
+
path: import("./endpoints/probes.js").ProbeRoutes;
|
|
579
|
+
method: import("@powersync/lib-services-framework").HTTPMethod.GET;
|
|
580
|
+
handler: () => Promise<import("@powersync/lib-services-framework").RouterResponse<{
|
|
581
|
+
ready: boolean;
|
|
582
|
+
started: boolean;
|
|
583
|
+
touched_at: Date;
|
|
584
|
+
}>>;
|
|
569
585
|
}) | (import("@powersync/lib-services-framework").Endpoint<{
|
|
570
586
|
content: string;
|
|
571
587
|
}, {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { registerFastifyRoutes } from './route-register.js';
|
|
2
2
|
import { ADMIN_ROUTES } from './endpoints/admin.js';
|
|
3
3
|
import { CHECKPOINT_ROUTES } from './endpoints/checkpointing.js';
|
|
4
|
+
import { PROBES_ROUTES } from './endpoints/probes.js';
|
|
4
5
|
import { SYNC_RULES_ROUTES } from './endpoints/sync-rules.js';
|
|
5
6
|
import { SYNC_STREAM_ROUTES } from './endpoints/sync-stream.js';
|
|
6
7
|
import { createRequestQueueHook } from './hooks.js';
|
|
7
8
|
export const DEFAULT_ROUTE_OPTIONS = {
|
|
8
9
|
api: {
|
|
9
|
-
routes: [...ADMIN_ROUTES, ...CHECKPOINT_ROUTES, ...SYNC_RULES_ROUTES],
|
|
10
|
+
routes: [...ADMIN_ROUTES, ...CHECKPOINT_ROUTES, ...SYNC_RULES_ROUTES, ...PROBES_ROUTES],
|
|
10
11
|
queueOptions: {
|
|
11
12
|
concurrency: 10,
|
|
12
13
|
max_queue_depth: 20
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configure-fastify.js","sourceRoot":"","sources":["../../src/routes/configure-fastify.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAI5D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAA4B,MAAM,YAAY,CAAC;AA0B9E,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,GAAG,EAAE;QACH,MAAM,EAAE,CAAC,GAAG,YAAY,EAAE,GAAG,iBAAiB,EAAE,GAAG,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"configure-fastify.js","sourceRoot":"","sources":["../../src/routes/configure-fastify.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAI5D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAA4B,MAAM,YAAY,CAAC;AA0B9E,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,GAAG,EAAE;QACH,MAAM,EAAE,CAAC,GAAG,YAAY,EAAE,GAAG,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,GAAG,aAAa,CAAC;QACvF,YAAY,EAAE;YACZ,WAAW,EAAE,EAAE;YACf,eAAe,EAAE,EAAE;SACpB;KACF;IACD,UAAU,EAAE;QACV,MAAM,EAAE,CAAC,GAAG,kBAAkB,CAAC;QAC/B,YAAY,EAAE;YACZ,WAAW,EAAE,GAAG;YAChB,eAAe,EAAE,CAAC;SACnB;KACF;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAA+B,EAAE,OAA4B;IAClG,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,qBAAqB,EAAE,GAAG,OAAO,CAAC;IAC3D;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,CAAC,KAAK,WAAW,YAAY;QAC1C,qBAAqB,CACnB,YAAY,EACZ,KAAK,IAAI,EAAE;YACT,OAAO;gBACL,OAAO,EAAE,SAAS;gBAClB,MAAM,EAAE,MAAM;aACf,CAAC;QACJ,CAAC,EACD,MAAM,CAAC,GAAG,EAAE,MAAM,IAAI,qBAAqB,CAAC,GAAG,CAAC,MAAM,CACvD,CAAC;QACF,uCAAuC;QACvC,YAAY,CAAC,OAAO,CAClB,WAAW,EACX,sBAAsB,CAAC,MAAM,CAAC,GAAG,EAAE,YAAY,IAAI,qBAAqB,CAAC,GAAG,CAAC,YAAY,CAAC,CAC3F,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,qDAAqD;IACrD,MAAM,CAAC,QAAQ,CAAC,KAAK,WAAW,YAAY;QAC1C,qBAAqB,CACnB,YAAY,EACZ,KAAK,IAAI,EAAE;YACT,OAAO;gBACL,OAAO,EAAE,SAAS;gBAClB,MAAM,EAAE,MAAM;aACf,CAAC;QACJ,CAAC,EACD,MAAM,CAAC,UAAU,EAAE,MAAM,IAAI,qBAAqB,CAAC,UAAU,CAAC,MAAM,CACrE,CAAC;QACF,uCAAuC;QACvC,YAAY,CAAC,OAAO,CAClB,WAAW,EACX,sBAAsB,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,IAAI,qBAAqB,CAAC,UAAU,CAAC,YAAY,CAAC,CACzG,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { router } from "@powersync/lib-services-framework";
|
|
2
|
+
export declare enum ProbeRoutes {
|
|
3
|
+
STARTUP = "/probes/startup",
|
|
4
|
+
LIVENESS = "/probes/liveness",
|
|
5
|
+
READINESS = "/probes/readiness"
|
|
6
|
+
}
|
|
7
|
+
export declare const startupCheck: router.Endpoint<unknown, router.RouterResponse<{
|
|
8
|
+
ready: boolean;
|
|
9
|
+
started: boolean;
|
|
10
|
+
touched_at: Date;
|
|
11
|
+
}>, import("../router.js").Context, import("../router.js").RequestEndpointHandlerPayload<unknown, import("../router.js").Context, import("../router.js").BasicRouterRequest>, router.EndpointHandler<import("../router.js").RequestEndpointHandlerPayload<unknown, import("../router.js").Context, import("../router.js").BasicRouterRequest>, router.RouterResponse<{
|
|
12
|
+
ready: boolean;
|
|
13
|
+
started: boolean;
|
|
14
|
+
touched_at: Date;
|
|
15
|
+
}>>> & {
|
|
16
|
+
path: ProbeRoutes;
|
|
17
|
+
method: router.HTTPMethod.GET;
|
|
18
|
+
handler: () => Promise<router.RouterResponse<{
|
|
19
|
+
ready: boolean;
|
|
20
|
+
started: boolean;
|
|
21
|
+
touched_at: Date;
|
|
22
|
+
}>>;
|
|
23
|
+
};
|
|
24
|
+
export declare const livenessCheck: router.Endpoint<unknown, router.RouterResponse<{
|
|
25
|
+
ready: boolean;
|
|
26
|
+
started: boolean;
|
|
27
|
+
touched_at: Date;
|
|
28
|
+
}>, import("../router.js").Context, import("../router.js").RequestEndpointHandlerPayload<unknown, import("../router.js").Context, import("../router.js").BasicRouterRequest>, router.EndpointHandler<import("../router.js").RequestEndpointHandlerPayload<unknown, import("../router.js").Context, import("../router.js").BasicRouterRequest>, router.RouterResponse<{
|
|
29
|
+
ready: boolean;
|
|
30
|
+
started: boolean;
|
|
31
|
+
touched_at: Date;
|
|
32
|
+
}>>> & {
|
|
33
|
+
path: ProbeRoutes;
|
|
34
|
+
method: router.HTTPMethod.GET;
|
|
35
|
+
handler: () => Promise<router.RouterResponse<{
|
|
36
|
+
ready: boolean;
|
|
37
|
+
started: boolean;
|
|
38
|
+
touched_at: Date;
|
|
39
|
+
}>>;
|
|
40
|
+
};
|
|
41
|
+
export declare const readinessCheck: router.Endpoint<unknown, router.RouterResponse<{
|
|
42
|
+
ready: boolean;
|
|
43
|
+
started: boolean;
|
|
44
|
+
touched_at: Date;
|
|
45
|
+
}>, import("../router.js").Context, import("../router.js").RequestEndpointHandlerPayload<unknown, import("../router.js").Context, import("../router.js").BasicRouterRequest>, router.EndpointHandler<import("../router.js").RequestEndpointHandlerPayload<unknown, import("../router.js").Context, import("../router.js").BasicRouterRequest>, router.RouterResponse<{
|
|
46
|
+
ready: boolean;
|
|
47
|
+
started: boolean;
|
|
48
|
+
touched_at: Date;
|
|
49
|
+
}>>> & {
|
|
50
|
+
path: ProbeRoutes;
|
|
51
|
+
method: router.HTTPMethod.GET;
|
|
52
|
+
handler: () => Promise<router.RouterResponse<{
|
|
53
|
+
ready: boolean;
|
|
54
|
+
started: boolean;
|
|
55
|
+
touched_at: Date;
|
|
56
|
+
}>>;
|
|
57
|
+
};
|
|
58
|
+
export declare const PROBES_ROUTES: (router.Endpoint<unknown, router.RouterResponse<{
|
|
59
|
+
ready: boolean;
|
|
60
|
+
started: boolean;
|
|
61
|
+
touched_at: Date;
|
|
62
|
+
}>, import("../router.js").Context, import("../router.js").RequestEndpointHandlerPayload<unknown, import("../router.js").Context, import("../router.js").BasicRouterRequest>, router.EndpointHandler<import("../router.js").RequestEndpointHandlerPayload<unknown, import("../router.js").Context, import("../router.js").BasicRouterRequest>, router.RouterResponse<{
|
|
63
|
+
ready: boolean;
|
|
64
|
+
started: boolean;
|
|
65
|
+
touched_at: Date;
|
|
66
|
+
}>>> & {
|
|
67
|
+
path: ProbeRoutes;
|
|
68
|
+
method: router.HTTPMethod.GET;
|
|
69
|
+
handler: () => Promise<router.RouterResponse<{
|
|
70
|
+
ready: boolean;
|
|
71
|
+
started: boolean;
|
|
72
|
+
touched_at: Date;
|
|
73
|
+
}>>;
|
|
74
|
+
})[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { container, router } from "@powersync/lib-services-framework";
|
|
2
|
+
import { routeDefinition } from "../router.js";
|
|
3
|
+
export var ProbeRoutes;
|
|
4
|
+
(function (ProbeRoutes) {
|
|
5
|
+
ProbeRoutes["STARTUP"] = "/probes/startup";
|
|
6
|
+
ProbeRoutes["LIVENESS"] = "/probes/liveness";
|
|
7
|
+
ProbeRoutes["READINESS"] = "/probes/readiness";
|
|
8
|
+
})(ProbeRoutes || (ProbeRoutes = {}));
|
|
9
|
+
export const startupCheck = routeDefinition({
|
|
10
|
+
path: ProbeRoutes.STARTUP,
|
|
11
|
+
method: router.HTTPMethod.GET,
|
|
12
|
+
handler: async () => {
|
|
13
|
+
const state = container.probes.state();
|
|
14
|
+
return new router.RouterResponse({
|
|
15
|
+
status: state.started ? 200 : 400,
|
|
16
|
+
data: {
|
|
17
|
+
...state,
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
export const livenessCheck = routeDefinition({
|
|
23
|
+
path: ProbeRoutes.LIVENESS,
|
|
24
|
+
method: router.HTTPMethod.GET,
|
|
25
|
+
handler: async () => {
|
|
26
|
+
const state = container.probes.state();
|
|
27
|
+
const timeDifference = Date.now() - state.touched_at.getTime();
|
|
28
|
+
const status = timeDifference < 10000 ? 200 : 400;
|
|
29
|
+
return new router.RouterResponse({
|
|
30
|
+
status,
|
|
31
|
+
data: {
|
|
32
|
+
...state,
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
export const readinessCheck = routeDefinition({
|
|
38
|
+
path: ProbeRoutes.READINESS,
|
|
39
|
+
method: router.HTTPMethod.GET,
|
|
40
|
+
handler: async () => {
|
|
41
|
+
const state = container.probes.state();
|
|
42
|
+
return new router.RouterResponse({
|
|
43
|
+
status: state.ready ? 200 : 400,
|
|
44
|
+
data: {
|
|
45
|
+
...state,
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
export const PROBES_ROUTES = [startupCheck, livenessCheck, readinessCheck];
|
|
51
|
+
//# sourceMappingURL=probes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probes.js","sourceRoot":"","sources":["../../../src/routes/endpoints/probes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,CAAN,IAAY,WAIX;AAJD,WAAY,WAAW;IACrB,0CAA2B,CAAA;IAC3B,4CAA6B,CAAA;IAC7B,8CAA+B,CAAA;AACjC,CAAC,EAJW,WAAW,KAAX,WAAW,QAItB;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAC;IAC1C,IAAI,EAAE,WAAW,CAAC,OAAO;IACzB,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG;IAC7B,OAAO,EAAE,KAAK,IAAI,EAAE;QAClB,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAEvC,OAAO,IAAI,MAAM,CAAC,cAAc,CAAC;YAC/B,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;YACjC,IAAI,EAAE;gBACJ,GAAG,KAAK;aACT;SACF,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;IAC3C,IAAI,EAAE,WAAW,CAAC,QAAQ;IAC1B,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG;IAC7B,OAAO,EAAE,KAAK,IAAI,EAAE;QAClB,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAEvC,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,CAAA;QAC9D,MAAM,MAAM,GAAG,cAAc,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QAElD,OAAO,IAAI,MAAM,CAAC,cAAc,CAAC;YAC/B,MAAM;YACN,IAAI,EAAE;gBACJ,GAAG,KAAK;aACT;SACF,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,eAAe,CAAC;IAC5C,IAAI,EAAE,WAAW,CAAC,SAAS;IAC3B,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG;IAC7B,OAAO,EAAE,KAAK,IAAI,EAAE;QAClB,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAEvC,OAAO,IAAI,MAAM,CAAC,cAAc,CAAC;YAC/B,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;YAC/B,IAAI,EAAE;gBACJ,GAAG,KAAK;aACT;SACF,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,YAAY,EAAE,aAAa,EAAE,cAAc,CAAC,CAAC"}
|
package/dist/routes/router.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { router } from '@powersync/lib-services-framework';
|
|
2
|
-
import
|
|
3
|
-
import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
|
|
2
|
+
import type { JwtPayload } from '../auth/auth-index.js';
|
|
3
|
+
import type { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
|
|
4
4
|
/**
|
|
5
5
|
* Common context for routes
|
|
6
6
|
*/
|
|
7
7
|
export type Context = {
|
|
8
8
|
user_id?: string;
|
|
9
9
|
system: CorePowerSyncSystem;
|
|
10
|
-
token_payload?:
|
|
10
|
+
token_payload?: JwtPayload;
|
|
11
11
|
token_errors?: string[];
|
|
12
12
|
/**
|
|
13
13
|
* Only on websocket endpoints.
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"version": "0.8.
|
|
8
|
+
"version": "0.8.8",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"license": "FSL-1.1-Apache-2.0",
|
|
11
11
|
"type": "module",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"winston": "^3.13.0",
|
|
35
35
|
"yaml": "^2.3.2",
|
|
36
36
|
"@powersync/lib-services-framework": "0.1.1",
|
|
37
|
-
"@powersync/service-jpgwire": "0.17.14",
|
|
38
37
|
"@powersync/service-jsonbig": "0.17.10",
|
|
38
|
+
"@powersync/service-jpgwire": "0.17.14",
|
|
39
39
|
"@powersync/service-rsocket-router": "0.0.13",
|
|
40
40
|
"@powersync/service-sync-rules": "0.20.0",
|
|
41
41
|
"@powersync/service-types": "0.2.0"
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"build": "tsc -b",
|
|
55
55
|
"build:tests": "tsc -b test/tsconfig.json",
|
|
56
56
|
"test": "vitest --no-threads",
|
|
57
|
-
"clean": "rm -rf ./
|
|
57
|
+
"clean": "rm -rf ./dist && tsc -b --clean"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -5,6 +5,7 @@ import * as system from '../system/system-index.js';
|
|
|
5
5
|
|
|
6
6
|
import { ADMIN_ROUTES } from './endpoints/admin.js';
|
|
7
7
|
import { CHECKPOINT_ROUTES } from './endpoints/checkpointing.js';
|
|
8
|
+
import { PROBES_ROUTES } from './endpoints/probes.js';
|
|
8
9
|
import { SYNC_RULES_ROUTES } from './endpoints/sync-rules.js';
|
|
9
10
|
import { SYNC_STREAM_ROUTES } from './endpoints/sync-stream.js';
|
|
10
11
|
import { createRequestQueueHook, CreateRequestQueueParams } from './hooks.js';
|
|
@@ -35,7 +36,7 @@ export type FastifyServerConfig = {
|
|
|
35
36
|
|
|
36
37
|
export const DEFAULT_ROUTE_OPTIONS = {
|
|
37
38
|
api: {
|
|
38
|
-
routes: [...ADMIN_ROUTES, ...CHECKPOINT_ROUTES, ...SYNC_RULES_ROUTES],
|
|
39
|
+
routes: [...ADMIN_ROUTES, ...CHECKPOINT_ROUTES, ...SYNC_RULES_ROUTES, ...PROBES_ROUTES],
|
|
39
40
|
queueOptions: {
|
|
40
41
|
concurrency: 10,
|
|
41
42
|
max_queue_depth: 20
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { container, router } from "@powersync/lib-services-framework";
|
|
2
|
+
import { routeDefinition } from "../router.js";
|
|
3
|
+
|
|
4
|
+
export enum ProbeRoutes {
|
|
5
|
+
STARTUP = '/probes/startup',
|
|
6
|
+
LIVENESS = '/probes/liveness',
|
|
7
|
+
READINESS = '/probes/readiness'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const startupCheck = routeDefinition({
|
|
11
|
+
path: ProbeRoutes.STARTUP,
|
|
12
|
+
method: router.HTTPMethod.GET,
|
|
13
|
+
handler: async () => {
|
|
14
|
+
const state = container.probes.state();
|
|
15
|
+
|
|
16
|
+
return new router.RouterResponse({
|
|
17
|
+
status: state.started ? 200 : 400,
|
|
18
|
+
data: {
|
|
19
|
+
...state,
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const livenessCheck = routeDefinition({
|
|
26
|
+
path: ProbeRoutes.LIVENESS,
|
|
27
|
+
method: router.HTTPMethod.GET,
|
|
28
|
+
handler: async () => {
|
|
29
|
+
const state = container.probes.state();
|
|
30
|
+
|
|
31
|
+
const timeDifference = Date.now() - state.touched_at.getTime()
|
|
32
|
+
const status = timeDifference < 10000 ? 200 : 400;
|
|
33
|
+
|
|
34
|
+
return new router.RouterResponse({
|
|
35
|
+
status,
|
|
36
|
+
data: {
|
|
37
|
+
...state,
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const readinessCheck = routeDefinition({
|
|
44
|
+
path: ProbeRoutes.READINESS,
|
|
45
|
+
method: router.HTTPMethod.GET,
|
|
46
|
+
handler: async () => {
|
|
47
|
+
const state = container.probes.state();
|
|
48
|
+
|
|
49
|
+
return new router.RouterResponse({
|
|
50
|
+
status: state.ready ? 200 : 400,
|
|
51
|
+
data: {
|
|
52
|
+
...state,
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const PROBES_ROUTES = [startupCheck, livenessCheck, readinessCheck];
|
package/src/routes/router.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { router } from '@powersync/lib-services-framework';
|
|
2
|
-
import
|
|
3
|
-
import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
|
|
2
|
+
import type { JwtPayload } from '../auth/auth-index.js';
|
|
3
|
+
import type { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Common context for routes
|
|
@@ -9,7 +9,7 @@ export type Context = {
|
|
|
9
9
|
user_id?: string;
|
|
10
10
|
system: CorePowerSyncSystem;
|
|
11
11
|
|
|
12
|
-
token_payload?:
|
|
12
|
+
token_payload?: JwtPayload;
|
|
13
13
|
token_errors?: string[];
|
|
14
14
|
/**
|
|
15
15
|
* Only on websocket endpoints.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import Fastify, { FastifyInstance } from 'fastify';
|
|
3
|
+
import { container } from '@powersync/lib-services-framework';
|
|
4
|
+
import * as auth from '../../../src/routes/auth.js';
|
|
5
|
+
import * as system from '../../../src/system/system-index.js';
|
|
6
|
+
import { configureFastifyServer } from '../../../src/index.js';
|
|
7
|
+
import { ProbeRoutes } from '../../../src/routes/endpoints/probes.js';
|
|
8
|
+
|
|
9
|
+
vi.mock("@powersync/lib-services-framework", async () => {
|
|
10
|
+
const actual = await vi.importActual("@powersync/lib-services-framework") as any;
|
|
11
|
+
return {
|
|
12
|
+
...actual,
|
|
13
|
+
container: {
|
|
14
|
+
...actual.container,
|
|
15
|
+
probes: {
|
|
16
|
+
state: vi.fn()
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('Probe Routes Integration', () => {
|
|
23
|
+
let app: FastifyInstance;
|
|
24
|
+
let mockSystem: system.CorePowerSyncSystem;
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
app = Fastify();
|
|
28
|
+
mockSystem = {} as system.CorePowerSyncSystem;
|
|
29
|
+
await configureFastifyServer(app, { system: mockSystem });
|
|
30
|
+
await app.ready();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
await app.close();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('Startup Probe', () => {
|
|
38
|
+
it('returns 200 when system is started', async () => {
|
|
39
|
+
const mockState = {
|
|
40
|
+
started: true,
|
|
41
|
+
ready: true,
|
|
42
|
+
touched_at: new Date()
|
|
43
|
+
};
|
|
44
|
+
vi.spyOn(auth, 'authUser').mockResolvedValue({ authorized: true });
|
|
45
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
46
|
+
|
|
47
|
+
const response = await app.inject({
|
|
48
|
+
method: 'GET',
|
|
49
|
+
url: ProbeRoutes.STARTUP,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(response.statusCode).toBe(200);
|
|
53
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
54
|
+
...mockState,
|
|
55
|
+
touched_at: mockState.touched_at.toISOString()
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('returns 400 when system is not started', async () => {
|
|
60
|
+
const mockState = {
|
|
61
|
+
started: false,
|
|
62
|
+
ready: false,
|
|
63
|
+
touched_at: new Date()
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
67
|
+
|
|
68
|
+
const response = await app.inject({
|
|
69
|
+
method: 'GET',
|
|
70
|
+
url: ProbeRoutes.STARTUP,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(response.statusCode).toBe(400);
|
|
74
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
75
|
+
...mockState,
|
|
76
|
+
touched_at: mockState.touched_at.toISOString()
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('Liveness Probe', () => {
|
|
82
|
+
it('returns 200 when system was touched recently', async () => {
|
|
83
|
+
const mockState = {
|
|
84
|
+
started: true,
|
|
85
|
+
ready: true,
|
|
86
|
+
touched_at: new Date()
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
90
|
+
|
|
91
|
+
const response = await app.inject({
|
|
92
|
+
method: 'GET',
|
|
93
|
+
url: ProbeRoutes.LIVENESS,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(response.statusCode).toBe(200);
|
|
97
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
98
|
+
...mockState,
|
|
99
|
+
touched_at: mockState.touched_at.toISOString()
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('returns 400 when system has not been touched recently', async () => {
|
|
104
|
+
const mockState = {
|
|
105
|
+
started: true,
|
|
106
|
+
ready: true,
|
|
107
|
+
touched_at: new Date(Date.now() - 15000) // 15 seconds ago
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
111
|
+
|
|
112
|
+
const response = await app.inject({
|
|
113
|
+
method: 'GET',
|
|
114
|
+
url: ProbeRoutes.LIVENESS,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(response.statusCode).toBe(400);
|
|
118
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
119
|
+
...mockState,
|
|
120
|
+
touched_at: mockState.touched_at.toISOString()
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('Readiness Probe', () => {
|
|
126
|
+
it('returns 200 when system is ready', async () => {
|
|
127
|
+
const mockState = {
|
|
128
|
+
started: true,
|
|
129
|
+
ready: true,
|
|
130
|
+
touched_at: new Date()
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
134
|
+
|
|
135
|
+
const response = await app.inject({
|
|
136
|
+
method: 'GET',
|
|
137
|
+
url: ProbeRoutes.READINESS,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(response.statusCode).toBe(200);
|
|
141
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
142
|
+
...mockState,
|
|
143
|
+
touched_at: mockState.touched_at.toISOString()
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('returns 400 when system is not ready', async () => {
|
|
148
|
+
const mockState = {
|
|
149
|
+
started: true,
|
|
150
|
+
ready: false,
|
|
151
|
+
touched_at: new Date()
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
155
|
+
|
|
156
|
+
const response = await app.inject({
|
|
157
|
+
method: 'GET',
|
|
158
|
+
url: ProbeRoutes.READINESS,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(response.statusCode).toBe(400);
|
|
162
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
163
|
+
...mockState,
|
|
164
|
+
touched_at: mockState.touched_at.toISOString()
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('Request Queue Behavior', () => {
|
|
170
|
+
it('handles concurrent requests within limits', async () => {
|
|
171
|
+
const mockState = { started: true, ready: true, touched_at: new Date() };
|
|
172
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
173
|
+
|
|
174
|
+
// Create array of 15 concurrent requests (default concurrency is 10)
|
|
175
|
+
const requests = Array(15).fill(null).map(() =>
|
|
176
|
+
app.inject({
|
|
177
|
+
method: 'GET',
|
|
178
|
+
url: ProbeRoutes.STARTUP,
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const responses = await Promise.all(requests);
|
|
183
|
+
|
|
184
|
+
// All requests should complete successfully
|
|
185
|
+
responses.forEach(response => {
|
|
186
|
+
expect(response.statusCode).toBe(200);
|
|
187
|
+
expect(JSON.parse(response.payload)).toEqual({
|
|
188
|
+
...mockState,
|
|
189
|
+
touched_at: mockState.touched_at.toISOString()
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('respects max queue depth', async () => {
|
|
195
|
+
const mockState = { started: true, ready: true, touched_at: new Date() };
|
|
196
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
197
|
+
|
|
198
|
+
// Create array of 35 concurrent requests (default max_queue_depth is 20)
|
|
199
|
+
const requests = Array(35).fill(null).map(() =>
|
|
200
|
+
app.inject({
|
|
201
|
+
method: 'GET',
|
|
202
|
+
url: ProbeRoutes.STARTUP,
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const responses = await Promise.all(requests);
|
|
207
|
+
|
|
208
|
+
// Some requests should succeed and some should fail with 429
|
|
209
|
+
const successCount = responses.filter(r => r.statusCode === 200).length;
|
|
210
|
+
const queueFullCount = responses.filter(r => r.statusCode === 429).length;
|
|
211
|
+
|
|
212
|
+
expect(successCount).toBeGreaterThan(0);
|
|
213
|
+
expect(queueFullCount).toBeGreaterThan(0);
|
|
214
|
+
expect(successCount + queueFullCount).toBe(35);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('Content Types', () => {
|
|
219
|
+
it('returns correct content type headers', async () => {
|
|
220
|
+
const mockState = { started: true, ready: true, touched_at: new Date() };
|
|
221
|
+
vi.mocked(container.probes.state).mockReturnValue(mockState);
|
|
222
|
+
|
|
223
|
+
const response = await app.inject({
|
|
224
|
+
method: 'GET',
|
|
225
|
+
url: ProbeRoutes.STARTUP,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(response.headers['content-type']).toMatch(/application\/json/);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|