@prisma-next/sql-runtime 0.3.0-pr.75.6 → 0.3.0-pr.75.7

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/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-runtime",
3
- "version": "0.3.0-pr.75.6",
3
+ "version": "0.3.0-pr.75.7",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "SQL runtime implementation for Prisma Next",
7
7
  "dependencies": {
8
- "@prisma-next/contract": "0.3.0-pr.75.6",
9
- "@prisma-next/operations": "0.3.0-pr.75.6",
10
- "@prisma-next/core-execution-plane": "0.3.0-pr.75.6",
11
- "@prisma-next/runtime-executor": "0.3.0-pr.75.6",
12
- "@prisma-next/sql-contract": "0.3.0-pr.75.6",
13
- "@prisma-next/sql-operations": "0.3.0-pr.75.6",
14
- "@prisma-next/sql-relational-core": "0.3.0-pr.75.6"
8
+ "@prisma-next/contract": "0.3.0-pr.75.7",
9
+ "@prisma-next/core-execution-plane": "0.3.0-pr.75.7",
10
+ "@prisma-next/operations": "0.3.0-pr.75.7",
11
+ "@prisma-next/runtime-executor": "0.3.0-pr.75.7",
12
+ "@prisma-next/sql-contract": "0.3.0-pr.75.7",
13
+ "@prisma-next/sql-operations": "0.3.0-pr.75.7",
14
+ "@prisma-next/sql-relational-core": "0.3.0-pr.75.7"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@types/pg": "8.16.0",
@@ -24,7 +24,8 @@
24
24
  },
25
25
  "files": [
26
26
  "dist",
27
- "src"
27
+ "src",
28
+ "test"
28
29
  ],
