@prisma-next/sql-runtime 0.3.0-dev.4 → 0.3.0-dev.40
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 +116 -26
- package/dist/exports-C8hi0N-a.mjs +622 -0
- package/dist/exports-C8hi0N-a.mjs.map +1 -0
- package/dist/index-SlQIrV_t.d.mts +131 -0
- package/dist/index-SlQIrV_t.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 +35 -26
- package/src/codecs/decoding.ts +221 -0
- package/src/codecs/encoding.ts +89 -0
- package/src/codecs/json-schema-validation.ts +61 -0
- package/src/codecs/validation.ts +67 -0
- package/src/exports/index.ts +45 -0
- package/src/lower-sql-plan.ts +32 -0
- package/src/sql-context.ts +443 -0
- package/src/sql-family-adapter.ts +47 -0
- package/src/sql-marker.ts +105 -0
- package/src/sql-runtime.ts +232 -0
- package/test/async-iterable-result.test.ts +144 -0
- 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/parameterized-types.test.ts +539 -0
- package/test/sql-context.test.ts +392 -0
- package/test/sql-family-adapter.test.ts +87 -0
- package/test/sql-runtime.test.ts +241 -0
- package/test/utils.ts +292 -0
- 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.d.ts +0 -29
- 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/sql-runtime-DgEbg2OP.d.ts +0 -109
- 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 -64
- 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/package.json
CHANGED
|
@@ -1,48 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/sql-runtime",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.40",
|
|
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/
|
|
8
|
+
"arktype": "^2.1.26",
|
|
9
|
+
"@prisma-next/contract": "0.3.0-dev.40",
|
|
10
|
+
"@prisma-next/utils": "0.3.0-dev.40",
|
|
11
|
+
"@prisma-next/core-execution-plane": "0.3.0-dev.40",
|
|
12
|
+
"@prisma-next/ids": "0.3.0-dev.40",
|
|
13
|
+
"@prisma-next/operations": "0.3.0-dev.40",
|
|
14
|
+
"@prisma-next/runtime-executor": "0.3.0-dev.40",
|
|
15
|
+
"@prisma-next/sql-contract": "0.3.0-dev.40",
|
|
16
|
+
"@prisma-next/sql-operations": "0.3.0-dev.40",
|
|
17
|
+
"@prisma-next/sql-relational-core": "0.3.0-dev.40"
|
|
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/tsconfig": "0.0.0",
|
|
27
|
+
"@prisma-next/tsdown": "0.0.0"
|
|
24
28
|
},
|
|
25
29
|
"files": [
|
|
26
|
-
"dist"
|
|
30
|
+
"dist",
|
|
31
|
+
"src",
|
|
32
|
+
"test"
|
|
27
33
|
],
|
|
28
34
|
"exports": {
|
|
29
|
-
".":
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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"
|
|
37
46
|
},
|
|
38
47
|
"scripts": {
|
|
39
|
-
"build": "
|
|
48
|
+
"build": "tsdown",
|
|
40
49
|
"test": "vitest run --passWithNoTests",
|
|
41
50
|
"test:coverage": "vitest run --coverage --passWithNoTests",
|
|
42
51
|
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
43
|
-
"lint": "biome check . --
|
|
44
|
-
"lint:fix": "biome check --write .
|
|
45
|
-
"lint:fix:unsafe": "biome check --write --unsafe .
|
|
46
|
-
"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"
|
|
47
56
|
}
|
|
48
57
|
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { ExecutionPlan } from '@prisma-next/contract/types';
|
|
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';
|
|
5
|
+
|
|
6
|
+
function resolveRowCodec(
|
|
7
|
+
alias: string,
|
|
8
|
+
plan: ExecutionPlan,
|
|
9
|
+
registry: CodecRegistry,
|
|
10
|
+
): Codec | null {
|
|
11
|
+
const planCodecId = plan.meta.annotations?.codecs?.[alias] as string | undefined;
|
|
12
|
+
if (planCodecId) {
|
|
13
|
+
const codec = registry.get(planCodecId);
|
|
14
|
+
if (codec) {
|
|
15
|
+
return codec;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (plan.meta.projectionTypes) {
|
|
20
|
+
const typeId = plan.meta.projectionTypes[alias];
|
|
21
|
+
if (typeId) {
|
|
22
|
+
const codec = registry.get(typeId);
|
|
23
|
+
if (codec) {
|
|
24
|
+
return codec;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
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
|
+
|
|
81
|
+
export function decodeRow(
|
|
82
|
+
row: Record<string, unknown>,
|
|
83
|
+
plan: ExecutionPlan,
|
|
84
|
+
registry: CodecRegistry,
|
|
85
|
+
jsonValidators?: JsonSchemaValidatorRegistry,
|
|
86
|
+
): Record<string, unknown> {
|
|
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;
|
|
93
|
+
|
|
94
|
+
let aliases: readonly string[];
|
|
95
|
+
if (projection && !Array.isArray(projection)) {
|
|
96
|
+
aliases = Object.keys(projection);
|
|
97
|
+
} else if (projection && Array.isArray(projection)) {
|
|
98
|
+
aliases = projection;
|
|
99
|
+
} else {
|
|
100
|
+
aliases = Object.keys(row);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const alias of aliases) {
|
|
104
|
+
const wireValue = row[alias];
|
|
105
|
+
|
|
106
|
+
const projectionValue =
|
|
107
|
+
projection && typeof projection === 'object' && !Array.isArray(projection)
|
|
108
|
+
? (projection as Record<string, string>)[alias]
|
|
109
|
+
: undefined;
|
|
110
|
+
|
|
111
|
+
if (typeof projectionValue === 'string' && projectionValue.startsWith('include:')) {
|
|
112
|
+
if (wireValue === null || wireValue === undefined) {
|
|
113
|
+
decoded[alias] = [];
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
let parsed: unknown;
|
|
119
|
+
if (typeof wireValue === 'string') {
|
|
120
|
+
parsed = JSON.parse(wireValue);
|
|
121
|
+
} else if (Array.isArray(wireValue)) {
|
|
122
|
+
parsed = wireValue;
|
|
123
|
+
} else {
|
|
124
|
+
parsed = JSON.parse(String(wireValue));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!Array.isArray(parsed)) {
|
|
128
|
+
throw new Error(`Expected array for include alias '${alias}', got ${typeof parsed}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
decoded[alias] = parsed;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const decodeError = new Error(
|
|
134
|
+
`Failed to parse JSON array for include alias '${alias}': ${error instanceof Error ? error.message : String(error)}`,
|
|
135
|
+
) as Error & {
|
|
136
|
+
code: string;
|
|
137
|
+
category: string;
|
|
138
|
+
severity: string;
|
|
139
|
+
details?: Record<string, unknown>;
|
|
140
|
+
};
|
|
141
|
+
decodeError.code = 'RUNTIME.DECODE_FAILED';
|
|
142
|
+
decodeError.category = 'RUNTIME';
|
|
143
|
+
decodeError.severity = 'error';
|
|
144
|
+
decodeError.details = {
|
|
145
|
+
alias,
|
|
146
|
+
wirePreview:
|
|
147
|
+
typeof wireValue === 'string' && wireValue.length > 100
|
|
148
|
+
? `${wireValue.substring(0, 100)}...`
|
|
149
|
+
: String(wireValue).substring(0, 100),
|
|
150
|
+
};
|
|
151
|
+
throw decodeError;
|
|
152
|
+
}
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (wireValue === null || wireValue === undefined) {
|
|
157
|
+
decoded[alias] = wireValue;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const codec = resolveRowCodec(alias, plan, registry);
|
|
162
|
+
|
|
163
|
+
if (!codec) {
|
|
164
|
+
decoded[alias] = wireValue;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
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;
|
|
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
|
+
|
|
197
|
+
const decodeError = new Error(
|
|
198
|
+
`Failed to decode row alias '${alias}' with codec '${codec.id}': ${error instanceof Error ? error.message : String(error)}`,
|
|
199
|
+
) as Error & {
|
|
200
|
+
code: string;
|
|
201
|
+
category: string;
|
|
202
|
+
severity: string;
|
|
203
|
+
details?: Record<string, unknown>;
|
|
204
|
+
};
|
|
205
|
+
decodeError.code = 'RUNTIME.DECODE_FAILED';
|
|
206
|
+
decodeError.category = 'RUNTIME';
|
|
207
|
+
decodeError.severity = 'error';
|
|
208
|
+
decodeError.details = {
|
|
209
|
+
alias,
|
|
210
|
+
codec: codec.id,
|
|
211
|
+
wirePreview:
|
|
212
|
+
typeof wireValue === 'string' && wireValue.length > 100
|
|
213
|
+
? `${wireValue.substring(0, 100)}...`
|
|
214
|
+
: String(wireValue).substring(0, 100),
|
|
215
|
+
};
|
|
216
|
+
throw decodeError;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return decoded;
|
|
221
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ExecutionPlan, ParamDescriptor } from '@prisma-next/contract/types';
|
|
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';
|
|
5
|
+
|
|
6
|
+
function resolveParamCodec(
|
|
7
|
+
paramDescriptor: ParamDescriptor,
|
|
8
|
+
plan: ExecutionPlan,
|
|
9
|
+
registry: CodecRegistry,
|
|
10
|
+
): Codec | null {
|
|
11
|
+
const paramName = paramDescriptor.name ?? `param_${paramDescriptor.index ?? 0}`;
|
|
12
|
+
|
|
13
|
+
const planCodecId = plan.meta.annotations?.codecs?.[paramName] as string | undefined;
|
|
14
|
+
if (planCodecId) {
|
|
15
|
+
const codec = registry.get(planCodecId);
|
|
16
|
+
if (codec) {
|
|
17
|
+
return codec;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (paramDescriptor.codecId) {
|
|
22
|
+
const codec = registry.get(paramDescriptor.codecId);
|
|
23
|
+
if (codec) {
|
|
24
|
+
return codec;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function encodeParam(
|
|
32
|
+
value: unknown,
|
|
33
|
+
paramDescriptor: ParamDescriptor,
|
|
34
|
+
plan: ExecutionPlan,
|
|
35
|
+
registry: CodecRegistry,
|
|
36
|
+
jsonValidators?: JsonSchemaValidatorRegistry,
|
|
37
|
+
): unknown {
|
|
38
|
+
if (value === null || value === undefined) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
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
|
+
|
|
48
|
+
const codec = resolveParamCodec(paramDescriptor, plan, registry);
|
|
49
|
+
if (!codec) {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (codec.encode) {
|
|
54
|
+
try {
|
|
55
|
+
return codec.encode(value);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Failed to encode parameter ${paramDescriptor.name ?? paramDescriptor.index}: ${error instanceof Error ? error.message : String(error)}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function encodeParams(
|
|
67
|
+
plan: ExecutionPlan,
|
|
68
|
+
registry: CodecRegistry,
|
|
69
|
+
jsonValidators?: JsonSchemaValidatorRegistry,
|
|
70
|
+
): readonly unknown[] {
|
|
71
|
+
if (plan.params.length === 0) {
|
|
72
|
+
return plan.params;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const encoded: unknown[] = [];
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < plan.params.length; i++) {
|
|
78
|
+
const paramValue = plan.params[i];
|
|
79
|
+
const paramDescriptor = plan.meta.paramDescriptors[i];
|
|
80
|
+
|
|
81
|
+
if (paramDescriptor) {
|
|
82
|
+
encoded.push(encodeParam(paramValue, paramDescriptor, plan, registry, jsonValidators));
|
|
83
|
+
} else {
|
|
84
|
+
encoded.push(paramValue);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return Object.freeze(encoded);
|
|
89
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { runtimeError } from '@prisma-next/runtime-executor';
|
|
2
|
+
import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
|
|
3
|
+
import type { CodecRegistry } from '@prisma-next/sql-relational-core/ast';
|
|
4
|
+
|
|
5
|
+
export function extractCodecIds(contract: SqlContract<SqlStorage>): Set<string> {
|
|
6
|
+
const codecIds = new Set<string>();
|
|
7
|
+
|
|
8
|
+
for (const table of Object.values(contract.storage.tables)) {
|
|
9
|
+
for (const column of Object.values(table.columns)) {
|
|
10
|
+
const codecId = column.codecId;
|
|
11
|
+
codecIds.add(codecId);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return codecIds;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function extractCodecIdsFromColumns(contract: SqlContract<SqlStorage>): Map<string, string> {
|
|
19
|
+
const codecIds = new Map<string, string>();
|
|
20
|
+
|
|
21
|
+
for (const [tableName, table] of Object.entries(contract.storage.tables)) {
|
|
22
|
+
for (const [columnName, column] of Object.entries(table.columns)) {
|
|
23
|
+
const codecId = column.codecId;
|
|
24
|
+
const key = `${tableName}.${columnName}`;
|
|
25
|
+
codecIds.set(key, codecId);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return codecIds;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function validateContractCodecMappings(
|
|
33
|
+
registry: CodecRegistry,
|
|
34
|
+
contract: SqlContract<SqlStorage>,
|
|
35
|
+
): void {
|
|
36
|
+
const codecIds = extractCodecIdsFromColumns(contract);
|
|
37
|
+
const invalidCodecs: Array<{ table: string; column: string; codecId: string }> = [];
|
|
38
|
+
|
|
39
|
+
for (const [key, codecId] of codecIds.entries()) {
|
|
40
|
+
if (!registry.has(codecId)) {
|
|
41
|
+
const parts = key.split('.');
|
|
42
|
+
const table = parts[0] ?? '';
|
|
43
|
+
const column = parts[1] ?? '';
|
|
44
|
+
invalidCodecs.push({ table, column, codecId });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (invalidCodecs.length > 0) {
|
|
49
|
+
const details: Record<string, unknown> = {
|
|
50
|
+
contractTarget: contract.target,
|
|
51
|
+
invalidCodecs,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
throw runtimeError(
|
|
55
|
+
'RUNTIME.CODEC_MISSING',
|
|
56
|
+
`Missing codec implementations for column codecIds: ${invalidCodecs.map((c) => `${c.table}.${c.column} (${c.codecId})`).join(', ')}`,
|
|
57
|
+
details,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function validateCodecRegistryCompleteness(
|
|
63
|
+
registry: CodecRegistry,
|
|
64
|
+
contract: SqlContract<SqlStorage>,
|
|
65
|
+
): void {
|
|
66
|
+
validateContractCodecMappings(registry, contract);
|
|
67
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
AfterExecuteResult,
|
|
3
|
+
BudgetsOptions,
|
|
4
|
+
LintsOptions,
|
|
5
|
+
Log,
|
|
6
|
+
Plugin,
|
|
7
|
+
PluginContext,
|
|
8
|
+
} from '@prisma-next/runtime-executor';
|
|
9
|
+
export { budgets, lints } from '@prisma-next/runtime-executor';
|
|
10
|
+
export {
|
|
11
|
+
extractCodecIds,
|
|
12
|
+
validateCodecRegistryCompleteness,
|
|
13
|
+
validateContractCodecMappings,
|
|
14
|
+
} from '../codecs/validation';
|
|
15
|
+
export { lowerSqlPlan } from '../lower-sql-plan';
|
|
16
|
+
export type {
|
|
17
|
+
ExecutionContext,
|
|
18
|
+
RuntimeParameterizedCodecDescriptor,
|
|
19
|
+
SqlExecutionStack,
|
|
20
|
+
SqlExecutionStackWithDriver,
|
|
21
|
+
SqlRuntimeAdapterDescriptor,
|
|
22
|
+
SqlRuntimeAdapterInstance,
|
|
23
|
+
SqlRuntimeDriverInstance,
|
|
24
|
+
SqlRuntimeExtensionDescriptor,
|
|
25
|
+
SqlRuntimeExtensionInstance,
|
|
26
|
+
SqlRuntimeTargetDescriptor,
|
|
27
|
+
SqlStaticContributions,
|
|
28
|
+
TypeHelperRegistry,
|
|
29
|
+
} from '../sql-context';
|
|
30
|
+
export { createExecutionContext, createSqlExecutionStack } from '../sql-context';
|
|
31
|
+
export type { SqlStatement } from '../sql-marker';
|
|
32
|
+
export {
|
|
33
|
+
ensureSchemaStatement,
|
|
34
|
+
ensureTableStatement,
|
|
35
|
+
readContractMarker,
|
|
36
|
+
writeContractMarker,
|
|
37
|
+
} from '../sql-marker';
|
|
38
|
+
export type {
|
|
39
|
+
CreateRuntimeOptions,
|
|
40
|
+
Runtime,
|
|
41
|
+
RuntimeTelemetryEvent,
|
|
42
|
+
RuntimeVerifyOptions,
|
|
43
|
+
TelemetryOutcome,
|
|
44
|
+
} from '../sql-runtime';
|
|
45
|
+
export { createRuntime } from '../sql-runtime';
|
|
@@ -0,0 +1,32 @@
|
|
|
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';
|
|
4
|
+
import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Lowers a SQL query plan to an executable Plan by calling the adapter's lower method.
|
|
8
|
+
*
|
|
9
|
+
* @param adapter - Adapter to lower AST to SQL
|
|
10
|
+
* @param contract - Contract for lowering context
|
|
11
|
+
* @param queryPlan - SQL query plan from a lane (contains AST, params, meta, but no SQL)
|
|
12
|
+
* @returns Fully executable Plan with SQL string
|
|
13
|
+
*/
|
|
14
|
+
export function lowerSqlPlan<Row>(
|
|
15
|
+
adapter: Adapter<QueryAst, SqlContract<SqlStorage>, LoweredStatement>,
|
|
16
|
+
contract: SqlContract<SqlStorage>,
|
|
17
|
+
queryPlan: SqlQueryPlan<Row>,
|
|
18
|
+
): ExecutionPlan<Row> {
|
|
19
|
+
const lowered = adapter.lower(queryPlan.ast, {
|
|
20
|
+
contract,
|
|
21
|
+
params: queryPlan.params,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const body = lowered.body;
|
|
25
|
+
|
|
26
|
+
return Object.freeze({
|
|
27
|
+
sql: body.sql,
|
|
28
|
+
params: body.params ?? queryPlan.params,
|
|
29
|
+
ast: queryPlan.ast,
|
|
30
|
+
meta: queryPlan.meta,
|
|
31
|
+
});
|
|
32
|
+
}
|