@dbos-inc/drizzle-datasource 3.0.7-preview → 3.0.8-preview

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.
@@ -0,0 +1,457 @@
1
+ import { DBOS } from '@dbos-inc/dbos-sdk';
2
+ import { Client, Pool, PoolConfig } from 'pg';
3
+ import { DrizzleDataSource } from '..';
4
+ import { dropDB, ensureDB } from './test-helpers';
5
+ import { randomUUID } from 'crypto';
6
+ import SuperJSON from 'superjson';
7
+ import { pgTable, text, integer } from 'drizzle-orm/pg-core';
8
+ import { drizzle } from 'drizzle-orm/node-postgres';
9
+ import { pushSchema } from 'drizzle-kit/api';
10
+ import { eq, sql } from 'drizzle-orm';
11
+
12
+ const config = { user: 'postgres', database: 'drizzle_ds_test_userdb' };
13
+ const dataSource = new DrizzleDataSource('app-db', config);
14
+
15
+ interface transaction_completion {
16
+ workflow_id: string;
17
+ function_num: number;
18
+ output: string | null;
19
+ error: string | null;
20
+ }
21
+
22
+ async function createSchema(config: PoolConfig, entities: { [key: string]: object }) {
23
+ const drizzlePool = new Pool(config);
24
+ const db = drizzle(drizzlePool);
25
+ try {
26
+ const res = await pushSchema(entities, db);
27
+ await res.apply();
28
+ } finally {
29
+ await drizzlePool.end();
30
+ }
31
+ }
32
+
33
+ describe('DrizzleDataSource', () => {
34
+ const userDB = new Pool(config);
35
+
36
+ beforeAll(async () => {
37
+ {
38
+ const client = new Client({ ...config, database: 'postgres' });
39
+ try {
40
+ await client.connect();
41
+ await dropDB(client, 'drizzle_ds_test', true);
42
+ await dropDB(client, 'drizzle_ds_test_dbos_sys', true);
43
+ await dropDB(client, config.database, true);
44
+ await ensureDB(client, config.database);
45
+ } finally {
46
+ await client.end();
47
+ }
48
+ }
49
+
50
+ await DrizzleDataSource.initializeInternalSchema(config);
51
+ await createSchema(config, { greetingsTable });
52
+ });
53
+
54
+ afterAll(async () => {
55
+ await userDB.end();
56
+ });
57
+
58
+ beforeEach(async () => {
59
+ DBOS.setConfig({ name: 'drizzle-ds-test' });
60
+ await DBOS.launch();
61
+ });
62
+
63
+ afterEach(async () => {
64
+ await DBOS.shutdown();
65
+ });
66
+
67
+ test('insert dataSource.register function', async () => {
68
+ const user = 'helloTest1';
69
+
70
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
71
+ const workflowID = randomUUID();
72
+
73
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user))).resolves.toMatchObject({
74
+ user,
75
+ greet_count: 1,
76
+ });
77
+
78
+ const { rows } = await userDB.query<transaction_completion>(
79
+ 'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
80
+ [workflowID],
81
+ );
82
+ expect(rows.length).toBe(1);
83
+ expect(rows[0].workflow_id).toBe(workflowID);
84
+ expect(rows[0].function_num).toBe(0);
85
+ expect(rows[0].output).not.toBeNull();
86
+ expect(SuperJSON.parse(rows[0].output!)).toMatchObject({ user, greet_count: 1 });
87
+ });
88
+
89
+ test('rerun insert dataSource.register function', async () => {
90
+ const user = 'rerunTest1';
91
+
92
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
93
+ const workflowID = randomUUID();
94
+
95
+ const result = await DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user));
96
+ expect(result).toMatchObject({ user, greet_count: 1 });
97
+
98
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowReg(user))).resolves.toMatchObject(result);
99
+ });
100
+
101
+ test('insert dataSource.runAsTx function', async () => {
102
+ const user = 'helloTest2';
103
+
104
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
105
+ const workflowID = randomUUID();
106
+
107
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user))).resolves.toMatchObject({
108
+ user,
109
+ greet_count: 1,
110
+ });
111
+
112
+ const { rows } = await userDB.query<transaction_completion>(
113
+ 'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
114
+ [workflowID],
115
+ );
116
+ expect(rows.length).toBe(1);
117
+ expect(rows[0].workflow_id).toBe(workflowID);
118
+ expect(rows[0].function_num).toBe(0);
119
+ expect(rows[0].output).not.toBeNull();
120
+ expect(SuperJSON.parse(rows[0].output!)).toMatchObject({ user, greet_count: 1 });
121
+ });
122
+
123
+ test('rerun insert dataSource.runAsTx function', async () => {
124
+ const user = 'rerunTest2';
125
+
126
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
127
+ const workflowID = randomUUID();
128
+
129
+ const result = await DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user));
130
+ expect(result).toMatchObject({ user, greet_count: 1 });
131
+
132
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInsertWorkflowRunTx(user))).resolves.toMatchObject(
133
+ result,
134
+ );
135
+ });
136
+
137
+ async function throws<R>(func: () => Promise<R>): Promise<unknown> {
138
+ try {
139
+ await func();
140
+ fail('Expected function to throw an error');
141
+ } catch (error) {
142
+ return error;
143
+ }
144
+ }
145
+
146
+ test('error dataSource.register function', async () => {
147
+ const user = 'errorTest1';
148
+
149
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
150
+ await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
151
+ const workflowID = randomUUID();
152
+
153
+ const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowReg(user)));
154
+ expect(error).toBeInstanceOf(Error);
155
+ expect((error as Error).message).toMatch(/^test error \d+$/);
156
+
157
+ interface greetings {
158
+ name: string;
159
+ greet_count: number;
160
+ }
161
+ const { rows } = await userDB.query<greetings>('SELECT * FROM greetings WHERE name = $1', [user]);
162
+ expect(rows.length).toBe(1);
163
+ expect(rows[0].greet_count).toBe(10);
164
+
165
+ const { rows: txOutput } = await userDB.query<transaction_completion>(
166
+ 'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
167
+ [workflowID],
168
+ );
169
+ expect(txOutput.length).toBe(1);
170
+ expect(txOutput[0].workflow_id).toBe(workflowID);
171
+ expect(txOutput[0].function_num).toBe(0);
172
+ expect(txOutput[0].output).toBeNull();
173
+ expect(txOutput[0].error).not.toBeNull();
174
+ const $error = SuperJSON.parse(txOutput[0].error!);
175
+ expect($error).toBeInstanceOf(Error);
176
+ expect(($error as Error).message).toMatch(/^test error \d+$/);
177
+ });
178
+
179
+ test('rerun error dataSource.register function', async () => {
180
+ const user = 'rerunErrorTest1';
181
+
182
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
183
+ await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
184
+ const workflowID = randomUUID();
185
+
186
+ const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowReg(user)));
187
+ expect(error).toBeInstanceOf(Error);
188
+ expect((error as Error).message).toMatch(/^test error \d+$/);
189
+
190
+ const error2 = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowReg(user)));
191
+ expect(error2).toBeInstanceOf(Error);
192
+ expect((error2 as Error).message).toMatch((error as Error).message);
193
+ });
194
+
195
+ test('error dataSource.runAsTx function', async () => {
196
+ const user = 'errorTest2';
197
+
198
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
199
+ await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
200
+ const workflowID = randomUUID();
201
+
202
+ const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowRunTx(user)));
203
+ expect(error).toBeInstanceOf(Error);
204
+ expect((error as Error).message).toMatch(/^test error \d+$/);
205
+
206
+ interface greetings {
207
+ name: string;
208
+ greet_count: number;
209
+ }
210
+ const { rows } = await userDB.query<greetings>('SELECT * FROM greetings WHERE name = $1', [user]);
211
+ expect(rows.length).toBe(1);
212
+ expect(rows[0].greet_count).toBe(10);
213
+
214
+ const { rows: txOutput } = await userDB.query<transaction_completion>(
215
+ 'SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1',
216
+ [workflowID],
217
+ );
218
+ expect(txOutput.length).toBe(1);
219
+ expect(txOutput[0].workflow_id).toBe(workflowID);
220
+ expect(txOutput[0].function_num).toBe(0);
221
+ expect(txOutput[0].output).toBeNull();
222
+ expect(txOutput[0].error).not.toBeNull();
223
+ const $error = SuperJSON.parse(txOutput[0].error!);
224
+ expect($error).toBeInstanceOf(Error);
225
+ expect(($error as Error).message).toMatch(/^test error \d+$/);
226
+ });
227
+
228
+ test('rerun error dataSource.runAsTx function', async () => {
229
+ const user = 'rerunErrorTest2';
230
+
231
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
232
+ await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
233
+ const workflowID = randomUUID();
234
+
235
+ const error = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowRunTx(user)));
236
+ expect(error).toBeInstanceOf(Error);
237
+ expect((error as Error).message).toMatch(/^test error \d+$/);
238
+
239
+ const error2 = await throws(() => DBOS.withNextWorkflowID(workflowID, () => regErrorWorkflowRunTx(user)));
240
+ expect(error2).toBeInstanceOf(Error);
241
+ expect((error2 as Error).message).toMatch((error as Error).message);
242
+ });
243
+
244
+ test('readonly dataSource.register function', async () => {
245
+ const user = 'readTest1';
246
+
247
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
248
+ await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
249
+
250
+ const workflowID = randomUUID();
251
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowReg(user))).resolves.toMatchObject({
252
+ user,
253
+ greet_count: 10,
254
+ });
255
+
256
+ const { rows } = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1', [
257
+ workflowID,
258
+ ]);
259
+ expect(rows.length).toBe(0);
260
+ });
261
+
262
+ test('readonly dataSource.runAsTx function', async () => {
263
+ const user = 'readTest2';
264
+
265
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
266
+ await userDB.query('INSERT INTO greetings("name","greet_count") VALUES($1,10);', [user]);
267
+
268
+ const workflowID = randomUUID();
269
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regReadWorkflowRunTx(user))).resolves.toMatchObject({
270
+ user,
271
+ greet_count: 10,
272
+ });
273
+
274
+ const { rows } = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE workflow_id = $1', [
275
+ workflowID,
276
+ ]);
277
+ expect(rows.length).toBe(0);
278
+ });
279
+
280
+ test('static dataSource.register methods', async () => {
281
+ const user = 'staticTest1';
282
+
283
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
284
+
285
+ const workflowID = randomUUID();
286
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regStaticWorkflow(user))).resolves.toMatchObject([
287
+ { user, greet_count: 1 },
288
+ { user, greet_count: 1 },
289
+ ]);
290
+ });
291
+
292
+ test('instance dataSource.register methods', async () => {
293
+ const user = 'instanceTest1';
294
+
295
+ await userDB.query('DELETE FROM greetings WHERE name = $1', [user]);
296
+
297
+ const workflowID = randomUUID();
298
+ await expect(DBOS.withNextWorkflowID(workflowID, () => regInstanceWorkflow(user))).resolves.toMatchObject([
299
+ { user, greet_count: 1 },
300
+ { user, greet_count: 1 },
301
+ ]);
302
+ });
303
+
304
+ test('invoke-reg-tx-fun-outside-wf', async () => {
305
+ const user = 'outsideWfUser' + Date.now();
306
+ const result = await regInsertFunction(user);
307
+ expect(result).toMatchObject({ user, greet_count: 1 });
308
+
309
+ const txResults = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE output LIKE $1', [
310
+ `%${user}%`,
311
+ ]);
312
+ expect(txResults.rows.length).toBe(0);
313
+ });
314
+
315
+ test('invoke-reg-tx-static-method-outside-wf', async () => {
316
+ const user = 'outsideWfUser' + Date.now();
317
+ const result = await StaticClass.insertFunction(user);
318
+ expect(result).toMatchObject({ user, greet_count: 1 });
319
+
320
+ const txResults = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE output LIKE $1', [
321
+ `%${user}%`,
322
+ ]);
323
+ expect(txResults.rows.length).toBe(0);
324
+ });
325
+
326
+ test('invoke-reg-tx-inst-method-outside-wf', async () => {
327
+ const user = 'outsideWfUser' + Date.now();
328
+ const instance = new InstanceClass();
329
+ const result = await instance.insertFunction(user);
330
+ expect(result).toMatchObject({ user, greet_count: 1 });
331
+
332
+ const txResults = await userDB.query('SELECT * FROM dbos.transaction_completion WHERE output LIKE $1', [
333
+ `%${user}%`,
334
+ ]);
335
+ expect(txResults.rows.length).toBe(0);
336
+ });
337
+ });
338
+
339
+ const greetingsTable = pgTable('greetings', {
340
+ name: text('name').primaryKey().notNull(),
341
+ greet_count: integer('greet_count').default(0),
342
+ });
343
+
344
+ async function insertFunction(user: string) {
345
+ const result = await DrizzleDataSource.client
346
+ .insert(greetingsTable)
347
+ .values({ name: user, greet_count: 1 })
348
+ .onConflictDoUpdate({
349
+ target: greetingsTable.name,
350
+ set: {
351
+ greet_count: sql`${greetingsTable.greet_count} + 1`,
352
+ },
353
+ })
354
+ .returning({ greet_count: greetingsTable.greet_count });
355
+
356
+ const row = result.length > 0 ? result[0] : undefined;
357
+ return { user, greet_count: row?.greet_count, now: Date.now() };
358
+ }
359
+
360
+ async function errorFunction(user: string) {
361
+ const _result = await insertFunction(user);
362
+ throw new Error(`test error ${Date.now()}`);
363
+ }
364
+
365
+ async function readFunction(user: string) {
366
+ const result = await DrizzleDataSource.client
367
+ .select({ greet_count: greetingsTable.greet_count })
368
+ .from(greetingsTable)
369
+ .where(eq(greetingsTable.name, user));
370
+ const row = result.length > 0 ? result[0] : undefined;
371
+ return { user, greet_count: row?.greet_count, now: Date.now() };
372
+ }
373
+
374
+ const regInsertFunction = dataSource.registerTransaction(insertFunction);
375
+ const regErrorFunction = dataSource.registerTransaction(errorFunction);
376
+ const regReadFunction = dataSource.registerTransaction(readFunction, { accessMode: 'read only' });
377
+
378
+ class StaticClass {
379
+ static async insertFunction(user: string) {
380
+ return await insertFunction(user);
381
+ }
382
+
383
+ static async readFunction(user: string) {
384
+ return await readFunction(user);
385
+ }
386
+ }
387
+
388
+ StaticClass.insertFunction = dataSource.registerTransaction(StaticClass.insertFunction);
389
+ StaticClass.readFunction = dataSource.registerTransaction(StaticClass.readFunction, {
390
+ accessMode: 'read only',
391
+ });
392
+
393
+ class InstanceClass {
394
+ async insertFunction(user: string) {
395
+ return await insertFunction(user);
396
+ }
397
+
398
+ async readFunction(user: string) {
399
+ return await readFunction(user);
400
+ }
401
+ }
402
+
403
+ InstanceClass.prototype.insertFunction = dataSource.registerTransaction(
404
+ // eslint-disable-next-line @typescript-eslint/unbound-method
405
+ InstanceClass.prototype.insertFunction,
406
+ );
407
+ InstanceClass.prototype.readFunction = dataSource.registerTransaction(
408
+ // eslint-disable-next-line @typescript-eslint/unbound-method
409
+ InstanceClass.prototype.readFunction,
410
+ { accessMode: 'read only' },
411
+ );
412
+
413
+ async function insertWorkflowReg(user: string) {
414
+ return await regInsertFunction(user);
415
+ }
416
+
417
+ async function insertWorkflowRunTx(user: string) {
418
+ return await dataSource.runTransaction(() => insertFunction(user), 'insertFunction');
419
+ }
420
+
421
+ async function errorWorkflowReg(user: string) {
422
+ return await regErrorFunction(user);
423
+ }
424
+
425
+ async function errorWorkflowRunTx(user: string) {
426
+ return await dataSource.runTransaction(() => errorFunction(user), 'errorFunction');
427
+ }
428
+
429
+ async function readWorkflowReg(user: string) {
430
+ return await regReadFunction(user);
431
+ }
432
+
433
+ async function readWorkflowRunTx(user: string) {
434
+ return await dataSource.runTransaction(() => readFunction(user), 'readFunction', { accessMode: 'read only' });
435
+ }
436
+
437
+ async function staticWorkflow(user: string) {
438
+ const result = await StaticClass.insertFunction(user);
439
+ const readResult = await StaticClass.readFunction(user);
440
+ return [result, readResult];
441
+ }
442
+
443
+ async function instanceWorkflow(user: string) {
444
+ const instance = new InstanceClass();
445
+ const result = await instance.insertFunction(user);
446
+ const readResult = await instance.readFunction(user);
447
+ return [result, readResult];
448
+ }
449
+
450
+ const regInsertWorkflowReg = DBOS.registerWorkflow(insertWorkflowReg, 'insertWorkflowReg');
451
+ const regInsertWorkflowRunTx = DBOS.registerWorkflow(insertWorkflowRunTx, 'insertWorkflowRunTx');
452
+ const regErrorWorkflowReg = DBOS.registerWorkflow(errorWorkflowReg, 'errorWorkflowReg');
453
+ const regErrorWorkflowRunTx = DBOS.registerWorkflow(errorWorkflowRunTx, 'errorWorkflowRunTx');
454
+ const regReadWorkflowReg = DBOS.registerWorkflow(readWorkflowReg, 'readWorkflowReg');
455
+ const regReadWorkflowRunTx = DBOS.registerWorkflow(readWorkflowRunTx, 'readWorkflowRunTx');
456
+ const regStaticWorkflow = DBOS.registerWorkflow(staticWorkflow, 'staticWorkflow');
457
+ const regInstanceWorkflow = DBOS.registerWorkflow(instanceWorkflow, 'instanceWorkflow');
@@ -0,0 +1,13 @@
1
+ import { Client } from 'pg';
2
+
3
+ export async function ensureDB(client: Client, name: string) {
4
+ const results = await client.query('SELECT 1 FROM pg_database WHERE datname = $1', [name]);
5
+ if (results.rows.length === 0) {
6
+ await client.query(`CREATE DATABASE ${name}`);
7
+ }
8
+ }
9
+
10
+ export async function dropDB(client: Client, name: string, force: boolean = false) {
11
+ const withForce = force ? ' WITH (FORCE)' : '';
12
+ await client.query(`DROP DATABASE IF EXISTS ${name} ${withForce}`);
13
+ }
package/tsconfig.json CHANGED
@@ -1,8 +1,9 @@
1
+ /* Visit https://aka.ms/tsconfig to read more about this file */
1
2
  {
2
3
  "extends": "../../tsconfig.shared.json",
3
4
  "compilerOptions": {
4
5
  "outDir": "./dist"
5
6
  },
6
- "exclude": ["dist", "*.test.ts", "testutils.ts"],
7
- "include": ["src/"]
7
+ "include": ["index.ts"],
8
+ "exclude": ["dist", "tests"]
8
9
  }
