@prisma-next/adapter-postgres 0.12.0-dev.4 → 0.12.0-dev.40
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/README.md +13 -20
- package/dist/{adapter-H8BiuXdq.mjs → adapter-DuD5XIwi.mjs} +13 -13
- package/dist/adapter-DuD5XIwi.mjs.map +1 -0
- package/dist/adapter.d.mts +2 -1
- package/dist/adapter.d.mts.map +1 -1
- package/dist/adapter.mjs +1 -1
- package/dist/control-adapter-CykaRJU9.mjs +1367 -0
- package/dist/control-adapter-CykaRJU9.mjs.map +1 -0
- package/dist/control.d.mts +62 -4
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +3 -659
- package/dist/control.mjs.map +1 -1
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.mjs +1 -1
- package/package.json +22 -22
- package/src/core/adapter.ts +28 -25
- package/src/core/control-adapter.ts +374 -90
- package/src/core/ddl-renderer.ts +73 -0
- package/src/core/marker-ledger.ts +124 -0
- package/src/core/sql-renderer.ts +68 -20
- package/dist/adapter-H8BiuXdq.mjs.map +0 -1
- package/dist/sql-renderer-DlZhVI9B.mjs +0 -457
- package/dist/sql-renderer-DlZhVI9B.mjs.map +0 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { ControlDriverInstance } from '@prisma-next/framework-components/control';
|
|
2
|
+
import {
|
|
3
|
+
type AnyQueryAst,
|
|
4
|
+
type LoweredStatement,
|
|
5
|
+
RawExpr,
|
|
6
|
+
} from '@prisma-next/sql-relational-core/ast';
|
|
7
|
+
import {
|
|
8
|
+
createAstCodecRegistry,
|
|
9
|
+
deriveParamMetadata,
|
|
10
|
+
encodeParamsWithMetadata,
|
|
11
|
+
} from '@prisma-next/sql-runtime';
|
|
12
|
+
import { PG_TIMESTAMPTZ_CODEC_ID } from '@prisma-next/target-postgres/codec-ids';
|
|
13
|
+
import { postgresCodecRegistry } from '@prisma-next/target-postgres/codecs';
|
|
14
|
+
import {
|
|
15
|
+
int4,
|
|
16
|
+
int8,
|
|
17
|
+
jsonb,
|
|
18
|
+
pgTable,
|
|
19
|
+
text,
|
|
20
|
+
textArray,
|
|
21
|
+
timestamptz,
|
|
22
|
+
} from '@prisma-next/target-postgres/contract-free';
|
|
23
|
+
|
|
24
|
+
const CONTROL_CODECS = createAstCodecRegistry(postgresCodecRegistry);
|
|
25
|
+
|
|
26
|
+
export const marker = pgTable(
|
|
27
|
+
{ name: 'marker', schema: 'prisma_contract' },
|
|
28
|
+
{
|
|
29
|
+
space: text(),
|
|
30
|
+
core_hash: text(),
|
|
31
|
+
profile_hash: text(),
|
|
32
|
+
contract_json: jsonb({ nullable: true }),
|
|
33
|
+
canonical_version: int4({ nullable: true }),
|
|
34
|
+
updated_at: timestamptz(),
|
|
35
|
+
app_tag: text({ nullable: true }),
|
|
36
|
+
meta: jsonb({ nullable: true }),
|
|
37
|
+
invariants: textArray(),
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Writeable subset of the `prisma_contract.ledger` table. Omits the
|
|
43
|
+
* DB-generated `id` (bigserial) and `created_at` (default `now()`) so the
|
|
44
|
+
* insert path doesn't have to pass them.
|
|
45
|
+
*/
|
|
46
|
+
export const ledger = pgTable(
|
|
47
|
+
{ name: 'ledger', schema: 'prisma_contract' },
|
|
48
|
+
{
|
|
49
|
+
space: text(),
|
|
50
|
+
migration_name: text(),
|
|
51
|
+
migration_hash: text(),
|
|
52
|
+
origin_core_hash: text({ nullable: true }),
|
|
53
|
+
destination_core_hash: text(),
|
|
54
|
+
operations: jsonb(),
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read-side handle covering every column of `prisma_contract.ledger`,
|
|
60
|
+
* including the DB-generated `id` (for ORDER BY) and `created_at`.
|
|
61
|
+
*/
|
|
62
|
+
export const ledgerReadShape = pgTable(
|
|
63
|
+
{ name: 'ledger', schema: 'prisma_contract' },
|
|
64
|
+
{
|
|
65
|
+
id: int8(),
|
|
66
|
+
space: text(),
|
|
67
|
+
migration_name: text(),
|
|
68
|
+
migration_hash: text(),
|
|
69
|
+
origin_core_hash: text({ nullable: true }),
|
|
70
|
+
destination_core_hash: text(),
|
|
71
|
+
operations: jsonb(),
|
|
72
|
+
created_at: timestamptz(),
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
export const infoSchemaTables = pgTable(
|
|
77
|
+
{ name: 'tables', schema: 'information_schema' },
|
|
78
|
+
{ table_schema: text(), table_name: text() },
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
export const NOW = new RawExpr({
|
|
82
|
+
parts: ['now()'],
|
|
83
|
+
returns: { codecId: PG_TIMESTAMPTZ_CODEC_ID, nullable: false },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
type Lower = (query: AnyQueryAst) => LoweredStatement;
|
|
87
|
+
|
|
88
|
+
type MarkerDriver = {
|
|
89
|
+
query<Row = Record<string, unknown>>(
|
|
90
|
+
sql: string,
|
|
91
|
+
params?: readonly unknown[],
|
|
92
|
+
): Promise<{ readonly rows: ReadonlyArray<Row> }>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export function mergeInvariants(
|
|
96
|
+
current: readonly string[],
|
|
97
|
+
incoming: readonly string[],
|
|
98
|
+
): readonly string[] {
|
|
99
|
+
return [...new Set([...current, ...incoming])].sort();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function execute(
|
|
103
|
+
lower: Lower,
|
|
104
|
+
driver: MarkerDriver,
|
|
105
|
+
query: AnyQueryAst,
|
|
106
|
+
): Promise<readonly Record<string, unknown>[]> {
|
|
107
|
+
const lowered = lower(query);
|
|
108
|
+
const values = lowered.params.map((slot) => {
|
|
109
|
+
if (slot.kind === 'literal') return slot.value;
|
|
110
|
+
throw new Error('Postgres control DML lowered to a bind parameter, which is unsupported');
|
|
111
|
+
});
|
|
112
|
+
const encoded = await encodeParamsWithMetadata(
|
|
113
|
+
values,
|
|
114
|
+
deriveParamMetadata(query),
|
|
115
|
+
{},
|
|
116
|
+
CONTROL_CODECS,
|
|
117
|
+
);
|
|
118
|
+
const result = await driver.query(lowered.sql, encoded);
|
|
119
|
+
return result.rows;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export type PostgresMarkerDriver = MarkerDriver;
|
|
123
|
+
export type PostgresMarkerLower = Lower;
|
|
124
|
+
export type PostgresMarkerWriteDriver = ControlDriverInstance<'sql', 'postgres'>;
|
package/src/core/sql-renderer.ts
CHANGED
|
@@ -27,9 +27,11 @@ import {
|
|
|
27
27
|
type RawSqlExpr,
|
|
28
28
|
type SelectAst,
|
|
29
29
|
type SubqueryExpr,
|
|
30
|
+
type TableSource,
|
|
30
31
|
type UpdateAst,
|
|
31
32
|
type WindowFuncExpr,
|
|
32
33
|
} from '@prisma-next/sql-relational-core/ast';
|
|
34
|
+
import { PostgresTableSource } from '@prisma-next/target-postgres/contract-free';
|
|
33
35
|
import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
|
|
34
36
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
35
37
|
import type { PostgresContract } from './types';
|
|
@@ -269,6 +271,39 @@ function renderDistinctPrefix(
|
|
|
269
271
|
return '';
|
|
270
272
|
}
|
|
271
273
|
|
|
274
|
+
function qualifyTableFromNamespaceCoordinate(
|
|
275
|
+
table: Pick<TableSource, 'name' | 'namespaceId'>,
|
|
276
|
+
contract: PostgresContract,
|
|
277
|
+
): string {
|
|
278
|
+
if (table instanceof PostgresTableSource && table.schema !== undefined) {
|
|
279
|
+
return `${quoteIdentifier(table.schema)}.${quoteIdentifier(table.name)}`;
|
|
280
|
+
}
|
|
281
|
+
if (table.namespaceId === undefined) {
|
|
282
|
+
return quoteIdentifier(table.name);
|
|
283
|
+
}
|
|
284
|
+
const namespace = contract.storage.namespaces[table.namespaceId];
|
|
285
|
+
if (namespace === undefined) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
`Table "${table.name}" references namespace "${table.namespaceId}" which is not present as a Postgres schema on the contract`,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
const qualifyTable = namespace.qualifyTable;
|
|
291
|
+
if (qualifyTable === undefined) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
`Table "${table.name}" references namespace "${table.namespaceId}" which is not materialised as a Postgres schema on the contract`,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
return qualifyTable.call(namespace, table.name);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function renderTableSource(source: TableSource, contract: PostgresContract): string {
|
|
300
|
+
const qualified = qualifyTableFromNamespaceCoordinate(source, contract);
|
|
301
|
+
if (!source.alias) {
|
|
302
|
+
return qualified;
|
|
303
|
+
}
|
|
304
|
+
return `${qualified} AS ${quoteIdentifier(source.alias)}`;
|
|
305
|
+
}
|
|
306
|
+
|
|
272
307
|
function renderSource(
|
|
273
308
|
source: AnyFromSource,
|
|
274
309
|
contract: PostgresContract,
|
|
@@ -276,13 +311,8 @@ function renderSource(
|
|
|
276
311
|
): string {
|
|
277
312
|
const node = source;
|
|
278
313
|
switch (node.kind) {
|
|
279
|
-
case 'table-source':
|
|
280
|
-
|
|
281
|
-
if (!node.alias) {
|
|
282
|
-
return table;
|
|
283
|
-
}
|
|
284
|
-
return `${table} AS ${quoteIdentifier(node.alias)}`;
|
|
285
|
-
}
|
|
314
|
+
case 'table-source':
|
|
315
|
+
return renderTableSource(node, contract);
|
|
286
316
|
case 'derived-table-source':
|
|
287
317
|
return `(${renderSelect(node.query, contract, pim)}) AS ${quoteIdentifier(node.alias)}`;
|
|
288
318
|
// v8 ignore next 4
|
|
@@ -659,8 +689,9 @@ function renderJoinOn(on: JoinOnExpr, contract: PostgresContract, pim: ParamInde
|
|
|
659
689
|
function getInsertColumnOrder(
|
|
660
690
|
rows: ReadonlyArray<Record<string, InsertValue>>,
|
|
661
691
|
contract: PostgresContract,
|
|
662
|
-
|
|
692
|
+
tableRef: Pick<TableSource, 'name' | 'namespaceId'>,
|
|
663
693
|
): string[] {
|
|
694
|
+
const tableName = tableRef.name;
|
|
664
695
|
const orderedColumns: string[] = [];
|
|
665
696
|
const seenColumns = new Set<string>();
|
|
666
697
|
|
|
@@ -679,13 +710,22 @@ function getInsertColumnOrder(
|
|
|
679
710
|
}
|
|
680
711
|
|
|
681
712
|
let table: { columns: Record<string, unknown> } | undefined;
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const found = ns.tables[tableName] as { columns: Record<string, unknown> } | undefined;
|
|
713
|
+
if (tableRef.namespaceId !== undefined) {
|
|
714
|
+
const ns = contract.storage.namespaces[tableRef.namespaceId];
|
|
715
|
+
const found = ns?.tables[tableName] as { columns: Record<string, unknown> } | undefined;
|
|
686
716
|
if (found !== undefined) {
|
|
687
717
|
table = found;
|
|
688
|
-
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (table === undefined) {
|
|
721
|
+
for (const ns of Object.values(contract.storage.namespaces)) {
|
|
722
|
+
// Namespace.tables is Record<string, IRNode> at the interface level;
|
|
723
|
+
// SQL family namespaces hold StorageTable instances which have .columns.
|
|
724
|
+
const found = ns.tables[tableName] as { columns: Record<string, unknown> } | undefined;
|
|
725
|
+
if (found !== undefined) {
|
|
726
|
+
table = found;
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
689
729
|
}
|
|
690
730
|
}
|
|
691
731
|
if (!table) {
|
|
@@ -694,7 +734,11 @@ function getInsertColumnOrder(
|
|
|
694
734
|
return Object.keys(table.columns);
|
|
695
735
|
}
|
|
696
736
|
|
|
697
|
-
function renderInsertValue(
|
|
737
|
+
function renderInsertValue(
|
|
738
|
+
value: InsertValue | undefined,
|
|
739
|
+
contract: PostgresContract,
|
|
740
|
+
pim: ParamIndexMap,
|
|
741
|
+
): string {
|
|
698
742
|
if (!value || value.kind === 'default-value') {
|
|
699
743
|
return 'DEFAULT';
|
|
700
744
|
}
|
|
@@ -705,6 +749,8 @@ function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap):
|
|
|
705
749
|
return renderParamRef(value, pim);
|
|
706
750
|
case 'column-ref':
|
|
707
751
|
return renderColumn(value);
|
|
752
|
+
case 'raw-expr':
|
|
753
|
+
return renderExpr(value, contract, pim);
|
|
708
754
|
// v8 ignore next 4
|
|
709
755
|
default:
|
|
710
756
|
throw new Error(
|
|
@@ -714,7 +760,7 @@ function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap):
|
|
|
714
760
|
}
|
|
715
761
|
|
|
716
762
|
function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamIndexMap): string {
|
|
717
|
-
const table =
|
|
763
|
+
const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
|
|
718
764
|
const rows = ast.rows;
|
|
719
765
|
if (rows.length === 0) {
|
|
720
766
|
throw new Error('INSERT requires at least one row');
|
|
@@ -726,7 +772,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
|
|
|
726
772
|
return `INSERT INTO ${table} DEFAULT VALUES`;
|
|
727
773
|
}
|
|
728
774
|
|
|
729
|
-
const defaultColumns = getInsertColumnOrder(rows, contract, ast.table
|
|
775
|
+
const defaultColumns = getInsertColumnOrder(rows, contract, ast.table);
|
|
730
776
|
if (defaultColumns.length === 0) {
|
|
731
777
|
return `INSERT INTO ${table} VALUES ${rows.map(() => '()').join(', ')}`;
|
|
732
778
|
}
|
|
@@ -738,11 +784,13 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
|
|
|
738
784
|
.join(', ')}`;
|
|
739
785
|
}
|
|
740
786
|
|
|
741
|
-
const columnOrder = getInsertColumnOrder(rows, contract, ast.table
|
|
787
|
+
const columnOrder = getInsertColumnOrder(rows, contract, ast.table);
|
|
742
788
|
const columns = columnOrder.map((column) => quoteIdentifier(column));
|
|
743
789
|
const values = rows
|
|
744
790
|
.map((row) => {
|
|
745
|
-
const renderedRow = columnOrder.map((column) =>
|
|
791
|
+
const renderedRow = columnOrder.map((column) =>
|
|
792
|
+
renderInsertValue(row[column], contract, pim),
|
|
793
|
+
);
|
|
746
794
|
return `(${renderedRow.join(', ')})`;
|
|
747
795
|
})
|
|
748
796
|
.join(', ');
|
|
@@ -786,7 +834,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
|
|
|
786
834
|
}
|
|
787
835
|
|
|
788
836
|
function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamIndexMap): string {
|
|
789
|
-
const table =
|
|
837
|
+
const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
|
|
790
838
|
const setEntries = Object.entries(ast.set);
|
|
791
839
|
if (setEntries.length === 0) {
|
|
792
840
|
throw new Error('UPDATE requires at least one SET assignment');
|
|
@@ -805,7 +853,7 @@ function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamInde
|
|
|
805
853
|
}
|
|
806
854
|
|
|
807
855
|
function renderDelete(ast: DeleteAst, contract: PostgresContract, pim: ParamIndexMap): string {
|
|
808
|
-
const table =
|
|
856
|
+
const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
|
|
809
857
|
const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';
|
|
810
858
|
const returningClause = ast.returning?.length
|
|
811
859
|
? ` RETURNING ${renderReturning(ast.returning, contract, pim)}`
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adapter-H8BiuXdq.mjs","names":[],"sources":["../src/core/adapter.ts"],"sourcesContent":["import type { CodecLookup } from '@prisma-next/framework-components/codec';\nimport { APP_SPACE_ID } from '@prisma-next/framework-components/control';\nimport type {\n Adapter,\n AdapterProfile,\n AnyQueryAst,\n LowererContext,\n MarkerReadResult,\n RawSqlLiteral,\n SqlQueryable,\n} from '@prisma-next/sql-relational-core/ast';\nimport type { RawCodecInferer } from '@prisma-next/sql-relational-core/expression';\nimport { parseContractMarkerRow } from '@prisma-next/sql-runtime';\nimport { createPostgresBuiltinCodecLookup } from './codec-lookup';\nimport { renderLoweredSql } from './sql-renderer';\nimport type { PostgresAdapterOptions, PostgresContract, PostgresLoweredStatement } from './types';\n\nconst defaultCapabilities = Object.freeze({\n postgres: {\n orderBy: true,\n limit: true,\n lateral: true,\n jsonAgg: true,\n returning: true,\n distinctOn: true,\n },\n sql: {\n enums: true,\n returning: true,\n defaultInInsert: true,\n lateral: true,\n },\n});\n\nclass PostgresAdapterImpl\n implements Adapter<AnyQueryAst, PostgresContract, PostgresLoweredStatement>\n{\n // These fields make the adapter instance structurally compatible with RuntimeAdapterInstance<'sql', 'postgres'> without introducing a runtime-plane dependency.\n readonly familyId = 'sql' as const;\n readonly targetId = 'postgres' as const;\n\n readonly profile: AdapterProfile<'postgres'>;\n private readonly codecLookup: CodecLookup;\n\n constructor(options?: PostgresAdapterOptions) {\n this.codecLookup = options?.codecLookup ?? createPostgresBuiltinCodecLookup();\n this.profile = Object.freeze({\n id: options?.profileId ?? 'postgres/default@1',\n target: 'postgres',\n capabilities: defaultCapabilities,\n readMarker: (queryable: SqlQueryable) => readPostgresMarker(queryable),\n });\n }\n\n lower(ast: AnyQueryAst, context: LowererContext<PostgresContract>): PostgresLoweredStatement {\n return renderLoweredSql(ast, context.contract, this.codecLookup);\n }\n}\n\n/** Codec-id lookup for bare-literal interpolations used by `fns.raw` on a postgres client. Contributed as the descriptor's static `rawCodecInferer` slot. */\nexport const postgresRawCodecInferer: RawCodecInferer = {\n inferCodec(value: RawSqlLiteral): string {\n switch (typeof value) {\n case 'number':\n return Number.isSafeInteger(value) && value % 1 === 0 ? 'pg/int4' : 'pg/float8';\n case 'bigint':\n return 'pg/int8';\n case 'string':\n return 'pg/text';\n case 'boolean':\n return 'pg/bool';\n case 'object':\n if (value instanceof Uint8Array) return 'pg/bytea';\n }\n throw new Error(\n 'unsupported JS value type for raw-SQL interpolation: wrap this value in `param(...)` with an explicit codec',\n );\n },\n};\n\nasync function readPostgresMarker(queryable: SqlQueryable): Promise<MarkerReadResult> {\n const exists = await queryable.query(\n 'select 1 from information_schema.tables where table_schema = $1 and table_name = $2',\n ['prisma_contract', 'marker'],\n );\n if (exists.rows.length === 0) {\n return { kind: 'no-table' };\n }\n\n const result = await queryable.query(\n 'select core_hash, profile_hash, contract_json, canonical_version, updated_at, app_tag, meta, invariants from prisma_contract.marker where space = $1',\n [APP_SPACE_ID],\n );\n const row = result.rows[0];\n if (!row) {\n return { kind: 'absent' };\n }\n // Postgres' driver hydrates `text[]` columns as native JS arrays, so the row is already in the shape the shared parser expects.\n return { kind: 'present', record: parseContractMarkerRow(row) };\n}\n\nexport function createPostgresAdapter(options?: PostgresAdapterOptions) {\n return Object.freeze(new PostgresAdapterImpl(options));\n}\n"],"mappings":";;;;AAiBA,MAAM,sBAAsB,OAAO,OAAO;CACxC,UAAU;EACR,SAAS;EACT,OAAO;EACP,SAAS;EACT,SAAS;EACT,WAAW;EACX,YAAY;CACd;CACA,KAAK;EACH,OAAO;EACP,WAAW;EACX,iBAAiB;EACjB,SAAS;CACX;AACF,CAAC;AAED,IAAM,sBAAN,MAEA;CAEE,WAAoB;CACpB,WAAoB;CAEpB;CACA;CAEA,YAAY,SAAkC;EAC5C,KAAK,cAAc,SAAS,eAAe,iCAAiC;EAC5E,KAAK,UAAU,OAAO,OAAO;GAC3B,IAAI,SAAS,aAAa;GAC1B,QAAQ;GACR,cAAc;GACd,aAAa,cAA4B,mBAAmB,SAAS;EACvE,CAAC;CACH;CAEA,MAAM,KAAkB,SAAqE;EAC3F,OAAO,iBAAiB,KAAK,QAAQ,UAAU,KAAK,WAAW;CACjE;AACF;;AAGA,MAAa,0BAA2C,EACtD,WAAW,OAA8B;CACvC,QAAQ,OAAO,OAAf;EACE,KAAK,UACH,OAAO,OAAO,cAAc,KAAK,KAAK,QAAQ,MAAM,IAAI,YAAY;EACtE,KAAK,UACH,OAAO;EACT,KAAK,UACH,OAAO;EACT,KAAK,WACH,OAAO;EACT,KAAK,UACH,IAAI,iBAAiB,YAAY,OAAO;CAC5C;CACA,MAAM,IAAI,MACR,6GACF;AACF,EACF;AAEA,eAAe,mBAAmB,WAAoD;CAKpF,KAAI,MAJiB,UAAU,MAC7B,uFACA,CAAC,mBAAmB,QAAQ,CAC9B,GACW,KAAK,WAAW,GACzB,OAAO,EAAE,MAAM,WAAW;CAO5B,MAAM,OAAM,MAJS,UAAU,MAC7B,wJACA,CAAC,YAAY,CACf,GACmB,KAAK;CACxB,IAAI,CAAC,KACH,OAAO,EAAE,MAAM,SAAS;CAG1B,OAAO;EAAE,MAAM;EAAW,QAAQ,uBAAuB,GAAG;CAAE;AAChE;AAEA,SAAgB,sBAAsB,SAAkC;CACtE,OAAO,OAAO,OAAO,IAAI,oBAAoB,OAAO,CAAC;AACvD"}
|