@prisma-next/sql-runtime 0.3.0-dev.6 → 0.3.0-dev.63
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,204 @@
|
|
|
1
|
+
import type { ExecutionPlan, PlanMeta } from '@prisma-next/contract/types';
|
|
2
|
+
import type { Plugin, PluginContext } from '@prisma-next/runtime-executor';
|
|
3
|
+
import { evaluateRawGuardrails } from '@prisma-next/runtime-executor';
|
|
4
|
+
import type {
|
|
5
|
+
DeleteAst,
|
|
6
|
+
QueryAst,
|
|
7
|
+
SelectAst,
|
|
8
|
+
UpdateAst,
|
|
9
|
+
} from '@prisma-next/sql-relational-core/ast';
|
|
10
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
11
|
+
|
|
12
|
+
const QUERY_AST_KINDS = new Set(['select', 'insert', 'update', 'delete']);
|
|
13
|
+
|
|
14
|
+
function isSqlQueryAst(ast: unknown): ast is QueryAst {
|
|
15
|
+
if (ast === null || typeof ast !== 'object' || !('kind' in ast)) return false;
|
|
16
|
+
const kind = (ast as { kind: string }).kind;
|
|
17
|
+
return typeof kind === 'string' && QUERY_AST_KINDS.has(kind);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface LintsOptions {
|
|
21
|
+
readonly severities?: {
|
|
22
|
+
readonly selectStar?: 'warn' | 'error';
|
|
23
|
+
readonly noLimit?: 'warn' | 'error';
|
|
24
|
+
readonly deleteWithoutWhere?: 'warn' | 'error';
|
|
25
|
+
readonly updateWithoutWhere?: 'warn' | 'error';
|
|
26
|
+
readonly readOnlyMutation?: 'warn' | 'error';
|
|
27
|
+
readonly unindexedPredicate?: 'warn' | 'error';
|
|
28
|
+
};
|
|
29
|
+
readonly fallbackWhenAstMissing?: 'raw' | 'skip';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface LintFinding {
|
|
33
|
+
readonly code: `LINT.${string}`;
|
|
34
|
+
readonly severity: 'error' | 'warn';
|
|
35
|
+
readonly message: string;
|
|
36
|
+
readonly details?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function lintError(code: string, message: string, details?: Record<string, unknown>) {
|
|
40
|
+
const error = new Error(message) as Error & {
|
|
41
|
+
code: string;
|
|
42
|
+
category: 'LINT';
|
|
43
|
+
severity: 'error';
|
|
44
|
+
details?: Record<string, unknown>;
|
|
45
|
+
};
|
|
46
|
+
Object.defineProperty(error, 'name', {
|
|
47
|
+
value: 'RuntimeError',
|
|
48
|
+
configurable: true,
|
|
49
|
+
});
|
|
50
|
+
return Object.assign(error, {
|
|
51
|
+
code,
|
|
52
|
+
category: 'LINT' as const,
|
|
53
|
+
severity: 'error' as const,
|
|
54
|
+
details,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function evaluateAstLints(ast: QueryAst, meta: PlanMeta): LintFinding[] {
|
|
59
|
+
const findings: LintFinding[] = [];
|
|
60
|
+
|
|
61
|
+
if (ast.kind === 'delete') {
|
|
62
|
+
const deleteAst = ast as DeleteAst;
|
|
63
|
+
if (deleteAst.where === undefined) {
|
|
64
|
+
findings.push({
|
|
65
|
+
code: 'LINT.DELETE_WITHOUT_WHERE',
|
|
66
|
+
severity: 'error',
|
|
67
|
+
message:
|
|
68
|
+
'DELETE without WHERE clause blocks execution to prevent accidental full-table deletion',
|
|
69
|
+
details: { table: deleteAst.table.name },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (ast.kind === 'update') {
|
|
75
|
+
const updateAst = ast as UpdateAst;
|
|
76
|
+
if (updateAst.where === undefined) {
|
|
77
|
+
findings.push({
|
|
78
|
+
code: 'LINT.UPDATE_WITHOUT_WHERE',
|
|
79
|
+
severity: 'error',
|
|
80
|
+
message:
|
|
81
|
+
'UPDATE without WHERE clause blocks execution to prevent accidental full-table update',
|
|
82
|
+
details: { table: updateAst.table.name },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (ast.kind === 'select') {
|
|
88
|
+
const selectAst = ast as SelectAst;
|
|
89
|
+
if (selectAst.limit === undefined) {
|
|
90
|
+
findings.push({
|
|
91
|
+
code: 'LINT.NO_LIMIT',
|
|
92
|
+
severity: 'warn',
|
|
93
|
+
message: 'Unbounded SELECT may return large result sets',
|
|
94
|
+
details: { table: selectAst.from.name },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const hasSelectAllIntent =
|
|
98
|
+
selectAst.selectAllIntent !== undefined ||
|
|
99
|
+
(meta.annotations as { selectAllIntent?: unknown })?.selectAllIntent !== undefined;
|
|
100
|
+
if (hasSelectAllIntent) {
|
|
101
|
+
const table =
|
|
102
|
+
selectAst.selectAllIntent?.table ??
|
|
103
|
+
(meta.annotations as { selectAllIntent?: { table?: string } })?.selectAllIntent?.table;
|
|
104
|
+
findings.push({
|
|
105
|
+
code: 'LINT.SELECT_STAR',
|
|
106
|
+
severity: 'warn',
|
|
107
|
+
message: 'Query selects all columns via selectAll intent',
|
|
108
|
+
...ifDefined('details', table !== undefined ? { table } : undefined),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return findings;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getConfiguredSeverity(code: string, options?: LintsOptions): 'warn' | 'error' | undefined {
|
|
117
|
+
const severities = options?.severities;
|
|
118
|
+
if (!severities) return undefined;
|
|
119
|
+
|
|
120
|
+
switch (code) {
|
|
121
|
+
case 'LINT.SELECT_STAR':
|
|
122
|
+
return severities.selectStar;
|
|
123
|
+
case 'LINT.NO_LIMIT':
|
|
124
|
+
return severities.noLimit;
|
|
125
|
+
case 'LINT.DELETE_WITHOUT_WHERE':
|
|
126
|
+
return severities.deleteWithoutWhere;
|
|
127
|
+
case 'LINT.UPDATE_WITHOUT_WHERE':
|
|
128
|
+
return severities.updateWithoutWhere;
|
|
129
|
+
case 'LINT.READ_ONLY_MUTATION':
|
|
130
|
+
return severities.readOnlyMutation;
|
|
131
|
+
case 'LINT.UNINDEXED_PREDICATE':
|
|
132
|
+
return severities.unindexedPredicate;
|
|
133
|
+
default:
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* AST-first lint plugin for SQL plans. When `plan.ast` is a SQL QueryAst, inspects
|
|
140
|
+
* the AST structurally. When `plan.ast` is missing, falls back to raw heuristic
|
|
141
|
+
* guardrails or skips linting depending on `fallbackWhenAstMissing`.
|
|
142
|
+
*
|
|
143
|
+
* Rules (AST-based):
|
|
144
|
+
* - DELETE without WHERE: blocks execution (configurable severity, default error)
|
|
145
|
+
* - UPDATE without WHERE: blocks execution (configurable severity, default error)
|
|
146
|
+
* - Unbounded SELECT: warn/error (severity from noLimit)
|
|
147
|
+
* - SELECT * intent: warn/error (severity from selectStar)
|
|
148
|
+
*
|
|
149
|
+
* Fallback: When ast is missing, `fallbackWhenAstMissing: 'raw'` uses heuristic
|
|
150
|
+
* SQL parsing; `'skip'` skips all lints. Default is `'raw'`.
|
|
151
|
+
*/
|
|
152
|
+
export function lints<TContract = unknown, TAdapter = unknown, TDriver = unknown>(
|
|
153
|
+
options?: LintsOptions,
|
|
154
|
+
): Plugin<TContract, TAdapter, TDriver> {
|
|
155
|
+
const fallback = options?.fallbackWhenAstMissing ?? 'raw';
|
|
156
|
+
|
|
157
|
+
return Object.freeze({
|
|
158
|
+
name: 'lints',
|
|
159
|
+
|
|
160
|
+
async beforeExecute(plan: ExecutionPlan, ctx: PluginContext<TContract, TAdapter, TDriver>) {
|
|
161
|
+
if (isSqlQueryAst(plan.ast)) {
|
|
162
|
+
const findings = evaluateAstLints(plan.ast, plan.meta);
|
|
163
|
+
|
|
164
|
+
for (const lint of findings) {
|
|
165
|
+
const configuredSeverity = getConfiguredSeverity(lint.code, options);
|
|
166
|
+
const effectiveSeverity = configuredSeverity ?? lint.severity;
|
|
167
|
+
|
|
168
|
+
if (effectiveSeverity === 'error') {
|
|
169
|
+
throw lintError(lint.code, lint.message, lint.details);
|
|
170
|
+
}
|
|
171
|
+
if (effectiveSeverity === 'warn') {
|
|
172
|
+
ctx.log.warn({
|
|
173
|
+
code: lint.code,
|
|
174
|
+
message: lint.message,
|
|
175
|
+
details: lint.details,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (fallback === 'skip') {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const evaluation = evaluateRawGuardrails(plan);
|
|
187
|
+
for (const lint of evaluation.lints) {
|
|
188
|
+
const configuredSeverity = getConfiguredSeverity(lint.code, options);
|
|
189
|
+
const effectiveSeverity = configuredSeverity ?? lint.severity;
|
|
190
|
+
|
|
191
|
+
if (effectiveSeverity === 'error') {
|
|
192
|
+
throw lintError(lint.code, lint.message, lint.details);
|
|
193
|
+
}
|
|
194
|
+
if (effectiveSeverity === 'warn') {
|
|
195
|
+
ctx.log.warn({
|
|
196
|
+
code: lint.code,
|
|
197
|
+
message: lint.message,
|
|
198
|
+
details: lint.details,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|