@livestore/sync-cf 0.4.0-dev.7 → 0.4.0-dev.9

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.
Files changed (62) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/cf-worker/do/durable-object.d.ts.map +1 -1
  3. package/dist/cf-worker/do/durable-object.js +5 -9
  4. package/dist/cf-worker/do/durable-object.js.map +1 -1
  5. package/dist/cf-worker/do/layer.d.ts +1 -1
  6. package/dist/cf-worker/do/pull.d.ts +1 -1
  7. package/dist/cf-worker/do/pull.d.ts.map +1 -1
  8. package/dist/cf-worker/do/pull.js +21 -13
  9. package/dist/cf-worker/do/pull.js.map +1 -1
  10. package/dist/cf-worker/do/push.d.ts.map +1 -1
  11. package/dist/cf-worker/do/push.js +66 -35
  12. package/dist/cf-worker/do/push.js.map +1 -1
  13. package/dist/cf-worker/do/sync-storage.d.ts +8 -5
  14. package/dist/cf-worker/do/sync-storage.d.ts.map +1 -1
  15. package/dist/cf-worker/do/sync-storage.js +64 -19
  16. package/dist/cf-worker/do/sync-storage.js.map +1 -1
  17. package/dist/cf-worker/do/transport/do-rpc-server.d.ts +2 -1
  18. package/dist/cf-worker/do/transport/do-rpc-server.d.ts.map +1 -1
  19. package/dist/cf-worker/do/transport/do-rpc-server.js.map +1 -1
  20. package/dist/cf-worker/do/transport/http-rpc-server.d.ts +1 -1
  21. package/dist/cf-worker/do/ws-chunking.d.ts +22 -0
  22. package/dist/cf-worker/do/ws-chunking.d.ts.map +1 -0
  23. package/dist/cf-worker/do/ws-chunking.js +49 -0
  24. package/dist/cf-worker/do/ws-chunking.js.map +1 -0
  25. package/dist/cf-worker/shared.d.ts +19 -13
  26. package/dist/cf-worker/shared.d.ts.map +1 -1
  27. package/dist/cf-worker/shared.js +15 -4
  28. package/dist/cf-worker/shared.js.map +1 -1
  29. package/dist/cf-worker/worker.d.ts +30 -45
  30. package/dist/cf-worker/worker.d.ts.map +1 -1
  31. package/dist/cf-worker/worker.js +30 -25
  32. package/dist/cf-worker/worker.js.map +1 -1
  33. package/dist/common/sync-message-types.d.ts +5 -5
  34. package/package.json +5 -5
  35. package/src/cf-worker/do/durable-object.ts +6 -10
  36. package/src/cf-worker/do/pull.ts +30 -17
  37. package/src/cf-worker/do/push.ts +85 -38
  38. package/src/cf-worker/do/sync-storage.ts +106 -27
  39. package/src/cf-worker/do/transport/do-rpc-server.ts +4 -2
  40. package/src/cf-worker/do/ws-chunking.ts +76 -0
  41. package/src/cf-worker/shared.ts +19 -6
  42. package/src/cf-worker/worker.ts +46 -69
  43. package/dist/cf-worker/cf-types.d.ts +0 -2
  44. package/dist/cf-worker/cf-types.d.ts.map +0 -1
  45. package/dist/cf-worker/cf-types.js +0 -2
  46. package/dist/cf-worker/cf-types.js.map +0 -1
  47. package/dist/cf-worker/durable-object.d.ts +0 -189
  48. package/dist/cf-worker/durable-object.d.ts.map +0 -1
  49. package/dist/cf-worker/durable-object.js +0 -317
  50. package/dist/cf-worker/durable-object.js.map +0 -1
  51. package/dist/common/ws-message-types.d.ts +0 -270
  52. package/dist/common/ws-message-types.d.ts.map +0 -1
  53. package/dist/common/ws-message-types.js +0 -57
  54. package/dist/common/ws-message-types.js.map +0 -1
  55. package/dist/sync-impl/mod.d.ts +0 -2
  56. package/dist/sync-impl/mod.d.ts.map +0 -1
  57. package/dist/sync-impl/mod.js +0 -2
  58. package/dist/sync-impl/mod.js.map +0 -1
  59. package/dist/sync-impl/ws-impl.d.ts +0 -7
  60. package/dist/sync-impl/ws-impl.d.ts.map +0 -1
  61. package/dist/sync-impl/ws-impl.js +0 -175
  62. package/dist/sync-impl/ws-impl.js.map +0 -1
