@weifuwujs/tasks 0.9.0

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 (50) hide show
  1. package/README.md +170 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +319 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/index.d.ts +4 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +3 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/postgres.d.ts +3 -0
  11. package/dist/postgres.d.ts.map +1 -0
  12. package/dist/postgres.js +2 -0
  13. package/dist/postgres.js.map +1 -0
  14. package/dist/redis.d.ts +3 -0
  15. package/dist/redis.d.ts.map +1 -0
  16. package/dist/redis.js +2 -0
  17. package/dist/redis.js.map +1 -0
  18. package/dist/sqlite.d.ts +3 -0
  19. package/dist/sqlite.d.ts.map +1 -0
  20. package/dist/sqlite.js +2 -0
  21. package/dist/sqlite.js.map +1 -0
  22. package/dist/stores/memory.d.ts +6 -0
  23. package/dist/stores/memory.d.ts.map +1 -0
  24. package/dist/stores/memory.js +169 -0
  25. package/dist/stores/memory.js.map +1 -0
  26. package/dist/stores/postgres.d.ts +22 -0
  27. package/dist/stores/postgres.d.ts.map +1 -0
  28. package/dist/stores/postgres.js +233 -0
  29. package/dist/stores/postgres.js.map +1 -0
  30. package/dist/stores/redis.d.ts +21 -0
  31. package/dist/stores/redis.d.ts.map +1 -0
  32. package/dist/stores/redis.js +340 -0
  33. package/dist/stores/redis.js.map +1 -0
  34. package/dist/stores/sqlite.d.ts +13 -0
  35. package/dist/stores/sqlite.d.ts.map +1 -0
  36. package/dist/stores/sqlite.js +216 -0
  37. package/dist/stores/sqlite.js.map +1 -0
  38. package/dist/tasks.d.ts +11 -0
  39. package/dist/tasks.d.ts.map +1 -0
  40. package/dist/tasks.js +214 -0
  41. package/dist/tasks.js.map +1 -0
  42. package/dist/testing.d.ts +13 -0
  43. package/dist/testing.d.ts.map +1 -0
  44. package/dist/testing.js +211 -0
  45. package/dist/testing.js.map +1 -0
  46. package/dist/types.d.ts +158 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +3 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +61 -0
