@bockit/sequel 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +191 -2
  3. package/THIRD_PARTY_NOTICES.md +268 -0
  4. package/binaries.json +106 -0
  5. package/dist/binaries/cache.d.ts +6 -0
  6. package/dist/binaries/cache.js +36 -0
  7. package/dist/binaries/ensure-binary.d.ts +10 -0
  8. package/dist/binaries/ensure-binary.js +85 -0
  9. package/dist/binaries/manifest.d.ts +30 -0
  10. package/dist/binaries/manifest.js +112 -0
  11. package/dist/binaries/platform.d.ts +3 -0
  12. package/dist/binaries/platform.js +10 -0
  13. package/dist/binaries/unpack.d.ts +2 -0
  14. package/dist/binaries/unpack.js +35 -0
  15. package/dist/binaries/verify.d.ts +2 -0
  16. package/dist/binaries/verify.js +16 -0
  17. package/dist/check.d.ts +9 -0
  18. package/dist/check.js +166 -0
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.js +324 -0
  21. package/dist/config/define-config.d.ts +23 -0
  22. package/dist/config/define-config.js +4 -0
  23. package/dist/config/load-config.d.ts +6 -0
  24. package/dist/config/load-config.js +34 -0
  25. package/dist/config/resolve-config.d.ts +34 -0
  26. package/dist/config/resolve-config.js +144 -0
  27. package/dist/db/connection.d.ts +6 -0
  28. package/dist/db/connection.js +38 -0
  29. package/dist/db/shadow.d.ts +11 -0
  30. package/dist/db/shadow.js +68 -0
  31. package/dist/engine/client.d.ts +24 -0
  32. package/dist/engine/client.js +123 -0
  33. package/dist/engine/diff.d.ts +12 -0
  34. package/dist/engine/diff.js +51 -0
  35. package/dist/errors.d.ts +3 -0
  36. package/dist/errors.js +5 -0
  37. package/dist/extras/snapshot.d.ts +3 -0
  38. package/dist/extras/snapshot.js +99 -0
  39. package/dist/files.d.ts +1 -0
  40. package/dist/files.js +15 -0
  41. package/dist/generate/generate-modules.d.ts +17 -0
  42. package/dist/generate/generate-modules.js +97 -0
  43. package/dist/generate/modules.d.ts +16 -0
  44. package/dist/generate/modules.js +118 -0
  45. package/dist/generate/pg-dump.d.ts +8 -0
  46. package/dist/generate/pg-dump.js +66 -0
  47. package/dist/generate/plugin-wrapper.d.ts +3 -0
  48. package/dist/generate/plugin-wrapper.js +20 -0
  49. package/dist/generate/sqlc.d.ts +27 -0
  50. package/dist/generate/sqlc.js +113 -0
  51. package/dist/generate/watch.d.ts +16 -0
  52. package/dist/generate/watch.js +73 -0
  53. package/dist/generator/generate.d.ts +21 -0
  54. package/dist/generator/generate.js +440 -0
  55. package/dist/generator/keys.d.ts +4 -0
  56. package/dist/generator/keys.js +24 -0
  57. package/dist/generator/lexer.d.ts +12 -0
  58. package/dist/generator/lexer.js +210 -0
  59. package/dist/generator/main.d.ts +1 -0
  60. package/dist/generator/main.js +12 -0
  61. package/dist/generator/pg-array-helper.d.ts +2 -0
  62. package/dist/generator/pg-array-helper.js +53 -0
  63. package/dist/generator/plugin.d.ts +6 -0
  64. package/dist/generator/plugin.js +26 -0
  65. package/dist/generator/request.d.ts +57 -0
  66. package/dist/generator/request.js +96 -0
  67. package/dist/generator/select-list.d.ts +27 -0
  68. package/dist/generator/select-list.js +580 -0
  69. package/dist/generator/strings.d.ts +2 -0
  70. package/dist/generator/strings.js +34 -0
  71. package/dist/generator/types.d.ts +23 -0
  72. package/dist/generator/types.js +216 -0
  73. package/dist/index.d.ts +2 -0
  74. package/dist/index.js +2 -0
  75. package/dist/migrate/adopt-prisma.d.ts +23 -0
  76. package/dist/migrate/adopt-prisma.js +119 -0
  77. package/dist/migrate/deploy.d.ts +7 -0
  78. package/dist/migrate/deploy.js +23 -0
  79. package/dist/migrate/drift.d.ts +10 -0
  80. package/dist/migrate/drift.js +36 -0
  81. package/dist/migrate/files.d.ts +19 -0
  82. package/dist/migrate/files.js +97 -0
  83. package/dist/migrate/history.d.ts +20 -0
  84. package/dist/migrate/history.js +54 -0
  85. package/dist/migrate/lint.d.ts +21 -0
  86. package/dist/migrate/lint.js +153 -0
  87. package/dist/migrate/new.d.ts +13 -0
  88. package/dist/migrate/new.js +41 -0
  89. package/dist/migrate/runner.d.ts +28 -0
  90. package/dist/migrate/runner.js +124 -0
  91. package/dist/migrate/sql-text.d.ts +33 -0
  92. package/dist/migrate/sql-text.js +233 -0
  93. package/dist/schema/schema.d.ts +16 -0
  94. package/dist/schema/schema.js +111 -0
  95. package/dist/schema/unsupported.d.ts +3 -0
  96. package/dist/schema/unsupported.js +36 -0
  97. package/dist/schema/wasm.d.ts +8 -0
  98. package/dist/schema/wasm.js +32 -0
  99. package/package.json +50 -4
  100. package/vendor/prisma-schema-wasm/LICENSE +201 -0
  101. package/vendor/prisma-schema-wasm/README.md +32 -0
  102. package/vendor/prisma-schema-wasm/package.json +3 -0
  103. package/vendor/prisma-schema-wasm/prisma_schema_build.js +539 -0
  104. package/vendor/prisma-schema-wasm/prisma_schema_build_bg.wasm +0 -0
