@telorun/sql 0.10.0 → 0.12.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 CHANGED
@@ -48,7 +48,9 @@ See [examples/](./examples/) for a list of working applications.
48
48
 
49
49
  ## Status
50
50
 
51
- Telo is under **active development**. The core runtime, module system, and standard library are functional, but the API surface — including YAML shapes — may change without notice. Not yet recommended for production use.
51
+ Telo is under heavy development. While it is pre-1.0, breaking changes ship in **minor** releases — manifest shapes, kind schemas, and APIs can change between versions. Pin your imports and expect to update manifests when you upgrade. The core runtime, module system, and standard library are functional, but Telo is not yet recommended for production use.
52
+
53
+ **1.0 lands when the Rust kernel reaches feature parity with the Node.js implementation** — that is the milestone that freezes the manifest contract across runtimes.
52
54
 
53
55
  ## The Meaning of Telo
54
56
 
@@ -1,4 +1,4 @@
1
- import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
1
+ import type { InvokeContext, ResourceContext, ResourceInstance } from "@telorun/sdk";
2
2
  import type { SqlConnection } from "./sql-connection.js";
3
3
  import type { SqlResult } from "./sql-query-controller.js";
4
4
  import type { SqlTransactionResource } from "./sql-transaction-controller.js";
@@ -18,7 +18,7 @@ declare class SqlCommandResource implements ResourceInstance {
18
18
  private readonly manifest;
19
19
  private readonly ctx;
20
20
  constructor(manifest: SqlCommandManifest, ctx: ResourceContext);
21
- invoke(input: unknown): Promise<SqlResult>;
21
+ invoke(input: unknown, invokeCtx?: InvokeContext): Promise<SqlResult>;
22
22
  }
23
23
  export declare function register(): void;
24
24
  export declare function create(resource: SqlCommandManifest, ctx: ResourceContext): Promise<SqlCommandResource>;
@@ -7,7 +7,7 @@ class SqlCommandResource {
7
7
  this.manifest = manifest;
8
8
  this.ctx = ctx;
9
9
  }