29
30
  "exports": {
30
31
  ".": {
@@ -0,0 +1,136 @@
1
+ import type { ExecutionPlan } from '@prisma-next/contract/types';
2
+ import type { AsyncIterableResult } from '@prisma-next/runtime-executor';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { createRuntime } from '../src/exports';
5
+ import { createStubAdapter, createTestContext, createTestContract } from './utils';
6
+
7
+ // Mock driver that implements SqlDriver interface
8
+ class MockDriver {
9
+ private rows: ReadonlyArray<Record<string, unknown>> = [];
10
+
11
+ setRows(rows: ReadonlyArray<Record<string, unknown>>): void {
12
+ this.rows = rows;
13
+ }
14
+
15
+ async query(
16
+ _sql: string,
17
+ _params: readonly unknown[],
18
+ ): Promise<{ rows: ReadonlyArray<unknown> }> {
19
+ // Return empty marker result for contract verification
20
+ return { rows: [] };
21
+ }
22
+
23
+ async *execute<Row = Record<string, unknown>>(_options: {
24
+ sql: string;
25
+ params: readonly unknown[];
26
+ }): AsyncIterable<Row> {
27
+ for (const row of this.rows) {
28
+ yield row as Row;
29
+ }
30
+ }
31
+
32
+ async connect(): Promise<void> {
33
+ // No-op
34
+ }
35
+
36
+ async close(): Promise<void> {
37
+ // No-op
38
+ }
39
+ }
40
+
41
+ const fixtureContract = createTestContract({
42
+ schemaVersion: '1',
43
+ targetFamily: 'sql',
44
+ target: 'postgres',
45
+ coreHash: 'test-hash',
46
+ profileHash: 'test-profile-hash',
47
+ storage: {
48
+ tables: {
49
+ user: {
50
+ columns: {
51
+ id: { nativeType: 'int4', codecId: 'pg/int4@1', nullable: false },
52
+ email: { nativeType: 'text', codecId: 'pg/text@1', nullable: false },
53
+ },
54
+ uniques: [],
55
+ indexes: [],
56
+ foreignKeys: [],
57
+ },
58
+ },
59
+ },
60
+ models: {},
61
+ relations: {},
62
+ mappings: { codecTypes: {}, operationTypes: {} },
63
+ });
64
+
65
+ describe('SqlRuntime AsyncIterableResult integration', () => {
66
+ it('returns AsyncIterableResult from execute', async () => {
67
+ const adapter = createStubAdapter();
68
+ const driver = new MockDriver();
69
+ driver.setRows([
70
+ { id: 1, email: 'test1@example.com' },
71
+ { id: 2, email: 'test2@example.com' },
72
+ ]);
73
+ const context = createTestContext(fixtureContract, adapter);
74
+ const runtime = createRuntime({
75
+ driver: driver as unknown as Parameters<typeof createRuntime>[0]['driver'],
76
+ context,
77
+ verify: { mode: 'onFirstUse', requireMarker: false },
78
+ });
79
+
80
+ const plan: ExecutionPlan<{ id: number; email: string }> = {
81
+ sql: 'SELECT id, email FROM "user" ORDER BY id',
82
+ params: [],
83
+ meta: {
84
+ target: 'postgres',
85
+ targetFamily: 'sql',
86
+ coreHash: 'test-hash',
87
+ lane: 'sql',
88
+ paramDescriptors: [],
89
+ },
90
+ };
91
+
92
+ const result = runtime.execute(plan);
93
+
94
+ // Verify it's an AsyncIterableResult
95
+ expect(result).toBeInstanceOf(Object);
96
+ expect(typeof result.toArray).toBe('function');
97
+ expect(typeof result[Symbol.asyncIterator]).toBe('function');
98
+
99
+ await runtime.close();
100
+ });
101
+
102
+ it('preserves type information', async () => {
103
+ const adapter = createStubAdapter();
104
+ const driver = new MockDriver();
105
+ driver.setRows([{ id: 1, email: 'test@example.com' }]);
106
+ const context = createTestContext(fixtureContract, adapter);
107
+ const runtime = createRuntime({
108
+ driver: driver as unknown as Parameters<typeof createRuntime>[0]['driver'],
109
+ context,
110
+ verify: { mode: 'onFirstUse', requireMarker: false },
111
+ });
112
+
113
+ const plan: ExecutionPlan<{ id: number; email: string }> = {
114
+ sql: 'SELECT id, email FROM "user" LIMIT 1',
115
+ params: [],
116
+ meta: {
117
+ target: 'postgres',
118
+ targetFamily: 'sql',
119
+ coreHash: 'test-hash',
120
+ lane: 'sql',
121
+ paramDescriptors: [],
122
+ },
123
+ };
124
+
125
+ const result: AsyncIterableResult<{ id: number; email: string }> = runtime.execute(plan);
126
+ const rows = await result.toArray();
127
+
128
+ expect(rows.length).toBe(1);
129
+ if (rows[0]) {
130
+ expect(typeof rows[0].id).toBe('number');
131
+ expect(typeof rows[0].email).toBe('string');
132
+ }
133
+
134
+ await runtime.close();
135
+ });
136
+ });
@@ -0,0 +1,217 @@
1
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
2
+ import type { SqlOperationSignature } from '@prisma-next/sql-operations';
3
+ import type { CodecRegistry, SelectAst } from '@prisma-next/sql-relational-core/ast';
4
+ import { codec, createCodecRegistry } from '@prisma-next/sql-relational-core/ast';
5
+ import { describe, expect, it } from 'vitest';
6
+ import {
7
+ createRuntimeContext,
8
+ type SqlRuntimeExtensionDescriptor,
9
+ type SqlRuntimeExtensionInstance,
10
+ } from '../src/sql-context';
11
+
12
+ // Minimal test contract
13
+ const testContract: SqlContract<SqlStorage> = {
14
+ schemaVersion: '1',
15
+ targetFamily: 'sql',
16
+ target: 'postgres',
17
+ coreHash: 'sha256:test',
18
+ models: {},
19
+ relations: {},
20
+ storage: { tables: {} },
21
+ extensionPacks: {},
22
+ capabilities: {},
23
+ meta: {},
24
+ sources: {},
25
+ mappings: {
26
+ codecTypes: {},
27
+ operationTypes: {},
28
+ },
29
+ };
30
+
31
+ // Stub adapter codecs
32
+ function createStubCodecs(): CodecRegistry {
33
+ const registry = createCodecRegistry();
34
+ registry.register(
35
+ codec({
36
+ typeId: 'pg/int4@1',
37
+ targetTypes: ['int4'],
38
+ encode: (v: number) => v,
39
+ decode: (w: number) => w,
40
+ }),
41
+ );
42
+ return registry;
43
+ }
44
+
45
+ // Create a test adapter descriptor
46
+ function createTestAdapterDescriptor() {
47
+ const codecs = createStubCodecs();
48
+ return {
49
+ kind: 'adapter' as const,
50
+ id: 'test-adapter',
51
+ version: '0.0.1',
52
+ familyId: 'sql' as const,
53
+ targetId: 'postgres' as const,
54
+ create() {
55
+ return {
56
+ familyId: 'sql' as const,
57
+ targetId: 'postgres' as const,
58
+ profile: {
59
+ id: 'test-profile',
60
+ target: 'postgres',
61
+ capabilities: {},
62
+ codecs() {
63
+ return codecs;
64
+ },
65
+ },
66
+ lower(ast: SelectAst) {
67
+ return {
68
+ profileId: 'test-profile',
69
+ body: Object.freeze({ sql: JSON.stringify(ast), params: [] }),
70
+ };
71
+ },
72
+ };
73
+ },
74
+ };
75
+ }
76
+
77
+ // Create a test target descriptor
78
+ function createTestTargetDescriptor() {
79
+ return {
80
+ kind: 'target' as const,
81
+ id: 'postgres',
82
+ version: '0.0.1',
83
+ familyId: 'sql' as const,
84
+ targetId: 'postgres' as const,
85
+ create() {
86
+ return { familyId: 'sql' as const, targetId: 'postgres' as const };
87
+ },
88
+ };
89
+ }
90
+
91
+ // Create a test extension descriptor
92
+ function createTestExtensionDescriptor(options?: {
93
+ hasCodecs?: boolean;
94
+ hasOperations?: boolean;
95
+ }): SqlRuntimeExtensionDescriptor<'postgres'> {
96
+ const { hasCodecs = false, hasOperations = false } = options ?? {};
97
+
98
+ // Build the codecs function if needed
99
+ const codecsFn = hasCodecs
100
+ ? () => {
101
+ const registry = createCodecRegistry();
102
+ registry.register(
103
+ codec({
104
+ typeId: 'test/ext@1',
105
+ targetTypes: ['ext'],
106
+ encode: (v: string) => v,
107
+ decode: (w: string) => w,
108
+ }),
109
+ );
110
+ return registry;
111
+ }
112
+ : undefined;
113
+
114
+ // Build the operations function if needed
115
+ const operationsFn = hasOperations
116
+ ? (): ReadonlyArray<SqlOperationSignature> => [
117
+ {
118
+ forTypeId: 'test/ext@1',
119
+ method: 'testOp',
120
+ args: [],
121
+ returns: { kind: 'builtin', type: 'number' },
122
+ lowering: { targetFamily: 'sql', strategy: 'function', template: 'test()' },
123
+ },
124
+ ]
125
+ : undefined;
126
+
127
+ return {
128
+ kind: 'extension' as const,
129
+ id: 'test-extension',
130
+ version: '0.0.1',
131
+ familyId: 'sql' as const,
132
+ targetId: 'postgres' as const,
133
+ create(): SqlRuntimeExtensionInstance<'postgres'> {
134
+ // Return object with optional methods only if they exist
135
+ const instance: SqlRuntimeExtensionInstance<'postgres'> = {
136
+ familyId: 'sql' as const,
137
+ targetId: 'postgres' as const,
138
+ };
139
+ if (codecsFn) {
140
+ (instance as { codecs?: () => CodecRegistry }).codecs = codecsFn;
141
+ }
142
+ if (operationsFn) {
143
+ (instance as { operations?: () => ReadonlyArray<SqlOperationSignature> }).operations =
144
+ operationsFn;
145
+ }
146
+ return instance;
147
+ },
148
+ };
149
+ }
150
+
151
+ describe('createRuntimeContext', () => {
152
+ it('creates context with adapter codecs', () => {
153
+ const context = createRuntimeContext({
154
+ contract: testContract,
155
+ target: createTestTargetDescriptor(),
156
+ adapter: createTestAdapterDescriptor(),
157
+ });
158
+
159
+ expect(context.contract).toBe(testContract);
160
+ expect(context.adapter).toBeDefined();
161
+ expect(context.codecs.has('pg/int4@1')).toBe(true);
162
+ expect(context.operations).toBeDefined();
163
+ });
164
+
165
+ it('creates context with empty extension packs', () => {
166
+ const context = createRuntimeContext({
167
+ contract: testContract,
168
+ target: createTestTargetDescriptor(),
169
+ adapter: createTestAdapterDescriptor(),
170
+ extensionPacks: [],
171
+ });
172
+
173
+ expect(context.codecs.has('pg/int4@1')).toBe(true);
174
+ // No extension codecs registered
175
+ expect(context.codecs.has('test/ext@1')).toBe(false);
176
+ });
177
+
178
+ it('registers extension codecs', () => {
179
+ const context = createRuntimeContext({
180
+ contract: testContract,
181
+ target: createTestTargetDescriptor(),
182
+ adapter: createTestAdapterDescriptor(),
183
+ extensionPacks: [createTestExtensionDescriptor({ hasCodecs: true })],
184
+ });
185
+
186
+ // Adapter codec
187
+ expect(context.codecs.has('pg/int4@1')).toBe(true);
188
+ // Extension codec
189
+ expect(context.codecs.has('test/ext@1')).toBe(true);
190
+ });
191
+
192
+ it('registers extension operations', () => {
193
+ const context = createRuntimeContext({
194
+ contract: testContract,
195
+ target: createTestTargetDescriptor(),
196
+ adapter: createTestAdapterDescriptor(),
197
+ extensionPacks: [createTestExtensionDescriptor({ hasOperations: true })],
198
+ });
199
+
200
+ const ops = context.operations.byType('test/ext@1');
201
+ expect(ops.length).toBe(1);
202
+ expect(ops[0]?.method).toBe('testOp');
203
+ });
204
+
205
+ it('handles extension without codecs or operations', () => {
206
+ const context = createRuntimeContext({
207
+ contract: testContract,
208
+ target: createTestTargetDescriptor(),
209
+ adapter: createTestAdapterDescriptor(),
210
+ extensionPacks: [createTestExtensionDescriptor({ hasCodecs: false, hasOperations: false })],
211
+ });
212
+
213
+ // Only adapter codec
214
+ expect(context.codecs.has('pg/int4@1')).toBe(true);
215
+ expect(context.codecs.has('test/ext@1')).toBe(false);
216
+ });
217
+ });
@@ -0,0 +1,86 @@
1
+ import type { ExecutionPlan } from '@prisma-next/contract/types';
2
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { SqlFamilyAdapter } from '../src/sql-family-adapter';
5
+
6
+ // Minimal test contract
7
+ const testContract: SqlContract<SqlStorage> = {
8
+ schemaVersion: '1',
9
+ targetFamily: 'sql',
10
+ target: 'postgres',
11
+ coreHash: 'sha256:test-hash',
12
+ models: {},
13
+ relations: {},
14
+ storage: { tables: {} },
15
+ extensionPacks: {},
16
+ capabilities: {},
17
+ meta: {},
18
+ sources: {},
19
+ mappings: {
20
+ codecTypes: {},
21
+ operationTypes: {},
22
+ },
23
+ };
24
+
25
+ describe('SqlFamilyAdapter', () => {
26
+ it('creates adapter with contract and marker reader', () => {
27
+ const adapter = new SqlFamilyAdapter(testContract);
28
+
29
+ expect(adapter.contract).toBe(testContract);
30
+ expect(adapter.markerReader).toBeDefined();
31
+ expect(adapter.markerReader.readMarkerStatement).toBeDefined();
32
+ });
33
+
34
+ it('validates plan with matching target and hash', () => {
35
+ const adapter = new SqlFamilyAdapter(testContract);
36
+ const plan: ExecutionPlan = {
37
+ meta: {
38
+ target: 'postgres',
39
+ coreHash: 'sha256:test-hash',
40
+ lane: 'sql',
41
+ paramDescriptors: [],
42
+ },
43
+ sql: 'SELECT 1',
44
+ params: [],
45
+ };
46
+
47
+ // Should not throw
48
+ expect(() => adapter.validatePlan(plan, testContract)).not.toThrow();
49
+ });
50
+
51
+ it('throws on plan target mismatch', () => {
52
+ const adapter = new SqlFamilyAdapter(testContract);
53
+ const plan: ExecutionPlan = {
54
+ meta: {
55
+ target: 'mysql', // Wrong target
56
+ coreHash: 'sha256:test-hash',
57
+ lane: 'sql',
58
+ paramDescriptors: [],
59
+ },
60
+ sql: 'SELECT 1',
61
+ params: [],
62
+ };
63
+
64
+ expect(() => adapter.validatePlan(plan, testContract)).toThrow(
65
+ 'Plan target does not match runtime target',
66
+ );
67
+ });
68
+
69
+ it('throws on plan coreHash mismatch', () => {
70
+ const adapter = new SqlFamilyAdapter(testContract);
71
+ const plan: ExecutionPlan = {
72
+ meta: {
73
+ target: 'postgres',
74
+ coreHash: 'sha256:different-hash', // Wrong hash
75
+ lane: 'sql',
76
+ paramDescriptors: [],
77
+ },
78
+ sql: 'SELECT 1',
79
+ params: [],
80
+ };
81
+
82
+ expect(() => adapter.validatePlan(plan, testContract)).toThrow(
83
+ 'Plan core hash does not match runtime contract',
84
+ );
85
+ });
86
+ });
@@ -0,0 +1,155 @@
1
+ import { createOperationRegistry } from '@prisma-next/operations';
2
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
3
+ import type {
4
+ CodecRegistry,
5
+ SelectAst,
6
+ SqlDriver,
7
+ SqlExecuteRequest,
8
+ } from '@prisma-next/sql-relational-core/ast';
9
+ import { codec, createCodecRegistry } from '@prisma-next/sql-relational-core/ast';
10
+ import { describe, expect, it, vi } from 'vitest';
11
+ import type { RuntimeContext } from '../src/sql-context';
12
+ import { createRuntime } from '../src/sql-runtime';
13
+
14
+ // Minimal test contract
15
+ const testContract: SqlContract<SqlStorage> = {
16
+ schemaVersion: '1',
17
+ targetFamily: 'sql',
18
+ target: 'postgres',
19
+ coreHash: 'sha256:test',
20
+ models: {},
21
+ relations: {},
22
+ storage: { tables: {} },
23
+ extensionPacks: {},
24
+ capabilities: {},
25
+ meta: {},
26
+ sources: {},
27
+ mappings: {
28
+ codecTypes: {},
29
+ operationTypes: {},
30
+ },
31
+ };
32
+
33
+ // Create a stub codec registry
34
+ function createStubCodecs(): CodecRegistry {
35
+ const registry = createCodecRegistry();
36
+ registry.register(
37
+ codec({
38
+ typeId: 'pg/int4@1',
39
+ targetTypes: ['int4'],
40
+ encode: (v: number) => v,
41
+ decode: (w: number) => w,
42
+ }),
43
+ );
44
+ return registry;
45
+ }
46
+
47
+ // Create a stub adapter
48
+ function createStubAdapter() {
49
+ const codecs = createStubCodecs();
50
+ return {
51
+ familyId: 'sql' as const,
52
+ targetId: 'postgres' as const,
53
+ profile: {
54
+ id: 'test-profile',
55
+ target: 'postgres',
56
+ capabilities: {},
57
+ codecs() {
58
+ return codecs;
59
+ },
60
+ },
61
+ lower(ast: SelectAst) {
62
+ return {
63
+ profileId: 'test-profile',
64
+ body: Object.freeze({ sql: JSON.stringify(ast), params: [] }),
65
+ };
66
+ },
67
+ };
68
+ }
69
+
70
+ // Create a mock driver that implements SqlDriver interface
71
+ function createMockDriver(): SqlDriver {
72
+ const execute = vi.fn().mockImplementation(async function* (_request: SqlExecuteRequest) {
73
+ yield { id: 1 };
74
+ });
75
+
76
+ return {
77
+ connect: vi.fn().mockResolvedValue(undefined),
78
+ execute,
79
+ query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
80
+ close: vi.fn().mockResolvedValue(undefined),
81
+ };
82
+ }
83
+
84
+ // Create a test runtime context
85
+ function createTestContext(contract: SqlContract<SqlStorage>): RuntimeContext<typeof contract> {
86
+ const adapter = createStubAdapter();
87
+ return {
88
+ contract,
89
+ adapter,
90
+ codecs: adapter.profile.codecs(),
91
+ operations: createOperationRegistry(),
92
+ };
93
+ }
94
+
95
+ describe('createRuntime', () => {
96
+ it('creates runtime with valid options', () => {
97
+ const context = createTestContext(testContract);
98
+ const driver = createMockDriver();
99
+
100
+ const runtime = createRuntime({
101
+ context,
102
+ driver,
103
+ verify: { mode: 'onFirstUse', requireMarker: false },
104
+ });
105
+
106
+ expect(runtime).toBeDefined();
107
+ expect(runtime.execute).toBeDefined();
108
+ expect(runtime.telemetry).toBeDefined();
109
+ expect(runtime.operations).toBeDefined();
110
+ expect(runtime.close).toBeDefined();
111
+ });
112
+
113
+ it('returns operations registry', () => {
114
+ const context = createTestContext(testContract);
115
+ const driver = createMockDriver();
116
+
117
+ const runtime = createRuntime({
118
+ context,
119
+ driver,
120
+ verify: { mode: 'onFirstUse', requireMarker: false },
121
+ });
122
+
123
+ const ops = runtime.operations();
124
+ expect(ops).toBeDefined();
125
+ expect(ops.byType).toBeDefined();
126
+ });
127
+
128
+ it('returns null telemetry when no events', () => {
129
+ const context = createTestContext(testContract);
130
+ const driver = createMockDriver();
131
+
132
+ const runtime = createRuntime({
133
+ context,
134
+ driver,
135
+ verify: { mode: 'onFirstUse', requireMarker: false },
136
+ });
137
+
138
+ // Before any execution, telemetry should be null
139
+ expect(runtime.telemetry()).toBeNull();
140
+ });
141
+
142
+ it('closes runtime', async () => {
143
+ const context = createTestContext(testContract);
144
+ const driver = createMockDriver();
145
+
146
+ const runtime = createRuntime({
147
+ context,
148
+ driver,
149
+ verify: { mode: 'onFirstUse', requireMarker: false },
150
+ });
151
+
152
+ await runtime.close();
153
+ expect(driver.close).toHaveBeenCalled();
154
+ });
155
+ });
package/test/utils.ts ADDED
@@ -0,0 +1,262 @@
1
+ import type { ExecutionPlan, ResultType } from '@prisma-next/contract/types';
2
+ import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
3
+ import type { Adapter, LoweredStatement, SelectAst } from '@prisma-next/sql-relational-core/ast';
4
+ import { codec, createCodecRegistry } from '@prisma-next/sql-relational-core/ast';
5
+ import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
6
+ import { collectAsync, drainAsyncIterable } from '@prisma-next/test-utils';
7
+ import type { Client } from 'pg';
8
+ import type { SqlStatement } from '../src/exports';
9
+ import {
10
+ type createRuntime,
11
+ createRuntimeContext,
12
+ ensureSchemaStatement,
13
+ ensureTableStatement,
14
+ writeContractMarker,
15
+ } from '../src/exports';
16
+ import type {
17
+ RuntimeContext,
18
+ SqlRuntimeAdapterInstance,
19
+ SqlRuntimeExtensionDescriptor,
20
+ } from '../src/sql-context';
21
+
22
+ /**
23
+ * Executes a plan and collects all results into an array.
24
+ * This helper DRYs up the common pattern of executing plans in tests.
25
+ * The return type is inferred from the plan's type parameter.
26
+ */
27
+ export async function executePlanAndCollect<
28
+ P extends ExecutionPlan<ResultType<P>> | SqlQueryPlan<ResultType<P>>,
29
+ >(runtime: ReturnType<typeof createRuntime>, plan: P): Promise<ResultType<P>[]> {
30
+ type Row = ResultType<P>;
31
+ return collectAsync<Row>(runtime.execute<Row>(plan));
32
+ }
33
+
34
+ /**
35
+ * Drains a plan execution, consuming all results without collecting them.
36
+ * Useful for testing side effects without memory overhead.
37
+ */
38
+ export async function drainPlanExecution(
39
+ runtime: ReturnType<typeof createRuntime>,
40
+ plan: ExecutionPlan | SqlQueryPlan<unknown>,
41
+ ): Promise<void> {
42
+ return drainAsyncIterable(runtime.execute(plan));
43
+ }
44
+
45
+ /**
46
+ * Executes a SQL statement on a database client.
47
+ */
48
+ export async function executeStatement(client: Client, statement: SqlStatement): Promise<void> {
49
+ if (statement.params.length > 0) {
50
+ await client.query(statement.sql, [...statement.params]);
51
+ return;
52
+ }
53
+
54
+ await client.query(statement.sql);
55
+ }
56
+
57
+ /**
58
+ * Sets up database schema and data, then writes the contract marker.
59
+ * This helper DRYs up the common pattern of database setup in tests.
60
+ */
61
+ export async function setupTestDatabase(
62
+ client: Client,
63
+ contract: SqlContract<SqlStorage>,
64
+ setupFn: (client: Client) => Promise<void>,
65
+ ): Promise<void> {
66
+ await client.query('drop schema if exists prisma_contract cascade');
67
+ await client.query('create schema if not exists public');
68
+
69
+ await setupFn(client);
70
+
71
+ await executeStatement(client, ensureSchemaStatement);
72
+ await executeStatement(client, ensureTableStatement);
73
+ const write = writeContractMarker({
74
+ coreHash: contract.coreHash,
75
+ profileHash: contract.profileHash ?? contract.coreHash,
76
+ contractJson: contract,
77
+ canonicalVersion: 1,
78
+ });
79
+ await executeStatement(client, write.insert);
80
+ }
81
+
82
+ /**
83
+ * Writes a contract marker to the database.
84
+ * This helper DRYs up the common pattern of writing contract markers in tests.
85
+ */
86
+ export async function writeTestContractMarker(
87
+ client: Client,
88
+ contract: SqlContract<SqlStorage>,
89
+ ): Promise<void> {
90
+ const write = writeContractMarker({
91
+ coreHash: contract.coreHash,
92
+ profileHash: contract.profileHash ?? contract.coreHash,
93
+ contractJson: contract,
94
+ canonicalVersion: 1,
95
+ });
96
+ await executeStatement(client, write.insert);
97
+ }
98
+
99
+ /**
100
+ * Creates a test adapter descriptor from a raw adapter.
101
+ * This wraps the adapter in a descriptor for descriptor-first context creation in tests.
102
+ * The adapter instance IS an Adapter (via intersection), with identity properties added.
103
+ */
104
+ function createTestAdapterDescriptor(
105
+ adapter: Adapter<SelectAst, SqlContract<SqlStorage>, LoweredStatement>,
106
+ ): {
107
+ readonly kind: 'adapter';
108
+ readonly id: string;
109
+ readonly version: string;
110
+ readonly familyId: 'sql';
111
+ readonly targetId: 'postgres';
112
+ create(): SqlRuntimeAdapterInstance<'postgres'>;
113
+ } {
114
+ return {
115
+ kind: 'adapter' as const,
116
+ id: 'test-adapter',
117
+ version: '0.0.1',
118
+ familyId: 'sql' as const,
119
+ targetId: 'postgres' as const,
120
+ create(): SqlRuntimeAdapterInstance<'postgres'> {
121
+ // Return an object that combines identity properties with the adapter's methods
122
+ return Object.assign(
123
+ {
124
+ familyId: 'sql' as const,
125
+ targetId: 'postgres' as const,
126
+ },
127
+ adapter,
128
+ );
129
+ },
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Creates a test target descriptor.
135
+ * This is a minimal descriptor for descriptor-first context creation in tests.
136
+ */
137
+ function createTestTargetDescriptor(): {
138
+ readonly kind: 'target';
139
+ readonly id: string;
140
+ readonly version: string;
141
+ readonly familyId: 'sql';
142
+ readonly targetId: 'postgres';
143
+ create(): { readonly familyId: 'sql'; readonly targetId: 'postgres' };
144
+ } {
145
+ return {
146
+ kind: 'target' as const,
147
+ id: 'postgres',
148
+ version: '0.0.1',
149
+ familyId: 'sql' as const,
150
+ targetId: 'postgres' as const,
151
+ create() {
152
+ return { familyId: 'sql' as const, targetId: 'postgres' as const };
153
+ },
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Creates a runtime context with standard test configuration.
159
+ * This helper DRYs up the common pattern of context creation in tests.
160
+ *
161
+ * Accepts a raw adapter and optional extension descriptors, wrapping the
162
+ * adapter in a descriptor internally for descriptor-first context creation.
163
+ */
164
+ export function createTestContext<TContract extends SqlContract<SqlStorage>>(
165
+ contract: TContract,
166
+ adapter: Adapter<SelectAst, SqlContract<SqlStorage>, LoweredStatement>,
167
+ options?: {
168
+ extensionPacks?: ReadonlyArray<SqlRuntimeExtensionDescriptor<'postgres'>>;
169
+ },
170
+ ): RuntimeContext<TContract> {
171
+ return createRuntimeContext<TContract, 'postgres'>({
172
+ contract,
173
+ target: createTestTargetDescriptor(),
174
+ adapter: createTestAdapterDescriptor(adapter),
175
+ extensionPacks: options?.extensionPacks ?? [],
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Creates a stub adapter for testing.
181
+ * This helper DRYs up the common pattern of adapter creation in tests.
182
+ *
183
+ * The stub adapter includes simple codecs for common test types (pg/int4@1, pg/text@1, pg/timestamptz@1)
184
+ * to enable type inference in tests without requiring the postgres adapter package.
185
+ */
186
+ export function createStubAdapter(): Adapter<SelectAst, SqlContract<SqlStorage>, LoweredStatement> {
187
+ const codecRegistry = createCodecRegistry();
188
+
189
+ // Register stub codecs for common test types
190
+ // These match the codec IDs used in test contracts (pg/int4@1, pg/text@1, pg/timestamptz@1)
191
+ // but don't require importing from the postgres adapter package
192
+ codecRegistry.register(
193
+ codec({
194
+ typeId: 'pg/int4@1',
195
+ targetTypes: ['int4'],
196
+ encode: (value: number) => value,
197
+ decode: (wire: number) => wire,
198
+ }),
199
+ );
200
+
201
+ codecRegistry.register(
202
+ codec({
203
+ typeId: 'pg/text@1',
204
+ targetTypes: ['text'],
205
+ encode: (value: string) => value,
206
+ decode: (wire: string) => wire,
207
+ }),
208
+ );
209
+
210
+ codecRegistry.register(
211
+ codec({
212
+ typeId: 'pg/timestamptz@1',
213
+ targetTypes: ['timestamptz'],
214
+ encode: (value: string | Date) => (value instanceof Date ? value.toISOString() : value),
215
+ decode: (wire: string | Date) =>
216
+ typeof wire === 'string' ? wire : wire instanceof Date ? wire.toISOString() : String(wire),
217
+ }),
218
+ );
219
+
220
+ return {
221
+ profile: {
222
+ id: 'stub-profile',
223
+ target: 'postgres',
224
+ capabilities: {},
225
+ codecs() {
226
+ return codecRegistry;
227
+ },
228
+ },
229
+ lower(ast: SelectAst, ctx: { contract: SqlContract<SqlStorage>; params?: readonly unknown[] }) {
230
+ const sqlText = JSON.stringify(ast);
231
+ return {
232
+ profileId: this.profile.id,
233
+ body: Object.freeze({ sql: sqlText, params: ctx.params ? [...ctx.params] : [] }),
234
+ };
235
+ },
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Creates a valid test contract without using validateContract.
241
+ * Ensures mappings are present and returns the contract with proper typing.
242
+ * This helper allows tests to create contracts without depending on sql-query.
243
+ */
244
+ export function createTestContract<T extends SqlContract<SqlStorage>>(contract: T): T {
245
+ // Ensure mappings are present
246
+ if (!contract.mappings) {
247
+ return {
248
+ ...contract,
249
+ mappings: { codecTypes: {}, operationTypes: {} },
250
+ } as T;
251
+ }
252
+ return contract;
253
+ }
254
+
255
+ // Re-export generic utilities from test-utils
256
+ export {
257
+ collectAsync,
258
+ createDevDatabase,
259
+ type DevDatabase,
260
+ teardownTestDatabase,
261
+ withClient,
262
+ } from '@prisma-next/test-utils';