@onreza/sqlx-js 0.8.0 → 0.9.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/README.md +98 -14
- package/ROADMAP.md +1 -1
- package/dist/bin/sqlx-js.js +39 -2
- package/dist/src/cache.d.ts +4 -1
- package/dist/src/cache.js +12 -63
- package/dist/src/commands/init.js +8 -0
- package/dist/src/commands/prepare.d.ts +2 -0
- package/dist/src/commands/prepare.js +46 -35
- package/dist/src/commands/queries.d.ts +38 -0
- package/dist/src/commands/queries.js +143 -0
- package/dist/src/config.d.ts +5 -0
- package/dist/src/config.js +29 -0
- package/dist/src/index.d.ts +10 -2
- package/dist/src/index.js +3 -1
- package/dist/src/pg/functions.d.ts +3 -1
- package/dist/src/pg/functions.js +11 -3
- package/dist/src/postgres-runtime.d.ts +2 -0
- package/dist/src/postgres-runtime.js +9 -6
- package/dist/src/query-id.d.ts +1 -0
- package/dist/src/query-id.js +61 -0
- package/dist/src/query.d.ts +53 -0
- package/dist/src/query.js +38 -0
- package/dist/src/runtime.d.ts +27 -3
- package/dist/src/runtime.js +67 -21
- package/dist/src/scan/scanner.d.ts +2 -0
- package/dist/src/scan/scanner.js +102 -17
- package/dist/src/typed.d.ts +4 -2
- package/package.json +1 -1
package/dist/src/scan/scanner.js
CHANGED
|
@@ -68,7 +68,7 @@ function classifyCallee(callee, scope) {
|
|
|
68
68
|
if (ts.isIdentifier(callee)) {
|
|
69
69
|
if (!scope.sqlAliases.has(callee.text))
|
|
70
70
|
return null;
|
|
71
|
-
return { kind: "inline" };
|
|
71
|
+
return { kind: "inline", cardinality: "many" };
|
|
72
72
|
}
|
|
73
73
|
if (!ts.isPropertyAccessExpression(callee))
|
|
74
74
|
return null;
|
|
@@ -79,7 +79,7 @@ function classifyCallee(callee, scope) {
|
|
|
79
79
|
const id = callee.expression.text;
|
|
80
80
|
if (scope.namespaces.has(id) || scope.clients.has(id)) {
|
|
81
81
|
if (methodName === "sql")
|
|
82
|
-
return { kind: "inline" };
|
|
82
|
+
return { kind: "inline", cardinality: "many" };
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
85
|
if (!scope.sqlAliases.has(id))
|
|
@@ -87,9 +87,10 @@ function classifyCallee(callee, scope) {
|
|
|
87
87
|
if (methodName === "transaction")
|
|
88
88
|
return { kind: "transaction" };
|
|
89
89
|
if (methodName === "file")
|
|
90
|
-
return { kind: "file" };
|
|
91
|
-
if (methodName === "one" || methodName === "optional" || methodName === "execute")
|
|
92
|
-
return { kind: "inline" };
|
|
90
|
+
return { kind: "file", cardinality: "many" };
|
|
91
|
+
if (methodName === "one" || methodName === "optional" || methodName === "execute") {
|
|
92
|
+
return { kind: "inline", cardinality: methodName };
|
|
93
|
+
}
|
|
93
94
|
return null;
|
|
94
95
|
}
|
|
95
96
|
if (ts.isPropertyAccessExpression(callee.expression)) {
|
|
@@ -101,10 +102,11 @@ function classifyCallee(callee, scope) {
|
|
|
101
102
|
(scope.namespaces.has(mid.expression.text) || scope.clients.has(mid.expression.text))) {
|
|
102
103
|
if (mid.name.text !== "sql")
|
|
103
104
|
return null;
|
|
104
|
-
if (methodName === "one" || methodName === "optional" || methodName === "execute")
|
|
105
|
-
return { kind: "inline" };
|
|
105
|
+
if (methodName === "one" || methodName === "optional" || methodName === "execute") {
|
|
106
|
+
return { kind: "inline", cardinality: methodName };
|
|
107
|
+
}
|
|
106
108
|
if (methodName === "file")
|
|
107
|
-
return { kind: "file" };
|
|
109
|
+
return { kind: "file", cardinality: "many" };
|
|
108
110
|
if (methodName === "transaction")
|
|
109
111
|
return { kind: "transaction" };
|
|
110
112
|
return null;
|
|
@@ -116,8 +118,9 @@ function classifyCallee(callee, scope) {
|
|
|
116
118
|
return null;
|
|
117
119
|
if (mid.name.text !== "file")
|
|
118
120
|
return null;
|
|
119
|
-
if (methodName === "one" || methodName === "optional" || methodName === "execute")
|
|
120
|
-
return { kind: "file" };
|
|
121
|
+
if (methodName === "one" || methodName === "optional" || methodName === "execute") {
|
|
122
|
+
return { kind: "file", cardinality: methodName };
|
|
123
|
+
}
|
|
121
124
|
return null;
|
|
122
125
|
}
|
|
123
126
|
// ns.sql.file.X(...) and client.sql.file.X(...) chains
|
|
@@ -128,11 +131,32 @@ function classifyCallee(callee, scope) {
|
|
|
128
131
|
mid.expression.name.text === "sql" &&
|
|
129
132
|
mid.name.text === "file" &&
|
|
130
133
|
(methodName === "one" || methodName === "optional" || methodName === "execute")) {
|
|
131
|
-
return { kind: "file" };
|
|
134
|
+
return { kind: "file", cardinality: methodName };
|
|
132
135
|
}
|
|
133
136
|
}
|
|
134
137
|
return null;
|
|
135
138
|
}
|
|
139
|
+
function classifyDefinitionCallee(callee, scope) {
|
|
140
|
+
if (ts.isIdentifier(callee))
|
|
141
|
+
return scope.queryFactories.has(callee.text) ? "many" : null;
|
|
142
|
+
if (!ts.isPropertyAccessExpression(callee) || !ts.isIdentifier(callee.name))
|
|
143
|
+
return null;
|
|
144
|
+
const method = callee.name.text;
|
|
145
|
+
if (ts.isIdentifier(callee.expression) && scope.queryFactories.has(callee.expression.text)) {
|
|
146
|
+
return method === "one" || method === "optional" || method === "execute" ? method : null;
|
|
147
|
+
}
|
|
148
|
+
if (method === "defineQuery" &&
|
|
149
|
+
ts.isIdentifier(callee.expression) &&
|
|
150
|
+
scope.namespaces.has(callee.expression.text))
|
|
151
|
+
return "many";
|
|
152
|
+
if ((method === "one" || method === "optional" || method === "execute") &&
|
|
153
|
+
ts.isPropertyAccessExpression(callee.expression) &&
|
|
154
|
+
ts.isIdentifier(callee.expression.expression) &&
|
|
155
|
+
scope.namespaces.has(callee.expression.expression.text) &&
|
|
156
|
+
callee.expression.name.text === "defineQuery")
|
|
157
|
+
return method;
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
136
160
|
export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
137
161
|
const text = readFileSync(absPath, "utf8");
|
|
138
162
|
const source = ts.createSourceFile(absPath, text, ts.ScriptTarget.ESNext, false, scriptKind(absPath));
|
|
@@ -147,6 +171,7 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
147
171
|
const importedAliases = new Set();
|
|
148
172
|
const importedNamespaces = new Set();
|
|
149
173
|
const importedClientFactories = new Set();
|
|
174
|
+
const importedQueryFactories = new Set();
|
|
150
175
|
for (const stmt of source.statements) {
|
|
151
176
|
if (!ts.isImportDeclaration(stmt))
|
|
152
177
|
continue;
|
|
@@ -171,10 +196,15 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
171
196
|
importedAliases.add(elem.name.text);
|
|
172
197
|
if (orig === "createSqlClient")
|
|
173
198
|
importedClientFactories.add(elem.name.text);
|
|
199
|
+
if (orig === "defineQuery")
|
|
200
|
+
importedQueryFactories.add(elem.name.text);
|
|
174
201
|
}
|
|
175
202
|
}
|
|
176
203
|
}
|
|
177
|
-
if (importedAliases.size === 0 &&
|
|
204
|
+
if (importedAliases.size === 0 &&
|
|
205
|
+
importedNamespaces.size === 0 &&
|
|
206
|
+
importedClientFactories.size === 0 &&
|
|
207
|
+
importedQueryFactories.size === 0)
|
|
178
208
|
return [];
|
|
179
209
|
const out = [];
|
|
180
210
|
const here = (node) => {
|
|
@@ -182,7 +212,7 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
182
212
|
return { line: line + 1, column: character + 1 };
|
|
183
213
|
};
|
|
184
214
|
const fileRel = relative(root, absPath).replace(/\\/g, "/");
|
|
185
|
-
const recordInline = (first, args) => {
|
|
215
|
+
const recordInline = (first, args, cardinality) => {
|
|
186
216
|
if (!ts.isStringLiteralLike(first)) {
|
|
187
217
|
const pos = here(first);
|
|
188
218
|
throw new ScanError(fileRel, pos.line, pos.column, "sql() requires a string literal as first argument");
|
|
@@ -205,10 +235,46 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
205
235
|
query: first.text,
|
|
206
236
|
paramCount: args.length - 1,
|
|
207
237
|
kind: "inline",
|
|
238
|
+
cardinality,
|
|
208
239
|
});
|
|
209
240
|
return true;
|
|
210
241
|
};
|
|
211
|
-
const
|
|
242
|
+
const recordDefinition = (args, callee, cardinality) => {
|
|
243
|
+
if (args.length < 1 || args.length > 2) {
|
|
244
|
+
const pos = here(callee);
|
|
245
|
+
throw new ScanError(fileRel, pos.line, pos.column, "defineQuery() requires a SQL literal and optional name");
|
|
246
|
+
}
|
|
247
|
+
const queryNode = args.length === 2 ? args[1] : args[0];
|
|
248
|
+
const nameNode = args.length === 2 ? args[0] : undefined;
|
|
249
|
+
if (!ts.isStringLiteralLike(queryNode) || (nameNode && !ts.isStringLiteralLike(nameNode))) {
|
|
250
|
+
const pos = here(queryNode);
|
|
251
|
+
throw new ScanError(fileRel, pos.line, pos.column, "defineQuery() requires string literals for its name and SQL");
|
|
252
|
+
}
|
|
253
|
+
if (nameNode && nameNode.text.trim() === "") {
|
|
254
|
+
const pos = here(nameNode);
|
|
255
|
+
throw new ScanError(fileRel, pos.line, pos.column, "defineQuery() name must not be empty");
|
|
256
|
+
}
|
|
257
|
+
const pos = here(queryNode);
|
|
258
|
+
let paramNames;
|
|
259
|
+
try {
|
|
260
|
+
paramNames = rewriteNamedParameters(queryNode.text).names;
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
throw new ScanError(fileRel, pos.line, pos.column, error.message.replace(/^sqlx-js: /, ""));
|
|
264
|
+
}
|
|
265
|
+
out.push({
|
|
266
|
+
file: fileRel,
|
|
267
|
+
line: pos.line,
|
|
268
|
+
column: pos.column,
|
|
269
|
+
query: queryNode.text,
|
|
270
|
+
paramCount: paramNames.length,
|
|
271
|
+
kind: "inline",
|
|
272
|
+
cardinality,
|
|
273
|
+
...(nameNode ? { queryName: nameNode.text } : {}),
|
|
274
|
+
});
|
|
275
|
+
return true;
|
|
276
|
+
};
|
|
277
|
+
const recordFile = (first, args, callee, cardinality) => {
|
|
212
278
|
if (!ts.isStringLiteralLike(first)) {
|
|
213
279
|
const pos = first ? here(first) : here(callee);
|
|
214
280
|
throw new ScanError(fileRel, pos.line, pos.column, "sql.file() requires a string literal path");
|
|
@@ -247,6 +313,7 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
247
313
|
query,
|
|
248
314
|
paramCount: args.length - 1,
|
|
249
315
|
kind: "file",
|
|
316
|
+
cardinality,
|
|
250
317
|
sqlFilePath: sqlPath,
|
|
251
318
|
});
|
|
252
319
|
return true;
|
|
@@ -267,6 +334,7 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
267
334
|
const nextSql = new Set(scope.sqlAliases);
|
|
268
335
|
const nextNs = new Set(scope.namespaces);
|
|
269
336
|
const nextFactories = new Set(scope.clientFactories);
|
|
337
|
+
const nextQueryFactories = new Set(scope.queryFactories);
|
|
270
338
|
const nextClients = new Set(scope.clients);
|
|
271
339
|
for (const binding of bindings) {
|
|
272
340
|
for (const a of scope.sqlAliases) {
|
|
@@ -287,6 +355,12 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
287
355
|
changed = true;
|
|
288
356
|
}
|
|
289
357
|
}
|
|
358
|
+
for (const a of scope.queryFactories) {
|
|
359
|
+
if (bindingDeclares(binding, a)) {
|
|
360
|
+
nextQueryFactories.delete(a);
|
|
361
|
+
changed = true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
290
364
|
for (const a of scope.clients) {
|
|
291
365
|
if (bindingDeclares(binding, a)) {
|
|
292
366
|
nextClients.delete(a);
|
|
@@ -295,7 +369,13 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
295
369
|
}
|
|
296
370
|
}
|
|
297
371
|
return changed
|
|
298
|
-
? {
|
|
372
|
+
? {
|
|
373
|
+
sqlAliases: nextSql,
|
|
374
|
+
namespaces: nextNs,
|
|
375
|
+
clientFactories: nextFactories,
|
|
376
|
+
queryFactories: nextQueryFactories,
|
|
377
|
+
clients: nextClients,
|
|
378
|
+
}
|
|
299
379
|
: scope;
|
|
300
380
|
};
|
|
301
381
|
const isClientFactoryCall = (initializer, scope) => {
|
|
@@ -322,6 +402,10 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
322
402
|
};
|
|
323
403
|
const visit = (node, scope) => {
|
|
324
404
|
if (ts.isCallExpression(node)) {
|
|
405
|
+
const definitionCardinality = classifyDefinitionCallee(node.expression, scope);
|
|
406
|
+
if (definitionCardinality) {
|
|
407
|
+
recordDefinition(node.arguments, node.expression, definitionCardinality);
|
|
408
|
+
}
|
|
325
409
|
const classified = classifyCallee(node.expression, scope);
|
|
326
410
|
if (classified) {
|
|
327
411
|
if (classified.kind === "transaction") {
|
|
@@ -340,12 +424,12 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
340
424
|
else if (classified.kind === "file") {
|
|
341
425
|
const first = node.arguments[0];
|
|
342
426
|
if (first)
|
|
343
|
-
recordFile(first, node.arguments, node.expression);
|
|
427
|
+
recordFile(first, node.arguments, node.expression, classified.cardinality ?? "many");
|
|
344
428
|
}
|
|
345
429
|
else if (classified.kind === "inline") {
|
|
346
430
|
const first = node.arguments[0];
|
|
347
431
|
if (first)
|
|
348
|
-
recordInline(first, node.arguments);
|
|
432
|
+
recordInline(first, node.arguments, classified.cardinality ?? "many");
|
|
349
433
|
}
|
|
350
434
|
}
|
|
351
435
|
}
|
|
@@ -399,6 +483,7 @@ export function scanFile(absPath, root, modules = DEFAULT_SQLX_MODULES) {
|
|
|
399
483
|
sqlAliases: importedAliases,
|
|
400
484
|
namespaces: importedNamespaces,
|
|
401
485
|
clientFactories: importedClientFactories,
|
|
486
|
+
queryFactories: importedQueryFactories,
|
|
402
487
|
clients: new Set(),
|
|
403
488
|
});
|
|
404
489
|
return out;
|
package/dist/src/typed.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { QUERY_EXECUTOR, QueryExecutorMethod } from "./query.js";
|
|
1
2
|
type ParamsOf<T> = T extends {
|
|
2
3
|
params: infer P;
|
|
3
4
|
} ? P extends readonly unknown[] ? P : [P] : never[];
|
|
@@ -5,10 +6,10 @@ type RowOf<T> = T extends {
|
|
|
5
6
|
row: infer R;
|
|
6
7
|
} ? R : never;
|
|
7
8
|
type ExecuteResult = import("./runtime.js").ExecuteResult;
|
|
8
|
-
type
|
|
9
|
+
type JsonCompatible<T> = import("./runtime.js").JsonCompatible<T>;
|
|
9
10
|
type JsonParameter<T> = import("./runtime.js").JsonParameter<T>;
|
|
10
11
|
type PgArrayParameter<T> = import("./runtime.js").PgArrayParameter<T>;
|
|
11
|
-
type JsonFn = <T
|
|
12
|
+
type JsonFn = <T>(value: T & JsonCompatible<T>) => JsonParameter<T>;
|
|
12
13
|
type ArrayFn = <T>(value: readonly (T | null)[]) => PgArrayParameter<T>;
|
|
13
14
|
export type TypedFile<TFileQueries> = {
|
|
14
15
|
<P extends keyof TFileQueries>(path: P, ...params: ParamsOf<TFileQueries[P]>): Promise<RowOf<TFileQueries[P]>[]>;
|
|
@@ -25,6 +26,7 @@ export type TypedSql<TQueries, TFileQueries> = {
|
|
|
25
26
|
id: (...parts: string[]) => string;
|
|
26
27
|
json: JsonFn;
|
|
27
28
|
array: ArrayFn;
|
|
29
|
+
readonly [QUERY_EXECUTOR]?: QueryExecutorMethod;
|
|
28
30
|
};
|
|
29
31
|
export type Typed<TQueries, TFileQueries, TTransactionOptions> = TypedSql<TQueries, TFileQueries> & {
|
|
30
32
|
transaction: {
|