@livestore/sync-cf 0.3.0-dev.50 → 0.3.0-dev.52
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/.tsbuildinfo +1 -1
- package/dist/cf-worker/worker.d.ts +28 -0
- package/dist/cf-worker/worker.d.ts.map +1 -1
- package/dist/cf-worker/worker.js +60 -26
- package/dist/cf-worker/worker.js.map +1 -1
- package/package.json +3 -3
- package/src/cf-worker/worker.ts +102 -58
|
@@ -9,4 +9,32 @@ export type MakeWorkerOptions = {
|
|
|
9
9
|
enableCORS?: boolean;
|
|
10
10
|
};
|
|
11
11
|
export declare const makeWorker: (options?: MakeWorkerOptions) => CFWorker;
|
|
12
|
+
/**
|
|
13
|
+
* Handles `/websocket` endpoint.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const validatePayload = (payload: Schema.JsonValue | undefined) => {
|
|
18
|
+
* if (payload?.authToken !== 'insecure-token-change-me') {
|
|
19
|
+
* throw new Error('Invalid auth token')
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* export default {
|
|
24
|
+
* fetch: async (request, env, ctx) => {
|
|
25
|
+
* if (request.url.endsWith('/websocket')) {
|
|
26
|
+
* return handleWebSocket(request, env, ctx, { headers: {}, validatePayload })
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* return new Response('Invalid path', { status: 400, headers: corsHeaders })
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @throws {UnexpectedError} If the payload is invalid
|
|
35
|
+
*/
|
|
36
|
+
export declare const handleWebSocket: (request: Request, env: Env, _ctx: ExecutionContext, options: {
|
|
37
|
+
headers?: HeadersInit;
|
|
38
|
+
validatePayload?: (payload: Schema.JsonValue | undefined) => void | Promise<void>;
|
|
39
|
+
}) => Promise<Response>;
|
|
12
40
|
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAIrD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAA;AAE9C,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAChF,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,GAAG,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjF,qBAAqB;IACrB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,UAAS,iBAAsB,KAAG,
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAIrD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAA;AAE9C,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAChF,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,GAAG,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjF,qBAAqB;IACrB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,UAAS,iBAAsB,KAAG,QA2C5D,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,OAAO,EAChB,KAAK,GAAG,EACR,MAAM,gBAAgB,EACtB,SAAS;IAAE,OAAO,CAAC,EAAE,WAAW,CAAC;IAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,GAAG,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,KACpH,OAAO,CAAC,QAAQ,CAqCmC,CAAA"}
|
package/dist/cf-worker/worker.js
CHANGED
|
@@ -3,10 +3,14 @@ import { Effect, UrlParams } from '@livestore/utils/effect';
|
|
|
3
3
|
import { SearchParamsSchema } from '../common/mod.js';
|
|
4
4
|
export const makeWorker = (options = {}) => {
|
|
5
5
|
return {
|
|
6
|
-
fetch: async (request, env, _ctx) =>
|
|
6
|
+
fetch: async (request, env, _ctx) => {
|
|
7
7
|
const url = new URL(request.url);
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
if (request.method === 'GET' && url.pathname === '/') {
|
|
9
|
+
return new Response('Info: WebSocket sync backend endpoint for @livestore/sync-cf.', {
|
|
10
|
+
status: 200,
|
|
11
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
10
14
|
const corsHeaders = options.enableCORS
|
|
11
15
|
? {
|
|
12
16
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -20,31 +24,11 @@ export const makeWorker = (options = {}) => {
|
|
|
20
24
|
headers: corsHeaders,
|
|
21
25
|
});
|
|
22
26
|
}
|
|
23
|
-
if (paramsResult._tag === 'Left') {
|
|
24
|
-
return new Response(`Invalid search params: ${paramsResult.left.toString()}`, {
|
|
25
|
-
status: 500,
|
|
26
|
-
headers: corsHeaders,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
const { storeId, payload } = paramsResult.right;
|
|
30
|
-
if (options.validatePayload !== undefined) {
|
|
31
|
-
const result = yield* Effect.promise(async () => options.validatePayload(payload)).pipe(UnexpectedError.mapToUnexpectedError, Effect.either);
|
|
32
|
-
if (result._tag === 'Left') {
|
|
33
|
-
console.error('Invalid payload', result.left);
|
|
34
|
-
return new Response(result.left.toString(), { status: 400, headers: corsHeaders });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const id = env.WEBSOCKET_SERVER.idFromName(storeId);
|
|
38
|
-
const durableObject = env.WEBSOCKET_SERVER.get(id);
|
|
39
27
|
if (url.pathname.endsWith('/websocket')) {
|
|
40
|
-
|
|
41
|
-
if (!upgradeHeader || upgradeHeader !== 'websocket') {
|
|
42
|
-
return new Response('Durable Object expected Upgrade: websocket', { status: 426, headers: corsHeaders });
|
|
43
|
-
}
|
|
44
|
-
return durableObject.fetch(request);
|
|
28
|
+
return handleWebSocket(request, env, _ctx, { headers: corsHeaders, validatePayload: options.validatePayload });
|
|
45
29
|
}
|
|
46
30
|
console.error('Invalid path', url.pathname);
|
|
47
|
-
return new Response(
|
|
31
|
+
return new Response('Invalid path', {
|
|
48
32
|
status: 400,
|
|
49
33
|
statusText: 'Bad Request',
|
|
50
34
|
headers: {
|
|
@@ -52,7 +36,57 @@ export const makeWorker = (options = {}) => {
|
|
|
52
36
|
'Content-Type': 'text/plain',
|
|
53
37
|
},
|
|
54
38
|
});
|
|
55
|
-
}
|
|
39
|
+
},
|
|
56
40
|
};
|
|
57
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Handles `/websocket` endpoint.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const validatePayload = (payload: Schema.JsonValue | undefined) => {
|
|
48
|
+
* if (payload?.authToken !== 'insecure-token-change-me') {
|
|
49
|
+
* throw new Error('Invalid auth token')
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* export default {
|
|
54
|
+
* fetch: async (request, env, ctx) => {
|
|
55
|
+
* if (request.url.endsWith('/websocket')) {
|
|
56
|
+
* return handleWebSocket(request, env, ctx, { headers: {}, validatePayload })
|
|
57
|
+
* }
|
|
58
|
+
*
|
|
59
|
+
* return new Response('Invalid path', { status: 400, headers: corsHeaders })
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @throws {UnexpectedError} If the payload is invalid
|
|
65
|
+
*/
|
|
66
|
+
export const handleWebSocket = (request, env, _ctx, options) => Effect.gen(function* () {
|
|
67
|
+
const url = new URL(request.url);
|
|
68
|
+
const urlParams = UrlParams.fromInput(url.searchParams);
|
|
69
|
+
const paramsResult = yield* UrlParams.schemaStruct(SearchParamsSchema)(urlParams).pipe(Effect.either);
|
|
70
|
+
if (paramsResult._tag === 'Left') {
|
|
71
|
+
return new Response(`Invalid search params: ${paramsResult.left.toString()}`, {
|
|
72
|
+
status: 500,
|
|
73
|
+
headers: options?.headers,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const { storeId, payload } = paramsResult.right;
|
|
77
|
+
if (options.validatePayload !== undefined) {
|
|
78
|
+
const result = yield* Effect.promise(async () => options.validatePayload(payload)).pipe(UnexpectedError.mapToUnexpectedError, Effect.either);
|
|
79
|
+
if (result._tag === 'Left') {
|
|
80
|
+
console.error('Invalid payload', result.left);
|
|
81
|
+
return new Response(result.left.toString(), { status: 400, headers: options.headers });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const id = env.WEBSOCKET_SERVER.idFromName(storeId);
|
|
85
|
+
const durableObject = env.WEBSOCKET_SERVER.get(id);
|
|
86
|
+
const upgradeHeader = request.headers.get('Upgrade');
|
|
87
|
+
if (!upgradeHeader || upgradeHeader !== 'websocket') {
|
|
88
|
+
return new Response('Durable Object expected Upgrade: websocket', { status: 426, headers: options?.headers });
|
|
89
|
+
}
|
|
90
|
+
return yield* Effect.promise(() => durableObject.fetch(request));
|
|
91
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.runPromise);
|
|
58
92
|
//# sourceMappingURL=worker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEnD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAE3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAarD,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,UAA6B,EAAE,EAAY,EAAE;IACtE,OAAO;QACL,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livestore/sync-cf",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.52",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@livestore/
|
|
18
|
-
"@livestore/
|
|
17
|
+
"@livestore/utils": "0.3.0-dev.52",
|
|
18
|
+
"@livestore/common": "0.3.0-dev.52"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@cloudflare/workers-types": "^4.20250303.0"
|
package/src/cf-worker/worker.ts
CHANGED
|
@@ -17,69 +17,113 @@ export type MakeWorkerOptions = {
|
|
|
17
17
|
|
|
18
18
|
export const makeWorker = (options: MakeWorkerOptions = {}): CFWorker => {
|
|
19
19
|
return {
|
|
20
|
-
fetch: async (request, env, _ctx) =>
|
|
21
|
-
|
|
22
|
-
const url = new URL(request.url)
|
|
23
|
-
const urlParams = UrlParams.fromInput(url.searchParams)
|
|
24
|
-
const paramsResult = yield* UrlParams.schemaStruct(SearchParamsSchema)(urlParams).pipe(Effect.either)
|
|
25
|
-
|
|
26
|
-
const corsHeaders: HeadersInit = options.enableCORS
|
|
27
|
-
? {
|
|
28
|
-
'Access-Control-Allow-Origin': '*',
|
|
29
|
-
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
30
|
-
'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers') ?? '*',
|
|
31
|
-
}
|
|
32
|
-
: {}
|
|
33
|
-
|
|
34
|
-
if (request.method === 'OPTIONS' && options.enableCORS) {
|
|
35
|
-
return new Response(null, {
|
|
36
|
-
status: 204,
|
|
37
|
-
headers: corsHeaders,
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (paramsResult._tag === 'Left') {
|
|
42
|
-
return new Response(`Invalid search params: ${paramsResult.left.toString()}`, {
|
|
43
|
-
status: 500,
|
|
44
|
-
headers: corsHeaders,
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const { storeId, payload } = paramsResult.right
|
|
49
|
-
|
|
50
|
-
if (options.validatePayload !== undefined) {
|
|
51
|
-
const result = yield* Effect.promise(async () => options.validatePayload!(payload)).pipe(
|
|
52
|
-
UnexpectedError.mapToUnexpectedError,
|
|
53
|
-
Effect.either,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
if (result._tag === 'Left') {
|
|
57
|
-
console.error('Invalid payload', result.left)
|
|
58
|
-
return new Response(result.left.toString(), { status: 400, headers: corsHeaders })
|
|
59
|
-
}
|
|
60
|
-
}
|
|
20
|
+
fetch: async (request, env, _ctx) => {
|
|
21
|
+
const url = new URL(request.url)
|
|
61
22
|
|
|
62
|
-
|
|
63
|
-
|
|
23
|
+
if (request.method === 'GET' && url.pathname === '/') {
|
|
24
|
+
return new Response('Info: WebSocket sync backend endpoint for @livestore/sync-cf.', {
|
|
25
|
+
status: 200,
|
|
26
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
27
|
+
})
|
|
28
|
+
}
|
|
64
29
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
30
|
+
const corsHeaders: HeadersInit = options.enableCORS
|
|
31
|
+
? {
|
|
32
|
+
'Access-Control-Allow-Origin': '*',
|
|
33
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
34
|
+
'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers') ?? '*',
|
|
69
35
|
}
|
|
36
|
+
: {}
|
|
70
37
|
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
console.error('Invalid path', url.pathname)
|
|
38
|
+
if (request.method === 'OPTIONS' && options.enableCORS) {
|
|
75
39
|
return new Response(null, {
|
|
76
|
-
status:
|
|
77
|
-
|
|
78
|
-
headers: {
|
|
79
|
-
...corsHeaders,
|
|
80
|
-
'Content-Type': 'text/plain',
|
|
81
|
-
},
|
|
40
|
+
status: 204,
|
|
41
|
+
headers: corsHeaders,
|
|
82
42
|
})
|
|
83
|
-
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (url.pathname.endsWith('/websocket')) {
|
|
46
|
+
return handleWebSocket(request, env, _ctx, { headers: corsHeaders, validatePayload: options.validatePayload })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.error('Invalid path', url.pathname)
|
|
50
|
+
|
|
51
|
+
return new Response('Invalid path', {
|
|
52
|
+
status: 400,
|
|
53
|
+
statusText: 'Bad Request',
|
|
54
|
+
headers: {
|
|
55
|
+
...corsHeaders,
|
|
56
|
+
'Content-Type': 'text/plain',
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
},
|
|
84
60
|
}
|
|
85
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Handles `/websocket` endpoint.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const validatePayload = (payload: Schema.JsonValue | undefined) => {
|
|
69
|
+
* if (payload?.authToken !== 'insecure-token-change-me') {
|
|
70
|
+
* throw new Error('Invalid auth token')
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
*
|
|
74
|
+
* export default {
|
|
75
|
+
* fetch: async (request, env, ctx) => {
|
|
76
|
+
* if (request.url.endsWith('/websocket')) {
|
|
77
|
+
* return handleWebSocket(request, env, ctx, { headers: {}, validatePayload })
|
|
78
|
+
* }
|
|
79
|
+
*
|
|
80
|
+
* return new Response('Invalid path', { status: 400, headers: corsHeaders })
|
|
81
|
+
* }
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* @throws {UnexpectedError} If the payload is invalid
|
|
86
|
+
*/
|
|
87
|
+
export const handleWebSocket = (
|
|
88
|
+
request: Request,
|
|
89
|
+
env: Env,
|
|
90
|
+
_ctx: ExecutionContext,
|
|
91
|
+
options: { headers?: HeadersInit; validatePayload?: (payload: Schema.JsonValue | undefined) => void | Promise<void> },
|
|
92
|
+
): Promise<Response> =>
|
|
93
|
+
Effect.gen(function* () {
|
|
94
|
+
const url = new URL(request.url)
|
|
95
|
+
|
|
96
|
+
const urlParams = UrlParams.fromInput(url.searchParams)
|
|
97
|
+
const paramsResult = yield* UrlParams.schemaStruct(SearchParamsSchema)(urlParams).pipe(Effect.either)
|
|
98
|
+
|
|
99
|
+
if (paramsResult._tag === 'Left') {
|
|
100
|
+
return new Response(`Invalid search params: ${paramsResult.left.toString()}`, {
|
|
101
|
+
status: 500,
|
|
102
|
+
headers: options?.headers,
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const { storeId, payload } = paramsResult.right
|
|
107
|
+
|
|
108
|
+
if (options.validatePayload !== undefined) {
|
|
109
|
+
const result = yield* Effect.promise(async () => options.validatePayload!(payload)).pipe(
|
|
110
|
+
UnexpectedError.mapToUnexpectedError,
|
|
111
|
+
Effect.either,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if (result._tag === 'Left') {
|
|
115
|
+
console.error('Invalid payload', result.left)
|
|
116
|
+
return new Response(result.left.toString(), { status: 400, headers: options.headers })
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const id = env.WEBSOCKET_SERVER.idFromName(storeId)
|
|
121
|
+
const durableObject = env.WEBSOCKET_SERVER.get(id)
|
|
122
|
+
|
|
123
|
+
const upgradeHeader = request.headers.get('Upgrade')
|
|
124
|
+
if (!upgradeHeader || upgradeHeader !== 'websocket') {
|
|
125
|
+
return new Response('Durable Object expected Upgrade: websocket', { status: 426, headers: options?.headers })
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return yield* Effect.promise(() => durableObject.fetch(request))
|
|
129
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.runPromise)
|