10
- async invoke(input) {
10
+ async invoke(input, invokeCtx) {
11
11
  const m = this.manifest;
12
12
  const ctx = this.ctx;
13
13
  const connection = resolveSqlConnection(m.connection, ctx, () => `Sql.Command "${m.metadata.name}": 'connection'`) ??
@@ -15,7 +15,12 @@ class SqlCommandResource {
15
15
  if (!connection) {
16
16
  throw new Error("Sql: either 'connection' or 'transaction' must be set");
17
17
  }
18
- const result = await runSql(connection, m.transaction, input, ctx);
18
+ // ERR_ZONE_REQUIRED when no sql.Transaction zone is open on THIS statement's
19
+ // connection — a transaction on another connection no longer answers. The
20
+ // kernel supplies the zone kind and the correlation key (including the
21
+ // `/transaction/connection` fallback) from the `transaction` annotation.
22
+ const zone = m.transaction ? ctx.requireZone("transaction", invokeCtx) : undefined;
23
+ const result = await runSql(connection, zone, input, ctx, invokeCtx);
19
24
  return { rows: result.rows, rowCount: connection.toRowCount(result) };
20
25
  }
21
26
  }
@@ -1,6 +1,6 @@
1
+ import { type InvokeContext, type ResourceContext, type ZoneEntry } from "@telorun/sdk";
1
2
  import { type Kysely, type QueryResult } from "kysely";
2
3
  import type { SqlConnection, SqlDialect } from "./sql-connection.js";
3
- import type { SqlTransactionResource } from "./sql-transaction-controller.js";
4
4
  /**
5
5
  * The dialect-neutral half of a connection: statement execution, transaction
6
6
  * scoping, template binding and row-count normalization over a kysely instance.
@@ -10,15 +10,18 @@ import type { SqlTransactionResource } from "./sql-transaction-controller.js";
10
10
  * where the driver has a native multi-statement path.
11
11
  */
12
12
  export declare abstract class SqlConnectionBase implements SqlConnection {
13
+ #private;
13
14
  readonly dialect: SqlDialect;
15
+ protected readonly ctx: ResourceContext;
14
16
  protected readonly db: Kysely<any>;
15
- constructor(db: Kysely<any>, dialect: SqlDialect);
17
+ constructor(db: Kysely<any>, dialect: SqlDialect, ctx: ResourceContext);
16
18
  get kysely(): Kysely<any>;
17
19
  init(): Promise<void>;
18
20
  teardown(): Promise<void>;
19
- transaction<T>(cb: () => Promise<T>): Promise<T>;
20
- execute<T>(sql: string, params?: unknown[], transaction?: SqlTransactionResource): Promise<QueryResult<T>>;
21
- executeTemplate<T>(fragments: string[], values: unknown[], transaction?: SqlTransactionResource): Promise<QueryResult<T>>;
21
+ runInTransaction<T>(body: (bind: (entry: ZoneEntry) => void) => Promise<T>): Promise<T>;
22
+ hasOpenTransaction(ctx?: InvokeContext): boolean;
23
+ execute<T>(sql: string, params?: unknown[], zone?: ZoneEntry, ctx?: InvokeContext): Promise<QueryResult<T>>;
24
+ executeTemplate<T>(fragments: string[], values: unknown[], zone?: ZoneEntry, ctx?: InvokeContext): Promise<QueryResult<T>>;
22
25
  /** Hand the whole script to the driver as one statement. Backends whose driver
23
26
  * needs a dedicated multi-statement entry point override this. */
24
27
  executeScript(sql: string): Promise<void>;
@@ -1,6 +1,5 @@
1
- import { randomUUID } from "crypto";
1
+ import { InvokeError, } from "@telorun/sdk";
2
2
  import { CompiledQuery } from "kysely";
3
- import { currentTxId, deleteTx, getTx, setTx, txStorage } from "./transaction-store.js";
4
3
  /**
5
4
  * The dialect-neutral half of a connection: statement execution, transaction
6
5
  * scoping, template binding and row-count normalization over a kysely instance.
@@ -11,9 +10,17 @@ import { currentTxId, deleteTx, getTx, setTx, txStorage } from "./transaction-st
11
10
  */
12
11
  export class SqlConnectionBase {
13
12
  dialect;
13
+ ctx;
14
14
  db;
15
- constructor(db, dialect) {
15
+ /** Executors for transaction zones open on THIS connection. An instance
16
+ * field, never a module global: the transaction controller and this base are
17
+ * delivered as different bundles, each inlining its own copy of a shared
18
+ * source file, so module scope would be one map per bundle and every lookup
19
+ * a miss (the payload rule, kernel/specs/execution-zones.md §8). */
20
+ #executors = new WeakMap();
21
+ constructor(db, dialect, ctx) {
16
22
  this.dialect = dialect;
23
+ this.ctx = ctx;
17
24
  this.db = db;
18
25
  }
19
26
  get kysely() {
@@ -27,28 +34,24 @@ export class SqlConnectionBase {
27
34
  async teardown() {
28
35
  await this.db.destroy();
29
36
  }
30
- async transaction(cb) {
31
- const txId = randomUUID();
32
- return this.db.transaction().execute(async (trx) => {
33
- setTx(txId, { executor: trx });
34
- try {
35
- return await txStorage.run(txId, cb);
36
- }
37
- finally {
38
- deleteTx(txId);
39
- }
40
- });
37
+ async runInTransaction(body) {
38
+ return this.db
39
+ .transaction()
40
+ .execute((trx) => body((entry) => this.#executors.set(entry, trx)));
41
+ }
42
+ hasOpenTransaction(ctx) {
43
+ return this.ctx.zonesFor(this, ctx).some((entry) => this.#executors.has(entry));
41
44
  }
42
- async execute(sql, params = [], transaction) {
43
- const executor = this.resolveExecutor(transaction);
45
+ async execute(sql, params = [], zone, ctx) {
46
+ const executor = this.resolveExecutor(zone, ctx);
44
47
  return executor.executeQuery(CompiledQuery.raw(sql, params));
45
48
  }
46
- async executeTemplate(fragments, values, transaction) {
49
+ async executeTemplate(fragments, values, zone, ctx) {
47
50
  let sql = fragments[0] ?? "";
48
51
  for (let i = 1; i < fragments.length; i++) {
49
52
  sql += this.placeholder(i) + fragments[i];
50
53
  }
51
- return this.execute(sql, values, transaction);
54
+ return this.execute(sql, values, zone, ctx);
52
55
  }
53
56
  /** Hand the whole script to the driver as one statement. Backends whose driver
54
57
  * needs a dedicated multi-statement entry point override this. */
@@ -67,17 +70,16 @@ export class SqlConnectionBase {
67
70
  placeholder(index) {
68
71
  return this.dialect.placeholderStyle === "numbered" ? `$${index}` : "?";
69
72
  }
70
- resolveExecutor(transaction) {
71
- if (transaction) {
72
- transaction.assertActive();
73
+ resolveExecutor(zone, ctx) {
74
+ if (zone && !this.#executors.has(zone)) {
75
+ // A zone this connection did not open cannot be silently ignored: the
76
+ // caller declared a requirement, and `?? this.db` here would execute it
77
+ // outside the transaction it asked for — silent non-transactional writes
78
+ // instead of a loud failure.
79
+ throw new InvokeError("ERR_SQL_ZONE_FOREIGN", `Sql: the ${zone.kind} zone provided by '${zone.provider.ref.name}' was not opened on ` +
80
+ `this connection — the statement would execute outside the transaction it names`);
73
81
  }
74
- const txId = currentTxId();
75
- if (txId) {
76
- const entry = getTx(txId);
77
- if (entry) {
78
- return entry.executor;
79
- }
80
- }
81
- return this.db;
82
+ const entry = zone ?? this.ctx.zonesFor(this, ctx).find((e) => this.#executors.has(e));
83
+ return (entry && this.#executors.get(entry)) ?? this.db;
82
84
  }
83
85
  }
@@ -1,6 +1,5 @@
1
- import type { ResourceInstance } from "@telorun/sdk";
1
+ import type { InvokeContext, ResourceInstance, ZoneEntry } from "@telorun/sdk";
2
2
  import type { Kysely, QueryResult } from "kysely";
3
- import type { SqlTransactionResource } from "./sql-transaction-controller.js";
4
3
  /** Native bind-placeholder syntax: SQLite binds anonymous `?`, PostgreSQL binds
5
4
  * numbered `$1`, `$2`, … */
6
5
  export type PlaceholderStyle = "qmark" | "numbered";
@@ -25,6 +24,12 @@ export declare function quoteAnsiIdentifier(name: string): string;
25
24
  * against. Backends (`sql-postgres`, `sql-sqlite`) own their own implementation
26
25
  * — usually by extending {@link SqlConnectionBase} — so nothing in this module
27
26
  * knows which databases exist.
27
+ *
28
+ * Transaction membership is ambient, carried by the kernel's execution-zone
29
+ * stack (kernel/specs/execution-zones.md) and keyed per connection: a statement
30
+ * executes on an open transaction's executor when a transaction zone
31
+ * correlated on THIS connection is ambient, or when the caller passes the
32
+ * zone entry explicitly.
28
33
  */
29
34
  export interface SqlConnection extends ResourceInstance {
30
35
  readonly dialect: SqlDialect;
@@ -33,13 +38,19 @@ export interface SqlConnection extends ResourceInstance {
33
38
  * must stay implementable by a driver kysely does not support; a consumer
34
39
  * that needs it (`Sql.Migrations`) checks and fails with a clear message. */
35
40
  readonly kysely?: Kysely<any>;
36
- execute<T>(sql: string, params?: unknown[], transaction?: SqlTransactionResource): Promise<QueryResult<T>>;
41
+ execute<T>(sql: string, params?: unknown[], zone?: ZoneEntry, ctx?: InvokeContext): Promise<QueryResult<T>>;
37
42
  /** Assemble SQL from literal fragments by interleaving dialect-native
38
43
  * placeholders, then bind `values` positionally. */
39
- executeTemplate<T>(fragments: string[], values: unknown[], transaction?: SqlTransactionResource): Promise<QueryResult<T>>;
44
+ executeTemplate<T>(fragments: string[], values: unknown[], zone?: ZoneEntry, ctx?: InvokeContext): Promise<QueryResult<T>>;
40
45
  /** Run a multi-statement script. */
41
46
  executeScript(sql: string): Promise<void>;
42
- transaction<T>(cb: () => Promise<T>): Promise<T>;
47
+ /** Open a database transaction and hand the caller a `bind` that keys the
48
+ * open executor on the zone entry the caller mints (via `ctx.withZone`).
49
+ * The executor stays private to this connection — the payload rule. */
50
+ runInTransaction<T>(body: (bind: (entry: ZoneEntry) => void) => Promise<T>, ctx?: InvokeContext): Promise<T>;
51
+ /** True when an ambient transaction zone correlated on this connection has an
52
+ * open executor here — the flat-nesting check `Sql.Transaction` reuses. */
53
+ hasOpenTransaction(ctx?: InvokeContext): boolean;
43
54
  /** Rows affected by a write, normalized across drivers. */
44
55
  toRowCount(result: QueryResult<unknown>): number;
45
56
  }
@@ -1,4 +1,4 @@
1
- import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
1
+ import type { InvokeContext, ResourceContext, ResourceInstance } from "@telorun/sdk";
2
2
  import type { SqlConnection } from "./sql-connection.js";
3
3
  import type { SqlTransactionResource } from "./sql-transaction-controller.js";
4
4
  interface SqlQueryManifest {
@@ -21,7 +21,7 @@ declare class SqlQueryResource implements ResourceInstance {
21
21
  private readonly manifest;
22
22
  private readonly ctx;
23
23
  constructor(manifest: SqlQueryManifest, ctx: ResourceContext);
24
- invoke(input: unknown): Promise<SqlResult>;
24
+ invoke(input: unknown, invokeCtx?: InvokeContext): Promise<SqlResult>;
25
25
  }
26
26
  export declare function register(): void;
27
27
  export declare function create(resource: SqlQueryManifest, ctx: ResourceContext): Promise<SqlQueryResource>;
@@ -7,11 +7,14 @@ class SqlQueryResource {
7
7
  this.manifest = manifest;
8
8
  this.ctx = ctx;
9
9
  }
10
- async invoke(input) {
10
+ async invoke(input, invokeCtx) {
11
11
  const m = this.manifest;
12
12
  const ctx = this.ctx;
13
13
  const connection = resolveConnection(m.connection, m.transaction, ctx, () => `Sql.Query "${m.metadata.name}": 'connection'`);
14
- const result = await runSql(connection, m.transaction, input, ctx);
14
+ // ERR_ZONE_REQUIRED when no sql.Transaction zone is open on THIS query's
15
+ // connection — a transaction on another connection no longer answers.
16
+ const zone = m.transaction ? ctx.requireZone("transaction", invokeCtx) : undefined;
17
+ const result = await runSql(connection, zone, input, ctx, invokeCtx);
15
18
  return { rows: result.rows, rowCount: result.rows.length };
16
19
  }
17
20
  }
package/dist/sql-run.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { type ResourceContext } from "@telorun/sdk";
1
+ import { type InvokeContext, type ResourceContext, type ZoneEntry } from "@telorun/sdk";
2
2
  import type { QueryResult } from "kysely";
3
3
  import type { SqlConnection } from "./sql-connection.js";
4
- import type { SqlTransactionResource } from "./sql-transaction-controller.js";
5
4
  /** Execute the `sql` input of a Query/Exec resource against `connection`.
6
5
  *
7
6
  * Two modes:
@@ -13,4 +12,4 @@ import type { SqlTransactionResource } from "./sql-transaction-controller.js";
13
12
  *
14
13
  * Mixing a `!sql` template with `bindings` is rejected. A plain string `sql`
15
14
  * with neither is executed verbatim. */
16
- export declare function runSql(connection: SqlConnection, transaction: SqlTransactionResource | undefined, input: unknown, ctx: ResourceContext): Promise<QueryResult<Record<string, unknown>>>;
15
+ export declare function runSql(connection: SqlConnection, zone: ZoneEntry | undefined, input: unknown, ctx: ResourceContext, invokeCtx?: InvokeContext): Promise<QueryResult<Record<string, unknown>>>;
package/dist/sql-run.js CHANGED
@@ -1,4 +1,4 @@
1
- import { InvokeError, isParameterizedSql } from "@telorun/sdk";
1
+ import { InvokeError, isParameterizedSql, } from "@telorun/sdk";
2
2
  /** Execute the `sql` input of a Query/Exec resource against `connection`.
3
3
  *
4
4
  * Two modes:
@@ -10,7 +10,7 @@ import { InvokeError, isParameterizedSql } from "@telorun/sdk";
10
10
  *
11
11
  * Mixing a `!sql` template with `bindings` is rejected. A plain string `sql`
12
12
  * with neither is executed verbatim. */
13
- export async function runSql(connection, transaction, input, ctx) {
13
+ export async function runSql(connection, zone, input, ctx, invokeCtx) {
14
14
  const expanded = ctx.expandValue(input, {});
15
15
  const sql = expanded.sql;
16
16
  const bindings = expanded.bindings;
@@ -21,7 +21,7 @@ export async function runSql(connection, transaction, input, ctx) {
21
21
  "Use one or the other — `!sql` binds each inline value automatically; " +
22
22
  "`bindings` is for hand-written ? / $n placeholders.");
23
23
  }
24
- return connection.executeTemplate(sql.fragments, sql.values, transaction);
24
+ return connection.executeTemplate(sql.fragments, sql.values, zone, invokeCtx);
25
25
  }
26
- return connection.execute(sql, hasBindings ? bindings : [], transaction);
26
+ return connection.execute(sql, hasBindings ? bindings : [], zone, invokeCtx);
27
27
  }
@@ -1,4 +1,4 @@
1
- import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
1
+ import type { InvokeContext, ResourceContext, ResourceInstance } from "@telorun/sdk";
2
2
  import type { SqlConnection } from "./sql-connection.js";
3
3
  import type { SqlResult } from "./sql-query-controller.js";
4
4
  import type { SqlTransactionResource } from "./sql-transaction-controller.js";
@@ -62,7 +62,7 @@ declare class SqlSelectionResource implements ResourceInstance {
62
62
  private readonly manifest;
63
63
  private readonly ctx;
64
64
  constructor(manifest: SelectManifest, ctx: ResourceContext);
65
- invoke(input: unknown): Promise<SqlResult>;
65
+ invoke(input: unknown, invokeCtx?: InvokeContext): Promise<SqlResult>;
66
66
  }
67
67
  export declare function register(): void;
68
68
  export declare function create(resource: SelectManifest, ctx: ResourceContext): Promise<SqlSelectionResource>;
@@ -7,7 +7,7 @@ class SqlSelectionResource {
7
7
  this.manifest = manifest;
8
8
  this.ctx = ctx;
9
9
  }
10
- async invoke(input) {
10
+ async invoke(input, invokeCtx) {
11
11
  const m = this.manifest;
12
12
  const ctx = this.ctx;
13
13
  const inputs = {
@@ -24,8 +24,11 @@ class SqlSelectionResource {
24
24
  if (!connection) {
25
25
  throw new Error("Sql.Selection: either 'connection' or 'transaction' must be set");
26
26
  }
27
+ // ERR_ZONE_REQUIRED when no sql.Transaction zone is open on THIS selection's
28
+ // connection — a transaction on another connection no longer answers.
29
+ const zone = m.transaction ? ctx.requireZone("transaction", invokeCtx) : undefined;
27
30
  const { sql, params } = buildSelect(m, where, having, limit, offset, connection.dialect);
28
- const result = await connection.execute(sql, params, m.transaction);
31
+ const result = await connection.execute(sql, params, zone, invokeCtx);
29
32
  return { rows: result.rows, rowCount: result.rows.length };
30
33
  }
31
34
  }
@@ -1,4 +1,4 @@
1
- import type { Invocable, ResourceContext, ResourceInstance } from "@telorun/sdk";
1
+ import { type InvokeContext, type ResourceContext, type ResourceInstance } from "@telorun/sdk";
2
2
  import type { SqlConnection } from "./sql-connection.js";
3
3
  interface SqlTransactionManifest {
4
4
  metadata: {
@@ -6,7 +6,7 @@ interface SqlTransactionManifest {
6
6
  module: string;
7
7
  };
8
8
  connection: SqlConnection;
9
- steps: Invocable;
9
+ steps: ResourceInstance;
10
10
  inputs?: Record<string, unknown>;
11
11
  }
12
12
  export declare class SqlTransactionResource implements ResourceInstance {
@@ -14,8 +14,8 @@ export declare class SqlTransactionResource implements ResourceInstance {
14
14
  private readonly ctx;
15
15
  constructor(manifest: SqlTransactionManifest, ctx: ResourceContext);
16
16
  getConnection(): SqlConnection;
17
- assertActive(): void;
18
- invoke(input: unknown): Promise<unknown>;
17
+ invoke(input: unknown, invokeCtx?: InvokeContext): Promise<unknown>;
18
+ private dispatchSteps;
19
19
  }
20
20
  export declare function register(): void;
21
21
  export declare function create(resource: SqlTransactionManifest, ctx: ResourceContext): Promise<SqlTransactionResource>;
@@ -1,5 +1,10 @@
1
+ import { InvokeError, getRefIdentity, } from "@telorun/sdk";
1
2
  import { resolveSqlConnection } from "./sql-connection-ref.js";
2
- import { currentTxId } from "./transaction-store.js";
3
+ /** The steps slot is `Telo.Executable`: an instance with either entry point. */
4
+ function isExecutable(value) {
5
+ const v = value;
6
+ return typeof v?.invoke === "function" || typeof v?.run === "function";
7
+ }
3
8
  export class SqlTransactionResource {
4
9
  manifest;
5
10
  ctx;
@@ -10,22 +15,49 @@ export class SqlTransactionResource {
10
15
  getConnection() {
11
16
  return (resolveSqlConnection(this.manifest.connection, this.ctx, () => `Sql.Transaction "${this.manifest.metadata.name}": 'connection'`) ?? failMissingConnection(this.manifest.metadata.name));
12
17
  }
13
- assertActive() {
14
- if (!currentTxId()) {
15
- throw new Error(`Sql.Transaction '${this.manifest.metadata.name}': used outside an active transaction`);
18
+ async invoke(input, invokeCtx) {
19
+ const m = this.manifest;
20
+ const ctx = this.ctx;
21
+ // The declared `inputs:` map's CEL reads the caller's invocation input as
22
+ // `inputs.<field>` — the same variable name a Run.Sequence step's inputs
23
+ // read, and what the slot's `inputs: /inputs` pointer names.
24
+ const celScope = { inputs: input ?? {} };
25
+ const expandedInputs = ctx.expandValue(m.inputs ?? {}, celScope);
26
+ // `invokeCtx` is threaded rather than left to the ambient throughout: the
27
+ // caller's context is the authority on which zones are open, and a runtime
28
+ // with no ambient store has nothing else to read.
29
+ //
30
+ // Flat nesting: an ambient transaction on the SAME connection is joined; a
31
+ // different connection's transaction is not ours and we open our own.
32
+ const conn = this.getConnection();
33
+ if (conn.hasOpenTransaction(invokeCtx)) {
34
+ return this.dispatchSteps(expandedInputs, invokeCtx);
16
35
  }
36
+ return conn.runInTransaction((bind) =>
37
+ // "steps" is this kind's own slot; the kernel reads `x-telo-provides-zone`
38
+ // there for the zone kind (this kind) and the correlation key
39
+ // (`/connection`). The entry is handed to the connection's own map —
40
+ // across the bundle/npm delivery split — and the derived context is
41
+ // threaded into the body dispatch, the discipline cancellation has.
42
+ ctx.withZone("steps", (zoneCtx, entry) => {
43
+ bind(entry);
44
+ return this.dispatchSteps(expandedInputs, zoneCtx);
45
+ }, invokeCtx));
17
46
  }
18
- async invoke(input) {
47
+ dispatchSteps(inputs, zoneCtx) {
19
48
  const m = this.manifest;
20
- const ctx = this.ctx;
21
- // Flat nesting: if already inside a transaction, reuse it
22
- if (currentTxId()) {
23
- const expandedInputs = ctx.expandValue(m.inputs ?? {}, input ?? {});
24
- return m.steps.invoke(expandedInputs);
49
+ const target = this.ctx.resolveRef(m.steps, isExecutable, () => `Sql.Transaction "${m.metadata.name}": 'steps'`, "Telo.Executable");
50
+ // The kernel stamps `!ref` identity at Phase-5 injection, and `resolveRef`
51
+ // rescues the sentinel form, so a resolved target always carries one.
52
+ // Guessing a label instead would emit malformed dispatch events (`.Invoked`
53
+ // with no kind) — a silent wrong answer where a missing identity means the
54
+ // resolution path changed under us.
55
+ const id = getRefIdentity(target);
56
+ if (!id) {
57
+ throw new InvokeError("ERR_SQL_STEPS_UNIDENTIFIED", `Sql.Transaction '${m.metadata.name}': the resolved 'steps' target carries no reference ` +
58
+ `identity, so its dispatch cannot be traced or named`);
25
59
  }
26
- const conn = this.getConnection();
27
- const expandedInputs = ctx.expandValue(m.inputs ?? {}, input ?? {});
28
- return conn.transaction(() => m.steps.invoke(expandedInputs));
60
+ return this.ctx.invokeResolved(id.kind, id.name, target, inputs, zoneCtx);
29
61
  }
30
62
  }
31
63
  function failMissingConnection(name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/sql",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Telo SQL module - SQL database resource kinds for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -26,33 +26,10 @@
26
26
  "types": "./dist/index.d.ts",
27
27
  "exports": {
28
28
  ".": {
29
+ "source": "./src/index.ts",
29
30
  "types": "./dist/index.d.ts",
30
31
  "bun": "./src/index.ts",
31
32
  "import": "./dist/index.js"
32
- },
33
- "./sql-query": {
34
- "bun": "./src/sql-query-controller.ts",
35
- "import": "./dist/sql-query-controller.js"
36
- },
37
- "./sql-selection": {
38
- "bun": "./src/sql-selection-controller.ts",
39
- "import": "./dist/sql-selection-controller.js"
40
- },
41
- "./sql-command": {
42
- "bun": "./src/sql-command-controller.ts",
43
- "import": "./dist/sql-command-controller.js"
44
- },
45
- "./sql-transaction": {
46
- "bun": "./src/sql-transaction-controller.ts",
47
- "import": "./dist/sql-transaction-controller.js"
48
- },
49
- "./sql-migration": {
50
- "bun": "./src/sql-migration-controller.ts",
51
- "import": "./dist/sql-migration-controller.js"
52
- },
53
- "./sql-migrations": {
54
- "bun": "./src/sql-migrations-controller.ts",
55
- "import": "./dist/sql-migrations-controller.js"
56
33
  }
57
34
  },
58
35
  "files": [
@@ -64,13 +41,14 @@
64
41
  },
65
42
  "devDependencies": {
66
43
  "@types/node": "^20.0.0",
44
+ "esbuild": "^0.25.12",
67
45
  "typescript": "^5.0.0",
68
- "@telorun/sdk": "0.58.0"
46
+ "@telorun/sdk": "0.67.0"
69
47
  },
70
48
  "peerDependencies": {
71
49
  "@telorun/sdk": "*"
72
50
  },
73
51
  "scripts": {
74
- "build": "tsc -p tsconfig.lib.json"
52
+ "build": "tsc -p tsconfig.lib.json && esbuild src/sql-command-controller.ts src/sql-migration-controller.ts src/sql-migrations-controller.ts src/sql-query-controller.ts src/sql-selection-controller.ts src/sql-transaction-controller.ts --bundle --format=esm --platform=node --target=node20 --external:@telorun/sdk --conditions=source --outdir=. --banner:js='import { createRequire as __teloCreateRequire } from \"node:module\";const require = __teloCreateRequire(import.meta.url);' --out-extension:.js=.mjs"
75
53
  }
76
54
  }
@@ -1,4 +1,4 @@
1
- import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
1
+ import type { InvokeContext, ResourceContext, ResourceInstance } from "@telorun/sdk";
2
2
  import type { SqlConnection } from "./sql-connection.js";
3
3
  import { resolveSqlConnection } from "./sql-connection-ref.js";
4
4
  import type { SqlResult } from "./sql-query-controller.js";
@@ -21,7 +21,7 @@ class SqlCommandResource implements ResourceInstance {
21
21
  private readonly ctx: ResourceContext,
22
22
  ) {}
23
23
 
24
- async invoke(input: unknown): Promise<SqlResult> {
24
+ async invoke(input: unknown, invokeCtx?: InvokeContext): Promise<SqlResult> {
25
25
  const m = this.manifest;
26
26
  const ctx = this.ctx;
27
27
 
@@ -32,7 +32,12 @@ class SqlCommandResource implements ResourceInstance {
32
32
  throw new Error("Sql: either 'connection' or 'transaction' must be set");
33
33
  }
34
34
 
35
- const result = await runSql(connection, m.transaction, input, ctx);
35
+ // ERR_ZONE_REQUIRED when no sql.Transaction zone is open on THIS statement's
36
+ // connection — a transaction on another connection no longer answers. The
37
+ // kernel supplies the zone kind and the correlation key (including the
38
+ // `/transaction/connection` fallback) from the `transaction` annotation.
39
+ const zone = m.transaction ? ctx.requireZone("transaction", invokeCtx) : undefined;
40
+ const result = await runSql(connection, zone, input, ctx, invokeCtx);
36
41
  return { rows: result.rows, rowCount: connection.toRowCount(result) };
37
42
  }
38
43
  }
@@ -1,8 +1,11 @@
1
- import { randomUUID } from "crypto";
1
+ import {
2
+ InvokeError,
3
+ type InvokeContext,
4
+ type ResourceContext,
5
+ type ZoneEntry,
6
+ } from "@telorun/sdk";
2
7
  import { CompiledQuery, type Kysely, type QueryResult, type Transaction } from "kysely";
3
8
  import type { SqlConnection, SqlDialect } from "./sql-connection.js";
4
- import type { SqlTransactionResource } from "./sql-transaction-controller.js";
5
- import { currentTxId, deleteTx, getTx, setTx, txStorage } from "./transaction-store.js";
6
9
 
7
10
  /**
8
11
  * The dialect-neutral half of a connection: statement execution, transaction
@@ -15,9 +18,17 @@ import { currentTxId, deleteTx, getTx, setTx, txStorage } from "./transaction-st
15
18
  export abstract class SqlConnectionBase implements SqlConnection {
16
19
  protected readonly db: Kysely<any>;
17
20
 
21
+ /** Executors for transaction zones open on THIS connection. An instance
22
+ * field, never a module global: the transaction controller and this base are
23
+ * delivered as different bundles, each inlining its own copy of a shared
24
+ * source file, so module scope would be one map per bundle and every lookup
25
+ * a miss (the payload rule, kernel/specs/execution-zones.md §8). */
26
+ readonly #executors = new WeakMap<ZoneEntry, Kysely<any>>();
27
+
18
28
  constructor(
19
29
  db: Kysely<any>,
20
30
  readonly dialect: SqlDialect,
31
+ protected readonly ctx: ResourceContext,
21
32
  ) {
22
33
  this.db = db;
23
34
  }
@@ -36,38 +47,39 @@ export abstract class SqlConnectionBase implements SqlConnection {
36
47
  await this.db.destroy();
37
48
  }
38
49
 
39
- async transaction<T>(cb: () => Promise<T>): Promise<T> {
40
- const txId = randomUUID();
50
+ async runInTransaction<T>(
51
+ body: (bind: (entry: ZoneEntry) => void) => Promise<T>,
52
+ ): Promise<T> {
53
+ return this.db
54
+ .transaction()
55
+ .execute((trx: Transaction<any>) => body((entry) => this.#executors.set(entry, trx)));
56
+ }
41
57
 
42
- return this.db.transaction().execute(async (trx: Transaction<any>) => {
43
- setTx(txId, { executor: trx });
44
- try {
45
- return await txStorage.run(txId, cb);
46
- } finally {
47
- deleteTx(txId);
48
- }
49
- });
58
+ hasOpenTransaction(ctx?: InvokeContext): boolean {
59
+ return this.ctx.zonesFor(this, ctx).some((entry) => this.#executors.has(entry));
50
60
  }
51
61
 
52
62
  async execute<T>(
53
63
  sql: string,
54
64
  params: unknown[] = [],
55
- transaction?: SqlTransactionResource,
65
+ zone?: ZoneEntry,
66
+ ctx?: InvokeContext,
56
67
  ): Promise<QueryResult<T>> {
57
- const executor = this.resolveExecutor(transaction);
68
+ const executor = this.resolveExecutor(zone, ctx);
58
69
  return executor.executeQuery<T>(CompiledQuery.raw(sql, params));
59
70
  }
60
71
 
61
72
  async executeTemplate<T>(
62
73
  fragments: string[],
63
74
  values: unknown[],
64
- transaction?: SqlTransactionResource,
75
+ zone?: ZoneEntry,
76
+ ctx?: InvokeContext,
65
77
  ): Promise<QueryResult<T>> {
66
78
  let sql = fragments[0] ?? "";
67
79
  for (let i = 1; i < fragments.length; i++) {
68
80
  sql += this.placeholder(i) + fragments[i];
69
81
  }
70
- return this.execute<T>(sql, values, transaction);
82
+ return this.execute<T>(sql, values, zone, ctx);
71
83
  }
72
84
 
73
85
  /** Hand the whole script to the driver as one statement. Backends whose driver
@@ -92,19 +104,19 @@ export abstract class SqlConnectionBase implements SqlConnection {
92
104
  return this.dialect.placeholderStyle === "numbered" ? `$${index}` : "?";
93
105
  }
94
106
 
95
- private resolveExecutor(transaction?: SqlTransactionResource): Kysely<any> {
96
- if (transaction) {
97
- transaction.assertActive();
107
+ private resolveExecutor(zone?: ZoneEntry, ctx?: InvokeContext): Kysely<any> {
108
+ if (zone && !this.#executors.has(zone)) {
109
+ // A zone this connection did not open cannot be silently ignored: the
110
+ // caller declared a requirement, and `?? this.db` here would execute it
111
+ // outside the transaction it asked for — silent non-transactional writes
112
+ // instead of a loud failure.
113
+ throw new InvokeError(
114
+ "ERR_SQL_ZONE_FOREIGN",
115
+ `Sql: the ${zone.kind} zone provided by '${zone.provider.ref.name}' was not opened on ` +
116
+ `this connection — the statement would execute outside the transaction it names`,
117
+ );
98
118
  }
99
-
100
- const txId = currentTxId();
101
- if (txId) {
102
- const entry = getTx(txId);
103
- if (entry) {
104
- return entry.executor as Kysely<any>;
105
- }
106
- }
107
-
108
- return this.db;
119
+ const entry = zone ?? this.ctx.zonesFor(this, ctx).find((e) => this.#executors.has(e));
120
+ return (entry && this.#executors.get(entry)) ?? this.db;
109
121
  }
110
122
  }
@@ -1,6 +1,5 @@
1
- import type { ResourceInstance } from "@telorun/sdk";
1
+ import type { InvokeContext, ResourceInstance, ZoneEntry } from "@telorun/sdk";
2
2
  import type { Kysely, QueryResult } from "kysely";
3
- import type { SqlTransactionResource } from "./sql-transaction-controller.js";
4
3
 
5
4
  /** Native bind-placeholder syntax: SQLite binds anonymous `?`, PostgreSQL binds
6
5
  * numbered `$1`, `$2`, … */
@@ -37,6 +36,12 @@ export function quoteAnsiIdentifier(name: string): string {
37
36
  * against. Backends (`sql-postgres`, `sql-sqlite`) own their own implementation
38
37
  * — usually by extending {@link SqlConnectionBase} — so nothing in this module
39
38
  * knows which databases exist.
39
+ *
40
+ * Transaction membership is ambient, carried by the kernel's execution-zone
41
+ * stack (kernel/specs/execution-zones.md) and keyed per connection: a statement
42
+ * executes on an open transaction's executor when a transaction zone
43
+ * correlated on THIS connection is ambient, or when the caller passes the
44
+ * zone entry explicitly.
40
45
  */
41
46
  export interface SqlConnection extends ResourceInstance {
42
47
  readonly dialect: SqlDialect;
@@ -50,7 +55,8 @@ export interface SqlConnection extends ResourceInstance {
50
55
  execute<T>(
51
56
  sql: string,
52
57
  params?: unknown[],
53
- transaction?: SqlTransactionResource,
58
+ zone?: ZoneEntry,
59
+ ctx?: InvokeContext,
54
60
  ): Promise<QueryResult<T>>;
55
61
 
56
62
  /** Assemble SQL from literal fragments by interleaving dialect-native
@@ -58,13 +64,24 @@ export interface SqlConnection extends ResourceInstance {
58
64
  executeTemplate<T>(
59
65
  fragments: string[],
60
66
  values: unknown[],
61
- transaction?: SqlTransactionResource,
67
+ zone?: ZoneEntry,
68
+ ctx?: InvokeContext,
62
69
  ): Promise<QueryResult<T>>;
63
70
 
64
71
  /** Run a multi-statement script. */
65
72
  executeScript(sql: string): Promise<void>;
66
73
 
67
- transaction<T>(cb: () => Promise<T>): Promise<T>;
74
+ /** Open a database transaction and hand the caller a `bind` that keys the
75
+ * open executor on the zone entry the caller mints (via `ctx.withZone`).
76
+ * The executor stays private to this connection — the payload rule. */
77
+ runInTransaction<T>(
78
+ body: (bind: (entry: ZoneEntry) => void) => Promise<T>,
79
+ ctx?: InvokeContext,
80
+ ): Promise<T>;
81
+
82
+ /** True when an ambient transaction zone correlated on this connection has an
83
+ * open executor here — the flat-nesting check `Sql.Transaction` reuses. */
84
+ hasOpenTransaction(ctx?: InvokeContext): boolean;
68
85
 
69
86
  /** Rows affected by a write, normalized across drivers. */
70
87
  toRowCount(result: QueryResult<unknown>): number;
@@ -1,4 +1,4 @@
1
- import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
1
+ import type { InvokeContext, ResourceContext, ResourceInstance } from "@telorun/sdk";
2
2
  import type { SqlConnection } from "./sql-connection.js";
3
3
  import { resolveSqlConnection } from "./sql-connection-ref.js";
4
4
  import { runSql } from "./sql-run.js";
@@ -25,7 +25,7 @@ class SqlQueryResource implements ResourceInstance {
25
25
  private readonly ctx: ResourceContext,
26
26
  ) {}
27
27
 
28
- async invoke(input: unknown): Promise<SqlResult> {
28
+ async invoke(input: unknown, invokeCtx?: InvokeContext): Promise<SqlResult> {
29
29
  const m = this.manifest;
30
30
  const ctx = this.ctx;
31
31
  const connection = resolveConnection(
@@ -34,7 +34,10 @@ class SqlQueryResource implements ResourceInstance {
34
34
  ctx,
35
35
  () => `Sql.Query "${m.metadata.name}": 'connection'`,
36
36
  );
37
- const result = await runSql(connection, m.transaction, input, ctx);
37
+ // ERR_ZONE_REQUIRED when no sql.Transaction zone is open on THIS query's
38
+ // connection — a transaction on another connection no longer answers.
39
+ const zone = m.transaction ? ctx.requireZone("transaction", invokeCtx) : undefined;
40
+ const result = await runSql(connection, zone, input, ctx, invokeCtx);
38
41
  return { rows: result.rows, rowCount: result.rows.length };
39
42
  }
40
43
  }
package/src/sql-run.ts CHANGED
@@ -1,7 +1,12 @@
1
- import { InvokeError, isParameterizedSql, type ResourceContext } from "@telorun/sdk";
1
+ import {
2
+ InvokeError,
3
+ isParameterizedSql,
4
+ type InvokeContext,
5
+ type ResourceContext,
6
+ type ZoneEntry,
7
+ } from "@telorun/sdk";
2
8
  import type { QueryResult } from "kysely";
3
9
  import type { SqlConnection } from "./sql-connection.js";
4
- import type { SqlTransactionResource } from "./sql-transaction-controller.js";
5
10
 
6
11
  /** Execute the `sql` input of a Query/Exec resource against `connection`.
7
12
  *
@@ -16,9 +21,10 @@ import type { SqlTransactionResource } from "./sql-transaction-controller.js";
16
21
  * with neither is executed verbatim. */
17
22
  export async function runSql(
18
23
  connection: SqlConnection,
19
- transaction: SqlTransactionResource | undefined,
24
+ zone: ZoneEntry | undefined,
20
25
  input: unknown,
21
26
  ctx: ResourceContext,
27
+ invokeCtx?: InvokeContext,
22
28
  ): Promise<QueryResult<Record<string, unknown>>> {
23
29
  const expanded = ctx.expandValue(input, {}) as { sql: unknown; bindings?: unknown[] };
24
30
  const sql = expanded.sql;
@@ -37,13 +43,15 @@ export async function runSql(
37
43
  return connection.executeTemplate<Record<string, unknown>>(
38
44
  sql.fragments,
39
45
  sql.values,
40
- transaction,
46
+ zone,
47
+ invokeCtx,
41
48
  );
42
49
  }
43
50
 
44
51
  return connection.execute<Record<string, unknown>>(
45
52
  sql as string,
46
53
  hasBindings ? (bindings as unknown[]) : [],
47
- transaction,
54
+ zone,
55
+ invokeCtx,
48
56
  );
49
57
  }
@@ -1,4 +1,4 @@
1
- import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
1
+ import type { InvokeContext, ResourceContext, ResourceInstance } from "@telorun/sdk";
2
2
  import type { SqlConnection, SqlDialect } from "./sql-connection.js";
3
3
  import { resolveSqlConnection } from "./sql-connection-ref.js";
4
4
  import type { SqlResult } from "./sql-query-controller.js";
@@ -82,7 +82,7 @@ class SqlSelectionResource implements ResourceInstance {
82
82
  private readonly ctx: ResourceContext,
83
83
  ) {}
84
84
 
85
- async invoke(input: unknown): Promise<SqlResult> {
85
+ async invoke(input: unknown, invokeCtx?: InvokeContext): Promise<SqlResult> {
86
86
  const m = this.manifest;
87
87
  const ctx = this.ctx;
88
88
  const inputs = {
@@ -103,8 +103,11 @@ class SqlSelectionResource implements ResourceInstance {
103
103
  throw new Error("Sql.Selection: either 'connection' or 'transaction' must be set");
104
104
  }
105
105
 
106
+ // ERR_ZONE_REQUIRED when no sql.Transaction zone is open on THIS selection's
107
+ // connection — a transaction on another connection no longer answers.
108
+ const zone = m.transaction ? ctx.requireZone("transaction", invokeCtx) : undefined;
106
109
  const { sql, params } = buildSelect(m, where, having, limit, offset, connection.dialect);
107
- const result = await connection.execute<Record<string, unknown>>(sql, params, m.transaction);
110
+ const result = await connection.execute<Record<string, unknown>>(sql, params, zone, invokeCtx);
108
111
  return { rows: result.rows, rowCount: result.rows.length };
109
112
  }
110
113
  }
@@ -1,15 +1,26 @@
1
- import type { Invocable, ResourceContext, ResourceInstance } from "@telorun/sdk";
1
+ import {
2
+ InvokeError,
3
+ getRefIdentity,
4
+ type InvokeContext,
5
+ type ResourceContext,
6
+ type ResourceInstance,
7
+ } from "@telorun/sdk";
2
8
  import type { SqlConnection } from "./sql-connection.js";
3
9
  import { resolveSqlConnection } from "./sql-connection-ref.js";
4
- import { currentTxId } from "./transaction-store.js";
5
10
 
6
11
  interface SqlTransactionManifest {
7
12
  metadata: { name: string; module: string };
8
13
  connection: SqlConnection;
9
- steps: Invocable;
14
+ steps: ResourceInstance;
10
15
  inputs?: Record<string, unknown>;
11
16
  }
12
17
 
18
+ /** The steps slot is `Telo.Executable`: an instance with either entry point. */
19
+ function isExecutable(value: unknown): value is ResourceInstance {
20
+ const v = value as ResourceInstance | undefined;
21
+ return typeof v?.invoke === "function" || typeof v?.run === "function";
22
+ }
23
+
13
24
  export class SqlTransactionResource implements ResourceInstance {
14
25
  constructor(
15
26
  private readonly manifest: SqlTransactionManifest,
@@ -26,28 +37,66 @@ export class SqlTransactionResource implements ResourceInstance {
26
37
  );
27
38
  }
28
39
 
29
- assertActive(): void {
30
- if (!currentTxId()) {
31
- throw new Error(
32
- `Sql.Transaction '${this.manifest.metadata.name}': used outside an active transaction`,
33
- );
34
- }
35
- }
36
-
37
- async invoke(input: unknown): Promise<unknown> {
40
+ async invoke(input: unknown, invokeCtx?: InvokeContext): Promise<unknown> {
38
41
  const m = this.manifest;
39
42
  const ctx = this.ctx;
40
43
 
41
- // Flat nesting: if already inside a transaction, reuse it
42
- if (currentTxId()) {
43
- const expandedInputs = ctx.expandValue(m.inputs ?? {}, input ?? {});
44
- return m.steps.invoke(expandedInputs);
45
- }
44
+ // The declared `inputs:` map's CEL reads the caller's invocation input as
45
+ // `inputs.<field>` — the same variable name a Run.Sequence step's inputs
46
+ // read, and what the slot's `inputs: /inputs` pointer names.
47
+ const celScope = { inputs: input ?? {} };
48
+ const expandedInputs = ctx.expandValue(m.inputs ?? {}, celScope) as Record<string, unknown>;
46
49
 
50
+ // `invokeCtx` is threaded rather than left to the ambient throughout: the
51
+ // caller's context is the authority on which zones are open, and a runtime
52
+ // with no ambient store has nothing else to read.
53
+ //
54
+ // Flat nesting: an ambient transaction on the SAME connection is joined; a
55
+ // different connection's transaction is not ours and we open our own.
47
56
  const conn = this.getConnection();
48
- const expandedInputs = ctx.expandValue(m.inputs ?? {}, input ?? {});
57
+ if (conn.hasOpenTransaction(invokeCtx)) {
58
+ return this.dispatchSteps(expandedInputs, invokeCtx);
59
+ }
49
60
 
50
- return conn.transaction(() => m.steps.invoke(expandedInputs));
61
+ return conn.runInTransaction((bind) =>
62
+ // "steps" is this kind's own slot; the kernel reads `x-telo-provides-zone`
63
+ // there for the zone kind (this kind) and the correlation key
64
+ // (`/connection`). The entry is handed to the connection's own map —
65
+ // across the bundle/npm delivery split — and the derived context is
66
+ // threaded into the body dispatch, the discipline cancellation has.
67
+ ctx.withZone(
68
+ "steps",
69
+ (zoneCtx, entry) => {
70
+ bind(entry);
71
+ return this.dispatchSteps(expandedInputs, zoneCtx);
72
+ },
73
+ invokeCtx,
74
+ ),
75
+ );
76
+ }
77
+
78
+ private dispatchSteps(inputs: Record<string, unknown>, zoneCtx?: InvokeContext): Promise<unknown> {
79
+ const m = this.manifest;
80
+ const target = this.ctx.resolveRef(
81
+ m.steps,
82
+ isExecutable,
83
+ () => `Sql.Transaction "${m.metadata.name}": 'steps'`,
84
+ "Telo.Executable",
85
+ );
86
+ // The kernel stamps `!ref` identity at Phase-5 injection, and `resolveRef`
87
+ // rescues the sentinel form, so a resolved target always carries one.
88
+ // Guessing a label instead would emit malformed dispatch events (`.Invoked`
89
+ // with no kind) — a silent wrong answer where a missing identity means the
90
+ // resolution path changed under us.
91
+ const id = getRefIdentity(target as object);
92
+ if (!id) {
93
+ throw new InvokeError(
94
+ "ERR_SQL_STEPS_UNIDENTIFIED",
95
+ `Sql.Transaction '${m.metadata.name}': the resolved 'steps' target carries no reference ` +
96
+ `identity, so its dispatch cannot be traced or named`,
97
+ );
98
+ }
99
+ return this.ctx.invokeResolved(id.kind, id.name, target, inputs, zoneCtx);
51
100
  }
52
101
  }
53
102
 
@@ -1,9 +0,0 @@
1
- import { AsyncLocalStorage } from "async_hooks";
2
- export interface TxEntry {
3
- executor: unknown;
4
- }
5
- export declare const txStorage: AsyncLocalStorage<string>;
6
- export declare const setTx: (id: string, entry: TxEntry) => void;
7
- export declare const getTx: (id: string) => TxEntry | undefined;
8
- export declare const deleteTx: (id: string) => void;
9
- export declare const currentTxId: () => string | undefined;
@@ -1,11 +0,0 @@
1
- import { AsyncLocalStorage } from "async_hooks";
2
- const txMap = new Map();
3
- export const txStorage = new AsyncLocalStorage();
4
- export const setTx = (id, entry) => {
5
- txMap.set(id, entry);
6
- };
7
- export const getTx = (id) => txMap.get(id);
8
- export const deleteTx = (id) => {
9
- txMap.delete(id);
10
- };
11
- export const currentTxId = () => txStorage.getStore();
@@ -1,20 +0,0 @@
1
- import { AsyncLocalStorage } from "async_hooks";
2
-
3
- export interface TxEntry {
4
- executor: unknown;
5
- }
6
-
7
- const txMap = new Map<string, TxEntry>();
8
- export const txStorage = new AsyncLocalStorage<string>();
9
-
10
- export const setTx = (id: string, entry: TxEntry): void => {
11
- txMap.set(id, entry);
12
- };
13
-
14
- export const getTx = (id: string): TxEntry | undefined => txMap.get(id);
15
-
16
- export const deleteTx = (id: string): void => {
17
- txMap.delete(id);
18
- };
19
-
20
- export const currentTxId = (): string | undefined => txStorage.getStore();