rado 1.0.6 → 1.0.8

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.
@@ -26,17 +26,20 @@ export declare class Database<Meta extends QueryMeta = Either> extends Builder<M
26
26
  transaction<T>(this: Database<Async>, run: (tx: Transaction<Meta>) => Promise<T>, options?: TransactionOptions[Meta['dialect']]): Promise<T>;
27
27
  transaction<T>(run: (tx: Transaction<Meta>) => T | Promise<T>, options?: TransactionOptions[Meta['dialect']]): Deliver<Meta, T>;
28
28
  }
29
+ export interface TransactionUniversalOptions {
30
+ async?: boolean;
31
+ }
29
32
  export interface TransactionOptions {
30
- universal: never;
31
- sqlite: {
33
+ universal: TransactionUniversalOptions;
34
+ sqlite: TransactionUniversalOptions & {
32
35
  behavior?: 'deferred' | 'immediate' | 'exclusive';
33
36
  };
34
- postgres: {
37
+ postgres: TransactionUniversalOptions & {
35
38
  isolationLevel?: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable';
36
39
  accessMode?: 'read only' | 'read write';
37
40
  deferrable?: boolean;
38
41
  };
39
- mysql: {
42
+ mysql: TransactionUniversalOptions & {
40
43
  isolationLevel?: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable';
41
44
  accessMode?: 'read only' | 'read write';
42
45
  withConsistentSnapshot?: boolean;
@@ -65,10 +65,13 @@ var Database = class extends Builder {
65
65
  return this.driver.exec(emitter.sql);
66
66
  }
67
67
  transaction(run, options = {}) {
68
- return this.driver.transaction((inner) => {
69
- const tx = new Transaction(inner, this.dialect, this.diff);
70
- return run(tx);
71
- }, options);
68
+ return this.driver.transaction(
69
+ (inner) => {
70
+ const tx = new Transaction(inner, this.dialect, this.diff);
71
+ return run(tx);
72
+ },
73
+ { async: run.constructor.name === "AsyncFunction", ...options }
74
+ );
72
75
  }
73
76
  };
74
77
  var Rollback = class extends Error {
@@ -1,3 +1,4 @@
1
+ import type { TransactionUniversalOptions } from './Database.js';
1
2
  export type Driver = SyncDriver | AsyncDriver;
2
3
  export type Statement = SyncStatement | AsyncStatement;
3
4
  export interface BatchQuery {
@@ -16,7 +17,7 @@ export interface SyncDriver extends DriverSpecs {
16
17
  close(): void;
17
18
  exec(query: string): void;
18
19
  prepare(query: string, options?: PrepareOptions): SyncStatement;
19
- transaction<T>(run: (inner: SyncDriver) => T, options: unknown): T;
20
+ transaction<T>(run: (inner: SyncDriver) => T, options: TransactionUniversalOptions): T;
20
21
  batch(queries: Array<BatchQuery>): Array<Array<unknown>>;
21
22
  }
22
23
  export interface SyncStatement {
@@ -30,7 +31,7 @@ export interface AsyncDriver extends DriverSpecs {
30
31
  close(): Promise<void>;
31
32
  exec(query: string): Promise<void>;
32
33
  prepare(query: string, options?: PrepareOptions): AsyncStatement;
33
- transaction<T>(run: (inner: AsyncDriver) => Promise<T>, options: unknown): Promise<T>;
34
+ transaction<T>(run: (inner: AsyncDriver) => Promise<T>, options: TransactionUniversalOptions): Promise<T>;
34
35
  batch(queries: Array<BatchQuery>): Promise<Array<Array<unknown>>>;
35
36
  }
36
37
  export interface AsyncStatement {
@@ -6,6 +6,7 @@ import { Sql } from './Sql.js';
6
6
  import type { TableApi } from './Table.js';
7
7
  import type { FieldData } from './expr/Field.js';
8
8
  import type { IncludeData } from './expr/Include.js';
9
+ import { type JsonPath } from './expr/Json.js';
9
10
  import type { Delete } from './query/Delete.js';
10
11
  import type { Insert } from './query/Insert.js';
11
12
  import type { SelectData } from './query/Select.js';
@@ -21,7 +22,7 @@ export declare abstract class Emitter {
21
22
  abstract emitIdentifier(value: string): void;
22
23
  abstract emitValue(value: unknown): void;
23
24
  abstract emitInline(value: unknown): void;
24
- abstract emitJsonPath(value: Array<number | string>): void;
25
+ abstract emitJsonPath(path: JsonPath): void;
25
26
  abstract emitPlaceholder(value: string): void;
26
27
  emitIdentifierOrSelf(value: string): void;
27
28
  selfName?: string;
@@ -76,9 +76,11 @@ var Selection = class {
76
76
  #fieldNames(input, names, name) {
77
77
  const expr = getSql(input);
78
78
  if (expr) {
79
- const exprName = name ?? expr.alias;
79
+ let exprName = name ?? expr.alias;
80
80
  if (!exprName)
81
81
  throw new Error("Missing field name");
82
+ while (names.has(exprName))
83
+ exprName = `${exprName}_`;
82
84
  return [exprName];
83
85
  }
84
86
  return Object.entries(input).flatMap(
@@ -98,7 +100,7 @@ var Selection = class {
98
100
  if (field.fieldName === exprName)
99
101
  return expr;
100
102
  }
101
- return sql`${expr} as ${sql.identifier(exprName)}`;
103
+ return sql`${expr.forSelection()} as ${sql.identifier(exprName)}`;
102
104
  }
103
105
  return expr;
104
106
  }
@@ -3,6 +3,7 @@ import type { Emitter } from './Emitter.js';
3
3
  import { type HasSql, internalSql } from './Internal.js';
4
4
  import type { Runtime } from './MetaData.js';
5
5
  import type { FieldData } from './expr/Field.js';
6
+ import type { JsonPath } from './expr/Json.js';
6
7
  type EmitMethods = {
7
8
  [K in keyof Emitter as K extends `emit${string}` ? K : never]: Emitter[K];
8
9
  };
@@ -31,7 +32,8 @@ export declare class Sql<Value = unknown> implements HasSql<Value> {
31
32
  value(value: unknown): Sql<Value>;
32
33
  getValue(): Value | undefined;
33
34
  inline(value: unknown): Sql<Value>;
34
- jsonPath(path: Array<string | number>): Sql<Value>;
35
+ jsonPath(path: JsonPath): Sql<Value>;
36
+ forSelection(): Sql<Value>;
35
37
  placeholder(name: string): Sql<Value>;
36
38
  identifier(identifier: string): Sql<Value>;
37
39
  inlineValues(): Sql<Value>;
@@ -48,6 +50,7 @@ export declare namespace sql {
48
50
  export function placeholder<T>(name: string): Sql<T>;
49
51
  export function identifier<T>(identifier: string): Sql<T>;
50
52
  export function field<T>(field: FieldData): Sql<T>;
53
+ export function jsonPath<T>(path: JsonPath): Sql<T>;
51
54
  export function chunk<Type extends keyof EmitMethods>(type: Type, inner: Parameters<EmitMethods[Type]>[0]): Sql;
52
55
  export function universal<T>(runtimes: Partial<Record<Runtime | 'default', Sql<T>>>): Sql<T>;
53
56
  type QueryChunk = HasSql | undefined;
package/dist/core/Sql.js CHANGED
@@ -57,11 +57,21 @@ var Sql = class _Sql {
57
57
  return this.chunk("emitInline", value);
58
58
  }
59
59
  jsonPath(path) {
60
- const last = this.#chunks.at(-1);
61
- if (last?.type === "emitJsonPath")
62
- last.inner.push(...path);
63
- else
64
- this.chunk("emitJsonPath", path);
60
+ const inner = path.target.#chunks[0];
61
+ if (inner?.type !== "emitJsonPath")
62
+ return this.chunk("emitJsonPath", path);
63
+ const innerPath = inner.inner;
64
+ return this.chunk("emitJsonPath", {
65
+ ...innerPath,
66
+ segments: [...innerPath.segments, ...path.segments]
67
+ });
68
+ }
69
+ forSelection() {
70
+ if (this.#chunks.length === 1) {
71
+ const first = this.#chunks[0];
72
+ if (first.type === "emitJsonPath")
73
+ return sql.jsonPath({ ...first.inner, asSql: false }).mapWith(this);
74
+ }
65
75
  return this;
66
76
  }
67
77
  placeholder(name) {
@@ -143,6 +153,10 @@ function sql(strings, ...inner) {
143
153
  return empty().field(field2);
144
154
  }
145
155
  sql2.field = field;
156
+ function jsonPath(path) {
157
+ return empty().jsonPath(path);
158
+ }
159
+ sql2.jsonPath = jsonPath;
146
160
  function chunk(type, inner) {
147
161
  return empty().chunk(type, inner);
148
162
  }
@@ -1,4 +1,5 @@
1
- import type { HasSql } from '../Internal.js';
1
+ import { type HasSql } from '../Internal.js';
2
+ import { type Sql } from '../Sql.js';
2
3
  import type { Input } from './Input.js';
3
4
  export interface JsonArrayHasSql<Value> extends HasSql<Array<Value>> {
4
5
  [index: number]: JsonExpr<Value>;
@@ -10,7 +11,12 @@ type NullableEach<T> = {
10
11
  [P in keyof T]: T[P] | null;
11
12
  };
12
13
  export type JsonExpr<Value> = [NonNullable<Value>] extends [Array<infer V>] ? JsonArrayHasSql<null extends Value ? V | null : V> : [NonNullable<Value>] extends [object] ? JsonRecordHasSql<null extends Value ? NullableEach<Value> : Value> : HasSql<Value>;
14
+ export interface JsonPath {
15
+ target: Sql;
16
+ segments: Array<number | string>;
17
+ asSql: boolean;
18
+ }
13
19
  export declare function jsonExpr<Value>(e: HasSql<Value>): JsonExpr<Value>;
14
- export declare function jsonAggregateArray(...args: Array<Input<unknown>>): import("../Sql.ts").Sql<unknown>;
15
- export declare function jsonArray(...args: Array<Input<unknown>>): import("../Sql.ts").Sql<unknown>;
20
+ export declare function jsonAggregateArray(...args: Array<Input<unknown>>): Sql<unknown>;
21
+ export declare function jsonArray(...args: Array<Input<unknown>>): Sql<unknown>;
16
22
  export {};
@@ -1,4 +1,5 @@
1
1
  // src/core/expr/Json.ts
2
+ import { getSql } from "../Internal.js";
2
3
  import { sql } from "../Sql.js";
3
4
  import { callFunction } from "./Functions.js";
4
5
  var INDEX_PROPERTY = /^\d+$/;
@@ -8,7 +9,17 @@ function jsonExpr(e) {
8
9
  if (typeof prop !== "string")
9
10
  return Reflect.get(target, prop);
10
11
  const isNumber = INDEX_PROPERTY.test(prop);
11
- return jsonExpr(sql`${target}`.jsonPath([isNumber ? Number(prop) : prop]));
12
+ return jsonExpr(
13
+ sql.jsonPath({
14
+ target: getSql(target),
15
+ asSql: true,
16
+ segments: [isNumber ? Number(prop) : prop]
17
+ }).mapWith({
18
+ mapFromDriverValue(value, specs) {
19
+ return specs.parsesJson ? value : JSON.parse(value);
20
+ }
21
+ })
22
+ );
12
23
  }
13
24
  });
14
25
  }
@@ -2,6 +2,7 @@
2
2
  import { SyncDatabase } from "../core/Database.js";
3
3
  import { sqliteDialect } from "../sqlite.js";
4
4
  import { sqliteDiff } from "../sqlite/diff.js";
5
+ import { execTransaction } from "../sqlite/transactions.js";
5
6
  var PreparedStatement = class {
6
7
  constructor(stmt, isSelection) {
7
8
  this.stmt = stmt;
@@ -25,9 +26,10 @@ var PreparedStatement = class {
25
26
  free() {
26
27
  }
27
28
  };
28
- var BetterSqlite3Driver = class {
29
- constructor(client) {
29
+ var BetterSqlite3Driver = class _BetterSqlite3Driver {
30
+ constructor(client, depth = 0) {
30
31
  this.client = client;
32
+ this.depth = depth;
31
33
  }
32
34
  parsesJson = false;
33
35
  exec(query) {
@@ -48,11 +50,13 @@ var BetterSqlite3Driver = class {
48
50
  );
49
51
  }
50
52
  transaction(run, options) {
51
- let result;
52
- this.client.transaction(() => {
53
- result = run(this);
54
- })[options.behavior ?? "deferred"]();
55
- return result;
53
+ return execTransaction(
54
+ this,
55
+ this.depth,
56
+ (depth) => new _BetterSqlite3Driver(this.client, depth),
57
+ run,
58
+ options
59
+ );
56
60
  }
57
61
  };
58
62
  function connect(db) {
@@ -2,6 +2,7 @@
2
2
  import { SyncDatabase } from "../core/Database.js";
3
3
  import { sqliteDialect } from "../sqlite.js";
4
4
  import { sqliteDiff } from "../sqlite/diff.js";
5
+ import { execTransaction } from "../sqlite/transactions.js";
5
6
  var PreparedStatement = class {
6
7
  constructor(stmt) {
7
8
  this.stmt = stmt;
@@ -22,9 +23,10 @@ var PreparedStatement = class {
22
23
  this.stmt.finalize();
23
24
  }
24
25
  };
25
- var BunSqliteDriver = class {
26
- constructor(client) {
26
+ var BunSqliteDriver = class _BunSqliteDriver {
27
+ constructor(client, depth = 0) {
27
28
  this.client = client;
29
+ this.depth = depth;
28
30
  }
29
31
  parsesJson = false;
30
32
  exec(query) {
@@ -42,11 +44,13 @@ var BunSqliteDriver = class {
42
44
  }, {});
43
45
  }
44
46
  transaction(run, options) {
45
- let result;
46
- this.client.transaction(() => {
47
- result = run(this);
48
- })[options.behavior ?? "deferred"]();
49
- return result;
47
+ return execTransaction(
48
+ this,
49
+ this.depth,
50
+ (depth) => new _BunSqliteDriver(this.client, depth),
51
+ run,
52
+ options
53
+ );
50
54
  }
51
55
  };
52
56
  function connect(db) {
@@ -2,6 +2,7 @@
2
2
  import { SyncDatabase } from "../core/Database.js";
3
3
  import { sqliteDialect } from "../sqlite.js";
4
4
  import { sqliteDiff } from "../sqlite/diff.js";
5
+ import { execTransaction } from "../sqlite/transactions.js";
5
6
  var PreparedStatement = class {
6
7
  constructor(client, stmt) {
7
8
  this.client = client;
@@ -59,16 +60,13 @@ var SqlJsDriver = class _SqlJsDriver {
59
60
  );
60
61
  }
61
62
  transaction(run, options) {
62
- const behavior = options.behavior ?? "deferred";
63
- this.exec(this.depth > 0 ? `savepoint d${this.depth}` : `begin ${behavior}`);
64
- try {
65
- const result = run(new _SqlJsDriver(this.client, this.depth + 1));
66
- this.exec(this.depth > 0 ? `release d${this.depth}` : "commit");
67
- return result;
68
- } catch (error) {
69
- this.exec(this.depth > 0 ? `rollback to d${this.depth}` : "rollback");
70
- throw error;
71
- }
63
+ return execTransaction(
64
+ this,
65
+ this.depth,
66
+ (depth) => new _SqlJsDriver(this.client, depth),
67
+ run,
68
+ options
69
+ );
72
70
  }
73
71
  };
74
72
  function connect(db) {
@@ -16,10 +16,12 @@ var mysqlDialect = new Dialect(
16
16
  this.sql += "?";
17
17
  this.params.push(new ValueParam(value));
18
18
  }
19
- emitJsonPath(path) {
20
- this.sql += `->>${this.quoteString(
21
- `$${path.map((p) => typeof p === "number" ? `[${p}]` : `.${p}`).join("")}`
22
- )}`;
19
+ emitJsonPath({ target, asSql, segments }) {
20
+ target.emitTo(this);
21
+ this.sql += asSql ? "->>" : "->";
22
+ this.sql += this.quoteString(
23
+ `$${segments.map((p) => typeof p === "number" ? `[${p}]` : `.${p}`).join("")}`
24
+ );
23
25
  }
24
26
  emitInline(value) {
25
27
  if (value === null || value === void 0)
@@ -16,13 +16,14 @@ var postgresDialect = new Dialect(
16
16
  this.sql += `$${++this.paramIndex}`;
17
17
  this.params.push(new ValueParam(value));
18
18
  }
19
- emitJsonPath(path) {
20
- for (let i = 0; i < path.length; i++) {
21
- const access = path[i];
22
- if (i <= path.length - 2)
19
+ emitJsonPath({ target, asSql, segments }) {
20
+ target.emitTo(this);
21
+ for (let i = 0; i < segments.length; i++) {
22
+ const access = segments[i];
23
+ if (i <= segments.length - 2)
23
24
  this.sql += "->";
24
- else if (i === path.length - 1)
25
- this.sql += "->>";
25
+ else if (i === segments.length - 1)
26
+ this.sql += asSql ? "->>" : "->";
26
27
  if (typeof access === "number")
27
28
  this.sql += access;
28
29
  else
@@ -18,10 +18,12 @@ var sqliteDialect = new Dialect(
18
18
  this.sql += "?";
19
19
  this.params.push(new ValueParam(value));
20
20
  }
21
- emitJsonPath(path) {
22
- this.sql += `->>${this.quoteString(
23
- `$${path.map((p) => typeof p === "number" ? `[${p}]` : `.${p}`).join("")}`
24
- )}`;
21
+ emitJsonPath({ target, asSql, segments }) {
22
+ target.emitTo(this);
23
+ this.sql += asSql ? "->>" : "->";
24
+ this.sql += this.quoteString(
25
+ `$${segments.map((p) => typeof p === "number" ? `[${p}]` : `.${p}`).join("")}`
26
+ );
25
27
  }
26
28
  emitInline(value) {
27
29
  if (value === null || value === void 0)
@@ -0,0 +1,3 @@
1
+ import type { TransactionOptions } from '../core/Database.js';
2
+ import type { SyncDriver } from '../core/Driver.js';
3
+ export declare function execTransaction<T>(driver: SyncDriver, depth: number, wrap: (depth: number) => SyncDriver, run: (inner: SyncDriver) => T, options: TransactionOptions['sqlite']): T;
@@ -0,0 +1,35 @@
1
+ // src/sqlite/transactions.ts
2
+ var locks = /* @__PURE__ */ new WeakMap();
3
+ function execTransaction(driver, depth, wrap, run, options) {
4
+ const needsLock = options.async;
5
+ const behavior = options.behavior ?? "deferred";
6
+ if (!needsLock)
7
+ return transact();
8
+ let trigger;
9
+ const lock = new Promise((resolve) => trigger = resolve);
10
+ const current = Promise.resolve(locks.get(driver));
11
+ locks.set(driver, lock);
12
+ return current.then(transact).finally(trigger);
13
+ function transact() {
14
+ try {
15
+ driver.exec(depth > 0 ? `savepoint d${depth}` : `begin ${behavior}`);
16
+ const result = run(wrap(depth + 1));
17
+ if (result instanceof Promise)
18
+ return result.then(release, rollback);
19
+ return release(result);
20
+ } catch (error) {
21
+ return rollback(error);
22
+ }
23
+ }
24
+ function release(result) {
25
+ driver.exec(depth > 0 ? `release d${depth}` : "commit");
26
+ return result;
27
+ }
28
+ function rollback(error) {
29
+ driver.exec(depth > 0 ? `rollback to d${depth}` : "rollback");
30
+ throw error;
31
+ }
32
+ }
33
+ export {
34
+ execTransaction
35
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rado",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -27,7 +27,7 @@
27
27
  "files": ["dist"],
28
28
  "devDependencies": {
29
29
  "@benmerckx/suite": "^0.3.0",
30
- "@biomejs/biome": "^1.6.1",
30
+ "@biomejs/biome": "^1.8.3",
31
31
  "@cloudflare/workers-types": "^4.20230628.0",
32
32
  "@electric-sql/pglite": "^0.1.5",
33
33
  "@sqlite.org/sqlite-wasm": "^3.42.0-build4",