@cipherstash/stack 0.18.0 → 1.0.0-rc.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.
- package/CHANGELOG.md +704 -0
- package/README.md +97 -79
- package/dist/adapter-kit.cjs +923 -0
- package/dist/adapter-kit.cjs.map +1 -0
- package/dist/adapter-kit.d.cts +82 -0
- package/dist/adapter-kit.d.ts +82 -0
- package/dist/adapter-kit.js +47 -0
- package/dist/adapter-kit.js.map +1 -0
- package/dist/base-operation-AOAIvsSB.d.cts +32 -0
- package/dist/base-operation-FXEzUXIq.d.ts +32 -0
- package/dist/{chunk-VPOTNP4K.js → chunk-3B5ZX3IS.js} +3 -1
- package/dist/chunk-3B5ZX3IS.js.map +1 -0
- package/dist/chunk-7333ZC6L.js +48 -0
- package/dist/chunk-7333ZC6L.js.map +1 -0
- package/dist/{chunk-XWPGAHBZ.js → chunk-BNO32ZMV.js} +1 -1
- package/dist/chunk-BNO32ZMV.js.map +1 -0
- package/dist/chunk-CLM7E4I6.js +4439 -0
- package/dist/chunk-CLM7E4I6.js.map +1 -0
- package/dist/{chunk-LLCCNKDL.js → chunk-HQANMV7R.js} +3 -3
- package/dist/{chunk-LLCCNKDL.js.map → chunk-HQANMV7R.js.map} +1 -1
- package/dist/chunk-JSG2AMDI.js +17 -0
- package/dist/chunk-JSG2AMDI.js.map +1 -0
- package/dist/chunk-OFQ555AX.js +191 -0
- package/dist/chunk-OFQ555AX.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-V2Q3NYIH.js +494 -0
- package/dist/chunk-V2Q3NYIH.js.map +1 -0
- package/dist/chunk-X3JRXEIB.js +98 -0
- package/dist/chunk-X3JRXEIB.js.map +1 -0
- package/dist/{chunk-ZYOZHZMJ.js → chunk-ZTP5QJ27.js} +193 -169
- package/dist/chunk-ZTP5QJ27.js.map +1 -0
- package/dist/client.cjs +4124 -49
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +6 -5
- package/dist/client.d.ts +6 -5
- package/dist/client.js +3 -1
- package/dist/columns-D2_YzrCX.d.cts +622 -0
- package/dist/columns-rZc7fQHI.d.ts +622 -0
- package/dist/dynamodb/index.cjs +19 -12
- package/dist/dynamodb/index.cjs.map +1 -1
- package/dist/dynamodb/index.d.cts +3 -2
- package/dist/dynamodb/index.d.ts +3 -2
- package/dist/dynamodb/index.js +8 -10
- package/dist/dynamodb/index.js.map +1 -1
- package/dist/encryption/index.cjs +4496 -222
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +807 -6
- package/dist/encryption/index.d.ts +807 -6
- package/dist/encryption/index.js +15 -7
- package/dist/encryption/v3.cjs +7896 -0
- package/dist/encryption/v3.cjs.map +1 -0
- package/dist/encryption/v3.d.cts +103 -0
- package/dist/encryption/v3.d.ts +103 -0
- package/dist/encryption/v3.js +189 -0
- package/dist/encryption/v3.js.map +1 -0
- package/dist/eql/v3/index.cjs +663 -0
- package/dist/eql/v3/index.cjs.map +1 -0
- package/dist/eql/v3/index.d.cts +173 -0
- package/dist/eql/v3/index.d.ts +173 -0
- package/dist/eql/v3/index.js +99 -0
- package/dist/eql/v3/index.js.map +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/identity/index.cjs +55 -35
- package/dist/identity/index.cjs.map +1 -1
- package/dist/identity/index.d.cts +54 -30
- package/dist/identity/index.d.ts +54 -30
- package/dist/identity/index.js +9 -177
- package/dist/identity/index.js.map +1 -1
- package/dist/index-BquA71_Y.d.ts +24 -0
- package/dist/index-fhWTOV0K.d.cts +24 -0
- package/dist/index.cjs +4547 -243
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -19
- package/dist/index.d.ts +18 -19
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/dist/schema/index.cjs +4128 -49
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.d.cts +1 -1
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.js +3 -1
- package/dist/{types-public-BTxqjmtv.d.cts → types-public-QMjYNfQO.d.cts} +448 -251
- package/dist/{types-public-BTxqjmtv.d.ts → types-public-QMjYNfQO.d.ts} +448 -251
- package/dist/types-public.cjs.map +1 -1
- package/dist/types-public.d.cts +2 -2
- package/dist/types-public.d.ts +2 -2
- package/dist/types-public.js +2 -1
- package/dist/wasm-inline.d.ts +1643 -0
- package/dist/wasm-inline.js +4901 -0
- package/dist/wasm-inline.js.map +1 -0
- package/package.json +74 -52
- package/dist/chunk-VPOTNP4K.js.map +0 -1
- package/dist/chunk-XWPGAHBZ.js.map +0 -1
- package/dist/chunk-YWQXTZ5M.js +0 -383
- package/dist/chunk-YWQXTZ5M.js.map +0 -1
- package/dist/chunk-ZYOZHZMJ.js.map +0 -1
- package/dist/client-CIsfZ2zg.d.ts +0 -692
- package/dist/client-Dt98Rugc.d.cts +0 -692
- package/dist/drizzle/index.cjs +0 -1558
- package/dist/drizzle/index.cjs.map +0 -1
- package/dist/drizzle/index.d.cts +0 -358
- package/dist/drizzle/index.d.ts +0 -358
- package/dist/drizzle/index.js +0 -1215
- package/dist/drizzle/index.js.map +0 -1
- package/dist/supabase/index.cjs +0 -1526
- package/dist/supabase/index.cjs.map +0 -1
- package/dist/supabase/index.d.cts +0 -144
- package/dist/supabase/index.d.ts +0 -144
- package/dist/supabase/index.js +0 -899
- package/dist/supabase/index.js.map +0 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// src/eql/v3/table.ts
|
|
2
|
+
var EncryptedTable = class {
|
|
3
|
+
constructor(tableName, columnBuilders) {
|
|
4
|
+
this.tableName = tableName;
|
|
5
|
+
this.columnBuilders = columnBuilders;
|
|
6
|
+
}
|
|
7
|
+
tableName;
|
|
8
|
+
columnBuilders;
|
|
9
|
+
build() {
|
|
10
|
+
const builtColumns = /* @__PURE__ */ Object.create(null);
|
|
11
|
+
for (const builder of Object.values(this.columnBuilders)) {
|
|
12
|
+
const name = builder.getName();
|
|
13
|
+
if (Object.hasOwn(builtColumns, name)) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`[eql/v3]: duplicate column name "${name}" in table "${this.tableName}" \u2014 two columns resolve to the same DB name`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
builtColumns[name] = builder.build();
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
tableName: this.tableName,
|
|
22
|
+
columns: builtColumns
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Map each column's JS property name to its DB column name (`getName()`).
|
|
27
|
+
* The model path matches user models by property name but must address the
|
|
28
|
+
* encrypt config and FFI by DB name — `build()` keys columns by DB name, so
|
|
29
|
+
* the two only agree when property == name. This recovers the mapping that
|
|
30
|
+
* `build()` discards.
|
|
31
|
+
*
|
|
32
|
+
* NULL PROTOTYPE — load-bearing. Callers index this map by a column name that
|
|
33
|
+
* ultimately comes from the database (`addJsonbCastsV3`, `filterColumnName`,
|
|
34
|
+
* the mutation transform). On a plain object literal, a column named
|
|
35
|
+
* `constructor` / `toString` / `valueOf` / `__proto__` resolves to an
|
|
36
|
+
* inherited `Object.prototype` member, which is truthy — so a *plaintext*
|
|
37
|
+
* column with such a name would be mistaken for a mapped encrypted column and
|
|
38
|
+
* its `Object.prototype` value interpolated into the emitted select string.
|
|
39
|
+
* `encryptedTable()` rejects such names as JS *properties*, but nothing
|
|
40
|
+
* constrains the DB column names a table may contain.
|
|
41
|
+
*/
|
|
42
|
+
buildColumnKeyMap() {
|
|
43
|
+
const map = /* @__PURE__ */ Object.create(null);
|
|
44
|
+
for (const [property, builder] of Object.entries(this.columnBuilders)) {
|
|
45
|
+
map[property] = builder.getName();
|
|
46
|
+
}
|
|
47
|
+
return map;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var RESERVED_TABLE_KEYS = /* @__PURE__ */ new Set([
|
|
51
|
+
"tableName",
|
|
52
|
+
"columnBuilders",
|
|
53
|
+
"_columnType",
|
|
54
|
+
"build",
|
|
55
|
+
"buildColumnKeyMap"
|
|
56
|
+
]);
|
|
57
|
+
function isReservedTableKey(tableBuilder, colName) {
|
|
58
|
+
return RESERVED_TABLE_KEYS.has(colName) || colName in tableBuilder;
|
|
59
|
+
}
|
|
60
|
+
function encryptedTable(tableName, columns) {
|
|
61
|
+
const tableBuilder = new EncryptedTable(
|
|
62
|
+
tableName,
|
|
63
|
+
columns
|
|
64
|
+
);
|
|
65
|
+
for (const [colName, colBuilder] of Object.entries(columns)) {
|
|
66
|
+
if (isReservedTableKey(tableBuilder, colName)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Column name "${colName}" collides with a reserved EncryptedTable property`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
;
|
|
72
|
+
tableBuilder[colName] = colBuilder;
|
|
73
|
+
}
|
|
74
|
+
return tableBuilder;
|
|
75
|
+
}
|
|
76
|
+
function buildEncryptConfig(...tables) {
|
|
77
|
+
const config = {
|
|
78
|
+
v: 1,
|
|
79
|
+
tables: {}
|
|
80
|
+
};
|
|
81
|
+
for (const tb of tables) {
|
|
82
|
+
const tableDef = tb.build();
|
|
83
|
+
if (Object.hasOwn(config.tables, tableDef.tableName)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`[eql/v3]: duplicate table name "${tableDef.tableName}" passed to buildEncryptConfig \u2014 each table must have a unique name`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
config.tables[tableDef.tableName] = tableDef.columns;
|
|
89
|
+
}
|
|
90
|
+
return config;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export {
|
|
94
|
+
EncryptedTable,
|
|
95
|
+
encryptedTable,
|
|
96
|
+
buildEncryptConfig
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=chunk-X3JRXEIB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/eql/v3/table.ts"],"sourcesContent":["import type { ColumnSchema, EncryptConfig } from '@/schema'\nimport type { Encrypted } from '@/types'\nimport type {\n EncryptedV3TableColumn,\n PlaintextForColumn,\n QueryTypesForColumn,\n} from './columns'\n\ninterface TableDefinition {\n tableName: string\n columns: Record<string, ColumnSchema>\n}\n\n/**\n * A v3 encrypted table. Mirrors the v2 `EncryptedTable` but only accepts v3\n * column builders. Emits the same `{ tableName, columns }` definition shape.\n */\nexport class EncryptedTable<T extends EncryptedV3TableColumn> {\n /** @internal Type-level brand so TypeScript can infer `T` from `EncryptedTable<T>`. */\n declare readonly _columnType: T\n\n constructor(\n public readonly tableName: string,\n public readonly columnBuilders: T,\n ) {}\n\n build(): TableDefinition {\n // NULL PROTOTYPE — load-bearing, for the same reason as\n // {@link buildColumnKeyMap}. Keys are DB column names, which the database\n // supplies. On a plain object literal, `builtColumns['__proto__'] = schema`\n // REPARENTS the object rather than adding an own key, so the column never\n // reaches the encrypt config — `decryptModel` then skips it and its\n // ciphertext is returned undecrypted. `encryptedTable()` rejects such names\n // as JS properties, but nothing constrains a table's DB column names.\n const builtColumns: Record<string, ColumnSchema> = Object.create(null)\n for (const builder of Object.values(this.columnBuilders)) {\n // Key by the column's DB name (`getName()`), NOT the JS property name.\n // `encrypt`/`decrypt` look columns up in the config by `column.getName()`,\n // so a camelCase JS key mapping to a snake_case DB column (e.g.\n // `createdOn: types.Date('created_on')`) must register under\n // `created_on` or the FFI reports \"column not found in Encrypt config\".\n const name = builder.getName()\n // Two JS properties resolving to the same DB name would silently overwrite\n // here (later wins), dropping the first column's config. Fail loudly —\n // matching the duplicate-tableName guard in buildEncryptConfig and the\n // reserved-key guard in encryptedTable.\n if (Object.hasOwn(builtColumns, name)) {\n throw new Error(\n `[eql/v3]: duplicate column name \"${name}\" in table \"${this.tableName}\" — two columns resolve to the same DB name`,\n )\n }\n builtColumns[name] = builder.build()\n }\n return {\n tableName: this.tableName,\n columns: builtColumns,\n }\n }\n\n /**\n * Map each column's JS property name to its DB column name (`getName()`).\n * The model path matches user models by property name but must address the\n * encrypt config and FFI by DB name — `build()` keys columns by DB name, so\n * the two only agree when property == name. This recovers the mapping that\n * `build()` discards.\n *\n * NULL PROTOTYPE — load-bearing. Callers index this map by a column name that\n * ultimately comes from the database (`addJsonbCastsV3`, `filterColumnName`,\n * the mutation transform). On a plain object literal, a column named\n * `constructor` / `toString` / `valueOf` / `__proto__` resolves to an\n * inherited `Object.prototype` member, which is truthy — so a *plaintext*\n * column with such a name would be mistaken for a mapped encrypted column and\n * its `Object.prototype` value interpolated into the emitted select string.\n * `encryptedTable()` rejects such names as JS *properties*, but nothing\n * constrains the DB column names a table may contain.\n */\n buildColumnKeyMap(): Record<string, string> {\n const map = Object.create(null) as Record<string, string>\n for (const [property, builder] of Object.entries(this.columnBuilders)) {\n map[property] = builder.getName()\n }\n return map\n }\n}\n\n/**\n * Own instance members of {@link EncryptedTable} that a column name must not\n * shadow. Because {@link encryptedTable} copies column builders onto the\n * instance for accessor access (`users.email`), a column named e.g. `build`\n * would otherwise overwrite the method that {@link buildEncryptConfig} relies\n * on. `_columnType` is a type-only `declare` (no runtime property) so it is\n * listed explicitly here rather than caught by the `in` check below.\n */\nconst RESERVED_TABLE_KEYS = new Set([\n 'tableName',\n 'columnBuilders',\n '_columnType',\n 'build',\n 'buildColumnKeyMap',\n])\n\n/**\n * Whether a column name would collide with a reserved property on the table\n * object — either an own member ({@link RESERVED_TABLE_KEYS}) or any inherited\n * `Object.prototype` member (`constructor`, `toString`, `valueOf`,\n * `hasOwnProperty`, …). The `in` check covers the prototype chain so assigning\n * the column as an own property can never shadow a prototype method/accessor.\n */\nfunction isReservedTableKey(tableBuilder: object, colName: string): boolean {\n return RESERVED_TABLE_KEYS.has(colName) || colName in tableBuilder\n}\n\n/**\n * Define a v3 encrypted table. Intentionally shadows the v2 `encryptedTable`\n * name but lives on the `/eql/v3` subpath — the importer picks the model by\n * import path. The returned object is also a column accessor (`users.email`).\n */\nexport function encryptedTable<T extends EncryptedV3TableColumn>(\n tableName: string,\n columns: T,\n): EncryptedTable<T> & T {\n const tableBuilder = new EncryptedTable(\n tableName,\n columns,\n ) as EncryptedTable<T> & T\n\n for (const [colName, colBuilder] of Object.entries(columns)) {\n if (isReservedTableKey(tableBuilder, colName)) {\n throw new Error(\n `Column name \"${colName}\" collides with a reserved EncryptedTable property`,\n )\n }\n ;(tableBuilder as EncryptedV3TableColumn)[colName] = colBuilder\n }\n\n return tableBuilder\n}\n\n/**\n * Build an `EncryptConfig` (`v: 1`) from one or more v3 tables. Emits the same\n * shape as v2's `buildEncryptConfig`.\n */\nexport function buildEncryptConfig(\n ...tables: Array<EncryptedTable<EncryptedV3TableColumn>>\n): EncryptConfig {\n const config: EncryptConfig = {\n v: 1,\n tables: {},\n }\n\n for (const tb of tables) {\n const tableDef = tb.build()\n // Config tables are keyed by name, so a duplicate would silently overwrite\n // the earlier table. Fail loudly instead. (v3-only additive guard; v2's\n // buildEncryptConfig keeps its existing silent-overwrite behavior.)\n if (Object.hasOwn(config.tables, tableDef.tableName)) {\n throw new Error(\n `[eql/v3]: duplicate table name \"${tableDef.tableName}\" passed to buildEncryptConfig — each table must have a unique name`,\n )\n }\n config.tables[tableDef.tableName] = tableDef.columns\n }\n\n return config\n}\n\n/**\n * Infer the plaintext (decrypted) shape from a v3 table schema. Each column maps\n * to the TypeScript type of its domain's `castAs` kind.\n */\nexport type InferPlaintext<T extends EncryptedTable<EncryptedV3TableColumn>> =\n T extends EncryptedTable<infer C>\n ? { [K in keyof C]: PlaintextForColumn<C[K]> }\n : never\n\n/**\n * Infer the encrypted shape from a v3 table schema. See {@link InferPlaintext}\n * for why no key-remap filter is needed in the flat single-type model.\n */\nexport type InferEncrypted<T extends EncryptedTable<EncryptedV3TableColumn>> =\n T extends EncryptedTable<infer C> ? { [K in keyof C]: Encrypted } : never\n\n// ---------------------------------------------------------------------------\n// Typed-client surface helpers (@cipherstash/stack/v3)\n// ---------------------------------------------------------------------------\n\n/** Any v3 table, regardless of its column map. */\nexport type AnyV3Table = EncryptedTable<EncryptedV3TableColumn>\n\n/** Union of the concrete column builders declared on a v3 table. */\nexport type ColumnsOf<T extends AnyV3Table> =\n T extends EncryptedTable<infer C> ? C[keyof C] : never\n\n/**\n * Union of the *queryable* column builders on a v3 table. Storage-only columns\n * (whose {@link QueryTypesForColumn} is `never`) are filtered out, so they can't\n * be passed to a query method.\n */\nexport type QueryableColumnsOf<T extends AnyV3Table> =\n T extends EncryptedTable<infer C>\n ? {\n [K in keyof C]: [QueryTypesForColumn<C[K]>] extends [never]\n ? never\n : C[K]\n }[keyof C]\n : never\n\n/**\n * The accepted input model for {@link import('@/encryption/v3').TypedEncryptionClient.encryptModel}.\n * `T` is inferred from the argument: keys that name a schema column are pinned to\n * the column's plaintext type (nullable if the field is nullable), so a wrong-typed\n * field fails assignability; all other keys pass through unchanged.\n */\nexport type V3ModelInput<Table extends AnyV3Table, T> = {\n [K in keyof T]: K extends keyof InferPlaintext<Table>\n ? null extends T[K]\n ? InferPlaintext<Table>[K] | null\n : InferPlaintext<Table>[K]\n : T[K]\n}\n\n/** The encrypted result model: schema columns become `Encrypted`, others pass through. */\nexport type V3EncryptedModel<Table extends AnyV3Table, T> = {\n [K in keyof T]: K extends keyof InferPlaintext<Table>\n ? null extends T[K]\n ? Encrypted | null\n : Encrypted\n : T[K]\n}\n\n/**\n * The decrypted result model: schema columns become their plaintext type, others\n * pass through. Structurally identical to {@link V3ModelInput} — decrypt yields\n * the same plaintext shape encrypt accepts — so it is aliased rather than\n * re-declared to keep the input and output shapes from silently drifting when\n * one copy is edited. The distinct name is kept for call-site readability.\n */\nexport type V3DecryptedModel<Table extends AnyV3Table, T> = V3ModelInput<\n Table,\n T\n>\n"],"mappings":";AAiBO,IAAM,iBAAN,MAAuD;AAAA,EAI5D,YACkB,WACA,gBAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA,EAGlB,QAAyB;AAQvB,UAAM,eAA6C,uBAAO,OAAO,IAAI;AACrE,eAAW,WAAW,OAAO,OAAO,KAAK,cAAc,GAAG;AAMxD,YAAM,OAAO,QAAQ,QAAQ;AAK7B,UAAI,OAAO,OAAO,cAAc,IAAI,GAAG;AACrC,cAAM,IAAI;AAAA,UACR,oCAAoC,IAAI,eAAe,KAAK,SAAS;AAAA,QACvE;AAAA,MACF;AACA,mBAAa,IAAI,IAAI,QAAQ,MAAM;AAAA,IACrC;AACA,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,SAAS;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,oBAA4C;AAC1C,UAAM,MAAM,uBAAO,OAAO,IAAI;AAC9B,eAAW,CAAC,UAAU,OAAO,KAAK,OAAO,QAAQ,KAAK,cAAc,GAAG;AACrE,UAAI,QAAQ,IAAI,QAAQ,QAAQ;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AACF;AAUA,IAAM,sBAAsB,oBAAI,IAAI;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AASD,SAAS,mBAAmB,cAAsB,SAA0B;AAC1E,SAAO,oBAAoB,IAAI,OAAO,KAAK,WAAW;AACxD;AAOO,SAAS,eACd,WACA,SACuB;AACvB,QAAM,eAAe,IAAI;AAAA,IACvB;AAAA,IACA;AAAA,EACF;AAEA,aAAW,CAAC,SAAS,UAAU,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC3D,QAAI,mBAAmB,cAAc,OAAO,GAAG;AAC7C,YAAM,IAAI;AAAA,QACR,gBAAgB,OAAO;AAAA,MACzB;AAAA,IACF;AACA;AAAC,IAAC,aAAwC,OAAO,IAAI;AAAA,EACvD;AAEA,SAAO;AACT;AAMO,SAAS,sBACX,QACY;AACf,QAAM,SAAwB;AAAA,IAC5B,GAAG;AAAA,IACH,QAAQ,CAAC;AAAA,EACX;AAEA,aAAW,MAAM,QAAQ;AACvB,UAAM,WAAW,GAAG,MAAM;AAI1B,QAAI,OAAO,OAAO,OAAO,QAAQ,SAAS,SAAS,GAAG;AACpD,YAAM,IAAI;AAAA,QACR,mCAAmC,SAAS,SAAS;AAAA,MACvD;AAAA,IACF;AACA,WAAO,OAAO,SAAS,SAAS,IAAI,SAAS;AAAA,EAC/C;AAEA,SAAO;AACT;","names":[]}
|