@prisma-next/sql-runtime 0.3.0-dev.6 → 0.3.0-dev.64
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/LICENSE +201 -0
- package/README.md +137 -26
- package/dist/exports-BhZqJPVb.mjs +771 -0
- package/dist/exports-BhZqJPVb.mjs.map +1 -0
- package/dist/index-D59jqEKF.d.mts +159 -0
- package/dist/index-D59jqEKF.d.mts.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +3 -0
- package/dist/test/utils.d.mts +82 -0
- package/dist/test/utils.d.mts.map +1 -0
- package/dist/test/utils.mjs +212 -0
- package/dist/test/utils.mjs.map +1 -0
- package/package.json +32 -25
- package/src/codecs/decoding.ts +84 -3
- package/src/codecs/encoding.ts +15 -2
- package/src/codecs/json-schema-validation.ts +61 -0
- package/src/exports/index.ts +14 -6
- package/src/lower-sql-plan.ts +8 -8
- package/src/plugins/lints.ts +204 -0
- package/src/sql-context.ts +385 -98
- package/src/sql-family-adapter.ts +9 -5
- package/src/sql-marker.ts +2 -2
- package/src/sql-runtime.ts +131 -31
- package/test/async-iterable-result.test.ts +42 -34
- package/test/context.types.test-d.ts +68 -0
- package/test/execution-stack.test.ts +166 -0
- package/test/json-schema-validation.test.ts +653 -0
- package/test/lints.test.ts +330 -0
- package/test/parameterized-types.test.ts +539 -0
- package/test/sql-context.test.ts +292 -117
- package/test/sql-family-adapter.test.ts +7 -6
- package/test/sql-runtime.test.ts +218 -30
- package/test/utils.ts +80 -51
- package/dist/accelerate-EEKAFGN3-P6A6XJWJ.js +0 -137863
- package/dist/accelerate-EEKAFGN3-P6A6XJWJ.js.map +0 -1
- package/dist/amcheck-24VY6X5V.js +0 -13
- package/dist/amcheck-24VY6X5V.js.map +0 -1
- package/dist/bloom-VS74NLHT.js +0 -13
- package/dist/bloom-VS74NLHT.js.map +0 -1
- package/dist/btree_gin-WBC4EAAI.js +0 -13
- package/dist/btree_gin-WBC4EAAI.js.map +0 -1
- package/dist/btree_gist-UNC6QD3M.js +0 -13
- package/dist/btree_gist-UNC6QD3M.js.map +0 -1
- package/dist/chunk-3KTOEDFX.js +0 -49
- package/dist/chunk-3KTOEDFX.js.map +0 -1
- package/dist/chunk-47DZBRQC.js +0 -1280
- package/dist/chunk-47DZBRQC.js.map +0 -1
- package/dist/chunk-52N6AFZM.js +0 -133
- package/dist/chunk-52N6AFZM.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-7D4SUZUM.js.map +0 -1
- package/dist/chunk-C6I3V3DM.js +0 -455
- package/dist/chunk-C6I3V3DM.js.map +0 -1
- package/dist/chunk-ECWIHLAT.js +0 -37
- package/dist/chunk-ECWIHLAT.js.map +0 -1
- package/dist/chunk-EI626SDC.js +0 -105
- package/dist/chunk-EI626SDC.js.map +0 -1
- package/dist/chunk-UKKOYUGL.js +0 -578
- package/dist/chunk-UKKOYUGL.js.map +0 -1
- package/dist/chunk-XPLNMXQV.js +0 -1537
- package/dist/chunk-XPLNMXQV.js.map +0 -1
- package/dist/citext-T7MXGUY7.js +0 -13
- package/dist/citext-T7MXGUY7.js.map +0 -1
- package/dist/client-5FENX6AW.js +0 -299
- package/dist/client-5FENX6AW.js.map +0 -1
- package/dist/cube-TFDQBZCI.js +0 -13
- package/dist/cube-TFDQBZCI.js.map +0 -1
- package/dist/dict_int-AEUOPGWP.js +0 -13
- package/dist/dict_int-AEUOPGWP.js.map +0 -1
- package/dist/dict_xsyn-DAAYX3FL.js +0 -13
- package/dist/dict_xsyn-DAAYX3FL.js.map +0 -1
- package/dist/dist-AQ3LWXOX.js +0 -570
- package/dist/dist-AQ3LWXOX.js.map +0 -1
- package/dist/dist-LBVX6BJW.js +0 -189
- package/dist/dist-LBVX6BJW.js.map +0 -1
- package/dist/dist-WLKUVDN2.js +0 -5127
- package/dist/dist-WLKUVDN2.js.map +0 -1
- package/dist/earthdistance-KIGTF4LE.js +0 -13
- package/dist/earthdistance-KIGTF4LE.js.map +0 -1
- package/dist/file_fdw-5N55UP6I.js +0 -13
- package/dist/file_fdw-5N55UP6I.js.map +0 -1
- package/dist/fuzzystrmatch-KN3YWBFP.js +0 -13
- package/dist/fuzzystrmatch-KN3YWBFP.js.map +0 -1
- package/dist/hstore-YX726NKN.js +0 -13
- package/dist/hstore-YX726NKN.js.map +0 -1
- package/dist/http-exception-FZY2H4OF.js +0 -8
- package/dist/http-exception-FZY2H4OF.js.map +0 -1
- package/dist/index.js +0 -30
- package/dist/index.js.map +0 -1
- package/dist/intarray-NKVXNO2D.js +0 -13
- package/dist/intarray-NKVXNO2D.js.map +0 -1
- package/dist/isn-FTEMJGEV.js +0 -13
- package/dist/isn-FTEMJGEV.js.map +0 -1
- package/dist/lo-DB7L4NGI.js +0 -13
- package/dist/lo-DB7L4NGI.js.map +0 -1
- package/dist/logger-WQ7SHNDD.js +0 -68
- package/dist/logger-WQ7SHNDD.js.map +0 -1
- package/dist/ltree-Z32TZT6W.js +0 -13
- package/dist/ltree-Z32TZT6W.js.map +0 -1
- package/dist/nodefs-NM46ACH7.js +0 -31
- package/dist/nodefs-NM46ACH7.js.map +0 -1
- package/dist/opfs-ahp-NJO33LVZ.js +0 -332
- package/dist/opfs-ahp-NJO33LVZ.js.map +0 -1
- package/dist/pageinspect-YP3IZR4X.js +0 -13
- package/dist/pageinspect-YP3IZR4X.js.map +0 -1
- package/dist/pg_buffercache-7TD5J2FB.js +0 -13
- package/dist/pg_buffercache-7TD5J2FB.js.map +0 -1
- package/dist/pg_dump-SG4KYBUB.js +0 -2492
- package/dist/pg_dump-SG4KYBUB.js.map +0 -1
- package/dist/pg_freespacemap-DZDNCPZK.js +0 -13
- package/dist/pg_freespacemap-DZDNCPZK.js.map +0 -1
- package/dist/pg_surgery-J2MUEWEP.js +0 -13
- package/dist/pg_surgery-J2MUEWEP.js.map +0 -1
- package/dist/pg_trgm-7VNQOYS6.js +0 -13
- package/dist/pg_trgm-7VNQOYS6.js.map +0 -1
- package/dist/pg_visibility-TTSIPHFL.js +0 -13
- package/dist/pg_visibility-TTSIPHFL.js.map +0 -1
- package/dist/pg_walinspect-KPFHSHRJ.js +0 -13
- package/dist/pg_walinspect-KPFHSHRJ.js.map +0 -1
- package/dist/proxy-signals-GUDAMDHV.js +0 -39
- package/dist/proxy-signals-GUDAMDHV.js.map +0 -1
- package/dist/seg-IYVDLE4O.js +0 -13
- package/dist/seg-IYVDLE4O.js.map +0 -1
- package/dist/src/codecs/decoding.d.ts +0 -4
- package/dist/src/codecs/decoding.d.ts.map +0 -1
- package/dist/src/codecs/encoding.d.ts +0 -5
- package/dist/src/codecs/encoding.d.ts.map +0 -1
- package/dist/src/codecs/validation.d.ts +0 -6
- package/dist/src/codecs/validation.d.ts.map +0 -1
- package/dist/src/exports/index.d.ts +0 -11
- package/dist/src/exports/index.d.ts.map +0 -1
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/lower-sql-plan.d.ts +0 -15
- package/dist/src/lower-sql-plan.d.ts.map +0 -1
- package/dist/src/sql-context.d.ts +0 -65
- package/dist/src/sql-context.d.ts.map +0 -1
- package/dist/src/sql-family-adapter.d.ts +0 -10
- package/dist/src/sql-family-adapter.d.ts.map +0 -1
- package/dist/src/sql-marker.d.ts +0 -22
- package/dist/src/sql-marker.d.ts.map +0 -1
- package/dist/src/sql-runtime.d.ts +0 -25
- package/dist/src/sql-runtime.d.ts.map +0 -1
- package/dist/tablefunc-EF4RCS7S.js +0 -13
- package/dist/tablefunc-EF4RCS7S.js.map +0 -1
- package/dist/tcn-3VT5BQYW.js +0 -13
- package/dist/tcn-3VT5BQYW.js.map +0 -1
- package/dist/test/utils.d.ts +0 -59
- package/dist/test/utils.d.ts.map +0 -1
- package/dist/test/utils.js +0 -24634
- package/dist/test/utils.js.map +0 -1
- package/dist/tiny-CW6F4GX6.js +0 -10
- package/dist/tiny-CW6F4GX6.js.map +0 -1
- package/dist/tsm_system_rows-ES7KNUQH.js +0 -13
- package/dist/tsm_system_rows-ES7KNUQH.js.map +0 -1
- package/dist/tsm_system_time-76WEIMBG.js +0 -13
- package/dist/tsm_system_time-76WEIMBG.js.map +0 -1
- package/dist/unaccent-7RYF3R64.js +0 -13
- package/dist/unaccent-7RYF3R64.js.map +0 -1
- package/dist/utility-Q5A254LJ-J4HTKZPT.js +0 -347
- package/dist/utility-Q5A254LJ-J4HTKZPT.js.map +0 -1
- package/dist/uuid_ossp-4ETE4FPE.js +0 -13
- package/dist/uuid_ossp-4ETE4FPE.js.map +0 -1
- package/dist/vector-74GPNV7V.js +0 -13
- package/dist/vector-74GPNV7V.js.map +0 -1
- package/src/index.ts +0 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { c as createSqlExecutionStack, i as ensureTableStatement, o as writeContractMarker, r as ensureSchemaStatement, s as createExecutionContext } from "../exports-BhZqJPVb.mjs";
|
|
2
|
+
import { instantiateExecutionStack } from "@prisma-next/core-execution-plane/stack";
|
|
3
|
+
import { codec, createCodecRegistry } from "@prisma-next/sql-relational-core/ast";
|
|
4
|
+
import { coreHash, profileHash } from "@prisma-next/contract/types";
|
|
5
|
+
import { collectAsync, collectAsync as collectAsync$1, createDevDatabase, drainAsyncIterable, teardownTestDatabase, withClient } from "@prisma-next/test-utils";
|
|
6
|
+
|
|
7
|
+
//#region test/utils.ts
|
|
8
|
+
/**
|
|
9
|
+
* Executes a plan and collects all results into an array.
|
|
10
|
+
* This helper DRYs up the common pattern of executing plans in tests.
|
|
11
|
+
* The return type is inferred from the plan's type parameter.
|
|
12
|
+
*/
|
|
13
|
+
async function executePlanAndCollect(runtime, plan) {
|
|
14
|
+
return collectAsync$1(runtime.execute(plan));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Drains a plan execution, consuming all results without collecting them.
|
|
18
|
+
* Useful for testing side effects without memory overhead.
|
|
19
|
+
*/
|
|
20
|
+
async function drainPlanExecution(runtime, plan) {
|
|
21
|
+
return drainAsyncIterable(runtime.execute(plan));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Executes a SQL statement on a database client.
|
|
25
|
+
*/
|
|
26
|
+
async function executeStatement(client, statement) {
|
|
27
|
+
if (statement.params.length > 0) {
|
|
28
|
+
await client.query(statement.sql, [...statement.params]);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
await client.query(statement.sql);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Sets up database schema and data, then writes the contract marker.
|
|
35
|
+
* This helper DRYs up the common pattern of database setup in tests.
|
|
36
|
+
*/
|
|
37
|
+
async function setupTestDatabase(client, contract, setupFn) {
|
|
38
|
+
await client.query("drop schema if exists prisma_contract cascade");
|
|
39
|
+
await client.query("create schema if not exists public");
|
|
40
|
+
await setupFn(client);
|
|
41
|
+
await executeStatement(client, ensureSchemaStatement);
|
|
42
|
+
await executeStatement(client, ensureTableStatement);
|
|
43
|
+
await executeStatement(client, writeContractMarker({
|
|
44
|
+
storageHash: contract.storageHash,
|
|
45
|
+
profileHash: contract.profileHash ?? contract.storageHash,
|
|
46
|
+
contractJson: contract,
|
|
47
|
+
canonicalVersion: 1
|
|
48
|
+
}).insert);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Writes a contract marker to the database.
|
|
52
|
+
* This helper DRYs up the common pattern of writing contract markers in tests.
|
|
53
|
+
*/
|
|
54
|
+
async function writeTestContractMarker(client, contract) {
|
|
55
|
+
await executeStatement(client, writeContractMarker({
|
|
56
|
+
storageHash: contract.storageHash,
|
|
57
|
+
profileHash: contract.profileHash ?? contract.storageHash,
|
|
58
|
+
contractJson: contract,
|
|
59
|
+
canonicalVersion: 1
|
|
60
|
+
}).insert);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Creates a test adapter descriptor from a raw adapter.
|
|
64
|
+
* Wraps the adapter in an SqlRuntimeAdapterDescriptor with static contributions
|
|
65
|
+
* derived from the adapter's codec registry.
|
|
66
|
+
*/
|
|
67
|
+
function createTestAdapterDescriptor(adapter) {
|
|
68
|
+
const codecRegistry = adapter.profile.codecs();
|
|
69
|
+
return {
|
|
70
|
+
kind: "adapter",
|
|
71
|
+
id: "test-adapter",
|
|
72
|
+
version: "0.0.1",
|
|
73
|
+
familyId: "sql",
|
|
74
|
+
targetId: "postgres",
|
|
75
|
+
codecs: () => codecRegistry,
|
|
76
|
+
operationSignatures: () => [],
|
|
77
|
+
parameterizedCodecs: () => [],
|
|
78
|
+
create() {
|
|
79
|
+
return Object.assign({
|
|
80
|
+
familyId: "sql",
|
|
81
|
+
targetId: "postgres"
|
|
82
|
+
}, adapter);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Creates a test target descriptor with empty static contributions.
|
|
88
|
+
*/
|
|
89
|
+
function createTestTargetDescriptor() {
|
|
90
|
+
return {
|
|
91
|
+
kind: "target",
|
|
92
|
+
id: "postgres",
|
|
93
|
+
version: "0.0.1",
|
|
94
|
+
familyId: "sql",
|
|
95
|
+
targetId: "postgres",
|
|
96
|
+
codecs: () => createCodecRegistry(),
|
|
97
|
+
operationSignatures: () => [],
|
|
98
|
+
parameterizedCodecs: () => [],
|
|
99
|
+
create() {
|
|
100
|
+
return {
|
|
101
|
+
familyId: "sql",
|
|
102
|
+
targetId: "postgres"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Creates an ExecutionContext for testing.
|
|
109
|
+
* This helper DRYs up the common pattern of context creation in tests.
|
|
110
|
+
*
|
|
111
|
+
* Accepts a raw adapter and optional extension descriptors, wrapping the
|
|
112
|
+
* adapter in a descriptor internally for descriptor-first context creation.
|
|
113
|
+
*/
|
|
114
|
+
function createTestContext(contract, adapter, options) {
|
|
115
|
+
return createExecutionContext({
|
|
116
|
+
contract,
|
|
117
|
+
stack: {
|
|
118
|
+
target: createTestTargetDescriptor(),
|
|
119
|
+
adapter: createTestAdapterDescriptor(adapter),
|
|
120
|
+
extensionPacks: options?.extensionPacks ?? []
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function createTestStackInstance(options) {
|
|
125
|
+
return instantiateExecutionStack(createSqlExecutionStack({
|
|
126
|
+
target: createTestTargetDescriptor(),
|
|
127
|
+
adapter: createTestAdapterDescriptor(createStubAdapter()),
|
|
128
|
+
driver: options?.driver,
|
|
129
|
+
extensionPacks: options?.extensionPacks ?? []
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Creates a stub adapter for testing.
|
|
134
|
+
* This helper DRYs up the common pattern of adapter creation in tests.
|
|
135
|
+
*
|
|
136
|
+
* The stub adapter includes simple codecs for common test types (pg/int4@1, pg/text@1, pg/timestamptz@1)
|
|
137
|
+
* to enable type inference in tests without requiring the postgres adapter package.
|
|
138
|
+
*/
|
|
139
|
+
function createStubAdapter() {
|
|
140
|
+
const codecRegistry = createCodecRegistry();
|
|
141
|
+
codecRegistry.register(codec({
|
|
142
|
+
typeId: "pg/int4@1",
|
|
143
|
+
targetTypes: ["int4"],
|
|
144
|
+
encode: (value) => value,
|
|
145
|
+
decode: (wire) => wire
|
|
146
|
+
}));
|
|
147
|
+
codecRegistry.register(codec({
|
|
148
|
+
typeId: "pg/text@1",
|
|
149
|
+
targetTypes: ["text"],
|
|
150
|
+
encode: (value) => value,
|
|
151
|
+
decode: (wire) => wire
|
|
152
|
+
}));
|
|
153
|
+
codecRegistry.register(codec({
|
|
154
|
+
typeId: "pg/timestamptz@1",
|
|
155
|
+
targetTypes: ["timestamptz"],
|
|
156
|
+
encode: (value) => value instanceof Date ? value.toISOString() : value,
|
|
157
|
+
decode: (wire) => wire instanceof Date ? wire : new Date(wire)
|
|
158
|
+
}));
|
|
159
|
+
return {
|
|
160
|
+
profile: {
|
|
161
|
+
id: "stub-profile",
|
|
162
|
+
target: "postgres",
|
|
163
|
+
capabilities: {},
|
|
164
|
+
codecs() {
|
|
165
|
+
return codecRegistry;
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
lower(ast, ctx) {
|
|
169
|
+
const sqlText = JSON.stringify(ast);
|
|
170
|
+
return {
|
|
171
|
+
profileId: this.profile.id,
|
|
172
|
+
body: Object.freeze({
|
|
173
|
+
sql: sqlText,
|
|
174
|
+
params: ctx.params ? [...ctx.params] : []
|
|
175
|
+
})
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Creates a valid test contract without using validateContract.
|
|
182
|
+
* Ensures all required fields are present (mappings, capabilities, extensionPacks, meta, sources)
|
|
183
|
+
* and returns the contract with proper typing.
|
|
184
|
+
* This helper allows tests to create contracts without depending on sql-query.
|
|
185
|
+
*/
|
|
186
|
+
function createTestContract(contract) {
|
|
187
|
+
const { execution, ...rest } = contract;
|
|
188
|
+
return {
|
|
189
|
+
...rest,
|
|
190
|
+
schemaVersion: rest.schemaVersion ?? "1",
|
|
191
|
+
target: rest.target ?? "postgres",
|
|
192
|
+
targetFamily: rest.targetFamily ?? "sql",
|
|
193
|
+
storage: rest.storage ?? { tables: {} },
|
|
194
|
+
models: rest.models ?? {},
|
|
195
|
+
relations: rest.relations ?? {},
|
|
196
|
+
mappings: rest.mappings ?? {
|
|
197
|
+
codecTypes: {},
|
|
198
|
+
operationTypes: {}
|
|
199
|
+
},
|
|
200
|
+
capabilities: rest.capabilities ?? {},
|
|
201
|
+
extensionPacks: rest.extensionPacks ?? {},
|
|
202
|
+
meta: rest.meta ?? {},
|
|
203
|
+
sources: rest.sources ?? {},
|
|
204
|
+
...execution ? { execution } : {},
|
|
205
|
+
storageHash: coreHash(rest.storageHash ?? "sha256:testcore"),
|
|
206
|
+
profileHash: profileHash(rest.profileHash ?? "sha256:testprofile")
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//#endregion
|
|
211
|
+
export { collectAsync, createDevDatabase, createStubAdapter, createTestAdapterDescriptor, createTestContext, createTestContract, createTestStackInstance, createTestTargetDescriptor, drainPlanExecution, executePlanAndCollect, executeStatement, setupTestDatabase, teardownTestDatabase, withClient, writeTestContractMarker };
|
|
212
|
+
//# sourceMappingURL=utils.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.mjs","names":["collectAsync"],"sources":["../../test/utils.ts"],"sourcesContent":["import type { ExecutionPlan, ResultType } from '@prisma-next/contract/types';\nimport { coreHash, profileHash } from '@prisma-next/contract/types';\nimport { instantiateExecutionStack } from '@prisma-next/core-execution-plane/stack';\nimport type { RuntimeDriverDescriptor } from '@prisma-next/core-execution-plane/types';\nimport type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { Adapter, LoweredStatement, SelectAst } from '@prisma-next/sql-relational-core/ast';\nimport { codec, createCodecRegistry } from '@prisma-next/sql-relational-core/ast';\nimport type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';\nimport { collectAsync, drainAsyncIterable } from '@prisma-next/test-utils';\nimport type { Client } from 'pg';\nimport type { SqlStatement } from '../src/exports';\nimport {\n createExecutionContext,\n type createRuntime,\n createSqlExecutionStack,\n ensureSchemaStatement,\n ensureTableStatement,\n writeContractMarker,\n} from '../src/exports';\nimport type {\n ExecutionContext,\n SqlRuntimeAdapterDescriptor,\n SqlRuntimeAdapterInstance,\n SqlRuntimeDriverInstance,\n SqlRuntimeExtensionDescriptor,\n SqlRuntimeTargetDescriptor,\n} from '../src/sql-context';\n\n/**\n * Executes a plan and collects all results into an array.\n * This helper DRYs up the common pattern of executing plans in tests.\n * The return type is inferred from the plan's type parameter.\n */\nexport async function executePlanAndCollect<\n P extends ExecutionPlan<ResultType<P>> | SqlQueryPlan<ResultType<P>>,\n>(runtime: ReturnType<typeof createRuntime>, plan: P): Promise<ResultType<P>[]> {\n type Row = ResultType<P>;\n return collectAsync<Row>(runtime.execute<Row>(plan));\n}\n\n/**\n * Drains a plan execution, consuming all results without collecting them.\n * Useful for testing side effects without memory overhead.\n */\nexport async function drainPlanExecution(\n runtime: ReturnType<typeof createRuntime>,\n plan: ExecutionPlan | SqlQueryPlan<unknown>,\n): Promise<void> {\n return drainAsyncIterable(runtime.execute(plan));\n}\n\n/**\n * Executes a SQL statement on a database client.\n */\nexport async function executeStatement(client: Client, statement: SqlStatement): Promise<void> {\n if (statement.params.length > 0) {\n await client.query(statement.sql, [...statement.params]);\n return;\n }\n\n await client.query(statement.sql);\n}\n\n/**\n * Sets up database schema and data, then writes the contract marker.\n * This helper DRYs up the common pattern of database setup in tests.\n */\nexport async function setupTestDatabase(\n client: Client,\n contract: SqlContract<SqlStorage>,\n setupFn: (client: Client) => Promise<void>,\n): Promise<void> {\n await client.query('drop schema if exists prisma_contract cascade');\n await client.query('create schema if not exists public');\n\n await setupFn(client);\n\n await executeStatement(client, ensureSchemaStatement);\n await executeStatement(client, ensureTableStatement);\n const write = writeContractMarker({\n storageHash: contract.storageHash,\n profileHash: contract.profileHash ?? contract.storageHash,\n contractJson: contract,\n canonicalVersion: 1,\n });\n await executeStatement(client, write.insert);\n}\n\n/**\n * Writes a contract marker to the database.\n * This helper DRYs up the common pattern of writing contract markers in tests.\n */\nexport async function writeTestContractMarker(\n client: Client,\n contract: SqlContract<SqlStorage>,\n): Promise<void> {\n const write = writeContractMarker({\n storageHash: contract.storageHash,\n profileHash: contract.profileHash ?? contract.storageHash,\n contractJson: contract,\n canonicalVersion: 1,\n });\n await executeStatement(client, write.insert);\n}\n\n/**\n * Creates a test adapter descriptor from a raw adapter.\n * Wraps the adapter in an SqlRuntimeAdapterDescriptor with static contributions\n * derived from the adapter's codec registry.\n */\nexport function createTestAdapterDescriptor(\n adapter: Adapter<SelectAst, SqlContract<SqlStorage>, LoweredStatement>,\n): SqlRuntimeAdapterDescriptor<'postgres'> {\n const codecRegistry = adapter.profile.codecs();\n return {\n kind: 'adapter' as const,\n id: 'test-adapter',\n version: '0.0.1',\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n codecs: () => codecRegistry,\n operationSignatures: () => [],\n parameterizedCodecs: () => [],\n create(): SqlRuntimeAdapterInstance<'postgres'> {\n return Object.assign({ familyId: 'sql' as const, targetId: 'postgres' as const }, adapter);\n },\n };\n}\n\n/**\n * Creates a test target descriptor with empty static contributions.\n */\nexport function createTestTargetDescriptor(): SqlRuntimeTargetDescriptor<'postgres'> {\n return {\n kind: 'target' as const,\n id: 'postgres',\n version: '0.0.1',\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n codecs: () => createCodecRegistry(),\n operationSignatures: () => [],\n parameterizedCodecs: () => [],\n create() {\n return { familyId: 'sql' as const, targetId: 'postgres' as const };\n },\n };\n}\n\n/**\n * Creates an ExecutionContext for testing.\n * This helper DRYs up the common pattern of context creation in tests.\n *\n * Accepts a raw adapter and optional extension descriptors, wrapping the\n * adapter in a descriptor internally for descriptor-first context creation.\n */\nexport function createTestContext<TContract extends SqlContract<SqlStorage>>(\n contract: TContract,\n adapter: Adapter<SelectAst, SqlContract<SqlStorage>, LoweredStatement>,\n options?: {\n extensionPacks?: ReadonlyArray<SqlRuntimeExtensionDescriptor<'postgres'>>;\n },\n): ExecutionContext<TContract> {\n return createExecutionContext({\n contract,\n stack: {\n target: createTestTargetDescriptor(),\n adapter: createTestAdapterDescriptor(adapter),\n extensionPacks: options?.extensionPacks ?? [],\n },\n });\n}\n\nexport function createTestStackInstance(options?: {\n extensionPacks?: ReadonlyArray<SqlRuntimeExtensionDescriptor<'postgres'>>;\n driver?: RuntimeDriverDescriptor<\n 'sql',\n 'postgres',\n unknown,\n SqlRuntimeDriverInstance<'postgres'>\n >;\n}) {\n const stack = createSqlExecutionStack({\n target: createTestTargetDescriptor(),\n adapter: createTestAdapterDescriptor(createStubAdapter()),\n driver: options?.driver,\n extensionPacks: options?.extensionPacks ?? [],\n });\n\n return instantiateExecutionStack(stack);\n}\n\n/**\n * Creates a stub adapter for testing.\n * This helper DRYs up the common pattern of adapter creation in tests.\n *\n * The stub adapter includes simple codecs for common test types (pg/int4@1, pg/text@1, pg/timestamptz@1)\n * to enable type inference in tests without requiring the postgres adapter package.\n */\nexport function createStubAdapter(): Adapter<SelectAst, SqlContract<SqlStorage>, LoweredStatement> {\n const codecRegistry = createCodecRegistry();\n\n // Register stub codecs for common test types\n // These match the codec IDs used in test contracts (pg/int4@1, pg/text@1, pg/timestamptz@1)\n // but don't require importing from the postgres adapter package\n codecRegistry.register(\n codec({\n typeId: 'pg/int4@1',\n targetTypes: ['int4'],\n encode: (value: number) => value,\n decode: (wire: number) => wire,\n }),\n );\n\n codecRegistry.register(\n codec({\n typeId: 'pg/text@1',\n targetTypes: ['text'],\n encode: (value: string) => value,\n decode: (wire: string) => wire,\n }),\n );\n\n codecRegistry.register(\n codec({\n typeId: 'pg/timestamptz@1',\n targetTypes: ['timestamptz'],\n encode: (value: string | Date) => (value instanceof Date ? value.toISOString() : value),\n decode: (wire: string | Date) => (wire instanceof Date ? wire : new Date(wire)),\n }),\n );\n\n return {\n profile: {\n id: 'stub-profile',\n target: 'postgres',\n capabilities: {},\n codecs() {\n return codecRegistry;\n },\n },\n lower(ast: SelectAst, ctx: { contract: SqlContract<SqlStorage>; params?: readonly unknown[] }) {\n const sqlText = JSON.stringify(ast);\n return {\n profileId: this.profile.id,\n body: Object.freeze({ sql: sqlText, params: ctx.params ? [...ctx.params] : [] }),\n };\n },\n };\n}\n\n/**\n * Creates a valid test contract without using validateContract.\n * Ensures all required fields are present (mappings, capabilities, extensionPacks, meta, sources)\n * and returns the contract with proper typing.\n * This helper allows tests to create contracts without depending on sql-query.\n */\nexport function createTestContract(\n contract: Partial<Omit<SqlContract<SqlStorage>, 'storageHash' | 'profileHash'>> & {\n storageHash?: string;\n profileHash?: string;\n },\n): SqlContract<SqlStorage> {\n const { execution, ...rest } = contract;\n\n return {\n ...rest,\n schemaVersion: rest.schemaVersion ?? '1',\n target: rest.target ?? 'postgres',\n targetFamily: rest.targetFamily ?? 'sql',\n storage: rest.storage ?? { tables: {} },\n models: rest.models ?? {},\n relations: rest.relations ?? {},\n mappings: rest.mappings ?? { codecTypes: {}, operationTypes: {} },\n capabilities: rest.capabilities ?? {},\n extensionPacks: rest.extensionPacks ?? {},\n meta: rest.meta ?? {},\n sources: rest.sources ?? {},\n ...(execution ? { execution } : {}),\n storageHash: coreHash(rest.storageHash ?? 'sha256:testcore'),\n profileHash: profileHash(rest.profileHash ?? 'sha256:testprofile'),\n } satisfies SqlContract<SqlStorage>;\n}\n\n// Re-export generic utilities from test-utils\nexport {\n collectAsync,\n createDevDatabase,\n type DevDatabase,\n teardownTestDatabase,\n withClient,\n} from '@prisma-next/test-utils';\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/sql-runtime",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.64",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "SQL runtime implementation for Prisma Next",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"
|
|
9
|
-
"@prisma-next/
|
|
10
|
-
"@prisma-next/
|
|
11
|
-
"@prisma-next/
|
|
12
|
-
"@prisma-next/
|
|
13
|
-
"@prisma-next/
|
|
14
|
-
"@prisma-next/sql-
|
|
8
|
+
"arktype": "^2.1.26",
|
|
9
|
+
"@prisma-next/contract": "0.3.0-dev.64",
|
|
10
|
+
"@prisma-next/utils": "0.3.0-dev.64",
|
|
11
|
+
"@prisma-next/core-execution-plane": "0.3.0-dev.64",
|
|
12
|
+
"@prisma-next/operations": "0.3.0-dev.64",
|
|
13
|
+
"@prisma-next/runtime-executor": "0.3.0-dev.64",
|
|
14
|
+
"@prisma-next/sql-operations": "0.3.0-dev.64",
|
|
15
|
+
"@prisma-next/sql-relational-core": "0.3.0-dev.64",
|
|
16
|
+
"@prisma-next/sql-contract": "0.3.0-dev.64",
|
|
17
|
+
"@prisma-next/ids": "0.3.0-dev.64"
|
|
15
18
|
},
|
|
16
19
|
"devDependencies": {
|
|
17
20
|
"@types/pg": "8.16.0",
|
|
18
|
-
"@vitest/coverage-v8": "4.0.16",
|
|
19
21
|
"pg": "8.16.3",
|
|
20
|
-
"
|
|
22
|
+
"tsdown": "0.18.4",
|
|
21
23
|
"typescript": "5.9.3",
|
|
22
|
-
"vitest": "4.0.
|
|
23
|
-
"@prisma-next/test-utils": "0.0.1"
|
|
24
|
+
"vitest": "4.0.17",
|
|
25
|
+
"@prisma-next/test-utils": "0.0.1",
|
|
26
|
+
"@prisma-next/tsdown": "0.0.0",
|
|
27
|
+
"@prisma-next/tsconfig": "0.0.0"
|
|
24
28
|
},
|
|
25
29
|
"files": [
|
|
26
30
|
"dist",
|
|
@@ -28,23 +32,26 @@
|
|
|
28
32
|
"test"
|
|
29
33
|
],
|
|
30
34
|
"exports": {
|
|
31
|
-
".":
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
".": "./dist/index.mjs",
|
|
36
|
+
"./test/utils": "./dist/test/utils.mjs",
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"main": "./dist/index.mjs",
|
|
40
|
+
"module": "./dist/index.mjs",
|
|
41
|
+
"types": "./dist/index.d.mts",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/prisma/prisma-next.git",
|
|
45
|
+
"directory": "packages/2-sql/5-runtime"
|
|
39
46
|
},
|
|
40
47
|
"scripts": {
|
|
41
|
-
"build": "
|
|
48
|
+
"build": "tsdown",
|
|
42
49
|
"test": "vitest run --passWithNoTests",
|
|
43
50
|
"test:coverage": "vitest run --coverage --passWithNoTests",
|
|
44
51
|
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
45
|
-
"lint": "biome check . --
|
|
46
|
-
"lint:fix": "biome check --write .
|
|
47
|
-
"lint:fix:unsafe": "biome check --write --unsafe .
|
|
48
|
-
"clean": "
|
|
52
|
+
"lint": "biome check . --error-on-warnings",
|
|
53
|
+
"lint:fix": "biome check --write .",
|
|
54
|
+
"lint:fix:unsafe": "biome check --write --unsafe .",
|
|
55
|
+
"clean": "rm -rf dist dist-tsc dist-tsc-prod coverage .tmp-output"
|
|
49
56
|
}
|
|
50
57
|
}
|
package/src/codecs/decoding.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ExecutionPlan } from '@prisma-next/contract/types';
|
|
2
2
|
import type { Codec, CodecRegistry } from '@prisma-next/sql-relational-core/ast';
|
|
3
|
+
import type { JsonSchemaValidatorRegistry } from '@prisma-next/sql-relational-core/query-lane-context';
|
|
4
|
+
import { validateJsonValue } from './json-schema-validation';
|
|
3
5
|
|
|
4
6
|
function resolveRowCodec(
|
|
5
7
|
alias: string,
|
|
@@ -27,15 +29,69 @@ function resolveRowCodec(
|
|
|
27
29
|
return null;
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
type ColumnRefIndex = Map<string, { table: string; column: string }>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Builds a lookup index from column name → { table, column } ref.
|
|
36
|
+
* Called once per decodeRow invocation to avoid O(aliases × refs) linear scans.
|
|
37
|
+
*/
|
|
38
|
+
function buildColumnRefIndex(plan: ExecutionPlan): ColumnRefIndex | null {
|
|
39
|
+
const columns = plan.meta.refs?.columns;
|
|
40
|
+
if (!columns) return null;
|
|
41
|
+
|
|
42
|
+
const index: ColumnRefIndex = new Map();
|
|
43
|
+
for (const ref of columns) {
|
|
44
|
+
index.set(ref.column, ref);
|
|
45
|
+
}
|
|
46
|
+
return index;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseProjectionRef(value: string): { table: string; column: string } | null {
|
|
50
|
+
if (value.startsWith('include:') || value.startsWith('operation:')) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const separatorIndex = value.indexOf('.');
|
|
55
|
+
if (separatorIndex <= 0 || separatorIndex === value.length - 1) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
table: value.slice(0, separatorIndex),
|
|
61
|
+
column: value.slice(separatorIndex + 1),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveColumnRefForAlias(
|
|
66
|
+
alias: string,
|
|
67
|
+
projection: ExecutionPlan['meta']['projection'],
|
|
68
|
+
fallbackColumnRefIndex: ColumnRefIndex | null,
|
|
69
|
+
): { table: string; column: string } | undefined {
|
|
70
|
+
if (projection && !Array.isArray(projection)) {
|
|
71
|
+
const mappedRef = (projection as Record<string, string>)[alias];
|
|
72
|
+
if (typeof mappedRef !== 'string') {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return parseProjectionRef(mappedRef) ?? undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return fallbackColumnRefIndex?.get(alias);
|
|
79
|
+
}
|
|
80
|
+
|
|
30
81
|
export function decodeRow(
|
|
31
82
|
row: Record<string, unknown>,
|
|
32
83
|
plan: ExecutionPlan,
|
|
33
84
|
registry: CodecRegistry,
|
|
85
|
+
jsonValidators?: JsonSchemaValidatorRegistry,
|
|
34
86
|
): Record<string, unknown> {
|
|
35
87
|
const decoded: Record<string, unknown> = {};
|
|
88
|
+
const projection = plan.meta.projection;
|
|
89
|
+
|
|
90
|
+
// Fallback for plans that do not provide projection alias -> table.column mapping.
|
|
91
|
+
const fallbackColumnRefIndex =
|
|
92
|
+
jsonValidators && (!projection || Array.isArray(projection)) ? buildColumnRefIndex(plan) : null;
|
|
36
93
|
|
|
37
94
|
let aliases: readonly string[];
|
|
38
|
-
const projection = plan.meta.projection;
|
|
39
95
|
if (projection && !Array.isArray(projection)) {
|
|
40
96
|
aliases = Object.keys(projection);
|
|
41
97
|
} else if (projection && Array.isArray(projection)) {
|
|
@@ -47,7 +103,6 @@ export function decodeRow(
|
|
|
47
103
|
for (const alias of aliases) {
|
|
48
104
|
const wireValue = row[alias];
|
|
49
105
|
|
|
50
|
-
const projection = plan.meta.projection;
|
|
51
106
|
const projectionValue =
|
|
52
107
|
projection && typeof projection === 'object' && !Array.isArray(projection)
|
|
53
108
|
? (projection as Record<string, string>)[alias]
|
|
@@ -111,8 +166,34 @@ export function decodeRow(
|
|
|
111
166
|
}
|
|
112
167
|
|
|
113
168
|
try {
|
|
114
|
-
|
|
169
|
+
const decodedValue = codec.decode(wireValue);
|
|
170
|
+
|
|
171
|
+
// Validate decoded JSON value against schema
|
|
172
|
+
if (jsonValidators) {
|
|
173
|
+
const ref = resolveColumnRefForAlias(alias, projection, fallbackColumnRefIndex);
|
|
174
|
+
if (ref) {
|
|
175
|
+
validateJsonValue(
|
|
176
|
+
jsonValidators,
|
|
177
|
+
ref.table,
|
|
178
|
+
ref.column,
|
|
179
|
+
decodedValue,
|
|
180
|
+
'decode',
|
|
181
|
+
codec.id,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
decoded[alias] = decodedValue;
|
|
115
187
|
} catch (error) {
|
|
188
|
+
// Re-throw JSON schema validation errors as-is
|
|
189
|
+
if (
|
|
190
|
+
error instanceof Error &&
|
|
191
|
+
'code' in error &&
|
|
192
|
+
(error as Error & { code: string }).code === 'RUNTIME.JSON_SCHEMA_VALIDATION_FAILED'
|
|
193
|
+
) {
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
|
|
116
197
|
const decodeError = new Error(
|
|
117
198
|
`Failed to decode row alias '${alias}' with codec '${codec.id}': ${error instanceof Error ? error.message : String(error)}`,
|
|
118
199
|
) as Error & {
|
package/src/codecs/encoding.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ExecutionPlan, ParamDescriptor } from '@prisma-next/contract/types';
|
|
2
2
|
import type { Codec, CodecRegistry } from '@prisma-next/sql-relational-core/ast';
|
|
3
|
+
import type { JsonSchemaValidatorRegistry } from '@prisma-next/sql-relational-core/query-lane-context';
|
|
4
|
+
import { validateJsonValue } from './json-schema-validation';
|
|
3
5
|
|
|
4
6
|
function resolveParamCodec(
|
|
5
7
|
paramDescriptor: ParamDescriptor,
|
|
@@ -31,11 +33,18 @@ export function encodeParam(
|
|
|
31
33
|
paramDescriptor: ParamDescriptor,
|
|
32
34
|
plan: ExecutionPlan,
|
|
33
35
|
registry: CodecRegistry,
|
|
36
|
+
jsonValidators?: JsonSchemaValidatorRegistry,
|
|
34
37
|
): unknown {
|
|
35
38
|
if (value === null || value === undefined) {
|
|
36
39
|
return null;
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
// Validate JSON value against schema before encoding
|
|
43
|
+
if (jsonValidators && paramDescriptor.refs) {
|
|
44
|
+
const { table, column } = paramDescriptor.refs;
|
|
45
|
+
validateJsonValue(jsonValidators, table, column, value, 'encode', paramDescriptor.codecId);
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
const codec = resolveParamCodec(paramDescriptor, plan, registry);
|
|
40
49
|
if (!codec) {
|
|
41
50
|
return value;
|
|
@@ -54,7 +63,11 @@ export function encodeParam(
|
|
|
54
63
|
return value;
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
export function encodeParams(
|
|
66
|
+
export function encodeParams(
|
|
67
|
+
plan: ExecutionPlan,
|
|
68
|
+
registry: CodecRegistry,
|
|
69
|
+
jsonValidators?: JsonSchemaValidatorRegistry,
|
|
70
|
+
): readonly unknown[] {
|
|
58
71
|
if (plan.params.length === 0) {
|
|
59
72
|
return plan.params;
|
|
60
73
|
}
|
|
@@ -66,7 +79,7 @@ export function encodeParams(plan: ExecutionPlan, registry: CodecRegistry): read
|
|
|
66
79
|
const paramDescriptor = plan.meta.paramDescriptors[i];
|
|
67
80
|
|
|
68
81
|
if (paramDescriptor) {
|
|
69
|
-
encoded.push(encodeParam(paramValue, paramDescriptor, plan, registry));
|
|
82
|
+
encoded.push(encodeParam(paramValue, paramDescriptor, plan, registry, jsonValidators));
|
|
70
83
|
} else {
|
|
71
84
|
encoded.push(paramValue);
|
|
72
85
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { runtimeError } from '@prisma-next/runtime-executor';
|
|
2
|
+
import type {
|
|
3
|
+
JsonSchemaValidationError,
|
|
4
|
+
JsonSchemaValidatorRegistry,
|
|
5
|
+
} from '@prisma-next/sql-relational-core/query-lane-context';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validates a JSON value against its column's JSON Schema, if a validator exists.
|
|
9
|
+
*
|
|
10
|
+
* Throws `RUNTIME.JSON_SCHEMA_VALIDATION_FAILED` on validation failure.
|
|
11
|
+
* No-ops if no validator is registered for the column.
|
|
12
|
+
*/
|
|
13
|
+
export function validateJsonValue(
|
|
14
|
+
registry: JsonSchemaValidatorRegistry,
|
|
15
|
+
table: string,
|
|
16
|
+
column: string,
|
|
17
|
+
value: unknown,
|
|
18
|
+
direction: 'encode' | 'decode',
|
|
19
|
+
codecId?: string,
|
|
20
|
+
): void {
|
|
21
|
+
const key = `${table}.${column}`;
|
|
22
|
+
const validate = registry.get(key);
|
|
23
|
+
if (!validate) return;
|
|
24
|
+
|
|
25
|
+
const result = validate(value);
|
|
26
|
+
if (result.valid) return;
|
|
27
|
+
|
|
28
|
+
throw createJsonSchemaValidationError(table, column, direction, result.errors, codecId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createJsonSchemaValidationError(
|
|
32
|
+
table: string,
|
|
33
|
+
column: string,
|
|
34
|
+
direction: 'encode' | 'decode',
|
|
35
|
+
errors: ReadonlyArray<JsonSchemaValidationError>,
|
|
36
|
+
codecId?: string,
|
|
37
|
+
): Error {
|
|
38
|
+
const summary = formatErrorSummary(errors);
|
|
39
|
+
return runtimeError(
|
|
40
|
+
'RUNTIME.JSON_SCHEMA_VALIDATION_FAILED',
|
|
41
|
+
`JSON schema validation failed for column '${table}.${column}' (${direction}): ${summary}`,
|
|
42
|
+
{
|
|
43
|
+
table,
|
|
44
|
+
column,
|
|
45
|
+
codecId,
|
|
46
|
+
direction,
|
|
47
|
+
errors: [...errors],
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function formatErrorSummary(errors: ReadonlyArray<JsonSchemaValidationError>): string {
|
|
53
|
+
if (errors.length === 0) return 'unknown validation error';
|
|
54
|
+
if (errors.length === 1) {
|
|
55
|
+
const err = errors[0] as JsonSchemaValidationError;
|
|
56
|
+
return err.path === '/' ? err.message : `${err.path}: ${err.message}`;
|
|
57
|
+
}
|
|
58
|
+
return errors
|
|
59
|
+
.map((err) => (err.path === '/' ? err.message : `${err.path}: ${err.message}`))
|
|
60
|
+
.join('; ');
|
|
61
|
+
}
|
package/src/exports/index.ts
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
export type {
|
|
2
2
|
AfterExecuteResult,
|
|
3
3
|
BudgetsOptions,
|
|
4
|
-
LintsOptions,
|
|
5
4
|
Log,
|
|
6
5
|
Plugin,
|
|
7
6
|
PluginContext,
|
|
8
7
|
} from '@prisma-next/runtime-executor';
|
|
9
|
-
export { budgets
|
|
8
|
+
export { budgets } from '@prisma-next/runtime-executor';
|
|
10
9
|
export {
|
|
11
10
|
extractCodecIds,
|
|
12
11
|
validateCodecRegistryCompleteness,
|
|
13
12
|
validateContractCodecMappings,
|
|
14
13
|
} from '../codecs/validation';
|
|
15
14
|
export { lowerSqlPlan } from '../lower-sql-plan';
|
|
15
|
+
export type { LintsOptions } from '../plugins/lints';
|
|
16
|
+
export { lints } from '../plugins/lints';
|
|
16
17
|
export type {
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
ExecutionContext,
|
|
19
|
+
RuntimeParameterizedCodecDescriptor,
|
|
20
|
+
SqlExecutionStack,
|
|
21
|
+
SqlExecutionStackWithDriver,
|
|
22
|
+
SqlRuntimeAdapterDescriptor,
|
|
19
23
|
SqlRuntimeAdapterInstance,
|
|
24
|
+
SqlRuntimeDriverInstance,
|
|
20
25
|
SqlRuntimeExtensionDescriptor,
|
|
21
26
|
SqlRuntimeExtensionInstance,
|
|
27
|
+
SqlRuntimeTargetDescriptor,
|
|
28
|
+
SqlStaticContributions,
|
|
29
|
+
TypeHelperRegistry,
|
|
22
30
|
} from '../sql-context';
|
|
23
|
-
export {
|
|
31
|
+
export { createExecutionContext, createSqlExecutionStack } from '../sql-context';
|
|
24
32
|
export type { SqlStatement } from '../sql-marker';
|
|
25
33
|
export {
|
|
26
34
|
ensureSchemaStatement,
|
|
@@ -29,8 +37,8 @@ export {
|
|
|
29
37
|
writeContractMarker,
|
|
30
38
|
} from '../sql-marker';
|
|
31
39
|
export type {
|
|
40
|
+
CreateRuntimeOptions,
|
|
32
41
|
Runtime,
|
|
33
|
-
RuntimeOptions,
|
|
34
42
|
RuntimeTelemetryEvent,
|
|
35
43
|
RuntimeVerifyOptions,
|
|
36
44
|
TelemetryOutcome,
|
package/src/lower-sql-plan.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import type { ExecutionPlan } from '@prisma-next/contract/types';
|
|
2
|
+
import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
|
|
3
|
+
import type { Adapter, LoweredStatement, QueryAst } from '@prisma-next/sql-relational-core/ast';
|
|
2
4
|
import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
3
|
-
import type { RuntimeContext } from './sql-context';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Lowers a SQL query plan to an executable Plan by calling the adapter's lower method.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* @param context - Runtime context containing the adapter
|
|
9
|
+
* @param adapter - Adapter to lower AST to SQL
|
|
10
|
+
* @param contract - Contract for lowering context
|
|
12
11
|
* @param queryPlan - SQL query plan from a lane (contains AST, params, meta, but no SQL)
|
|
13
12
|
* @returns Fully executable Plan with SQL string
|
|
14
13
|
*/
|
|
15
14
|
export function lowerSqlPlan<Row>(
|
|
16
|
-
|
|
15
|
+
adapter: Adapter<QueryAst, SqlContract<SqlStorage>, LoweredStatement>,
|
|
16
|
+
contract: SqlContract<SqlStorage>,
|
|
17
17
|
queryPlan: SqlQueryPlan<Row>,
|
|
18
18
|
): ExecutionPlan<Row> {
|
|
19
|
-
const lowered =
|
|
20
|
-
contract
|
|
19
|
+
const lowered = adapter.lower(queryPlan.ast, {
|
|
20
|
+
contract,
|
|
21
21
|
params: queryPlan.params,
|
|
22
22
|
});
|
|
23
23
|
|