@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.
@@ -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,QAmE5D,CAAA"}
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"}
@@ -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) => Effect.gen(function* () {
6
+ fetch: async (request, env, _ctx) => {
7
7
  const url = new URL(request.url);
8
- const urlParams = UrlParams.fromInput(url.searchParams);
9
- const paramsResult = yield* UrlParams.schemaStruct(SearchParamsSchema)(urlParams).pipe(Effect.either);
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
- const upgradeHeader = request.headers.get('Upgrade');
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(null, {
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
- }).pipe(Effect.tapCauseLogPretty, Effect.runPromise),
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":"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"}
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.50",
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/common": "0.3.0-dev.50",
18
- "@livestore/utils": "0.3.0-dev.50"
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"
@@ -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
- Effect.gen(function* () {
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
- const id = env.WEBSOCKET_SERVER.idFromName(storeId)
63
- const durableObject = env.WEBSOCKET_SERVER.get(id)
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
- if (url.pathname.endsWith('/websocket')) {
66
- const upgradeHeader = request.headers.get('Upgrade')
67
- if (!upgradeHeader || upgradeHeader !== 'websocket') {
68
- return new Response('Durable Object expected Upgrade: websocket', { status: 426, headers: corsHeaders })
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
- return durableObject.fetch(request)
72
- }
73
-
74
- console.error('Invalid path', url.pathname)
38
+ if (request.method === 'OPTIONS' && options.enableCORS) {
75
39
  return new Response(null, {
76
- status: 400,
77
- statusText: 'Bad Request',
78
- headers: {
79
- ...corsHeaders,
80
- 'Content-Type': 'text/plain',
81
- },
40
+ status: 204,
41
+ headers: corsHeaders,
82
42
  })
83
- }).pipe(Effect.tapCauseLogPretty, Effect.runPromise),
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)