@remix-run/data-table 0.0.0 → 0.2.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/LICENSE +21 -0
- package/README.md +549 -2
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/lib/adapter.d.ts +550 -0
- package/dist/lib/adapter.d.ts.map +1 -0
- package/dist/lib/adapter.js +1 -0
- package/dist/lib/column.d.ts +193 -0
- package/dist/lib/column.d.ts.map +1 -0
- package/dist/lib/column.js +302 -0
- package/dist/lib/database/execution-context.d.ts +10 -0
- package/dist/lib/database/execution-context.d.ts.map +1 -0
- package/dist/lib/database/execution-context.js +1 -0
- package/dist/lib/database/helpers.d.ts +26 -0
- package/dist/lib/database/helpers.d.ts.map +1 -0
- package/dist/lib/database/helpers.js +116 -0
- package/dist/lib/database/query-execution.d.ts +7 -0
- package/dist/lib/database/query-execution.d.ts.map +1 -0
- package/dist/lib/database/query-execution.js +401 -0
- package/dist/lib/database/relations.d.ts +4 -0
- package/dist/lib/database/relations.d.ts.map +1 -0
- package/dist/lib/database/relations.js +207 -0
- package/dist/lib/database/write-lifecycle.d.ts +13 -0
- package/dist/lib/database/write-lifecycle.d.ts.map +1 -0
- package/dist/lib/database/write-lifecycle.js +279 -0
- package/dist/lib/database.d.ts +264 -0
- package/dist/lib/database.d.ts.map +1 -0
- package/dist/lib/database.js +319 -0
- package/dist/lib/errors.d.ts +59 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +76 -0
- package/dist/lib/inflection.d.ts +3 -0
- package/dist/lib/inflection.d.ts.map +1 -0
- package/dist/lib/inflection.js +56 -0
- package/dist/lib/migrations/filename.d.ts +12 -0
- package/dist/lib/migrations/filename.d.ts.map +1 -0
- package/dist/lib/migrations/filename.js +20 -0
- package/dist/lib/migrations/helpers.d.ts +11 -0
- package/dist/lib/migrations/helpers.d.ts.map +1 -0
- package/dist/lib/migrations/helpers.js +77 -0
- package/dist/lib/migrations/journal-store.d.ts +15 -0
- package/dist/lib/migrations/journal-store.d.ts.map +1 -0
- package/dist/lib/migrations/journal-store.js +83 -0
- package/dist/lib/migrations/registry.d.ts +27 -0
- package/dist/lib/migrations/registry.d.ts.map +1 -0
- package/dist/lib/migrations/registry.js +51 -0
- package/dist/lib/migrations/runner.d.ts +20 -0
- package/dist/lib/migrations/runner.d.ts.map +1 -0
- package/dist/lib/migrations/runner.js +273 -0
- package/dist/lib/migrations/schema-api.d.ts +7 -0
- package/dist/lib/migrations/schema-api.d.ts.map +1 -0
- package/dist/lib/migrations/schema-api.js +326 -0
- package/dist/lib/migrations-node.d.ts +17 -0
- package/dist/lib/migrations-node.d.ts.map +1 -0
- package/dist/lib/migrations-node.js +65 -0
- package/dist/lib/migrations.d.ts +292 -0
- package/dist/lib/migrations.d.ts.map +1 -0
- package/dist/lib/migrations.js +38 -0
- package/dist/lib/operators.d.ts +154 -0
- package/dist/lib/operators.d.ts.map +1 -0
- package/dist/lib/operators.js +218 -0
- package/dist/lib/query.d.ts +159 -0
- package/dist/lib/query.d.ts.map +1 -0
- package/dist/lib/query.js +401 -0
- package/dist/lib/references.d.ts +41 -0
- package/dist/lib/references.d.ts.map +1 -0
- package/dist/lib/references.js +33 -0
- package/dist/lib/sql-helpers.d.ts +50 -0
- package/dist/lib/sql-helpers.d.ts.map +1 -0
- package/dist/lib/sql-helpers.js +111 -0
- package/dist/lib/sql.d.ts +45 -0
- package/dist/lib/sql.d.ts.map +1 -0
- package/dist/lib/sql.js +65 -0
- package/dist/lib/table.d.ts +569 -0
- package/dist/lib/table.d.ts.map +1 -0
- package/dist/lib/table.js +519 -0
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +1 -0
- package/dist/migrations/node.d.ts +2 -0
- package/dist/migrations/node.d.ts.map +1 -0
- package/dist/migrations/node.js +1 -0
- package/dist/migrations.d.ts +8 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +5 -0
- package/dist/operators.d.ts +3 -0
- package/dist/operators.d.ts.map +1 -0
- package/dist/operators.js +1 -0
- package/dist/sql-helpers.d.ts +3 -0
- package/dist/sql-helpers.d.ts.map +1 -0
- package/dist/sql-helpers.js +1 -0
- package/package.json +56 -7
- package/src/index.ts +198 -0
- package/src/lib/adapter.ts +653 -0
- package/src/lib/column.ts +384 -0
- package/src/lib/database/execution-context.ts +15 -0
- package/src/lib/database/helpers.ts +216 -0
- package/src/lib/database/query-execution.ts +638 -0
- package/src/lib/database/relations.ts +332 -0
- package/src/lib/database/write-lifecycle.ts +487 -0
- package/src/lib/database.ts +856 -0
- package/src/lib/errors.ts +119 -0
- package/src/lib/inflection.ts +69 -0
- package/src/lib/migrations/filename.ts +25 -0
- package/src/lib/migrations/helpers.ts +108 -0
- package/src/lib/migrations/journal-store.ts +122 -0
- package/src/lib/migrations/registry.ts +62 -0
- package/src/lib/migrations/runner.ts +374 -0
- package/src/lib/migrations/schema-api.ts +417 -0
- package/src/lib/migrations-node.ts +71 -0
- package/src/lib/migrations.ts +328 -0
- package/src/lib/operators.ts +436 -0
- package/src/lib/query.ts +958 -0
- package/src/lib/references.ts +78 -0
- package/src/lib/sql-helpers.ts +146 -0
- package/src/lib/sql.ts +84 -0
- package/src/lib/table.ts +1309 -0
- package/src/lib/types.ts +3 -0
- package/src/migrations/node.ts +1 -0
- package/src/migrations.ts +26 -0
- package/src/operators.ts +18 -0
- package/src/sql-helpers.ts +9 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { DataTableAdapterError, DataTableQueryError } from "./errors.js";
|
|
2
|
+
import { executeOperation } from "./database/execution-context.js";
|
|
3
|
+
import { asQueryTableInput, getPrimaryKeyWhere, getPrimaryKeyWhereFromRow, normalizeOrderByInput, resolveCreateRowWhere, toWriteResult, } from "./database/helpers.js";
|
|
4
|
+
import { executeQuery } from "./database/query-execution.js";
|
|
5
|
+
import { bindQueryRuntime, query as createQuery } from "./query.js";
|
|
6
|
+
import { isSqlStatement, rawSql } from "./sql.js";
|
|
7
|
+
import { getTableName } from "./table.js";
|
|
8
|
+
const createInternalDatabase = Symbol('createInternalDatabase');
|
|
9
|
+
/**
|
|
10
|
+
* High-level database runtime used to build and execute data manipulation operations.
|
|
11
|
+
*
|
|
12
|
+
* Create instances directly with `new Database(adapter, options)` or use
|
|
13
|
+
* `createDatabase(adapter, options)` as a thin wrapper.
|
|
14
|
+
*/
|
|
15
|
+
export class Database {
|
|
16
|
+
#adapter;
|
|
17
|
+
#token;
|
|
18
|
+
#now;
|
|
19
|
+
#savepointCounter;
|
|
20
|
+
constructor(adapter, options) {
|
|
21
|
+
this.#adapter = adapter;
|
|
22
|
+
this.#now = options?.now ?? defaultNow;
|
|
23
|
+
this.#savepointCounter = { value: 0 };
|
|
24
|
+
}
|
|
25
|
+
static [createInternalDatabase](adapter, options, internal) {
|
|
26
|
+
let database = new Database(adapter, options);
|
|
27
|
+
database.#token = internal.token;
|
|
28
|
+
database.#savepointCounter = internal.savepointCounter;
|
|
29
|
+
return database;
|
|
30
|
+
}
|
|
31
|
+
get adapter() {
|
|
32
|
+
return this.#adapter;
|
|
33
|
+
}
|
|
34
|
+
now() {
|
|
35
|
+
return this.#now();
|
|
36
|
+
}
|
|
37
|
+
query(table) {
|
|
38
|
+
return createQuery(table)[bindQueryRuntime](this);
|
|
39
|
+
}
|
|
40
|
+
async create(table, values, options) {
|
|
41
|
+
let touch = options?.touch;
|
|
42
|
+
let query = this.query(asQueryTableInput(table));
|
|
43
|
+
if (options?.returnRow !== true) {
|
|
44
|
+
let result = await query.insert(values, { touch });
|
|
45
|
+
return toWriteResult(result);
|
|
46
|
+
}
|
|
47
|
+
if (this.#adapter.capabilities.returning) {
|
|
48
|
+
let result = (await query.insert(values, {
|
|
49
|
+
returning: '*',
|
|
50
|
+
touch,
|
|
51
|
+
}));
|
|
52
|
+
let row = result.row;
|
|
53
|
+
if (!row) {
|
|
54
|
+
throw new DataTableQueryError('create({ returnRow: true }) failed to return an inserted row');
|
|
55
|
+
}
|
|
56
|
+
if (!options.with) {
|
|
57
|
+
return row;
|
|
58
|
+
}
|
|
59
|
+
let where = getPrimaryKeyWhereFromRow(table, row);
|
|
60
|
+
let loaded = await this.findOne(table, {
|
|
61
|
+
where,
|
|
62
|
+
with: options.with,
|
|
63
|
+
});
|
|
64
|
+
if (!loaded) {
|
|
65
|
+
throw new DataTableQueryError('create({ returnRow: true }) failed to load inserted row');
|
|
66
|
+
}
|
|
67
|
+
return loaded;
|
|
68
|
+
}
|
|
69
|
+
let insertResult = await query.insert(values, { touch });
|
|
70
|
+
let where = resolveCreateRowWhere(table, values, toWriteResult(insertResult).insertId);
|
|
71
|
+
let loaded = await this.findOne(table, {
|
|
72
|
+
where,
|
|
73
|
+
with: options.with,
|
|
74
|
+
});
|
|
75
|
+
if (!loaded) {
|
|
76
|
+
throw new DataTableQueryError('create({ returnRow: true }) failed to load inserted row');
|
|
77
|
+
}
|
|
78
|
+
return loaded;
|
|
79
|
+
}
|
|
80
|
+
async createMany(table, values, options) {
|
|
81
|
+
let query = this.query(asQueryTableInput(table));
|
|
82
|
+
if (options?.returnRows === true) {
|
|
83
|
+
if (!this.#adapter.capabilities.returning) {
|
|
84
|
+
throw new DataTableQueryError('createMany({ returnRows: true }) is not supported by this adapter');
|
|
85
|
+
}
|
|
86
|
+
let result = (await query.insertMany(values, {
|
|
87
|
+
returning: '*',
|
|
88
|
+
touch: options.touch,
|
|
89
|
+
}));
|
|
90
|
+
return result.rows;
|
|
91
|
+
}
|
|
92
|
+
let result = await query.insertMany(values, {
|
|
93
|
+
touch: options?.touch,
|
|
94
|
+
});
|
|
95
|
+
return toWriteResult(result);
|
|
96
|
+
}
|
|
97
|
+
async find(table, value, options) {
|
|
98
|
+
if (value == null) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
let query = this.query(asQueryTableInput(table));
|
|
102
|
+
if (options?.with) {
|
|
103
|
+
return query.with(options.with).find(value);
|
|
104
|
+
}
|
|
105
|
+
return query.find(value);
|
|
106
|
+
}
|
|
107
|
+
async findOne(table, options) {
|
|
108
|
+
let query = this.query(asQueryTableInput(table)).where(options.where);
|
|
109
|
+
let orderBy = normalizeOrderByInput(options.orderBy);
|
|
110
|
+
for (let [column, direction] of orderBy) {
|
|
111
|
+
query = query.orderBy(column, direction);
|
|
112
|
+
}
|
|
113
|
+
if (options.with) {
|
|
114
|
+
return query.with(options.with).first();
|
|
115
|
+
}
|
|
116
|
+
return query.first();
|
|
117
|
+
}
|
|
118
|
+
async findMany(table, options) {
|
|
119
|
+
let query = this.query(asQueryTableInput(table));
|
|
120
|
+
if (options?.where) {
|
|
121
|
+
query = query.where(options.where);
|
|
122
|
+
}
|
|
123
|
+
let orderBy = normalizeOrderByInput(options?.orderBy);
|
|
124
|
+
for (let [column, direction] of orderBy) {
|
|
125
|
+
query = query.orderBy(column, direction);
|
|
126
|
+
}
|
|
127
|
+
if (options?.limit !== undefined) {
|
|
128
|
+
query = query.limit(options.limit);
|
|
129
|
+
}
|
|
130
|
+
if (options?.offset !== undefined) {
|
|
131
|
+
query = query.offset(options.offset);
|
|
132
|
+
}
|
|
133
|
+
if (options?.with) {
|
|
134
|
+
return query.with(options.with).all();
|
|
135
|
+
}
|
|
136
|
+
return query.all();
|
|
137
|
+
}
|
|
138
|
+
async count(table, options) {
|
|
139
|
+
let query = this.query(asQueryTableInput(table));
|
|
140
|
+
if (options?.where) {
|
|
141
|
+
query = query.where(options.where);
|
|
142
|
+
}
|
|
143
|
+
return query.count();
|
|
144
|
+
}
|
|
145
|
+
async update(table, value, changes, options) {
|
|
146
|
+
let where = getPrimaryKeyWhere(table, value);
|
|
147
|
+
if (this.#adapter.capabilities.returning) {
|
|
148
|
+
let updateResult = (await this.query(asQueryTableInput(table)).where(where).update(changes, {
|
|
149
|
+
touch: options?.touch,
|
|
150
|
+
returning: '*',
|
|
151
|
+
}));
|
|
152
|
+
let updatedRow = updateResult.rows[0];
|
|
153
|
+
if (!updatedRow) {
|
|
154
|
+
throw new DataTableQueryError('update() failed to find row for table "' + getTableName(table) + '"');
|
|
155
|
+
}
|
|
156
|
+
if (!options?.with) {
|
|
157
|
+
return updatedRow;
|
|
158
|
+
}
|
|
159
|
+
let loaded = await this.findOne(table, {
|
|
160
|
+
where: getPrimaryKeyWhereFromRow(table, updatedRow),
|
|
161
|
+
with: options.with,
|
|
162
|
+
});
|
|
163
|
+
if (!loaded) {
|
|
164
|
+
throw new DataTableQueryError('update() failed to find row for table "' + getTableName(table) + '"');
|
|
165
|
+
}
|
|
166
|
+
return loaded;
|
|
167
|
+
}
|
|
168
|
+
await this.query(asQueryTableInput(table)).where(where).update(changes, {
|
|
169
|
+
touch: options?.touch,
|
|
170
|
+
});
|
|
171
|
+
let loaded = await this.find(table, value, { with: options?.with });
|
|
172
|
+
if (!loaded) {
|
|
173
|
+
throw new DataTableQueryError('update() failed to find row for table "' + getTableName(table) + '"');
|
|
174
|
+
}
|
|
175
|
+
return loaded;
|
|
176
|
+
}
|
|
177
|
+
async updateMany(table, changes, options) {
|
|
178
|
+
let query = this.query(asQueryTableInput(table)).where(options.where);
|
|
179
|
+
let orderBy = normalizeOrderByInput(options.orderBy);
|
|
180
|
+
for (let [column, direction] of orderBy) {
|
|
181
|
+
query = query.orderBy(column, direction);
|
|
182
|
+
}
|
|
183
|
+
if (options.limit !== undefined) {
|
|
184
|
+
query = query.limit(options.limit);
|
|
185
|
+
}
|
|
186
|
+
if (options.offset !== undefined) {
|
|
187
|
+
query = query.offset(options.offset);
|
|
188
|
+
}
|
|
189
|
+
let result = await query.update(changes, { touch: options.touch });
|
|
190
|
+
return toWriteResult(result);
|
|
191
|
+
}
|
|
192
|
+
async delete(table, value) {
|
|
193
|
+
let where = getPrimaryKeyWhere(table, value);
|
|
194
|
+
let result = await this.query(asQueryTableInput(table)).where(where).delete();
|
|
195
|
+
return toWriteResult(result).affectedRows > 0;
|
|
196
|
+
}
|
|
197
|
+
async deleteMany(table, options) {
|
|
198
|
+
let query = this.query(asQueryTableInput(table)).where(options.where);
|
|
199
|
+
let orderBy = normalizeOrderByInput(options.orderBy);
|
|
200
|
+
for (let [column, direction] of orderBy) {
|
|
201
|
+
query = query.orderBy(column, direction);
|
|
202
|
+
}
|
|
203
|
+
if (options.limit !== undefined) {
|
|
204
|
+
query = query.limit(options.limit);
|
|
205
|
+
}
|
|
206
|
+
if (options.offset !== undefined) {
|
|
207
|
+
query = query.offset(options.offset);
|
|
208
|
+
}
|
|
209
|
+
let result = await query.delete();
|
|
210
|
+
return toWriteResult(result);
|
|
211
|
+
}
|
|
212
|
+
async exec(statementOrInput, values = []) {
|
|
213
|
+
if (typeof statementOrInput === 'string' || isSqlStatement(statementOrInput)) {
|
|
214
|
+
let sqlStatement = isSqlStatement(statementOrInput)
|
|
215
|
+
? statementOrInput
|
|
216
|
+
: rawSql(statementOrInput, values);
|
|
217
|
+
return this[executeOperation]({
|
|
218
|
+
kind: 'raw',
|
|
219
|
+
sql: sqlStatement,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return executeQuery(this, statementOrInput);
|
|
223
|
+
}
|
|
224
|
+
async transaction(callback, options) {
|
|
225
|
+
if (!this.#token) {
|
|
226
|
+
let token = await this.#adapter.beginTransaction(options);
|
|
227
|
+
let tx = Database[createInternalDatabase](this.#adapter, { now: this.#now }, {
|
|
228
|
+
token,
|
|
229
|
+
savepointCounter: this.#savepointCounter,
|
|
230
|
+
});
|
|
231
|
+
try {
|
|
232
|
+
let result = await callback(tx);
|
|
233
|
+
await this.#adapter.commitTransaction(token);
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
await this.#adapter.rollbackTransaction(token);
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (!this.#adapter.capabilities.savepoints) {
|
|
242
|
+
throw new DataTableQueryError('Nested transactions require adapter savepoint support');
|
|
243
|
+
}
|
|
244
|
+
let savepointName = 'sp_' + String(this.#savepointCounter.value);
|
|
245
|
+
this.#savepointCounter.value += 1;
|
|
246
|
+
await this.#adapter.createSavepoint(this.#token, savepointName);
|
|
247
|
+
try {
|
|
248
|
+
let result = await callback(this);
|
|
249
|
+
await this.#adapter.releaseSavepoint(this.#token, savepointName);
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
await this.#adapter.rollbackToSavepoint(this.#token, savepointName);
|
|
254
|
+
await this.#adapter.releaseSavepoint(this.#token, savepointName);
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async [executeOperation](operation) {
|
|
259
|
+
try {
|
|
260
|
+
return await this.#adapter.execute({
|
|
261
|
+
operation,
|
|
262
|
+
transaction: this.#token,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
throw new DataTableAdapterError('Adapter execution failed', {
|
|
267
|
+
cause: error,
|
|
268
|
+
metadata: {
|
|
269
|
+
dialect: this.#adapter.dialect,
|
|
270
|
+
operationKind: operation.kind,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Creates a database runtime from an adapter.
|
|
278
|
+
* Thin wrapper around `new Database(adapter, options)`.
|
|
279
|
+
* @param adapter Adapter implementation responsible for SQL execution.
|
|
280
|
+
* @param options Optional runtime options.
|
|
281
|
+
* @param options.now Clock function used for auto-managed timestamps.
|
|
282
|
+
* @returns A {@link Database} API instance.
|
|
283
|
+
* @example
|
|
284
|
+
* ```ts
|
|
285
|
+
* import { column as c, createDatabase, table } from 'remix/data-table'
|
|
286
|
+
*
|
|
287
|
+
* let users = table({
|
|
288
|
+
* name: 'users',
|
|
289
|
+
* columns: {
|
|
290
|
+
* id: c.integer(),
|
|
291
|
+
* email: c.varchar(255),
|
|
292
|
+
* },
|
|
293
|
+
* })
|
|
294
|
+
*
|
|
295
|
+
* let db = createDatabase(adapter)
|
|
296
|
+
* let rows = await db.query(users).where({ id: 1 }).all()
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
export function createDatabase(adapter, options) {
|
|
300
|
+
return new Database(adapter, options);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Creates a database runtime bound to an existing adapter transaction token.
|
|
304
|
+
* This is an internal helper used by the migration runner.
|
|
305
|
+
* @param adapter Adapter implementation responsible for SQL execution.
|
|
306
|
+
* @param token Active adapter transaction token.
|
|
307
|
+
* @param options Optional runtime options.
|
|
308
|
+
* @param options.now Clock function used for auto-managed timestamps.
|
|
309
|
+
* @returns A {@link Database} API instance bound to the provided transaction.
|
|
310
|
+
*/
|
|
311
|
+
export function createDatabaseWithTransaction(adapter, token, options) {
|
|
312
|
+
return Database[createInternalDatabase](adapter, options, {
|
|
313
|
+
token,
|
|
314
|
+
savepointCounter: { value: 0 },
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function defaultNow() {
|
|
318
|
+
return new Date();
|
|
319
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error for all `data-table` failures.
|
|
3
|
+
*/
|
|
4
|
+
export declare class DataTableError extends Error {
|
|
5
|
+
/**
|
|
6
|
+
* Stable error code identifying the failure category.
|
|
7
|
+
*/
|
|
8
|
+
code: string;
|
|
9
|
+
/**
|
|
10
|
+
* Optional structured metadata attached to the failure.
|
|
11
|
+
*/
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
constructor(message: string, options?: {
|
|
14
|
+
code?: string;
|
|
15
|
+
cause?: unknown;
|
|
16
|
+
metadata?: Record<string, unknown>;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Thrown when input data fails schema validation.
|
|
21
|
+
*/
|
|
22
|
+
export declare class DataTableValidationError extends DataTableError {
|
|
23
|
+
/**
|
|
24
|
+
* Validation issues reported by the schema validator.
|
|
25
|
+
*/
|
|
26
|
+
issues: ReadonlyArray<unknown>;
|
|
27
|
+
constructor(message: string, issues: ReadonlyArray<unknown>, options?: {
|
|
28
|
+
cause?: unknown;
|
|
29
|
+
metadata?: Record<string, unknown>;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Thrown when a query is invalid for the current builder state.
|
|
34
|
+
*/
|
|
35
|
+
export declare class DataTableQueryError extends DataTableError {
|
|
36
|
+
constructor(message: string, options?: {
|
|
37
|
+
cause?: unknown;
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Thrown when adapter execution fails.
|
|
43
|
+
*/
|
|
44
|
+
export declare class DataTableAdapterError extends DataTableError {
|
|
45
|
+
constructor(message: string, options?: {
|
|
46
|
+
cause?: unknown;
|
|
47
|
+
metadata?: Record<string, unknown>;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Thrown when a database constraint is violated.
|
|
52
|
+
*/
|
|
53
|
+
export declare class DataTableConstraintError extends DataTableError {
|
|
54
|
+
constructor(message: string, options?: {
|
|
55
|
+
cause?: unknown;
|
|
56
|
+
metadata?: Record<string, unknown>;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;IAEZ;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAElC,YACE,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACnC,EAMF;CACF;AAED;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,cAAc;IAC1D;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IAE9B,YACE,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACnC,EAUF;CACF;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,cAAc;IACrD,YACE,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACnC,EASF;CACF;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,cAAc;IACvD,YACE,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACnC,EASF;CACF;AAED;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,cAAc;IAC1D,YACE,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACnC,EASF;CACF"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error for all `data-table` failures.
|
|
3
|
+
*/
|
|
4
|
+
export class DataTableError extends Error {
|
|
5
|
+
/**
|
|
6
|
+
* Stable error code identifying the failure category.
|
|
7
|
+
*/
|
|
8
|
+
code;
|
|
9
|
+
/**
|
|
10
|
+
* Optional structured metadata attached to the failure.
|
|
11
|
+
*/
|
|
12
|
+
metadata;
|
|
13
|
+
constructor(message, options) {
|
|
14
|
+
super(message, { cause: options?.cause });
|
|
15
|
+
this.name = 'DataTableError';
|
|
16
|
+
this.code = options?.code ?? 'DATA_TABLE_ERROR';
|
|
17
|
+
this.metadata = options?.metadata;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Thrown when input data fails schema validation.
|
|
22
|
+
*/
|
|
23
|
+
export class DataTableValidationError extends DataTableError {
|
|
24
|
+
/**
|
|
25
|
+
* Validation issues reported by the schema validator.
|
|
26
|
+
*/
|
|
27
|
+
issues;
|
|
28
|
+
constructor(message, issues, options) {
|
|
29
|
+
super(message, {
|
|
30
|
+
code: 'DATA_TABLE_VALIDATION_ERROR',
|
|
31
|
+
cause: options?.cause,
|
|
32
|
+
metadata: options?.metadata,
|
|
33
|
+
});
|
|
34
|
+
this.name = 'DataTableValidationError';
|
|
35
|
+
this.issues = issues;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Thrown when a query is invalid for the current builder state.
|
|
40
|
+
*/
|
|
41
|
+
export class DataTableQueryError extends DataTableError {
|
|
42
|
+
constructor(message, options) {
|
|
43
|
+
super(message, {
|
|
44
|
+
code: 'DATA_TABLE_QUERY_ERROR',
|
|
45
|
+
cause: options?.cause,
|
|
46
|
+
metadata: options?.metadata,
|
|
47
|
+
});
|
|
48
|
+
this.name = 'DataTableQueryError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Thrown when adapter execution fails.
|
|
53
|
+
*/
|
|
54
|
+
export class DataTableAdapterError extends DataTableError {
|
|
55
|
+
constructor(message, options) {
|
|
56
|
+
super(message, {
|
|
57
|
+
code: 'DATA_TABLE_ADAPTER_ERROR',
|
|
58
|
+
cause: options?.cause,
|
|
59
|
+
metadata: options?.metadata,
|
|
60
|
+
});
|
|
61
|
+
this.name = 'DataTableAdapterError';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Thrown when a database constraint is violated.
|
|
66
|
+
*/
|
|
67
|
+
export class DataTableConstraintError extends DataTableError {
|
|
68
|
+
constructor(message, options) {
|
|
69
|
+
super(message, {
|
|
70
|
+
code: 'DATA_TABLE_CONSTRAINT_ERROR',
|
|
71
|
+
cause: options?.cause,
|
|
72
|
+
metadata: options?.metadata,
|
|
73
|
+
});
|
|
74
|
+
this.name = 'DataTableConstraintError';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inflection.d.ts","sourceRoot":"","sources":["../../src/lib/inflection.ts"],"names":[],"mappings":"AAoBA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAwBhD;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAUzD"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const IRREGULAR_SINGULAR_FORMS = {
|
|
2
|
+
people: 'person',
|
|
3
|
+
men: 'man',
|
|
4
|
+
women: 'woman',
|
|
5
|
+
children: 'child',
|
|
6
|
+
teeth: 'tooth',
|
|
7
|
+
feet: 'foot',
|
|
8
|
+
geese: 'goose',
|
|
9
|
+
mice: 'mouse',
|
|
10
|
+
data: 'datum',
|
|
11
|
+
media: 'medium',
|
|
12
|
+
indices: 'index',
|
|
13
|
+
vertices: 'vertex',
|
|
14
|
+
analyses: 'analysis',
|
|
15
|
+
statuses: 'status',
|
|
16
|
+
categories: 'category',
|
|
17
|
+
companies: 'company',
|
|
18
|
+
addresses: 'address',
|
|
19
|
+
};
|
|
20
|
+
export function singularize(word) {
|
|
21
|
+
let lower = word.toLowerCase();
|
|
22
|
+
if (IRREGULAR_SINGULAR_FORMS[lower]) {
|
|
23
|
+
return preserveCase(word, IRREGULAR_SINGULAR_FORMS[lower]);
|
|
24
|
+
}
|
|
25
|
+
if (lower.endsWith('ies') && lower.length > 3) {
|
|
26
|
+
return preserveCase(word, word.slice(0, -3) + 'y');
|
|
27
|
+
}
|
|
28
|
+
if (lower.endsWith('sses') || lower.endsWith('shes') || lower.endsWith('ches')) {
|
|
29
|
+
return preserveCase(word, word.slice(0, -2));
|
|
30
|
+
}
|
|
31
|
+
if (lower.endsWith('xes') || lower.endsWith('zes')) {
|
|
32
|
+
return preserveCase(word, word.slice(0, -2));
|
|
33
|
+
}
|
|
34
|
+
if (lower.endsWith('s') && !lower.endsWith('ss')) {
|
|
35
|
+
return preserveCase(word, word.slice(0, -1));
|
|
36
|
+
}
|
|
37
|
+
return word;
|
|
38
|
+
}
|
|
39
|
+
export function inferForeignKey(tableName) {
|
|
40
|
+
let segments = tableName.split('_');
|
|
41
|
+
let tail = segments.pop() ?? tableName;
|
|
42
|
+
let singularTail = singularize(tail);
|
|
43
|
+
if (segments.length === 0) {
|
|
44
|
+
return singularTail + '_id';
|
|
45
|
+
}
|
|
46
|
+
return [...segments, singularTail + '_id'].join('_');
|
|
47
|
+
}
|
|
48
|
+
function preserveCase(source, replacement) {
|
|
49
|
+
if (source.length === 0) {
|
|
50
|
+
return replacement;
|
|
51
|
+
}
|
|
52
|
+
if (source[0] === source[0].toUpperCase()) {
|
|
53
|
+
return replacement[0].toUpperCase() + replacement.slice(1);
|
|
54
|
+
}
|
|
55
|
+
return replacement;
|
|
56
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a migration filename into `{ id, name }`.
|
|
3
|
+
*
|
|
4
|
+
* Expected format: `YYYYMMDDHHmmss_name.(ts|js|mts|mjs|cts|cjs)`.
|
|
5
|
+
* @param filename Migration file basename.
|
|
6
|
+
* @returns Parsed migration id and name.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseMigrationFilename(filename: string): {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=filename.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../../../src/lib/migrations/filename.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAerF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const migrationFilenamePattern = /^(\d{14})_(.+)\.(?:m?ts|m?js|cts|cjs)$/;
|
|
2
|
+
/**
|
|
3
|
+
* Parses a migration filename into `{ id, name }`.
|
|
4
|
+
*
|
|
5
|
+
* Expected format: `YYYYMMDDHHmmss_name.(ts|js|mts|mjs|cts|cjs)`.
|
|
6
|
+
* @param filename Migration file basename.
|
|
7
|
+
* @returns Parsed migration id and name.
|
|
8
|
+
*/
|
|
9
|
+
export function parseMigrationFilename(filename) {
|
|
10
|
+
let match = filename.match(migrationFilenamePattern);
|
|
11
|
+
if (!match) {
|
|
12
|
+
throw new Error('Invalid migration filename "' +
|
|
13
|
+
filename +
|
|
14
|
+
'". Expected format YYYYMMDDHHmmss_name.ts (or .js/.mts/.mjs/.cts/.cjs)');
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
id: match[1],
|
|
18
|
+
name: match[2],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TableRef } from '../adapter.ts';
|
|
2
|
+
import type { IndexColumns, KeyColumns } from '../migrations.ts';
|
|
3
|
+
export declare function toTableRef(name: string): TableRef;
|
|
4
|
+
export declare function normalizeIndexColumns(columns: IndexColumns): string[];
|
|
5
|
+
export declare function normalizeKeyColumns(columns: KeyColumns): string[];
|
|
6
|
+
export declare function createPrimaryKeyName(table: TableRef): string;
|
|
7
|
+
export declare function createUniqueName(table: TableRef, columns: string[]): string;
|
|
8
|
+
export declare function createForeignKeyName(table: TableRef, columns: string[], references: TableRef, referenceColumns: string[]): string;
|
|
9
|
+
export declare function createCheckName(table: TableRef, expression: string): string;
|
|
10
|
+
export declare function createIndexName(table: TableRef, columns: string[]): string;
|
|
11
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/migrations/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAEhE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAWjD;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,EAAE,CAErE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,EAAE,CAMjE;AAgDD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAE5D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAE3E;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,MAAM,EAAE,EACjB,UAAU,EAAE,QAAQ,EACpB,gBAAgB,EAAE,MAAM,EAAE,GACzB,MAAM,CAWR;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAG3E;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAE1E"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export function toTableRef(name) {
|
|
2
|
+
let segments = name.split('.');
|
|
3
|
+
if (segments.length === 1) {
|
|
4
|
+
return { name };
|
|
5
|
+
}
|
|
6
|
+
return {
|
|
7
|
+
schema: segments[0],
|
|
8
|
+
name: segments.slice(1).join('.'),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function normalizeIndexColumns(columns) {
|
|
12
|
+
return normalizeKeyColumns(columns);
|
|
13
|
+
}
|
|
14
|
+
export function normalizeKeyColumns(columns) {
|
|
15
|
+
if (Array.isArray(columns)) {
|
|
16
|
+
return [...columns];
|
|
17
|
+
}
|
|
18
|
+
return [columns];
|
|
19
|
+
}
|
|
20
|
+
function normalizeNamePart(value) {
|
|
21
|
+
let normalized = value
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
24
|
+
.replace(/^_+|_+$/g, '');
|
|
25
|
+
if (normalized.length === 0) {
|
|
26
|
+
return 'item';
|
|
27
|
+
}
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
function tableNamePart(table) {
|
|
31
|
+
if (table.schema) {
|
|
32
|
+
return normalizeNamePart(table.schema + '_' + table.name);
|
|
33
|
+
}
|
|
34
|
+
return normalizeNamePart(table.name);
|
|
35
|
+
}
|
|
36
|
+
function hashString(value) {
|
|
37
|
+
let hash = 5381;
|
|
38
|
+
for (let index = 0; index < value.length; index++) {
|
|
39
|
+
hash = ((hash << 5) + hash) ^ value.charCodeAt(index);
|
|
40
|
+
}
|
|
41
|
+
return hash >>> 0;
|
|
42
|
+
}
|
|
43
|
+
function withNameLimit(name) {
|
|
44
|
+
let limit = 63;
|
|
45
|
+
if (name.length <= limit) {
|
|
46
|
+
return name;
|
|
47
|
+
}
|
|
48
|
+
let suffix = hashString(name).toString(36).padStart(8, '0').slice(0, 8);
|
|
49
|
+
return name.slice(0, limit - 9) + '_' + suffix;
|
|
50
|
+
}
|
|
51
|
+
function columnsNamePart(columns) {
|
|
52
|
+
return columns.map((column) => normalizeNamePart(column)).join('_');
|
|
53
|
+
}
|
|
54
|
+
export function createPrimaryKeyName(table) {
|
|
55
|
+
return withNameLimit(tableNamePart(table) + '_pk');
|
|
56
|
+
}
|
|
57
|
+
export function createUniqueName(table, columns) {
|
|
58
|
+
return withNameLimit(tableNamePart(table) + '_' + columnsNamePart(columns) + '_uq');
|
|
59
|
+
}
|
|
60
|
+
export function createForeignKeyName(table, columns, references, referenceColumns) {
|
|
61
|
+
let base = tableNamePart(table) +
|
|
62
|
+
'_' +
|
|
63
|
+
columnsNamePart(columns) +
|
|
64
|
+
'_' +
|
|
65
|
+
tableNamePart(references) +
|
|
66
|
+
'_' +
|
|
67
|
+
columnsNamePart(referenceColumns) +
|
|
68
|
+
'_fk';
|
|
69
|
+
return withNameLimit(base);
|
|
70
|
+
}
|
|
71
|
+
export function createCheckName(table, expression) {
|
|
72
|
+
let suffix = hashString(expression).toString(36);
|
|
73
|
+
return withNameLimit(tableNamePart(table) + '_chk_' + suffix);
|
|
74
|
+
}
|
|
75
|
+
export function createIndexName(table, columns) {
|
|
76
|
+
return withNameLimit(tableNamePart(table) + '_' + columnsNamePart(columns) + '_idx');
|
|
77
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DatabaseAdapter, TransactionToken } from '../adapter.ts';
|
|
2
|
+
import type { MigrationDescriptor, MigrationJournalRow } from '../migrations.ts';
|
|
3
|
+
export declare function normalizeChecksum(migration: MigrationDescriptor): string;
|
|
4
|
+
export declare function ensureMigrationJournal(adapter: DatabaseAdapter, tableName: string): Promise<void>;
|
|
5
|
+
export declare function hasMigrationJournal(adapter: DatabaseAdapter, tableName: string): Promise<boolean>;
|
|
6
|
+
export declare function loadJournalRows(adapter: DatabaseAdapter, tableName: string): Promise<MigrationJournalRow[]>;
|
|
7
|
+
export declare function insertJournalRow(adapter: DatabaseAdapter, tableName: string, row: {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
checksum: string;
|
|
11
|
+
batch: number;
|
|
12
|
+
}, transaction?: TransactionToken): Promise<void>;
|
|
13
|
+
export declare function deleteJournalRow(adapter: DatabaseAdapter, tableName: string, id: string, transaction?: TransactionToken): Promise<void>;
|
|
14
|
+
export declare function getBatch(rows: MigrationJournalRow[]): number;
|
|
15
|
+
//# sourceMappingURL=journal-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal-store.d.ts","sourceRoot":"","sources":["../../../src/lib/migrations/journal-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAEhF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,mBAAmB,GAAG,MAAM,CAMxE;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAalB;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAmBhC;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE;IACH,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd,EACD,WAAW,CAAC,EAAE,gBAAgB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,EACV,WAAW,CAAC,EAAE,gBAAgB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,mBAAmB,EAAE,GAAG,MAAM,CAO5D"}
|