@@ -1,42 +1,14 @@
1
1
  import { UnexpectedError } from '@livestore/common'
2
+ import type { HelperTypes } from '@livestore/common-cf'
2
3
  import type { Schema } from '@livestore/utils/effect'
3
4
  import { Effect } from '@livestore/utils/effect'
4
5
  import type { CfTypes, SearchParams } from '../common/mod.ts'
5
6
  import type { CfDeclare } from './mod.ts'
6
- import { DEFAULT_SYNC_DURABLE_OBJECT_NAME, type Env, getSyncRequestSearchParams } from './shared.ts'
7
+ import { type Env, matchSyncRequest } from './shared.ts'
7
8
 
8
9
  // NOTE We need to redeclare runtime types here to avoid type conflicts with the lib.dom Response type.
9
10
  declare class Response extends CfDeclare.Response {}
10
11
 
11
- export namespace HelperTypes {
12
- type AnyDON = CfTypes.DurableObjectNamespace<undefined>
13
-
14
- type DOKeys<T> = {
15
- [K in keyof T]-?: T[K] extends AnyDON ? K : never
16
- }[keyof T]
17
-
18
- type NonBuiltins<T> = Omit<T, keyof Env>
19
-
20
- /**
21
- * Helper type to extract DurableObject keys from Env to give consumer type safety.
22
- *
23
- * @example
24
- * ```ts
25
- * type PlatformEnv = {
26
- * DB: D1Database
27
- * ADMIN_TOKEN: string
28
- * SYNC_BACKEND_DO: DurableObjectNamespace<SyncBackendDO>
29
- * }
30
- * export default makeWorker<PlatformEnv>({
31
- * durableObject: { name: "SYNC_BACKEND_DO" },
32
- * // ^ (property) name?: "SYNC_BACKEND_DO" | undefined
33
- * });
34
- */
35
- export type ExtractDurableObjectKeys<TEnv = Env> = DOKeys<NonBuiltins<TEnv>> extends never
36
- ? string
37
- : DOKeys<NonBuiltins<TEnv>>
38
- }
39
-
40
12
  // HINT: If we ever extend user's custom worker RPC, type T can help here with expected return type safety. Currently unused.
41
13
  export type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined> = {
42
14
  fetch: <CFHostMetada = unknown>(
@@ -46,7 +18,15 @@ export type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjec
46
18
  ) => Promise<CfTypes.Response>
47
19
  }
48
20
 
21
+ /**
22
+ * Options accepted by {@link makeWorker}. The Durable Object binding has to be
23
+ * supplied explicitly so we never fall back to deprecated defaults when Cloudflare config changes.
24
+ */
49
25
  export type MakeWorkerOptions<TEnv extends Env = Env> = {
26
+ /**
27
+ * Binding name of the sync Durable Object declared in wrangler config.
28
+ */
29
+ syncBackendBinding: HelperTypes.ExtractDurableObjectKeys<TEnv>
50
30
  /**
51
31
  * Validates the payload during WebSocket connection establishment.
52
32
  * Note: This runs only at connection time, not for individual push events.
@@ -55,21 +35,20 @@ export type MakeWorkerOptions<TEnv extends Env = Env> = {
55
35
  validatePayload?: (payload: Schema.JsonValue | undefined, context: { storeId: string }) => void | Promise<void>
56
36
  /** @default false */
57
37
  enableCORS?: boolean
58
- durableObject?: {
59
- /**
60
- * Needs to match the binding name from the wrangler config
61
- *
62
- * @default 'SYNC_BACKEND_DO'
63
- */
64
- name?: HelperTypes.ExtractDurableObjectKeys<TEnv>
65
- }
66
38
  }
