@comity/sql-kysely 0.9.0 → 0.9.1
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 +0 -4
- package/dist/cjs/batch/execute.js +122 -0
- package/dist/cjs/client/create-kysely.js +7 -4
- package/dist/esm/batch/execute.js +119 -0
- package/dist/esm/client/create-kysely.js +3 -0
- package/dist/types/batch/execute.d.ts +24 -0
- package/dist/types/client/create-kysely.d.ts +10 -0
- package/dist/types/client/types.d.ts +54 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/internal/map-sql-error.d.ts +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeAtomicBatch = executeAtomicBatch;
|
|
4
|
+
const errors_1 = require("@comity/sql/errors");
|
|
5
|
+
const map_sql_error_js_1 = require("../internal/map-sql-error.js");
|
|
6
|
+
/**
|
|
7
|
+
* Validate the complete batch before any execution is attempted.
|
|
8
|
+
*
|
|
9
|
+
* @param queries - Immutable SQL query payloads
|
|
10
|
+
* @param adapter - Adapter name for diagnostics
|
|
11
|
+
*
|
|
12
|
+
* @returns The normalised statements, or a failure when validation fails
|
|
13
|
+
*/
|
|
14
|
+
function prepareBatch(queries, adapter) {
|
|
15
|
+
const statements = [];
|
|
16
|
+
for (const query of queries) {
|
|
17
|
+
// Named parameters are unsupported by the existing execution path, so the
|
|
18
|
+
// batch path MUST NOT introduce a second parameter model.
|
|
19
|
+
if (query.params && !Array.isArray(query.params)) {
|
|
20
|
+
return {
|
|
21
|
+
success: false,
|
|
22
|
+
error: new errors_1.SqlError("invalid_query", {
|
|
23
|
+
details: { retriable: false, adapter, operation: "batch" },
|
|
24
|
+
context: { violation: "named_parameters" },
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
statements.push({
|
|
29
|
+
statement: query.statement,
|
|
30
|
+
params: (query.params ?? []),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return { success: true, value: statements };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Map an executor statement result onto the generic SqlResult representation.
|
|
37
|
+
*
|
|
38
|
+
* @param result - Executor result for a single statement
|
|
39
|
+
*
|
|
40
|
+
* @returns SqlResult with mapped row count
|
|
41
|
+
*/
|
|
42
|
+
function toSqlResult(result) {
|
|
43
|
+
return {
|
|
44
|
+
rows: result.rows,
|
|
45
|
+
rowCount: typeof result.numAffectedRows === "bigint"
|
|
46
|
+
? Number(result.numAffectedRows)
|
|
47
|
+
: result.rows.length,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Execute a batch of statements atomically via the injected executor.
|
|
52
|
+
*
|
|
53
|
+
* @param queries - Immutable SQL query payloads, executed in order
|
|
54
|
+
* @param executor - Atomic batch executor supplied by composition
|
|
55
|
+
* @param adapter - Adapter name for diagnostics
|
|
56
|
+
*
|
|
57
|
+
* @returns Operation result wrapping a SqlBatchResult aligned with `queries`
|
|
58
|
+
*
|
|
59
|
+
* @throws Errors are not thrown; failures are returned via SqlOperationResult
|
|
60
|
+
*
|
|
61
|
+
* @remarks
|
|
62
|
+
* Validation of the whole batch completes before the executor is invoked, so an
|
|
63
|
+
* invalid statement can never result in a partial execution.
|
|
64
|
+
*
|
|
65
|
+
* Atomicity is provided by the executor, not by this function. This function
|
|
66
|
+
* MUST NOT fall back to sequential execution or to a transaction, because either
|
|
67
|
+
* would violate the all-or-nothing guarantee. A failure yields exactly one
|
|
68
|
+
* SqlError and no partial result; the failing statement index is not reported
|
|
69
|
+
* because the contract does not define one.
|
|
70
|
+
*/
|
|
71
|
+
async function executeAtomicBatch(queries, executor, adapter) {
|
|
72
|
+
// An empty batch is rejected before the executor is consulted, so behaviour is
|
|
73
|
+
// deterministic and independent of whether an executor was supplied.
|
|
74
|
+
if (queries.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
error: new errors_1.SqlError("invalid_query", {
|
|
78
|
+
details: { retriable: false, adapter, operation: "batch" },
|
|
79
|
+
context: { violation: "empty_batch" },
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// An unsupported capability is a configuration failure, not an exception.
|
|
84
|
+
if (!executor) {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
error: new errors_1.SqlError("invalid_configuration", {
|
|
88
|
+
details: { retriable: false, adapter, operation: "batch" },
|
|
89
|
+
context: { violation: "atomic_batch_executor_missing" },
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const prepared = prepareBatch(queries, adapter);
|
|
94
|
+
if (!prepared.success) {
|
|
95
|
+
return prepared;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const results = await executor.execute(prepared.value);
|
|
99
|
+
// The executor guarantees positional alignment; a mismatch is a defect in
|
|
100
|
+
// the injected implementation and is surfaced rather than silently accepted.
|
|
101
|
+
if (results.length !== queries.length) {
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
error: new errors_1.SqlError("query_failed", {
|
|
105
|
+
details: { retriable: false, adapter, operation: "batch" },
|
|
106
|
+
context: { violation: "executor_result_count_mismatch" },
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
success: true,
|
|
112
|
+
value: results.map(toSqlResult),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
error: (0, map_sql_error_js_1.mapSqlError)(e, "batch", adapter),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=execute.js.map
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createKyselySqlClient = createKyselySqlClient;
|
|
4
|
-
const execute_js_1 = require("../
|
|
5
|
-
const execute_js_2 = require("../
|
|
4
|
+
const execute_js_1 = require("../batch/execute.js");
|
|
5
|
+
const execute_js_2 = require("../query/execute.js");
|
|
6
|
+
const execute_js_3 = require("../transaction/execute.js");
|
|
6
7
|
/**
|
|
7
8
|
* Create a SqlClient backed by a Kysely database instance.
|
|
8
9
|
*
|
|
@@ -21,9 +22,11 @@ const execute_js_2 = require("../transaction/execute.js");
|
|
|
21
22
|
function createKyselySqlClient(options) {
|
|
22
23
|
return {
|
|
23
24
|
/** @inheritdoc */
|
|
24
|
-
query: (q) => (0,
|
|
25
|
+
query: (q) => (0, execute_js_2.executeQuery)(options.db, q, options.adapter),
|
|
25
26
|
/** @inheritdoc */
|
|
26
|
-
begin: () => (0,
|
|
27
|
+
begin: () => (0, execute_js_3.executeTransaction)(options.db, options.adapter),
|
|
28
|
+
/** @inheritdoc */
|
|
29
|
+
atomicBatch: (queries) => (0, execute_js_1.executeAtomicBatch)(queries, options.atomicBatchExecutor, options.adapter),
|
|
27
30
|
};
|
|
28
31
|
}
|
|
29
32
|
//# sourceMappingURL=create-kysely.js.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { SqlError } from "@comity/sql/errors";
|
|
2
|
+
import { mapSqlError } from "../internal/map-sql-error.js";
|
|
3
|
+
/**
|
|
4
|
+
* Validate the complete batch before any execution is attempted.
|
|
5
|
+
*
|
|
6
|
+
* @param queries - Immutable SQL query payloads
|
|
7
|
+
* @param adapter - Adapter name for diagnostics
|
|
8
|
+
*
|
|
9
|
+
* @returns The normalised statements, or a failure when validation fails
|
|
10
|
+
*/
|
|
11
|
+
function prepareBatch(queries, adapter) {
|
|
12
|
+
const statements = [];
|
|
13
|
+
for (const query of queries) {
|
|
14
|
+
// Named parameters are unsupported by the existing execution path, so the
|
|
15
|
+
// batch path MUST NOT introduce a second parameter model.
|
|
16
|
+
if (query.params && !Array.isArray(query.params)) {
|
|
17
|
+
return {
|
|
18
|
+
success: false,
|
|
19
|
+
error: new SqlError("invalid_query", {
|
|
20
|
+
details: { retriable: false, adapter, operation: "batch" },
|
|
21
|
+
context: { violation: "named_parameters" },
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
statements.push({
|
|
26
|
+
statement: query.statement,
|
|
27
|
+
params: (query.params ?? []),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return { success: true, value: statements };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Map an executor statement result onto the generic SqlResult representation.
|
|
34
|
+
*
|
|
35
|
+
* @param result - Executor result for a single statement
|
|
36
|
+
*
|
|
37
|
+
* @returns SqlResult with mapped row count
|
|
38
|
+
*/
|
|
39
|
+
function toSqlResult(result) {
|
|
40
|
+
return {
|
|
41
|
+
rows: result.rows,
|
|
42
|
+
rowCount: typeof result.numAffectedRows === "bigint"
|
|
43
|
+
? Number(result.numAffectedRows)
|
|
44
|
+
: result.rows.length,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Execute a batch of statements atomically via the injected executor.
|
|
49
|
+
*
|
|
50
|
+
* @param queries - Immutable SQL query payloads, executed in order
|
|
51
|
+
* @param executor - Atomic batch executor supplied by composition
|
|
52
|
+
* @param adapter - Adapter name for diagnostics
|
|
53
|
+
*
|
|
54
|
+
* @returns Operation result wrapping a SqlBatchResult aligned with `queries`
|
|
55
|
+
*
|
|
56
|
+
* @throws Errors are not thrown; failures are returned via SqlOperationResult
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* Validation of the whole batch completes before the executor is invoked, so an
|
|
60
|
+
* invalid statement can never result in a partial execution.
|
|
61
|
+
*
|
|
62
|
+
* Atomicity is provided by the executor, not by this function. This function
|
|
63
|
+
* MUST NOT fall back to sequential execution or to a transaction, because either
|
|
64
|
+
* would violate the all-or-nothing guarantee. A failure yields exactly one
|
|
65
|
+
* SqlError and no partial result; the failing statement index is not reported
|
|
66
|
+
* because the contract does not define one.
|
|
67
|
+
*/
|
|
68
|
+
export async function executeAtomicBatch(queries, executor, adapter) {
|
|
69
|
+
// An empty batch is rejected before the executor is consulted, so behaviour is
|
|
70
|
+
// deterministic and independent of whether an executor was supplied.
|
|
71
|
+
if (queries.length === 0) {
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
error: new SqlError("invalid_query", {
|
|
75
|
+
details: { retriable: false, adapter, operation: "batch" },
|
|
76
|
+
context: { violation: "empty_batch" },
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// An unsupported capability is a configuration failure, not an exception.
|
|
81
|
+
if (!executor) {
|
|
82
|
+
return {
|
|
83
|
+
success: false,
|
|
84
|
+
error: new SqlError("invalid_configuration", {
|
|
85
|
+
details: { retriable: false, adapter, operation: "batch" },
|
|
86
|
+
context: { violation: "atomic_batch_executor_missing" },
|
|
87
|
+
}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const prepared = prepareBatch(queries, adapter);
|
|
91
|
+
if (!prepared.success) {
|
|
92
|
+
return prepared;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const results = await executor.execute(prepared.value);
|
|
96
|
+
// The executor guarantees positional alignment; a mismatch is a defect in
|
|
97
|
+
// the injected implementation and is surfaced rather than silently accepted.
|
|
98
|
+
if (results.length !== queries.length) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: new SqlError("query_failed", {
|
|
102
|
+
details: { retriable: false, adapter, operation: "batch" },
|
|
103
|
+
context: { violation: "executor_result_count_mismatch" },
|
|
104
|
+
}),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
value: results.map(toSqlResult),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
error: mapSqlError(e, "batch", adapter),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=execute.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { executeAtomicBatch } from "../batch/execute.js";
|
|
1
2
|
import { executeQuery } from "../query/execute.js";
|
|
2
3
|
import { executeTransaction } from "../transaction/execute.js";
|
|
3
4
|
/**
|
|
@@ -21,6 +22,8 @@ export function createKyselySqlClient(options) {
|
|
|
21
22
|
query: (q) => executeQuery(options.db, q, options.adapter),
|
|
22
23
|
/** @inheritdoc */
|
|
23
24
|
begin: () => executeTransaction(options.db, options.adapter),
|
|
25
|
+
/** @inheritdoc */
|
|
26
|
+
atomicBatch: (queries) => executeAtomicBatch(queries, options.atomicBatchExecutor, options.adapter),
|
|
24
27
|
};
|
|
25
28
|
}
|
|
26
29
|
//# sourceMappingURL=create-kysely.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SqlBatchResult, SqlOperationResult, SqlQuery } from "@comity/sql";
|
|
2
|
+
import type { KyselyAtomicBatchExecutor } from "../client/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Execute a batch of statements atomically via the injected executor.
|
|
5
|
+
*
|
|
6
|
+
* @param queries - Immutable SQL query payloads, executed in order
|
|
7
|
+
* @param executor - Atomic batch executor supplied by composition
|
|
8
|
+
* @param adapter - Adapter name for diagnostics
|
|
9
|
+
*
|
|
10
|
+
* @returns Operation result wrapping a SqlBatchResult aligned with `queries`
|
|
11
|
+
*
|
|
12
|
+
* @throws Errors are not thrown; failures are returned via SqlOperationResult
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* Validation of the whole batch completes before the executor is invoked, so an
|
|
16
|
+
* invalid statement can never result in a partial execution.
|
|
17
|
+
*
|
|
18
|
+
* Atomicity is provided by the executor, not by this function. This function
|
|
19
|
+
* MUST NOT fall back to sequential execution or to a transaction, because either
|
|
20
|
+
* would violate the all-or-nothing guarantee. A failure yields exactly one
|
|
21
|
+
* SqlError and no partial result; the failing statement index is not reported
|
|
22
|
+
* because the contract does not define one.
|
|
23
|
+
*/
|
|
24
|
+
export declare function executeAtomicBatch(queries: readonly Readonly<SqlQuery>[], executor: KyselyAtomicBatchExecutor | undefined, adapter: string): Promise<SqlOperationResult<SqlBatchResult>>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SqlClient, SqlClientOptions } from "@comity/sql";
|
|
2
2
|
import type { Kysely } from "kysely";
|
|
3
|
+
import type { KyselyAtomicBatchExecutor } from "./types.js";
|
|
3
4
|
/**
|
|
4
5
|
* Kysely client factory options.
|
|
5
6
|
*
|
|
@@ -10,6 +11,15 @@ export interface KyselySqlClientOptions<DB> extends SqlClientOptions {
|
|
|
10
11
|
readonly db: Kysely<DB>;
|
|
11
12
|
/** Adapter name for diagnostics */
|
|
12
13
|
readonly adapter: string;
|
|
14
|
+
/**
|
|
15
|
+
* Executor providing atomic all-or-nothing multi-statement execution.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* Injected by composition because Kysely exposes no such primitive. When
|
|
19
|
+
* omitted, `atomicBatch` reports `invalid_configuration` rather than falling
|
|
20
|
+
* back to sequential execution.
|
|
21
|
+
*/
|
|
22
|
+
readonly atomicBatchExecutor?: KyselyAtomicBatchExecutor;
|
|
13
23
|
}
|
|
14
24
|
/**
|
|
15
25
|
* Create a SqlClient backed by a Kysely database instance.
|
|
@@ -1,4 +1,58 @@
|
|
|
1
1
|
import type { Compilable } from "kysely";
|
|
2
|
+
/**
|
|
3
|
+
* A single already-parameterised statement submitted to an atomic batch executor.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Deliberately free of Comity and database-engine concepts. It carries only
|
|
7
|
+
* normalised SQL text and positional parameters.
|
|
8
|
+
*/
|
|
9
|
+
export interface KyselyAtomicStatement {
|
|
10
|
+
/** Normalised SQL statement text */
|
|
11
|
+
readonly statement: string;
|
|
12
|
+
/** Positional parameters bound to `statement` */
|
|
13
|
+
readonly params: readonly unknown[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Outcome of a single statement within an atomic batch.
|
|
17
|
+
*/
|
|
18
|
+
export interface KyselyAtomicStatementResult {
|
|
19
|
+
/** Rows produced by the statement */
|
|
20
|
+
readonly rows: readonly unknown[];
|
|
21
|
+
/** Optional affected rows count */
|
|
22
|
+
readonly numAffectedRows?: bigint;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Executes a set of already-parameterised statements as one all-or-nothing
|
|
26
|
+
* operation and returns one result per statement in input order.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* This abstraction exists because Kysely's public API does not expose an atomic
|
|
30
|
+
* multi-statement primitive. Composition injects the implementation that
|
|
31
|
+
* provides one, for example by delegating to a database-specific batch call.
|
|
32
|
+
*
|
|
33
|
+
* Implementations MUST provide all-or-nothing semantics: either every statement
|
|
34
|
+
* takes effect, or none does. They MUST NOT apply retries, sequential fallback,
|
|
35
|
+
* or compensation.
|
|
36
|
+
*
|
|
37
|
+
* Implementations MUST NOT assume atomicity on their own behalf; an
|
|
38
|
+
* implementation that cannot guarantee it must not be injected here.
|
|
39
|
+
*
|
|
40
|
+
* The adapter maps `numAffectedRows` to `SqlResult.rowCount`. A statement that
|
|
41
|
+
* affects zero rows is still a successful result; the executor MUST NOT treat a
|
|
42
|
+
* zero count as a failure.
|
|
43
|
+
*/
|
|
44
|
+
export interface KyselyAtomicBatchExecutor {
|
|
45
|
+
/**
|
|
46
|
+
* Execute every statement atomically.
|
|
47
|
+
*
|
|
48
|
+
* @param statements - Statements to execute, in execution order
|
|
49
|
+
*
|
|
50
|
+
* @returns One result per statement, positionally aligned with `statements`
|
|
51
|
+
*
|
|
52
|
+
* @throws Underlying driver failures. The adapter maps them to SqlError.
|
|
53
|
+
*/
|
|
54
|
+
execute(statements: readonly KyselyAtomicStatement[]): Promise<readonly KyselyAtomicStatementResult[]>;
|
|
55
|
+
}
|
|
2
56
|
/**
|
|
3
57
|
* Minimal Kysely database interface used by this adapter.
|
|
4
58
|
*/
|
package/dist/types/index.d.ts
CHANGED
|
@@ -22,5 +22,5 @@ export declare const DRIVER_ERROR_MAPPINGS: Record<string, Record<string, ErrorD
|
|
|
22
22
|
*
|
|
23
23
|
* @returns A standardized SqlError instance
|
|
24
24
|
*/
|
|
25
|
-
export declare function mapSqlError(cause: unknown, operation: "connect" | "query" | "transaction", adapter: string): SqlError;
|
|
25
|
+
export declare function mapSqlError(cause: unknown, operation: "connect" | "query" | "transaction" | "batch", adapter: string): SqlError;
|
|
26
26
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comity/sql-kysely",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Kysely ORM adapter for @comity/sql contracts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -58,13 +58,13 @@
|
|
|
58
58
|
"sideEffects": false,
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"kysely": "^0.28.0",
|
|
61
|
-
"@comity/sql": "0.9.
|
|
61
|
+
"@comity/sql": "0.9.1"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@comity/sql": "0.9.
|
|
64
|
+
"@comity/sql": "0.9.1"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@types/node": "^24.
|
|
67
|
+
"@types/node": "^24.19.1",
|
|
68
68
|
"kysely": "^0.28.17",
|
|
69
69
|
"typescript": "^5.9.3"
|
|
70
70
|
},
|