@onreza/sqlx-js 0.9.0 → 0.9.2

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 CHANGED
@@ -512,7 +512,7 @@ Regular `prepare` describes queries across a small connection pool (default 8, o
512
512
  | `--json` | Machine-readable prepare diagnostics, doctor output, migration inspection and dry-runs. |
513
513
  | `--embed <path>` | For `queries`: write a deterministic TypeScript map of referenced external SQL files. |
514
514
  | `--jsonl` | Versioned streaming events for `prepare --watch`. |
515
- | `--strict-inference` | Fail prepare/dev/verify when nullability degrades or a generated query type contains `unknown`. |
515
+ | `--strict-inference` | Fail prepare/dev/verify when nullability degrades or a generated query type contains unresolved `unknown`. Intentional `JsonParameter<unknown>` wrappers remain accepted. |
516
516
  | `--force` | For `migrate archive restore`: allow overwriting existing migration files. |
517
517
  | `--lock-timeout <ms>` | Advisory-lock acquisition timeout for `migrate run` / `revert` / `dev` / `verify` / `squash`. |
518
518
  | `--shadow-url <url>` | Use an existing disposable shadow DB instead of auto-creating one. |
@@ -714,7 +714,7 @@ declare global {
714
714
  export {};
715
715
  ```
716
716
 
717
- After re-running `prepare`, every `jsonb` column or parameter declared in `jsonbTypes` is checked against the corresponding TypeScript type. Columns without a custom mapping use `JsonValue` for result rows and `JsonParameter<unknown>` for parameters: the existential parameter type accepts any wrapper already proven JSON-safe by `sql.json(value)` without requiring domain interfaces to declare a string index signature. Non-JSON inputs such as `Date`, functions, and `bigint` are rejected by TypeScript while plain JSON objects, arrays, strings, numbers, booleans, and nested JSON `null` values are accepted. A bare top-level `null` remains SQL `NULL` and is allowed only when the mapped database parameter is nullable; use `sql.json(null)` for JSON `null`.
717
+ After re-running `prepare`, every `jsonb` column or parameter declared in `jsonbTypes` is checked against the corresponding TypeScript type. Columns without a custom mapping use `JsonValue` for result rows and `JsonParameter<unknown>` for parameters: the existential parameter type accepts any wrapper already proven JSON-safe by `sql.json(value)` without requiring domain interfaces to declare a string index signature. `--strict-inference` accepts this intentional wrapper while continuing to reject unresolved `unknown` elsewhere in generated query contracts. Non-JSON inputs such as `Date`, functions, and `bigint` are rejected by TypeScript while plain JSON objects, arrays, strings, numbers, booleans, and nested JSON `null` values are accepted. A bare top-level `null` remains SQL `NULL` and is allowed only when the mapped database parameter is nullable; use `sql.json(null)` for JSON `null`.
718
718
 
719
719
  ### Direct scalar `columnTypes`
720
720
 
@@ -1,5 +1,5 @@
1
1
  import * as rt from "./postgres-runtime.js";
2
- import type { Typed as TypedFor, TypedFile as TypedFileFor, TypedSql as TypedSqlFor } from "./typed.js";
2
+ import type { TypedFile as TypedFileFor, TypedForRegistry, TypedSqlForRegistry } from "./typed.js";
3
3
  import type { QueryParamsFor, QueryResultFor, QueryRowFor } from "./query.js";
4
4
  export interface KnownQueries {
5
5
  }
@@ -28,8 +28,8 @@ export type { TransactionOptions, MigrateOptions, OnQueryEvent, OnQueryHook, OnQ
28
28
  export type { ExecuteResult, JsonParameter, PgArrayParameter, JsonCompatible, KnownSqlState } from "./runtime.js";
29
29
  export type { PostgresClient, PostgresOptions, CreateClientOptions } from "./postgres-runtime.js";
30
30
  export type TypedFile = TypedFileFor<KnownFileQueries>;
31
- export type TypedSql = TypedSqlFor<KnownQueries, KnownFileQueries>;
32
- export type Typed = TypedFor<KnownQueries, KnownFileQueries, import("./runtime.js").TransactionOptions>;
31
+ export type TypedSql = TypedSqlForRegistry<DefaultQueryRegistry>;
32
+ export type Typed = TypedForRegistry<DefaultQueryRegistry, import("./runtime.js").TransactionOptions>;
33
33
  export type QueryRegistry = {
34
34
  queries: object;
35
35
  fileQueries: object;
@@ -40,12 +40,12 @@ export interface DefaultQueryRegistry {
40
40
  functions: KnownFunctions;
41
41
  }
42
42
  export type SqlClient<Registry extends QueryRegistry = DefaultQueryRegistry> = {
43
- sql: TypedFor<Registry["queries"], Registry["fileQueries"], import("./runtime.js").TransactionOptions>;
43
+ sql: TypedForRegistry<Registry, import("./runtime.js").TransactionOptions>;
44
44
  unsafe: Unsafe;
45
45
  client: import("./postgres-runtime.js").PostgresClient;
46
46
  close: () => Promise<void>;
47
47
  };
48
- export type SqlExecutor<Registry extends QueryRegistry = DefaultQueryRegistry> = TypedSqlFor<Registry["queries"], Registry["fileQueries"]>;
48
+ export type SqlExecutor<Registry extends QueryRegistry = DefaultQueryRegistry> = TypedSqlForRegistry<Registry>;
49
49
  export type QueryParams<Definition, Registry extends QueryRegistry = DefaultQueryRegistry> = QueryParamsFor<Definition, Registry>;
50
50
  export type QueryRow<Definition, Registry extends QueryRegistry = DefaultQueryRegistry> = QueryRowFor<Definition, Registry>;
51
51
  export type QueryResult<Definition, Registry extends QueryRegistry = DefaultQueryRegistry> = QueryResultFor<Definition, Registry>;
@@ -1,5 +1,5 @@
1
1
  import type { ExecuteResult } from "./runtime.js";
2
- import type { TypedSql } from "./typed.js";
2
+ import type { TypedSqlForRegistry } from "./typed.js";
3
3
  export type QueryExecutionMode = "many" | "one" | "optional" | "execute";
4
4
  export type QueryExecutionMetadata = {
5
5
  queryId: string;
@@ -7,37 +7,46 @@ export type QueryExecutionMetadata = {
7
7
  };
8
8
  export declare const QUERY_EXECUTOR: unique symbol;
9
9
  export type QueryExecutorMethod = (mode: QueryExecutionMode, query: string, params: unknown[], metadata: QueryExecutionMetadata) => Promise<unknown>;
10
- type QueryEntry = {
11
- params: unknown;
10
+ type NamedQueryEntry = {
11
+ params: Record<string, unknown>;
12
+ row: unknown;
13
+ };
14
+ type PositionalQueryEntry = {
15
+ params: readonly unknown[];
12
16
  row: unknown;
13
17
  };
14
- type ParamsOf<T> = T extends {
15
- params: infer P;
16
- } ? P extends readonly unknown[] ? P : [P] : never[];
17
- type RowOf<T> = T extends {
18
- row: infer R;
19
- } ? R : never;
20
18
  type QueryModeResult<Mode extends QueryExecutionMode, Row> = Mode extends "many" ? Row[] : Mode extends "one" ? Row : Mode extends "optional" ? Row | null : ExecuteResult;
21
19
  export type QueryDefinition<Query extends string = string, Mode extends QueryExecutionMode = QueryExecutionMode> = {
22
20
  readonly query: Query;
23
21
  readonly mode: Mode;
24
22
  readonly queryId: string;
25
23
  readonly queryName?: string;
26
- run<Queries extends Record<Query, QueryEntry>, FileQueries>(executor: TypedSql<Queries, FileQueries>, ...params: ParamsOf<Queries[Query]>): Promise<QueryModeResult<Mode, RowOf<Queries[Query]>>>;
24
+ run<Registry extends {
25
+ queries: Record<Query, NamedQueryEntry>;
26
+ fileQueries: object;
27
+ }>(executor: TypedSqlForRegistry<Registry>, params: RegistryParams<Query, Registry>): Promise<QueryResultFor<QueryDefinition<Query, Mode>, Registry>>;
28
+ run<Registry extends {
29
+ queries: Record<Query, PositionalQueryEntry>;
30
+ fileQueries: object;
31
+ }>(executor: TypedSqlForRegistry<Registry>, ...params: RegistryParams<Query, Registry> & readonly unknown[]): Promise<QueryResultFor<QueryDefinition<Query, Mode>, Registry>>;
27
32
  };
28
33
  type DefinitionQuery<Definition> = Definition extends QueryDefinition<infer Query, QueryExecutionMode> ? Query : never;
29
34
  type DefinitionMode<Definition> = Definition extends QueryDefinition<string, infer Mode> ? Mode : never;
30
- type RegistryQuery<Definition, Registry extends {
35
+ type RegistryQuery<Query extends string, Registry extends {
36
+ queries: object;
37
+ }> = Registry["queries"][Query & keyof Registry["queries"]];
38
+ type RegistryParams<Query extends string, Registry extends {
39
+ queries: object;
40
+ }> = RegistryQuery<Query, Registry>["params" & keyof RegistryQuery<Query, Registry>];
41
+ type RegistryRow<Query extends string, Registry extends {
31
42
  queries: object;
32
- }> = DefinitionQuery<Definition> extends keyof Registry["queries"] ? Registry["queries"][DefinitionQuery<Definition>] : never;
43
+ }> = RegistryQuery<Query, Registry>["row" & keyof RegistryQuery<Query, Registry>];
33
44
  export type QueryParamsFor<Definition, Registry extends {
34
45
  queries: object;
35
- }> = RegistryQuery<Definition, Registry> extends {
36
- params: infer Params;
37
- } ? Params : never;
46
+ }> = RegistryParams<DefinitionQuery<Definition>, Registry>;
38
47
  export type QueryRowFor<Definition, Registry extends {
39
48
  queries: object;
40
- }> = RowOf<RegistryQuery<Definition, Registry>>;
49
+ }> = RegistryRow<DefinitionQuery<Definition>, Registry>;
41
50
  export type QueryResultFor<Definition, Registry extends {
42
51
  queries: object;
43
52
  }> = QueryModeResult<DefinitionMode<Definition>, QueryRowFor<Definition, Registry>>;
@@ -1,5 +1,16 @@
1
1
  import ts from "typescript";
2
2
  const unknownTypeCache = new Map();
3
+ function isExistentialJsonParameter(node) {
4
+ if (!ts.isImportTypeNode(node))
5
+ return false;
6
+ if (!ts.isLiteralTypeNode(node.argument) || !ts.isStringLiteral(node.argument.literal))
7
+ return false;
8
+ if (node.argument.literal.text !== "@onreza/sqlx-js")
9
+ return false;
10
+ if (!node.qualifier || !ts.isIdentifier(node.qualifier) || node.qualifier.text !== "JsonParameter")
11
+ return false;
12
+ return node.typeArguments?.length === 1 && node.typeArguments[0]?.kind === ts.SyntaxKind.UnknownKeyword;
13
+ }
3
14
  export function containsUnknownType(type) {
4
15
  const cached = unknownTypeCache.get(type);
5
16
  if (cached !== undefined)
@@ -7,6 +18,8 @@ export function containsUnknownType(type) {
7
18
  const source = ts.createSourceFile("sqlx-js-type.ts", `type SqlxJsType = ${type};`, ts.ScriptTarget.Latest, true);
8
19
  let found = false;
9
20
  const visit = (node) => {
21
+ if (isExistentialJsonParameter(node))
22
+ return;
10
23
  if (node.kind === ts.SyntaxKind.UnknownKeyword) {
11
24
  found = true;
12
25
  return;
@@ -1,4 +1,5 @@
1
1
  import type { QUERY_EXECUTOR, QueryExecutorMethod } from "./query.js";
2
+ declare const QUERY_REGISTRY: unique symbol;
2
3
  type ParamsOf<T> = T extends {
3
4
  params: infer P;
4
5
  } ? P extends readonly unknown[] ? P : [P] : never[];
@@ -17,21 +18,36 @@ export type TypedFile<TFileQueries> = {
17
18
  optional: <P extends keyof TFileQueries>(path: P, ...params: ParamsOf<TFileQueries[P]>) => Promise<RowOf<TFileQueries[P]> | null>;
18
19
  execute: <P extends keyof TFileQueries>(path: P, ...params: ParamsOf<TFileQueries[P]>) => Promise<ExecuteResult>;
19
20
  };
20
- export type TypedSql<TQueries, TFileQueries> = {
21
- <Q extends keyof TQueries>(query: Q, ...params: ParamsOf<TQueries[Q]>): Promise<RowOf<TQueries[Q]>[]>;
22
- one: <Q extends keyof TQueries>(query: Q, ...params: ParamsOf<TQueries[Q]>) => Promise<RowOf<TQueries[Q]>>;
23
- optional: <Q extends keyof TQueries>(query: Q, ...params: ParamsOf<TQueries[Q]>) => Promise<RowOf<TQueries[Q]> | null>;
24
- execute: <Q extends keyof TQueries>(query: Q, ...params: ParamsOf<TQueries[Q]>) => Promise<ExecuteResult>;
25
- file: TypedFile<TFileQueries>;
21
+ export type TypedSqlForRegistry<Registry extends {
22
+ queries: object;
23
+ fileQueries: object;
24
+ }> = {
25
+ <Q extends keyof Registry["queries"]>(query: Q, ...params: ParamsOf<Registry["queries"][Q]>): Promise<RowOf<Registry["queries"][Q]>[]>;
26
+ one: <Q extends keyof Registry["queries"]>(query: Q, ...params: ParamsOf<Registry["queries"][Q]>) => Promise<RowOf<Registry["queries"][Q]>>;
27
+ optional: <Q extends keyof Registry["queries"]>(query: Q, ...params: ParamsOf<Registry["queries"][Q]>) => Promise<RowOf<Registry["queries"][Q]> | null>;
28
+ execute: <Q extends keyof Registry["queries"]>(query: Q, ...params: ParamsOf<Registry["queries"][Q]>) => Promise<ExecuteResult>;
29
+ file: TypedFile<Registry["fileQueries"]>;
26
30
  id: (...parts: string[]) => string;
27
31
  json: JsonFn;
28
32
  array: ArrayFn;
29
33
  readonly [QUERY_EXECUTOR]?: QueryExecutorMethod;
34
+ readonly [QUERY_REGISTRY]?: Registry;
30
35
  };
31
- export type Typed<TQueries, TFileQueries, TTransactionOptions> = TypedSql<TQueries, TFileQueries> & {
36
+ export type TypedSql<TQueries extends object, TFileQueries extends object> = TypedSqlForRegistry<{
37
+ queries: TQueries;
38
+ fileQueries: TFileQueries;
39
+ }>;
40
+ export type TypedForRegistry<Registry extends {
41
+ queries: object;
42
+ fileQueries: object;
43
+ }, TTransactionOptions> = TypedSqlForRegistry<Registry> & {
32
44
  transaction: {
33
- <R>(fn: (tx: TypedSql<TQueries, TFileQueries>) => Promise<R>): Promise<R>;
34
- <R>(opts: TTransactionOptions, fn: (tx: TypedSql<TQueries, TFileQueries>) => Promise<R>): Promise<R>;
45
+ <R>(fn: (tx: TypedSqlForRegistry<Registry>) => Promise<R>): Promise<R>;
46
+ <R>(opts: TTransactionOptions, fn: (tx: TypedSqlForRegistry<Registry>) => Promise<R>): Promise<R>;
35
47
  };
36
48
  };
49
+ export type Typed<TQueries extends object, TFileQueries extends object, TTransactionOptions> = TypedForRegistry<{
50
+ queries: TQueries;
51
+ fileQueries: TFileQueries;
52
+ }, TTransactionOptions>;
37
53
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onreza/sqlx-js",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Compile-time-checked raw SQL for TypeScript + PostgreSQL. Inspired by sqlx.",
5
5
  "license": "MIT",
6
6
  "type": "module",