67
39
 
40
+ /**
41
+ * Produces a Cloudflare Worker `fetch` handler that delegates sync traffic to the
42
+ * Durable Object identified by `syncBackendBinding`.
43
+ *
44
+ * For more complex setups prefer implementing a custom `fetch` and call {@link handleSyncRequest}
45
+ * from the branch that handles LiveStore sync requests.
46
+ */
68
47
  export const makeWorker = <
69
48
  TEnv extends Env = Env,
70
49
  TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined,
71
50
  >(
72
- options: MakeWorkerOptions<TEnv> = {},
51
+ options: MakeWorkerOptions<TEnv>,
73
52
  ): CFWorker<TEnv, TDurableObjectRpc> => {
74
53
  return {
75
54
  fetch: async (request, env, _ctx) => {
@@ -90,20 +69,18 @@ export const makeWorker = <
90
69
  })
91
70
  }
92
71
 
93
- const requestParamsResult = getSyncRequestSearchParams(request)
72
+ const searchParams = matchSyncRequest(request)
94
73
 
95
74
  // Check if this is a sync request first, before showing info message
96
- if (requestParamsResult._tag === 'Some') {
75
+ if (searchParams !== undefined) {
97
76
  return handleSyncRequest<TEnv, TDurableObjectRpc>({
98
77
  request,
99
- searchParams: requestParamsResult.value,
78
+ searchParams,
100
79
  env,
101
80
  ctx: _ctx,
102
- options: {
103
- headers: corsHeaders,
104
- validatePayload: options.validatePayload,
105
- durableObject: options.durableObject,
106
- },
81
+ syncBackendBinding: options.syncBackendBinding,
82
+ headers: corsHeaders,
83
+ validatePayload: options.validatePayload,
107
84
  })
108
85
  }
109
86
 
@@ -143,16 +120,18 @@ export const makeWorker = <
143
120
  *
144
121
  * export default {
145
122
  * fetch: async (request, env, ctx) => {
146
- * const requestParamsResult = getSyncRequestSearchParams(request)
123
+ * const searchParams = matchSyncRequest(request)
147
124
  *
148
125
  * // Is LiveStore sync request
149
- * if (requestParamsResult._tag === 'Some') {
126
+ * if (searchParams !== undefined) {
150
127
  * return handleSyncRequest({
151
128
  * request,
152
- * searchParams: requestParamsResult.value,
129
+ * searchParams,
153
130
  * env,
154
131
  * ctx,
155
- * options: { headers: {}, validatePayload }
132
+ * syncBackendBinding: 'SYNC_BACKEND_DO',
133
+ * headers: {},
134
+ * validatePayload,
156
135
  * })
157
136
  * }
158
137
  *
@@ -169,49 +148,47 @@ export const handleSyncRequest = <
169
148
  CFHostMetada = unknown,
170
149
  >({
171
150
  request,
172
- searchParams,
151
+ searchParams: { storeId, payload, transport },
173
152
  env,
174
- options = {},
153
+ syncBackendBinding,
154
+ headers,
155
+ validatePayload,
175
156
  }: {
176
157
  request: CfTypes.Request<CFHostMetada>
177
158
  searchParams: SearchParams
178
159
  env: TEnv
179
160
  /** Only there for type-level reasons */
180
161
  ctx: CfTypes.ExecutionContext
181
- options?: {
182
- headers?: CfTypes.HeadersInit
183
- durableObject?: MakeWorkerOptions<TEnv>['durableObject']
184
- validatePayload?: (payload: Schema.JsonValue | undefined, context: { storeId: string }) => void | Promise<void>
185
- }
162
+ /** Binding name of the sync backend Durable Object */
163
+ syncBackendBinding: MakeWorkerOptions<TEnv>['syncBackendBinding']
164
+ headers?: CfTypes.HeadersInit | undefined
165
+ validatePayload?: (payload: Schema.JsonValue | undefined, context: { storeId: string }) => void | Promise<void>
186
166
  }): Promise<CfTypes.Response> =>
187
167
  Effect.gen(function* () {
188
- const { storeId, payload, transport } = searchParams
189
-
190
- if (options.validatePayload !== undefined) {
191
- const result = yield* Effect.promise(async () => options.validatePayload!(payload, { storeId })).pipe(
168
+ if (validatePayload !== undefined) {
169
+ const result = yield* Effect.promise(async () => validatePayload!(payload, { storeId })).pipe(
192
170
  UnexpectedError.mapToUnexpectedError,
193
171
  Effect.either,
194
172
  )
195
173
 
196
174
  if (result._tag === 'Left') {
197
175
  console.error('Invalid payload', result.left)
198
- return new Response(result.left.toString(), { status: 400, headers: options.headers })
176
+ return new Response(result.left.toString(), { status: 400, headers })
199
177
  }
200
178
  }
201
179
 
202
- const durableObjectName = options.durableObject?.name ?? DEFAULT_SYNC_DURABLE_OBJECT_NAME
203
- if (!(durableObjectName in env)) {
180
+ if (!(syncBackendBinding in env)) {
204
181
  return new Response(
205
- `Failed dependency: Required Durable Object binding '${durableObjectName as string}' not available`,
182
+ `Failed dependency: Required Durable Object binding '${syncBackendBinding as string}' not available`,
206
183
  {
207
184
  status: 424,
208
- headers: options.headers,
185
+ headers,
209
186
  },
210
187
  )
211
188
  }
212
189
 
213
190
  const durableObjectNamespace = env[
214
- durableObjectName as keyof TEnv
191
+ syncBackendBinding as keyof TEnv
215
192
  ] as CfTypes.DurableObjectNamespace<TDurableObjectRpc>
216
193
 
217
194
  const id = durableObjectNamespace.idFromName(storeId)
@@ -222,7 +199,7 @@ export const handleSyncRequest = <
222
199
  if (transport === 'ws' && (upgradeHeader === null || upgradeHeader !== 'websocket')) {
223
200
  return new Response('Durable Object expected Upgrade: websocket', {
224
201
  status: 426,
225
- headers: options?.headers,
202
+ headers,
226
203
  })
227
204
  }
228
205
 
@@ -1,2 +0,0 @@
1
- export { type D1Database, type D1Result, type DurableObject, type DurableObjectState, Request, Response, Rpc, WebSocket, WebSocketPair, WebSocketRequestResponsePair, } from '@cloudflare/workers-types';
2
- //# sourceMappingURL=cf-types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cf-types.d.ts","sourceRoot":"","sources":["../../src/cf-worker/cf-types.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,OAAO,EACP,QAAQ,EACR,GAAG,EACH,SAAS,EACT,aAAa,EACb,4BAA4B,GAC7B,MAAM,2BAA2B,CAAA"}
@@ -1,2 +0,0 @@
1
- export { Request, Response, Rpc, WebSocket, WebSocketPair, WebSocketRequestResponsePair, } from '@cloudflare/workers-types';
2
- //# sourceMappingURL=cf-types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cf-types.js","sourceRoot":"","sources":["../../src/cf-worker/cf-types.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,OAAO,EACP,QAAQ,EACR,GAAG,EACH,SAAS,EACT,aAAa,EACb,4BAA4B,GAC7B,MAAM,2BAA2B,CAAA"}
@@ -1,189 +0,0 @@
1
- import { State } from '@livestore/common/schema';
2
- import { Effect, Option, Schema } from '@livestore/utils/effect';
3
- import { WSMessage } from '../common/mod.ts';
4
- import type * as CfWorker from './cf-types.ts';
5
- export interface Env {
6
- DB: CfWorker.D1Database;
7
- ADMIN_SECRET: string;
8
- }
9
- export declare const eventlogTable: State.SQLite.TableDef<State.SQLite.SqliteTableDefForInput<"eventlog_$PERSISTENCE_FORMAT_VERSION_$storeId", {
10
- readonly seqNum: {
11
- columnType: "integer";
12
- schema: Schema.Schema<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never>;
13
- default: Option.None<never>;
14
- nullable: false;
15
- primaryKey: true;
16
- autoIncrement: false;
17
- };
18
- readonly parentSeqNum: {
19
- columnType: "integer";
20
- schema: Schema.Schema<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never>;
21
- default: Option.None<never>;
22
- nullable: false;
23
- primaryKey: false;
24
- autoIncrement: false;
25
- };
26
- readonly name: {
27
- columnType: "text";
28
- schema: Schema.Schema<string, string, never>;
29
- default: Option.None<never>;
30
- nullable: false;
31
- primaryKey: false;
32
- autoIncrement: false;
33
- };
34
- readonly args: {
35
- columnType: "text";
36
- schema: Schema.Schema<any, string | null, never>;
37
- default: Option.None<never>;
38
- nullable: true;
39
- primaryKey: false;
40
- autoIncrement: false;
41
- };
42
- /** ISO date format. Currently only used for debugging purposes. */
43
- readonly createdAt: {
44
- columnType: "text";
45
- schema: Schema.Schema<string, string, never>;
46
- default: Option.None<never>;
47
- nullable: false;
48
- primaryKey: false;
49
- autoIncrement: false;
50
- };
51
- readonly clientId: {
52
- columnType: "text";
53
- schema: Schema.Schema<string, string, never>;
54
- default: Option.None<never>;
55
- nullable: false;
56
- primaryKey: false;
57
- autoIncrement: false;
58
- };
59
- readonly sessionId: {
60
- columnType: "text";
61
- schema: Schema.Schema<string, string, never>;
62
- default: Option.None<never>;
63
- nullable: false;
64
- primaryKey: false;
65
- autoIncrement: false;
66
- };
67
- }>, State.SQLite.WithDefaults<{
68
- readonly seqNum: {
69
- columnType: "integer";
70
- schema: Schema.Schema<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never>;
71
- default: Option.None<never>;
72
- nullable: false;
73
- primaryKey: true;
74
- autoIncrement: false;
75
- };
76
- readonly parentSeqNum: {
77
- columnType: "integer";
78
- schema: Schema.Schema<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never>;
79
- default: Option.None<never>;
80
- nullable: false;
81
- primaryKey: false;
82
- autoIncrement: false;
83
- };
84
- readonly name: {
85
- columnType: "text";
86
- schema: Schema.Schema<string, string, never>;
87
- default: Option.None<never>;
88
- nullable: false;
89
- primaryKey: false;
90
- autoIncrement: false;
91
- };
92
- readonly args: {
93
- columnType: "text";
94
- schema: Schema.Schema<any, string | null, never>;
95
- default: Option.None<never>;
96
- nullable: true;
97
- primaryKey: false;
98
- autoIncrement: false;
99
- };
100
- /** ISO date format. Currently only used for debugging purposes. */
101
- readonly createdAt: {
102
- columnType: "text";
103
- schema: Schema.Schema<string, string, never>;
104
- default: Option.None<never>;
105
- nullable: false;
106
- primaryKey: false;
107
- autoIncrement: false;
108
- };
109
- readonly clientId: {
110
- columnType: "text";
111
- schema: Schema.Schema<string, string, never>;
112
- default: Option.None<never>;
113
- nullable: false;
114
- primaryKey: false;
115
- autoIncrement: false;
116
- };
117
- readonly sessionId: {
118
- columnType: "text";
119
- schema: Schema.Schema<string, string, never>;
120
- default: Option.None<never>;
121
- nullable: false;
122
- primaryKey: false;
123
- autoIncrement: false;
124
- };
125
- }>, Schema.Schema<{
126
- readonly seqNum: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
127
- readonly parentSeqNum: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
128
- readonly name: string;
129
- readonly args: any;
130
- readonly createdAt: string;
131
- readonly clientId: string;
132
- readonly sessionId: string;
133
- }, {
134
- readonly seqNum: number;
135
- readonly parentSeqNum: number;
136
- readonly name: string;
137
- readonly args: string | null;
138
- readonly createdAt: string;
139
- readonly clientId: string;
140
- readonly sessionId: string;
141
- }, never>>;
142
- export declare const PULL_CHUNK_SIZE = 100;
143
- /**
144
- * Needs to be bumped when the storage format changes (e.g. eventlogTable schema changes)
145
- *
146
- * Changing this version number will lead to a "soft reset".
147
- */
148
- export declare const PERSISTENCE_FORMAT_VERSION = 7;
149
- export type MakeDurableObjectClassOptions = {
150
- onPush?: (message: WSMessage.PushReq, context: {
151
- storeId: string;
152
- payload?: Schema.JsonValue;
153
- }) => Effect.Effect<void> | Promise<void>;
154
- onPushRes?: (message: WSMessage.PushAck | WSMessage.Error) => Effect.Effect<void> | Promise<void>;
155
- onPull?: (message: WSMessage.PullReq, context: {
156
- storeId: string;
157
- payload?: Schema.JsonValue;
158
- }) => Effect.Effect<void> | Promise<void>;
159
- onPullRes?: (message: WSMessage.PullRes | WSMessage.Error) => Effect.Effect<void> | Promise<void>;
160
- };
161
- export type MakeDurableObjectClass = (options?: MakeDurableObjectClassOptions) => {
162
- new (ctx: CfWorker.DurableObjectState, env: Env): CfWorker.DurableObject;
163
- };
164
- /**
165
- * Creates a Durable Object class for handling WebSocket-based sync.
166
- *
167
- * Example:
168
- * ```ts
169
- * // In your Cloudflare Worker file
170
- * import { makeDurableObject } from '@livestore/sync-cf/cf-worker'
171
- *
172
- * export class WebSocketServer extends makeDurableObject({
173
- * onPush: async (message) => {
174
- * console.log('onPush', message.batch)
175
- * },
176
- * onPull: async (message) => {
177
- * console.log('onPull', message)
178
- * },
179
- * }) {}
180
- * ```
181
- *
182
- * ```toml
183
- * # wrangler.toml
184
- * [new_classes]
185
- * WebSocketServer = "src/websocket-server.ts"
186
- * ```
187
- */
188
- export declare const makeDurableObject: MakeDurableObjectClass;
189
- //# sourceMappingURL=durable-object.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"durable-object.d.ts","sourceRoot":"","sources":["../../src/cf-worker/durable-object.ts"],"names":[],"mappings":"AACA,OAAO,EAA4C,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAE1F,OAAO,EAAE,MAAM,EAAoB,MAAM,EAAE,MAAM,EAAa,MAAM,yBAAyB,CAAA;AAC7F,OAAO,EAAsB,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEhE,OAAO,KAAK,KAAK,QAAQ,MAAM,eAAe,CAAA;AAO9C,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,QAAQ,CAAC,UAAU,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;CACrB;AAKD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAQtB,mEAAmE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAAnE,mEAAmE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAKrE,CAAA;AASF,eAAO,MAAM,eAAe,MAAM,CAAA;AAElC;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAE3C,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,CAAC,EAAE,CACP,OAAO,EAAE,SAAS,CAAC,OAAO,EAC1B,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,SAAS,CAAA;KAAE,KACrD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjG,MAAM,CAAC,EAAE,CACP,OAAO,EAAE,SAAS,CAAC,OAAO,EAC1B,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,SAAS,CAAA;KAAE,KACrD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClG,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,CAAC,EAAE,6BAA6B,KAAK;IAChF,KAAK,GAAG,EAAE,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAA;CACzE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,iBAAiB,EAAE,sBA4Q/B,CAAA"}