@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.
@@ -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 && importedNamespaces.size === 0 && importedClientFactories.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 recordFile = (first, args, callee) => {
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
- ? { sqlAliases: nextSql, namespaces: nextNs, clientFactories: nextFactories, clients: nextClients }
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;
@@ -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 JsonInputValue = import("./runtime.js").JsonInputValue;
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 extends JsonInputValue>(value: T) => JsonParameter<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: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onreza/sqlx-js",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Compile-time-checked raw SQL for TypeScript + PostgreSQL. Inspired by sqlx.",
5
5
  "license": "MIT",
6
6
  "type": "module",