@@ -0,0 +1,440 @@
1
+ import { camelCase, propertyAccess, propertyName } from './keys.js';
2
+ import { HELPER_NAME, PG_ARRAY_HELPER } from './pg-array-helper.js';
3
+ import { arrayAggElementsNotNull, coalesce, isNeverNullCall, isNullTest, selectList, } from './select-list.js';
4
+ import { templateLiteral } from './strings.js';
5
+ import { isJson, needsParse, paramType, resolveType, rowType } from './types.js';
6
+ // R17: names a function can't have.
7
+ const RESERVED = new Set([
8
+ 'await',
9
+ 'break',
10
+ 'case',
11
+ 'catch',
12
+ 'class',
13
+ 'const',
14
+ 'continue',
15
+ 'debugger',
16
+ 'default',
17
+ 'delete',
18
+ 'do',
19
+ 'else',
20
+ 'enum',
21
+ 'export',
22
+ 'extends',
23
+ 'false',
24
+ 'finally',
25
+ 'for',
26
+ 'function',
27
+ 'if',
28
+ 'implements',
29
+ 'import',
30
+ 'in',
31
+ 'instanceof',
32
+ 'interface',
33
+ 'let',
34
+ 'new',
35
+ 'null',
36
+ 'package',
37
+ 'private',
38
+ 'protected',
39
+ 'public',
40
+ 'return',
41
+ 'static',
42
+ 'super',
43
+ 'switch',
44
+ 'this',
45
+ 'throw',
46
+ 'true',
47
+ 'try',
48
+ 'typeof',
49
+ 'var',
50
+ 'void',
51
+ 'while',
52
+ 'with',
53
+ 'yield',
54
+ 'arguments',
55
+ 'eval',
56
+ ]);
57
+ const SUPPORTED_KINDS = new Set([':one', ':many', ':exec', ':execrows']);
58
+ // R22: the fix for each kind sqlc accepts and sequel doesn't.
59
+ const UNSUPPORTED_KINDS = {
60
+ ':execresult': 'Use :exec or :execrows.',
61
+ ':execlastid': 'MySQL only. Use :one with RETURNING.',
62
+ ':copyfrom': 'Use :exec with INSERT … SELECT … FROM unnest(…) AS u(…).',
63
+ ':batchexec': 'pg has no pipelining. Use :exec, :many or :one.',
64
+ ':batchmany': 'pg has no pipelining. Use :exec, :many or :one.',
65
+ ':batchone': 'pg has no pipelining. Use :exec, :many or :one.',
66
+ };
67
+ // R3: plugin options are the base64 of a JSON object, or empty for the defaults.
68
+ export function readOptions(encoded) {
69
+ const options = { bigint: 'string' };
70
+ if (encoded === '')
71
+ return { options, failures: [] };
72
+ let value;
73
+ try {
74
+ value = JSON.parse(Buffer.from(encoded, 'base64').toString('utf8'));
75
+ }
76
+ catch {
77
+ return { options, failures: ['invalid plugin options: not a JSON object'] };
78
+ }
79
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
80
+ return { options, failures: ['invalid plugin options: not a JSON object'] };
81
+ }
82
+ const failures = [];
83
+ for (const [key, setting] of Object.entries(value).sort(([a], [b]) => compare(a, b))) {
84
+ if (key === 'bigint' &&
85
+ (setting === 'string' || setting === 'number' || setting === 'bigint')) {
86
+ options.bigint = setting;
87
+ }
88
+ else {
89
+ failures.push(`invalid plugin option ${key}`);
90
+ }
91
+ }
92
+ return { options, failures };
93
+ }
94
+ export function generate(request) {
95
+ const { options, failures: optionFailures } = readOptions(request.plugin_options);
96
+ const failures = optionFailures.map((message) => ({ file: '', order: -1, message }));
97
+ // R7: one module per query file, in the order the request lists the queries (R9).
98
+ const groups = new Map();
99
+ request.queries.forEach((query, order) => {
100
+ const group = groups.get(query.filename) ?? [];
101
+ group.push({ query, order });
102
+ groups.set(query.filename, group);
103
+ });
104
+ const files = [];
105
+ for (const [filename, group] of groups) {
106
+ const fail = (order, message) => failures.push({ file: filename, order, message });
107
+ if (!filename.endsWith('.sql')) {
108
+ fail(-1, `${filename}: query file name must end in .sql`);
109
+ }
110
+ const prepared = [];
111
+ const functions = new Map();
112
+ for (const { query, order } of group) {
113
+ const queryFailures = [];
114
+ const result = prepare(query, request.catalog, options.bigint, functions, queryFailures);
115
+ for (const message of queryFailures)
116
+ fail(order, `${filename}: ${query.name}: ${message}`);
117
+ if (result !== null && queryFailures.length === 0)
118
+ prepared.push(result);
119
+ }
120
+ if (filename.endsWith('.sql')) {
121
+ files.push({ name: `${filename.slice(0, -4)}.ts`, contents: emitModule(filename, prepared) });
122
+ }
123
+ }
124
+ if (failures.length > 0) {
125
+ // R56: by file name, then the query's order in the request, then position, which is the
126
+ // order each query's checks report in.
127
+ const sorted = failures
128
+ .map((failure, index) => ({ ...failure, index }))
129
+ .sort((a, b) => compare(a.file, b.file) || a.order - b.order || a.index - b.index);
130
+ return { ok: false, failures: sorted.map((failure) => failure.message) };
131
+ }
132
+ files.sort((a, b) => compare(a.name, b.name));
133
+ return { ok: true, files };
134
+ }
135
+ // Code-unit order, as R4 asks.
136
+ function compare(a, b) {
137
+ return a < b ? -1 : a > b ? 1 : 0;
138
+ }
139
+ function prepare(query, catalog, bigint, functions, failures) {
140
+ // R16, R17: names.
141
+ const fn = firstChar(query.name, (c) => c.toLowerCase());
142
+ const base = firstChar(query.name, (c) => c.toUpperCase());
143
+ if (RESERVED.has(fn)) {
144
+ failures.push(`the function name ${fn} is reserved. Rename the query.`);
145
+ }
146
+ else if (fn === HELPER_NAME) {
147
+ failures.push(`the function name ${fn} is reserved. Rename the query.`);
148
+ }
149
+ else {
150
+ const other = functions.get(fn);
151
+ if (other !== undefined) {
152
+ failures.push(`gives the same function name as ${other}. Rename the query.`);
153
+ }
154
+ else {
155
+ functions.set(fn, query.name);
156
+ }
157
+ }
158
+ // R22, R23: kinds.
159
+ const cmd = query.cmd;
160
+ if (!SUPPORTED_KINDS.has(cmd)) {
161
+ const fix = UNSUPPORTED_KINDS[cmd] ?? 'Use :one, :many, :exec or :execrows.';
162
+ failures.push(`${cmd} is not supported. ${fix}`);
163
+ }
164
+ const returnsRows = cmd === ':one' || cmd === ':many';
165
+ if (returnsRows && query.columns.length === 0) {
166
+ failures.push('returns no columns. Use :exec or :execrows.');
167
+ }
168
+ const params = prepareParams(query, catalog, failures);
169
+ const columns = prepareColumns(query, catalog, bigint, returnsRows, failures);
170
+ const doc = jsdoc(query.comments);
171
+ if (failures.length > 0 || params === null || columns === null)
172
+ return null;
173
+ return { name: query.name, fn, base, cmd, text: query.text, doc, params, columns };
174
+ }
175
+ function firstChar(name, change) {
176
+ const [first = '', ...rest] = Array.from(name);
177
+ return change(first) + rest.join('');
178
+ }
179
+ // R27–R34.
180
+ function prepareParams(query, catalog, failures) {
181
+ const sorted = [...query.params].sort((a, b) => a.number - b.number);
182
+ const numbered = sorted.every((param, i) => param.number === i + 1);
183
+ if (!numbered) {
184
+ failures.push(`parameter numbers are not 1 to ${sorted.length}.`);
185
+ }
186
+ if (sorted.some((param) => param.column?.is_sqlc_slice === true)) {
187
+ failures.push('sqlc.slice is not supported for Postgres. Use = ANY(@name::type[]).');
188
+ }
189
+ const result = [];
190
+ const keys = new Map();
191
+ for (const param of sorted) {
192
+ const column = param.column;
193
+ const name = column?.name ?? '';
194
+ if (name === '') {
195
+ failures.push(`parameter $${param.number} has no name. Name it with @name or sqlc.arg(name).`);
196
+ continue;
197
+ }
198
+ const key = camelCase(name);
199
+ if (key === '__proto__') {
200
+ failures.push('the key __proto__ is not allowed. Alias it.');
201
+ continue;
202
+ }
203
+ const earlier = keys.get(key);
204
+ if (earlier !== undefined) {
205
+ failures.push(`parameters $${earlier} and $${param.number} both give the key ${key}. Name them with @name.`);
206
+ continue;
207
+ }
208
+ keys.set(key, param.number);
209
+ const nullable = column?.not_null !== true;
210
+ const type = resolveType(column ?? emptyColumn(), catalog);
211
+ const access = propertyAccess('params', key);
212
+ let value = access;
213
+ if (isJson(type)) {
214
+ let inner = 'JSON.stringify(v)';
215
+ for (let level = 1; level < type.dims; level++)
216
+ inner = `v.map((v) => ${inner})`;
217
+ const stringified = type.dims === 0 ? `JSON.stringify(${access})` : `${access}.map((v) => ${inner})`;
218
+ value = nullable ? `${access} === null ? null : ${stringified}` : stringified;
219
+ }
220
+ result.push({ key, type: paramType(type, nullable), value });
221
+ }
222
+ return numbered ? result : null;
223
+ }
224
+ // R35–R43 and R59–R65.
225
+ function prepareColumns(query, catalog, bigint, returnsRows, failures) {
226
+ if (query.columns.some((column) => column.embed_table !== null && column.embed_table.name !== '')) {
227
+ failures.push('sqlc.embed is not supported. List the columns instead.');
228
+ return null;
229
+ }
230
+ const keys = new Map();
231
+ let ok = true;
232
+ query.columns.forEach((column, i) => {
233
+ if (column.name === '' || column.name === '?column?') {
234
+ failures.push(`column ${i + 1} has no name. Give it an alias.`);
235
+ ok = false;
236
+ return;
237
+ }
238
+ const key = camelCase(column.name);
239
+ if (key === '__proto__') {
240
+ failures.push('the key __proto__ is not allowed. Alias it.');
241
+ ok = false;
242
+ return;
243
+ }
244
+ const earlier = keys.get(key);
245
+ if (earlier !== undefined) {
246
+ failures.push(`columns ${earlier} and ${i + 1} both give the key ${key}. Alias one of them.`);
247
+ ok = false;
248
+ return;
249
+ }
250
+ keys.set(key, i + 1);
251
+ });
252
+ const asserted = notNullAssertions(query, catalog, failures);
253
+ if (!ok || asserted === null || !returnsRows)
254
+ return ok && asserted !== null ? [] : null;
255
+ const nullability = correctNullability(query, catalog);
256
+ return query.columns.map((column, i) => {
257
+ const type = resolveType(column, catalog);
258
+ const corrected = nullability[i];
259
+ const notNull = asserted.columns.has(column.name) || corrected.notNull;
260
+ const elementsNotNull = asserted.elements.has(column.name) || corrected.elementsNotNull;
261
+ const parse = needsParse(type);
262
+ return {
263
+ key: camelCase(column.name),
264
+ type: rowType(type, !notNull, elementsNotNull, bigint),
265
+ value: parse ? `${HELPER_NAME}(row[${i}])` : `row[${i}]`,
266
+ parse,
267
+ };
268
+ });
269
+ }
270
+ // R65: `-- @notnull name name[]` lines in the query's comments.
271
+ function notNullAssertions(query, catalog, failures) {
272
+ const columns = new Set();
273
+ const elements = new Set();
274
+ let ok = true;
275
+ for (const comment of query.comments) {
276
+ const words = comment.trim().split(/\s+/);
277
+ if (words[0] !== '@notnull')
278
+ continue;
279
+ const names = words.slice(1);
280
+ if (names.length === 0) {
281
+ failures.push('@notnull names no columns.');
282
+ ok = false;
283
+ continue;
284
+ }
285
+ for (const name of names) {
286
+ const arrayElements = name.endsWith('[]');
287
+ const columnName = arrayElements ? name.slice(0, -2) : name;
288
+ const matches = query.columns.filter((column) => column.name === columnName);
289
+ if (matches.length === 0) {
290
+ failures.push(`@notnull names ${columnName}, which is not a column of this query.`);
291
+ ok = false;
292
+ }
293
+ else if (arrayElements) {
294
+ if (matches.some((column) => resolveType(column, catalog).dims === 0)) {
295
+ failures.push(`@notnull names ${columnName}[], but ${columnName} is not an array.`);
296
+ ok = false;
297
+ }
298
+ else {
299
+ elements.add(columnName);
300
+ }
301
+ }
302
+ else {
303
+ columns.add(columnName);
304
+ }
305
+ }
306
+ }
307
+ return ok ? { columns, elements } : null;
308
+ }
309
+ // R59–R64: sqlc's `not_null`, corrected for function calls, coalesce, null tests and the
310
+ // elements of array_agg.
311
+ export function correctNullability(query, catalog) {
312
+ const { items, from } = selectList(query.text, query.columns.length);
313
+ const lookup = columnLookup(catalog);
314
+ return query.columns.map((column, i) => {
315
+ let notNull = column.not_null;
316
+ const item = items?.[i];
317
+ if (column.is_func_call)
318
+ notNull = item !== undefined && isNeverNullCall(item);
319
+ let elementsNotNull = false;
320
+ if (item !== undefined) {
321
+ const coalesced = coalesce(item);
322
+ if (coalesced !== null)
323
+ notNull = coalesced === 'never-null';
324
+ if (isNullTest(item))
325
+ notNull = true;
326
+ elementsNotNull = arrayAggElementsNotNull(item, from, lookup);
327
+ }
328
+ return { notNull, elementsNotNull };
329
+ });
330
+ }
331
+ function columnLookup(catalog) {
332
+ return (table, columnName) => {
333
+ if (catalog === null)
334
+ return null;
335
+ const schemaName = table.schema ?? catalog.default_schema;
336
+ const found = catalog.schemas
337
+ .find((schema) => schema.name === schemaName)
338
+ ?.tables.find((t) => t.rel?.name === table.name);
339
+ if (found === undefined)
340
+ return null;
341
+ const column = found.columns.find((c) => c.name === columnName);
342
+ if (column === undefined)
343
+ return 'missing';
344
+ return column.not_null ? 'not-null' : 'nullable';
345
+ };
346
+ }
347
+ function emptyColumn() {
348
+ return {
349
+ name: '',
350
+ not_null: false,
351
+ is_array: false,
352
+ is_func_call: false,
353
+ is_sqlc_slice: false,
354
+ type: null,
355
+ embed_table: null,
356
+ array_dims: 0,
357
+ };
358
+ }
359
+ // R26: the JSDoc lines for a query's comments.
360
+ function jsdoc(comments) {
361
+ const lines = comments
362
+ .map((comment) => (comment.startsWith(' ') ? comment.slice(1) : comment).trimEnd())
363
+ .filter((line) => !line.trimStart().startsWith('@'));
364
+ while (lines.length > 0 && lines[0] === '')
365
+ lines.shift();
366
+ while (lines.length > 0 && lines[lines.length - 1] === '')
367
+ lines.pop();
368
+ return lines.map((line) => line.replaceAll('*/', '*\\/'));
369
+ }
370
+ // --- Emitting -----------------------------------------------------------------------------
371
+ // R10–R14, R52, R54.
372
+ function emitModule(filename, queries) {
373
+ const blocks = [
374
+ `// Generated by sequel from ${filename}. Do not edit.`,
375
+ "import type { QueryArrayConfig, QueryArrayResult } from 'pg'",
376
+ [
377
+ 'export interface Queryable {',
378
+ ' query(config: QueryArrayConfig): Promise<QueryArrayResult>',
379
+ '}',
380
+ ].join('\n'),
381
+ ];
382
+ let usesHelper = false;
383
+ for (const query of queries) {
384
+ if (query.params.length > 0) {
385
+ blocks.push(interfaceBlock(`${query.base}Params`, query.params));
386
+ }
387
+ const returnsRows = query.cmd === ':one' || query.cmd === ':many';
388
+ if (returnsRows) {
389
+ blocks.push(interfaceBlock(`${query.base}Row`, query.columns));
390
+ if (query.columns.some((column) => column.parse))
391
+ usesHelper = true;
392
+ }
393
+ blocks.push(functionBlock(query));
394
+ }
395
+ if (usesHelper)
396
+ blocks.push(PG_ARRAY_HELPER.trimEnd());
397
+ return `${blocks.join('\n\n')}\n`;
398
+ }
399
+ function interfaceBlock(name, properties) {
400
+ const lines = properties.map((p) => ` ${propertyName(p.key)}: ${p.type}`);
401
+ return [`export interface ${name} {`, ...lines, '}'].join('\n');
402
+ }
403
+ function functionBlock(query) {
404
+ const lines = [];
405
+ if (query.doc.length > 0) {
406
+ lines.push('/**', ...query.doc.map((line) => (line === '' ? ' *' : ` * ${line}`)), ' */');
407
+ }
408
+ const args = query.params.length > 0 ? `client: Queryable, params: ${query.base}Params` : 'client: Queryable';
409
+ const returns = {
410
+ ':one': `${query.base}Row | null`,
411
+ ':many': `${query.base}Row[]`,
412
+ ':exec': 'void',
413
+ ':execrows': 'number',
414
+ }[query.cmd];
415
+ lines.push(`export async function ${query.fn}(${args}): Promise<${returns}> {`);
416
+ const call = [
417
+ 'client.query({',
418
+ ` text: ${templateLiteral(`-- name: ${query.name} ${query.cmd}\n${query.text}`)},`,
419
+ ` values: [${query.params.map((p) => p.value).join(', ')}],`,
420
+ " rowMode: 'array',",
421
+ ' })',
422
+ ].join('\n');
423
+ const properties = (indent) => query.columns.map((c) => `${indent}${propertyName(c.key)}: ${c.value},`);
424
+ switch (query.cmd) {
425
+ case ':one':
426
+ lines.push(` const result = await ${call}`, ' const row = result.rows[0]', ' if (row === undefined) {', ' return null', ' }', ' return {', ...properties(' '), ' }');
427
+ break;
428
+ case ':many':
429
+ lines.push(` const result = await ${call}`, ' return result.rows.map((row) => ({', ...properties(' '), ' }))');
430
+ break;
431
+ case ':exec':
432
+ lines.push(` await ${call}`);
433
+ break;
434
+ case ':execrows':
435
+ lines.push(` const result = await ${call}`, ' return result.rowCount ?? 0');
436
+ break;
437
+ }
438
+ lines.push('}');
439
+ return lines.join('\n');
440
+ }
@@ -0,0 +1,4 @@
1
+ export declare function camelCase(name: string): string;
2
+ export declare function propertyName(key: string): string;
3
+ export declare function propertyAccess(object: string, key: string): string;
4
+ export declare function isIdentifier(text: string): boolean;
@@ -0,0 +1,24 @@
1
+ import { singleQuoted } from './strings.js';
2
+ // R41: a name with no `_` is kept exactly. Otherwise leading and trailing underscores are
3
+ // kept, and in between each run of `_` is removed and the character after it upper-cased.
4
+ export function camelCase(name) {
5
+ if (!name.includes('_'))
6
+ return name;
7
+ const match = /^(_*)(.*?)(_*)$/su.exec(name);
8
+ if (match === null)
9
+ return name;
10
+ const [, leading = '', middle = '', trailing = ''] = match;
11
+ return leading + middle.replace(/_+(.)/gsu, (_, next) => next.toUpperCase()) + trailing;
12
+ }
13
+ const IDENTIFIER = /^[\p{ID_Start}$_][\p{ID_Continue}$‌‍]*$/u;
14
+ // R42: a valid identifier is written bare, and any other key as a single-quoted string.
15
+ export function propertyName(key) {
16
+ return IDENTIFIER.test(key) ? key : singleQuoted(key);
17
+ }
18
+ // `params.k`, or `params['k']` for a key that must be quoted (R34).
19
+ export function propertyAccess(object, key) {
20
+ return IDENTIFIER.test(key) ? `${object}.${key}` : `${object}[${singleQuoted(key)}]`;
21
+ }
22
+ export function isIdentifier(text) {
23
+ return IDENTIFIER.test(text);
24
+ }
@@ -0,0 +1,12 @@
1
+ export type TokenKind = 'word' | 'qident' | 'string' | 'number' | 'param' | 'op' | 'punct';
2
+ export type Token = {
3
+ kind: TokenKind;
4
+ text: string;
5
+ upper: string;
6
+ depth: number;
7
+ };
8
+ export type Scan = {
9
+ tokens: Token[];
10
+ closed: boolean;
11
+ };
12
+ export declare function scan(text: string): Scan;
@@ -0,0 +1,210 @@
1
+ // R60's lexer over `Query.text`. It knows just enough SQL to find the select list and match
2
+ // the shapes of R61–R64: comments, strings, quoted identifiers, dollar quotes and brackets.
3
+ const OPERATOR_CHARS = new Set('+-*/<>=~!@#%^&|`?');
4
+ const STRING_PREFIXES = new Set(['E', 'B', 'X', 'N']);
5
+ function isWordStart(char) {
6
+ return /[\p{L}_]/u.test(char);
7
+ }
8
+ function isWordChar(char) {
9
+ return /[\p{L}\p{N}_$]/u.test(char);
10
+ }
11
+ export function scan(text) {
12
+ const tokens = [];
13
+ let depth = 0;
14
+ let closed = true;
15
+ let i = 0;
16
+ const push = (kind, value, at = depth) => {
17
+ tokens.push({ kind, text: value, upper: kind === 'word' ? value.toUpperCase() : '', depth: at });
18
+ };
19
+ while (i < text.length) {
20
+ const char = text[i];
21
+ const next = text[i + 1] ?? '';
22
+ if (/\s/u.test(char)) {
23
+ i++;
24
+ continue;
25
+ }
26
+ if (char === '-' && next === '-') {
27
+ const end = text.indexOf('\n', i);
28
+ i = end === -1 ? text.length : end + 1;
29
+ continue;
30
+ }
31
+ if (char === '/' && next === '*') {
32
+ // Block comments nest.
33
+ let level = 1;
34
+ i += 2;
35
+ while (i < text.length && level > 0) {
36
+ if (text[i] === '/' && text[i + 1] === '*') {
37
+ level++;
38
+ i += 2;
39
+ }
40
+ else if (text[i] === '*' && text[i + 1] === '/') {
41
+ level--;
42
+ i += 2;
43
+ }
44
+ else {
45
+ i++;
46
+ }
47
+ }
48
+ if (level > 0)
49
+ closed = false;
50
+ continue;
51
+ }
52
+ if (char === "'") {
53
+ const end = quoted(text, i, false);
54
+ if (end === -1)
55
+ closed = false;
56
+ push('string', text.slice(i, end === -1 ? text.length : end));
57
+ i = end === -1 ? text.length : end;
58
+ continue;
59
+ }
60
+ if (char === '"') {
61
+ let value = '';
62
+ let j = i + 1;
63
+ let done = false;
64
+ while (j < text.length) {
65
+ if (text[j] === '"') {
66
+ if (text[j + 1] === '"') {
67
+ value += '"';
68
+ j += 2;
69
+ continue;
70
+ }
71
+ done = true;
72
+ j++;
73
+ break;
74
+ }
75
+ value += text[j];
76
+ j++;
77
+ }
78
+ if (!done)
79
+ closed = false;
80
+ push('qident', value);
81
+ i = j;
82
+ continue;
83
+ }
84
+ if (char === '$') {
85
+ if (/[0-9]/.test(next)) {
86
+ let j = i + 1;
87
+ while (j < text.length && /[0-9]/.test(text[j]))
88
+ j++;
89
+ push('param', text.slice(i, j));
90
+ i = j;
91
+ continue;
92
+ }
93
+ const tag = /^\$([\p{L}_][\p{L}\p{N}_]*)?\$/u.exec(text.slice(i));
94
+ if (tag !== null) {
95
+ const end = text.indexOf(tag[0], i + tag[0].length);
96
+ if (end === -1) {
97
+ closed = false;
98
+ push('string', text.slice(i));
99
+ i = text.length;
100
+ }
101
+ else {
102
+ push('string', text.slice(i, end + tag[0].length));
103
+ i = end + tag[0].length;
104
+ }
105
+ continue;
106
+ }
107
+ push('op', char);
108
+ i++;
109
+ continue;
110
+ }
111
+ if (isWordStart(char)) {
112
+ let j = i + 1;
113
+ while (j < text.length && isWordChar(text[j]))
114
+ j++;
115
+ const word = text.slice(i, j);
116
+ // A string with a prefix: E'…', B'…', X'…', N'…' or U&'…'.
117
+ if (text[j] === "'" && STRING_PREFIXES.has(word.toUpperCase())) {
118
+ const end = quoted(text, j, word.toUpperCase() === 'E');
119
+ if (end === -1)
120
+ closed = false;
121
+ push('string', text.slice(i, end === -1 ? text.length : end));
122
+ i = end === -1 ? text.length : end;
123
+ continue;
124
+ }
125
+ if (word.toUpperCase() === 'U' && text[j] === '&' && text[j + 1] === "'") {
126
+ const end = quoted(text, j + 1, false);
127
+ if (end === -1)
128
+ closed = false;
129
+ push('string', text.slice(i, end === -1 ? text.length : end));
130
+ i = end === -1 ? text.length : end;
131
+ continue;
132
+ }
133
+ push('word', word);
134
+ i = j;
135
+ continue;
136
+ }
137
+ if (/[0-9]/.test(char) || (char === '.' && /[0-9]/.test(next))) {
138
+ const match = /^(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?/.exec(text.slice(i));
139
+ const value = match?.[0] ?? char;
140
+ push('number', value);
141
+ i += value.length;
142
+ continue;
143
+ }
144
+ if (char === '(' || char === '[') {
145
+ push('punct', char);
146
+ depth++;
147
+ i++;
148
+ continue;
149
+ }
150
+ if (char === ')' || char === ']') {
151
+ depth--;
152
+ if (depth < 0) {
153
+ closed = false;
154
+ depth = 0;
155
+ }
156
+ push('punct', char);
157
+ i++;
158
+ continue;
159
+ }
160
+ if (char === ':' && next === ':') {
161
+ push('punct', '::');
162
+ i += 2;
163
+ continue;
164
+ }
165
+ if (char === ',' || char === '.' || char === ';' || char === ':') {
166
+ push('punct', char);
167
+ i++;
168
+ continue;
169
+ }
170
+ if (OPERATOR_CHARS.has(char)) {
171
+ // A run of operator characters, stopping before a comment.
172
+ let j = i;
173
+ while (j < text.length &&
174
+ OPERATOR_CHARS.has(text[j]) &&
175
+ !(text[j] === '-' && text[j + 1] === '-') &&
176
+ !(text[j] === '/' && text[j + 1] === '*')) {
177
+ j++;
178
+ }
179
+ push('op', text.slice(i, j));
180
+ i = j;
181
+ continue;
182
+ }
183
+ push('op', char);
184
+ i++;
185
+ }
186
+ if (depth !== 0)
187
+ closed = false;
188
+ return { tokens, closed };
189
+ }
190
+ // Returns the index just past the string that opens at `start`, or -1 if it never closes.
191
+ // `''` is an escape everywhere; in an E'…' string, a backslash escapes the next character.
192
+ function quoted(text, start, backslashes) {
193
+ let j = start + 1;
194
+ while (j < text.length) {
195
+ const char = text[j];
196
+ if (backslashes && char === '\\') {
197
+ j += 2;
198
+ continue;
199
+ }
200
+ if (char === "'") {
201
+ if (text[j + 1] === "'") {
202
+ j += 2;
203
+ continue;
204
+ }
205
+ return j + 1;
206
+ }
207
+ j++;
208
+ }
209
+ return -1;
210
+ }
@@ -0,0 +1 @@
1
+ export {};