@@ -0,0 +1,169 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ const DEFAULT_MAX_ATTEMPTS = 5;
3
+ function clone(task) {
4
+ return { ...task, payload: task.payload };
5
+ }
6
+ /**
7
+ * 内存驱动:dev/test 用(单进程;不作生产承诺)。
8
+ */
9
+ export function memoryTaskStore() {
10
+ const rows = new Map();
11
+ const byKey = new Map();
12
+ const find = (id) => rows.get(id);
13
+ const store = {
14
+ async enqueue(input) {
15
+ const key = input.idempotencyKey ?? null;
16
+ if (key) {
17
+ const existingId = byKey.get(key);
18
+ const existing = existingId ? find(existingId) : undefined;
19
+ if (existing)
20
+ return { task: clone(existing), created: false };
21
+ }
22
+ const timestamp = new Date();
23
+ const task = {
24
+ id: randomUUID(),
25
+ name: input.name,
26
+ payload: input.payload ?? null,
27
+ runAt: input.runAt ?? timestamp,
28
+ attempts: 0,
29
+ maxAttempts: input.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
30
+ status: 'pending',
31
+ lockedBy: null,
32
+ lockedAt: null,
33
+ idempotencyKey: key,
34
+ lastError: null,
35
+ createdAt: timestamp,
36
+ updatedAt: timestamp,
37
+ };
38
+ rows.set(task.id, task);
39
+ if (key)
40
+ byKey.set(key, task.id);
41
+ return { task: clone(task), created: true };
42
+ },
43
+ async claim(options) {
44
+ const nowMs = options.now.getTime();
45
+ const wanted = options.names;
46
+ // 1) 锁过期回收:attempts 已达上限 → dead;否则回队重领(不无限重跑)
47
+ for (const task of rows.values()) {
48
+ if (task.status !== 'running' || !task.lockedAt)
49
+ continue;
50
+ if (task.lockedAt.getTime() + options.visibilityTimeoutMs > nowMs)
51
+ continue;
52
+ if (task.attempts >= task.maxAttempts) {
53
+ task.status = 'dead';
54
+ task.lastError = 'visibility timeout exceeded';
55
+ }
56
+ else {
57
+ task.status = 'pending';
58
+ }
59
+ task.lockedBy = null;
60
+ task.lockedAt = null;
61
+ task.updatedAt = options.now;
62
+ }
63
+ const due = [...rows.values()]
64
+ .filter((task) => task.status === 'pending' &&
65
+ task.runAt.getTime() <= nowMs &&
66
+ (!wanted || wanted.includes(task.name)))
67
+ .sort((a, b) => a.runAt.getTime() - b.runAt.getTime())
68
+ .slice(0, Math.max(0, options.limit));
69
+ for (const task of due) {
70
+ task.status = 'running';
71
+ task.lockedBy = options.workerId;
72
+ task.lockedAt = options.now;
73
+ task.attempts += 1;
74
+ task.updatedAt = options.now;
75
+ }
76
+ return due.map(clone);
77
+ },
78
+ async complete(id, now) {
79
+ const task = find(id);
80
+ if (!task)
81
+ return;
82
+ task.status = 'done';
83
+ task.lockedBy = null;
84
+ task.lockedAt = null;
85
+ task.updatedAt = now;
86
+ },
87
+ async fail(id, input, now) {
88
+ const task = find(id);
89
+ if (!task)
90
+ return;
91
+ task.lastError = input.error;
92
+ task.lockedBy = null;
93
+ task.lockedAt = null;
94
+ task.updatedAt = now;
95
+ if (input.retryAt) {
96
+ task.status = 'pending';
97
+ task.runAt = input.retryAt;
98
+ }
99
+ else {
100
+ task.status = 'dead';
101
+ }
102
+ },
103
+ async release(id, runAt, now) {
104
+ const task = find(id);
105
+ if (!task)
106
+ return;
107
+ task.status = 'pending';
108
+ task.runAt = runAt;
109
+ task.lockedBy = null;
110
+ task.lockedAt = null;
111
+ task.updatedAt = now;
112
+ },
113
+ async retry(id, now) {
114
+ const task = find(id);
115
+ if (!task || task.status !== 'dead')
116
+ return false;
117
+ task.status = 'pending';
118
+ task.runAt = now;
119
+ task.attempts = 0;
120
+ task.lockedBy = null;
121
+ task.lockedAt = null;
122
+ task.updatedAt = now;
123
+ return true;
124
+ },
125
+ async get(id) {
126
+ const task = find(id);
127
+ return task ? clone(task) : null;
128
+ },
129
+ async list(filter = {}) {
130
+ let result = [...rows.values()];
131
+ if (filter.statuses) {
132
+ result = result.filter((task) => filter.statuses.includes(task.status));
133
+ }
134
+ if (filter.name)
135
+ result = result.filter((task) => task.name === filter.name);
136
+ result.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
137
+ if (filter.limit !== undefined)
138
+ result = result.slice(0, filter.limit);
139
+ return result.map(clone);
140
+ },
141
+ async prune(options) {
142
+ const statuses = options.statuses ?? ['done', 'dead'];
143
+ const threshold = options.now.getTime() - options.olderThanMs;
144
+ let removed = 0;
145
+ for (const [id, task] of rows) {
146
+ if (!statuses.includes(task.status))
147
+ continue;
148
+ if (task.updatedAt.getTime() > threshold)
149
+ continue;
150
+ rows.delete(id);
151
+ if (task.idempotencyKey)
152
+ byKey.delete(task.idempotencyKey);
153
+ removed += 1;
154
+ }
155
+ return removed;
156
+ },
157
+ async pendingNames(now) {
158
+ const names = new Set();
159
+ for (const task of rows.values()) {
160
+ if (task.status === 'pending' && task.runAt.getTime() <= now.getTime()) {
161
+ names.add(task.name);
162
+ }
163
+ }
164
+ return [...names];
165
+ },
166
+ };
167
+ return store;
168
+ }
169
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type postgresJs from 'postgres';
2
+ import type { TaskStore } from '../types.ts';
3
+ /** postgres.js 的 options(其泛型无默认值,同 `./postgres`) */
4
+ type PostgresOptions = NonNullable<Parameters<typeof postgresJs>[1]>;
5
+ export interface PostgresTaskStore extends TaskStore {
6
+ migrate(): Promise<void>;
7
+ close(): Promise<void>;
8
+ }
9
+ /**
10
+ * postgres 驱动(复用 `postgres.js`,与 `./postgres` 的 `ctx.sql` 同一客户端)。
11
+ *
12
+ * ```ts
13
+ * postgresTaskStore('postgres://…')
14
+ * postgresTaskStore(ctx.sql) // 注入已有客户端(不负责 close)
15
+ * ```
16
+ *
17
+ * claim 单语句 CTE:先回收过期锁(达上限 dead/否则 pending),再 `FOR UPDATE SKIP LOCKED` 领取;
18
+ * 表结构与 sqlite 对齐,`migrate()` 幂等(`task_migrations`)。
19
+ */
20
+ export declare function postgresTaskStore(urlOrSql?: string | postgresJs.Sql, options?: PostgresOptions): PostgresTaskStore;
21
+ export {};
22
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/stores/postgres.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,UAAU,MAAM,UAAU,CAAA;AACtC,OAAO,KAAK,EAQV,SAAS,EACV,MAAM,aAAa,CAAA;AAIpB,oDAAoD;AACpD,KAAK,eAAe,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAyDpE,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IAClD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC,GAAG,EAClC,OAAO,CAAC,EAAE,eAAe,GACxB,iBAAiB,CA0MnB"}
@@ -0,0 +1,233 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ const DEFAULT_MAX_ATTEMPTS = 5;
3
+ const SCHEMA = [
4
+ `
5
+ CREATE TABLE IF NOT EXISTS tasks (
6
+ id TEXT PRIMARY KEY,
7
+ name TEXT NOT NULL,
8
+ payload TEXT NOT NULL,
9
+ run_at TEXT NOT NULL,
10
+ attempts INTEGER NOT NULL DEFAULT 0,
11
+ max_attempts INTEGER NOT NULL DEFAULT 5,
12
+ status TEXT NOT NULL,
13
+ locked_by TEXT,
14
+ locked_at TEXT,
15
+ idempotency_key TEXT UNIQUE,
16
+ last_error TEXT,
17
+ created_at TEXT NOT NULL,
18
+ updated_at TEXT NOT NULL
19
+ )`,
20
+ 'CREATE INDEX IF NOT EXISTS tasks_due_idx ON tasks(status, run_at)',
21
+ 'CREATE INDEX IF NOT EXISTS tasks_name_idx ON tasks(name)',
22
+ ];
23
+ function toTask(row) {
24
+ return {
25
+ id: row.id,
26
+ name: row.name,
27
+ payload: JSON.parse(row.payload),
28
+ runAt: new Date(row.run_at),
29
+ attempts: row.attempts,
30
+ maxAttempts: row.max_attempts,
31
+ status: row.status,
32
+ lockedBy: row.locked_by,
33
+ lockedAt: row.locked_at ? new Date(row.locked_at) : null,
34
+ idempotencyKey: row.idempotency_key,
35
+ lastError: row.last_error,
36
+ createdAt: new Date(row.created_at),
37
+ updatedAt: new Date(row.updated_at),
38
+ };
39
+ }
40
+ /**
41
+ * postgres 驱动(复用 `postgres.js`,与 `./postgres` 的 `ctx.sql` 同一客户端)。
42
+ *
43
+ * ```ts
44
+ * postgresTaskStore('postgres://…')
45
+ * postgresTaskStore(ctx.sql) // 注入已有客户端(不负责 close)
46
+ * ```
47
+ *
48
+ * claim 单语句 CTE:先回收过期锁(达上限 dead/否则 pending),再 `FOR UPDATE SKIP LOCKED` 领取;
49
+ * 表结构与 sqlite 对齐,`migrate()` 幂等(`task_migrations`)。
50
+ */
51
+ export function postgresTaskStore(urlOrSql, options) {
52
+ const injected = typeof urlOrSql === 'function' ? urlOrSql : null;
53
+ let client = injected
54
+ ? Promise.resolve(injected)
55
+ : null;
56
+ const sql = () => {
57
+ client ??= (async () => {
58
+ const { default: createPostgres } = (await import('postgres'));
59
+ if (typeof urlOrSql === 'string')
60
+ return createPostgres(urlOrSql, options);
61
+ const url = process.env.DATABASE_URL;
62
+ if (url)
63
+ return createPostgres(url, options);
64
+ return createPostgres(options);
65
+ })();
66
+ return client;
67
+ };
68
+ const applyMigrations = async (db) => {
69
+ await db `
70
+ CREATE TABLE IF NOT EXISTS task_migrations (
71
+ version INTEGER PRIMARY KEY,
72
+ applied_at TEXT NOT NULL
73
+ )`;
74
+ const applied = await db `SELECT version FROM task_migrations WHERE version = 1`;
75
+ if (applied.length > 0)
76
+ return;
77
+ await db.begin(async (tx) => {
78
+ for (const statement of SCHEMA)
79
+ await tx.unsafe(statement);
80
+ await tx `INSERT INTO task_migrations (version, applied_at) VALUES (1, ${new Date().toISOString()})`;
81
+ });
82
+ };
83
+ const store = {
84
+ async migrate() {
85
+ await applyMigrations(await sql());
86
+ },
87
+ async close() {
88
+ if (!client || injected)
89
+ return;
90
+ const db = await client;
91
+ client = null;
92
+ await db.end({ timeout: 5 });
93
+ },
94
+ async enqueue(input) {
95
+ const db = await sql();
96
+ const timestamp = new Date().toISOString();
97
+ const key = input.idempotencyKey ?? null;
98
+ const rows = (await db `
99
+ INSERT INTO tasks (id, name, payload, run_at, attempts, max_attempts, status,
100
+ locked_by, locked_at, idempotency_key, last_error, created_at, updated_at)
101
+ VALUES (${randomUUID()}, ${input.name}, ${JSON.stringify(input.payload ?? null)},
102
+ ${(input.runAt ?? new Date()).toISOString()}, 0, ${input.maxAttempts ?? DEFAULT_MAX_ATTEMPTS},
103
+ 'pending', NULL, NULL, ${key}, NULL, ${timestamp}, ${timestamp})
104
+ ON CONFLICT (idempotency_key) DO NOTHING
105
+ RETURNING *`);
106
+ if (rows[0])
107
+ return { task: toTask(rows[0]), created: true };
108
+ // 幂等键命中:返回已有任务
109
+ const existing = (await db `
110
+ SELECT * FROM tasks WHERE idempotency_key = ${key} LIMIT 1`);
111
+ if (existing[0])
112
+ return { task: toTask(existing[0]), created: false };
113
+ throw new Error('enqueue 冲突但未找到已有任务(idempotency_key 非空?)');
114
+ },
115
+ async claim(claimOptions) {
116
+ const db = await sql();
117
+ const nowIso = claimOptions.now.toISOString();
118
+ const cutoff = new Date(claimOptions.now.getTime() - claimOptions.visibilityTimeoutMs).toISOString();
119
+ const names = claimOptions.names;
120
+ const nameFilter = names && names.length > 0
121
+ ? db `AND name = ANY(${names})`
122
+ : names && names.length === 0
123
+ ? db `AND false`
124
+ : db ``;
125
+ const rows = (await db `
126
+ WITH updated AS (
127
+ UPDATE tasks SET
128
+ status = CASE WHEN status = 'running' AND attempts >= max_attempts
129
+ THEN 'dead' ELSE 'running' END,
130
+ last_error = CASE WHEN status = 'running' AND attempts >= max_attempts
131
+ THEN 'visibility timeout exceeded' ELSE last_error END,
132
+ locked_by = CASE WHEN status = 'running' AND attempts >= max_attempts
133
+ THEN NULL ELSE ${claimOptions.workerId} END,
134
+ locked_at = CASE WHEN status = 'running' AND attempts >= max_attempts
135
+ THEN NULL ELSE ${nowIso} END,
136
+ attempts = CASE WHEN status = 'running' AND attempts >= max_attempts
137
+ THEN attempts ELSE attempts + 1 END,
138
+ updated_at = ${nowIso}
139
+ WHERE id IN (
140
+ SELECT id FROM tasks
141
+ WHERE (
142
+ (status = 'pending' AND run_at <= ${nowIso})
143
+ OR (status = 'running' AND locked_at <= ${cutoff})
144
+ ) ${nameFilter}
145
+ ORDER BY run_at
146
+ LIMIT ${Math.max(0, claimOptions.limit)}
147
+ FOR UPDATE SKIP LOCKED
148
+ )
149
+ RETURNING *
150
+ )
151
+ SELECT * FROM updated WHERE status = 'running'`);
152
+ return rows.map(toTask);
153
+ },
154
+ async complete(id, now) {
155
+ const db = await sql();
156
+ await db `
157
+ UPDATE tasks SET status = 'done', locked_by = NULL, locked_at = NULL,
158
+ updated_at = ${now.toISOString()} WHERE id = ${id}`;
159
+ },
160
+ async fail(id, input, now) {
161
+ const db = await sql();
162
+ if (input.retryAt) {
163
+ await db `
164
+ UPDATE tasks SET status = 'pending', run_at = ${input.retryAt.toISOString()},
165
+ last_error = ${input.error}, locked_by = NULL, locked_at = NULL,
166
+ updated_at = ${now.toISOString()} WHERE id = ${id}`;
167
+ }
168
+ else {
169
+ await db `
170
+ UPDATE tasks SET status = 'dead', last_error = ${input.error},
171
+ locked_by = NULL, locked_at = NULL,
172
+ updated_at = ${now.toISOString()} WHERE id = ${id}`;
173
+ }
174
+ },
175
+ async release(id, runAt, now) {
176
+ const db = await sql();
177
+ await db `
178
+ UPDATE tasks SET status = 'pending', run_at = ${runAt.toISOString()},
179
+ locked_by = NULL, locked_at = NULL,
180
+ updated_at = ${now.toISOString()} WHERE id = ${id}`;
181
+ },
182
+ async retry(id, now) {
183
+ const db = await sql();
184
+ const rows = (await db `
185
+ UPDATE tasks SET status = 'pending', run_at = ${now.toISOString()}, attempts = 0,
186
+ locked_by = NULL, locked_at = NULL, updated_at = ${now.toISOString()}
187
+ WHERE id = ${id} AND status = 'dead'
188
+ RETURNING id`);
189
+ return rows.length === 1;
190
+ },
191
+ async get(id) {
192
+ const db = await sql();
193
+ const rows = (await db `SELECT * FROM tasks WHERE id = ${id} LIMIT 1`);
194
+ return rows[0] ? toTask(rows[0]) : null;
195
+ },
196
+ async list(filter = {}) {
197
+ const db = await sql();
198
+ const conditions = [];
199
+ if (filter.statuses && filter.statuses.length > 0) {
200
+ conditions.push(db `status = ANY(${filter.statuses})`);
201
+ }
202
+ if (filter.name)
203
+ conditions.push(db `name = ${filter.name}`);
204
+ const where = conditions.length > 0
205
+ ? conditions
206
+ .slice(1)
207
+ .reduce((acc, next) => db `${acc} AND ${next}`, conditions[0])
208
+ : null;
209
+ const limit = filter.limit !== undefined ? db `LIMIT ${filter.limit}` : db ``;
210
+ const rows = (await db `
211
+ SELECT * FROM tasks ${where ? db `WHERE ${where}` : db ``}
212
+ ORDER BY created_at ${limit}`);
213
+ return rows.map(toTask);
214
+ },
215
+ async prune(pruneOptions) {
216
+ const db = await sql();
217
+ const statuses = pruneOptions.statuses ?? ['done', 'dead'];
218
+ const threshold = new Date(pruneOptions.now.getTime() - pruneOptions.olderThanMs).toISOString();
219
+ const rows = (await db `
220
+ DELETE FROM tasks WHERE status = ANY(${statuses}) AND updated_at <= ${threshold}
221
+ RETURNING id`);
222
+ return rows.length;
223
+ },
224
+ async pendingNames(now) {
225
+ const db = await sql();
226
+ const rows = (await db `
227
+ SELECT DISTINCT name FROM tasks WHERE status = 'pending' AND run_at <= ${now.toISOString()}`);
228
+ return rows.map((row) => row.name);
229
+ },
230
+ };
231
+ return store;
232
+ }
233
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/stores/postgres.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { Redis, RedisOptions } from 'ioredis';
2
+ import type { TaskStore } from '../types.ts';
3
+ export interface RedisTaskStoreOptions extends RedisOptions {
4
+ /** 键前缀(默认 `tasks`);测试用独立前缀隔离。 */
5
+ prefix?: string;
6
+ }
7
+ export interface RedisTaskStore extends TaskStore {
8
+ /** redis 无 schema;no-op 仅为统一 CLI/store 形态。 */
9
+ migrate(): Promise<void>;
10
+ close(): Promise<void>;
11
+ }
12
+ /**
13
+ * redis 驱动(ZSET + Lua,复用 `ctx.redis` 同一客户端)。
14
+ *
15
+ * ```ts
16
+ * redisTaskStore('redis://…', { prefix: 'tasks' })
17
+ * redisTaskStore(ctx.redis) // 注入已有客户端(不负责 close)
18
+ * ```
19
+ */
20
+ export declare function redisTaskStore(urlOrRedis?: string | Redis, options?: RedisTaskStoreOptions): RedisTaskStore;
21
+ //# sourceMappingURL=redis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/stores/redis.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,KAAK,EAQV,SAAS,EACV,MAAM,aAAa,CAAA;AAIpB,MAAM,WAAW,qBAAsB,SAAQ,YAAY;IACzD,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAwID,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C,8CAA8C;IAC9C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,EAC3B,OAAO,GAAE,qBAA0B,GAClC,cAAc,CAuPhB"}