@prisma-next/adapter-postgres 0.12.0-dev.5 → 0.12.0-dev.50
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-BI6bq3e0.mjs} +13 -13
- package/dist/adapter-BI6bq3e0.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-1Yv2bsSN.mjs +1357 -0
- package/dist/control-adapter-1Yv2bsSN.mjs.map +1 -0
- package/dist/control.d.mts +60 -10
- 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 +380 -105
- package/src/core/ddl-renderer.ts +73 -0
- package/src/core/enum-control-hooks.ts +2 -2
- package/src/core/marker-ledger.ts +124 -0
- package/src/core/sql-renderer.ts +66 -23
- 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 { SqlControlDriverInstance } from '@prisma-next/sql-contract/types';
|
|
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 = SqlControlDriverInstance<'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
|
|
|
@@ -678,14 +709,18 @@ function getInsertColumnOrder(
|
|
|
678
709
|
return orderedColumns;
|
|
679
710
|
}
|
|
680
711
|
|
|
681
|
-
let table: { columns: Record<string, unknown
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
712
|
+
let table: { columns: Readonly<Record<string, unknown>> } | undefined;
|
|
713
|
+
if (tableRef.namespaceId !== undefined) {
|
|
714
|
+
const ns = contract.storage.namespaces[tableRef.namespaceId];
|
|
715
|
+
table = ns?.entries.table[tableName];
|
|
716
|
+
}
|
|
717
|
+
if (table === undefined) {
|
|
718
|
+
for (const ns of Object.values(contract.storage.namespaces)) {
|
|
719
|
+
const found = ns.entries.table[tableName];
|
|
720
|
+
if (found !== undefined) {
|
|
721
|
+
table = found;
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
689
724
|
}
|
|
690
725
|
}
|
|
691
726
|
if (!table) {
|
|
@@ -694,7 +729,11 @@ function getInsertColumnOrder(
|
|
|
694
729
|
return Object.keys(table.columns);
|
|
695
730
|
}
|
|
696
731
|
|
|
697
|
-
function renderInsertValue(
|
|
732
|
+
function renderInsertValue(
|
|
733
|
+
value: InsertValue | undefined,
|
|
734
|
+
contract: PostgresContract,
|
|
735
|
+
pim: ParamIndexMap,
|
|
736
|
+
): string {
|
|
698
737
|
if (!value || value.kind === 'default-value') {
|
|
699
738
|
return 'DEFAULT';
|
|
700
739
|
}
|
|
@@ -705,6 +744,8 @@ function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap):
|
|
|
705
744
|
return renderParamRef(value, pim);
|
|
706
745
|
case 'column-ref':
|
|
707
746
|
return renderColumn(value);
|
|
747
|
+
case 'raw-expr':
|
|
748
|
+
return renderExpr(value, contract, pim);
|
|
708
749
|
// v8 ignore next 4
|
|
709
750
|
default:
|
|
710
751
|
throw new Error(
|
|
@@ -714,7 +755,7 @@ function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap):
|
|
|
714
755
|
}
|
|
715
756
|
|
|
716
757
|
function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamIndexMap): string {
|
|
717
|
-
const table =
|
|
758
|
+
const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
|
|
718
759
|
const rows = ast.rows;
|
|
719
760
|
if (rows.length === 0) {
|
|
720
761
|
throw new Error('INSERT requires at least one row');
|
|
@@ -726,7 +767,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
|
|
|
726
767
|
return `INSERT INTO ${table} DEFAULT VALUES`;
|
|
727
768
|
}
|
|
728
769
|
|
|
729
|
-
const defaultColumns = getInsertColumnOrder(rows, contract, ast.table
|
|
770
|
+
const defaultColumns = getInsertColumnOrder(rows, contract, ast.table);
|
|
730
771
|
if (defaultColumns.length === 0) {
|
|
731
772
|
return `INSERT INTO ${table} VALUES ${rows.map(() => '()').join(', ')}`;
|
|
732
773
|
}
|
|
@@ -738,11 +779,13 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
|
|
|
738
779
|
.join(', ')}`;
|
|
739
780
|
}
|
|
740
781
|
|
|
741
|
-
const columnOrder = getInsertColumnOrder(rows, contract, ast.table
|
|
782
|
+
const columnOrder = getInsertColumnOrder(rows, contract, ast.table);
|
|
742
783
|
const columns = columnOrder.map((column) => quoteIdentifier(column));
|
|
743
784
|
const values = rows
|
|
744
785
|
.map((row) => {
|
|
745
|
-
const renderedRow = columnOrder.map((column) =>
|
|
786
|
+
const renderedRow = columnOrder.map((column) =>
|
|
787
|
+
renderInsertValue(row[column], contract, pim),
|
|
788
|
+
);
|
|
746
789
|
return `(${renderedRow.join(', ')})`;
|
|
747
790
|
})
|
|
748
791
|
.join(', ');
|
|
@@ -786,7 +829,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
|
|
|
786
829
|
}
|
|
787
830
|
|
|
788
831
|
function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamIndexMap): string {
|
|
789
|
-
const table =
|
|
832
|
+
const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
|
|
790
833
|
const setEntries = Object.entries(ast.set);
|
|
791
834
|
if (setEntries.length === 0) {
|
|
792
835
|
throw new Error('UPDATE requires at least one SET assignment');
|
|
@@ -805,7 +848,7 @@ function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamInde
|
|
|
805
848
|
}
|
|
806
849
|
|
|
807
850
|
function renderDelete(ast: DeleteAst, contract: PostgresContract, pim: ParamIndexMap): string {
|
|
808
|
-
const table =
|
|
851
|
+
const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
|
|
809
852
|
const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';
|
|
810
853
|
const returningClause = ast.returning?.length
|
|
811
854
|
? ` 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"}
|