@@ -1,36 +0,0 @@
1
- import { PoolConfig } from 'pg';
2
- import { PGIsolationLevel as IsolationLevel, PGTransactionConfig as DrizzleTransactionConfig, DBOSDataSource } from '@dbos-inc/dbos-sdk/datasource';
3
- import { NodePgDatabase } from 'drizzle-orm/node-postgres';
4
- export { IsolationLevel, DrizzleTransactionConfig };
5
- export interface transaction_completion {
6
- workflow_id: string;
7
- function_num: number;
8
- output: string | null;
9
- error: string | null;
10
- }
11
- export declare class DrizzleDataSource implements DBOSDataSource<DrizzleTransactionConfig> {
12
- #private;
13
- readonly name: string;
14
- readonly config: PoolConfig;
15
- readonly entities: {
16
- [key: string]: object;
17
- };
18
- constructor(name: string, config: PoolConfig, entities?: {
19
- [key: string]: object;
20
- });
21
- static get drizzleClient(): NodePgDatabase<{
22
- [key: string]: object;
23
- }>;
24
- get dataSource(): NodePgDatabase<{
25
- [key: string]: object;
26
- }> | undefined;
27
- initializeInternalSchema(): Promise<void>;
28
- runTransaction<T>(callback: () => Promise<T>, funcName: string, config?: DrizzleTransactionConfig): Promise<T>;
29
- registerTransaction<This, Args extends unknown[], Return>(func: (this: This, ...args: Args) => Promise<Return>, name: string, config?: DrizzleTransactionConfig): (this: This, ...args: Args) => Promise<Return>;
30
- transaction(config?: DrizzleTransactionConfig): <This, Args extends unknown[], Return>(_target: object, propertyKey: string, descriptor: TypedPropertyDescriptor<(this: This, ...args: Args) => Promise<Return>>) => TypedPropertyDescriptor<(this: This, ...args: Args) => Promise<Return>>;
31
- /**
32
- * Create user schema in database (for testing)
33
- */
34
- createSchema(): Promise<void>;
35
- }
36
- //# sourceMappingURL=drizzle_datasource.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"drizzle_datasource.d.ts","sourceRoot":"","sources":["../../src/drizzle_datasource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,MAAM,IAAI,CAAC;AAEtC,OAAO,EASL,gBAAgB,IAAI,cAAc,EAClC,mBAAmB,IAAI,wBAAwB,EAC/C,cAAc,EAEf,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAW,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAKpE,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,CAAC;AAiBpD,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAyMD,qBAAa,iBAAkB,YAAW,cAAc,CAAC,wBAAwB,CAAC;;IAI9E,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,UAAU;IAC3B,QAAQ,CAAC,QAAQ,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE;gBAFnC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,UAAU,EAClB,QAAQ,GAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAO;IAOnD,MAAM,KAAK,aAAa,IAAI,cAAc,CAAC;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC,CAOpE;IAED,IAAI,UAAU;;mBAEb;IAEK,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBzC,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,wBAAwB;IAIvG,mBAAmB,CAAC,IAAI,EAAE,IAAI,SAAS,OAAO,EAAE,EAAE,MAAM,EACtD,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,EACpD,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,wBAAwB,GAChC,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC;IAKjD,WAAW,CAAC,MAAM,CAAC,EAAE,wBAAwB,mDAIhC,MAAM,eACF,MAAM,cACP,wBAAwB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,QAAQ,MAAM,CAAC,CAAC,oCAAxC,IAAI,WAAW,IAAI,KAAK,QAAQ,MAAM,CAAC;IAYtF;;OAEG;IACG,YAAY;CAUnB"}