@contractspec/lib.runtime-sandbox 2.7.5 → 2.7.9
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 +42 -22
- package/dist/browser/index.js +364 -120
- package/dist/index.js +364 -120
- package/dist/node/index.js +364 -120
- package/dist/web/index.d.ts +3 -3
- package/package.json +7 -7
- package/src/adapters/pglite/adapter.ts +132 -132
- package/src/index.ts +9 -9
- package/src/ports/database.port.ts +53 -53
- package/src/ports/index.ts +2 -2
- package/src/types/database.types.ts +22 -22
- package/src/web/database/migrations.ts +195 -195
- package/src/web/database/schema.ts +419 -419
- package/src/web/events/local-pubsub.ts +22 -22
- package/src/web/graphql/local-client.ts +495 -496
- package/src/web/index.ts +6 -11
- package/src/web/runtime/seeders/index.ts +611 -352
- package/src/web/runtime/services.ts +104 -105
- package/src/web/storage/indexeddb.ts +98 -98
- package/src/web/utils/id.ts +5 -5
|
@@ -2,12 +2,12 @@ import { PGlite } from '@electric-sql/pglite';
|
|
|
2
2
|
|
|
3
3
|
import type { DatabasePort } from '../../ports/database.port';
|
|
4
4
|
import type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
DatabaseInitOptions,
|
|
6
|
+
DbRow,
|
|
7
|
+
DbValue,
|
|
8
|
+
Migration,
|
|
9
|
+
QueryResult,
|
|
10
|
+
TransactionContext,
|
|
11
11
|
} from '../../types/database.types';
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -17,136 +17,136 @@ import type {
|
|
|
17
17
|
* Supports in-memory or IndexedDB persistence.
|
|
18
18
|
*/
|
|
19
19
|
export class PGLiteDatabaseAdapter implements DatabasePort {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
20
|
+
private client: PGlite | null = null;
|
|
21
|
+
private initialized = false;
|
|
22
|
+
|
|
23
|
+
async init(options?: DatabaseInitOptions): Promise<void> {
|
|
24
|
+
if (this.initialized) return;
|
|
25
|
+
|
|
26
|
+
// Create PGLite instance
|
|
27
|
+
// - undefined/empty = in-memory
|
|
28
|
+
// - 'idb://dbname' = IndexedDB persistence
|
|
29
|
+
const dataDir = options?.dataDir;
|
|
30
|
+
this.client = dataDir ? new PGlite(`idb://${dataDir}`) : new PGlite();
|
|
31
|
+
|
|
32
|
+
// Wait for PGLite to be ready
|
|
33
|
+
await this.client.waitReady;
|
|
34
|
+
this.initialized = true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async close(): Promise<void> {
|
|
38
|
+
if (this.client) {
|
|
39
|
+
await this.client.close();
|
|
40
|
+
this.client = null;
|
|
41
|
+
this.initialized = false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
isInitialized(): boolean {
|
|
46
|
+
return this.initialized;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async query<T = DbRow>(
|
|
50
|
+
sql: string,
|
|
51
|
+
params?: DbValue[]
|
|
52
|
+
): Promise<QueryResult<T>> {
|
|
53
|
+
const client = this.getClient();
|
|
54
|
+
const normalizedParams = this.normalizeParams(params);
|
|
55
|
+
const result = await client.query<T>(sql, normalizedParams);
|
|
56
|
+
return {
|
|
57
|
+
rows: result.rows,
|
|
58
|
+
rowCount: result.rows.length,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async execute(sql: string, params?: DbValue[]): Promise<void> {
|
|
63
|
+
const client = this.getClient();
|
|
64
|
+
const normalizedParams = this.normalizeParams(params);
|
|
65
|
+
await client.query(sql, normalizedParams);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async transaction<T>(
|
|
69
|
+
callback: (ctx: TransactionContext) => Promise<T>
|
|
70
|
+
): Promise<T> {
|
|
71
|
+
const client = this.getClient();
|
|
72
|
+
|
|
73
|
+
await client.query('BEGIN');
|
|
74
|
+
try {
|
|
75
|
+
const ctx: TransactionContext = {
|
|
76
|
+
execute: async (sql: string, params?: DbValue[]) => {
|
|
77
|
+
const normalizedParams = this.normalizeParams(params);
|
|
78
|
+
await client.query(sql, normalizedParams);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
const result = await callback(ctx);
|
|
82
|
+
await client.query('COMMIT');
|
|
83
|
+
return result;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
await client.query('ROLLBACK');
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async migrate(migrations: Migration[]): Promise<void> {
|
|
91
|
+
const client = this.getClient();
|
|
92
|
+
|
|
93
|
+
// Create migrations tracking table if not exists
|
|
94
|
+
await client.query(`
|
|
95
95
|
CREATE TABLE IF NOT EXISTS _sandbox_migrations (
|
|
96
96
|
id TEXT PRIMARY KEY,
|
|
97
97
|
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
98
98
|
)
|
|
99
99
|
`);
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
101
|
+
// Apply each migration if not already applied
|
|
102
|
+
for (const migration of migrations) {
|
|
103
|
+
const existing = await client.query<{ id: string }>(
|
|
104
|
+
'SELECT id FROM _sandbox_migrations WHERE id = $1',
|
|
105
|
+
[migration.id]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (existing.rows.length === 0) {
|
|
109
|
+
await client.query(migration.sql);
|
|
110
|
+
await client.query('INSERT INTO _sandbox_migrations (id) VALUES ($1)', [
|
|
111
|
+
migration.id,
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async export(): Promise<Uint8Array> {
|
|
118
|
+
this.getClient(); // Ensure initialized
|
|
119
|
+
// PGLite doesn't support export the same way sql.js does
|
|
120
|
+
// For now, return empty array - can be implemented with pg_dump style later
|
|
121
|
+
return new Uint8Array();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get the initialized PGLite client.
|
|
126
|
+
* Throws if not initialized.
|
|
127
|
+
*/
|
|
128
|
+
private getClient(): PGlite {
|
|
129
|
+
if (!this.client || !this.initialized) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
'PGLiteDatabaseAdapter not initialized. Call init() first.'
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return this.client;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private normalizeParams(params?: DbValue[]): unknown[] {
|
|
138
|
+
if (!params) return [];
|
|
139
|
+
return params.map((value) => {
|
|
140
|
+
if (typeof value === 'boolean') {
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
if (value instanceof Date) {
|
|
144
|
+
return value.toISOString();
|
|
145
|
+
}
|
|
146
|
+
if (value === undefined) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
152
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,12 +10,12 @@ export { type DatabaseAdapterFactory, type DatabasePort } from './ports';
|
|
|
10
10
|
|
|
11
11
|
// Types
|
|
12
12
|
export {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
type DatabaseInitOptions,
|
|
14
|
+
type DbRow,
|
|
15
|
+
type DbValue,
|
|
16
|
+
type Migration,
|
|
17
|
+
type QueryResult,
|
|
18
|
+
type TransactionContext,
|
|
19
19
|
} from './types';
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -32,10 +32,10 @@ export {
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export async function createPGLiteAdapter(): Promise<
|
|
35
|
-
|
|
35
|
+
import('./ports').DatabasePort
|
|
36
36
|
> {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const { PGLiteDatabaseAdapter } = await import('./adapters/pglite');
|
|
38
|
+
return new PGLiteDatabaseAdapter();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export * as web from './web';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
DatabaseInitOptions,
|
|
3
|
+
DbRow,
|
|
4
|
+
DbValue,
|
|
5
|
+
Migration,
|
|
6
|
+
QueryResult,
|
|
7
|
+
TransactionContext,
|
|
8
8
|
} from '../types/database.types';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -18,59 +18,59 @@ import type {
|
|
|
18
18
|
* All implementations must be lazy-loadable to avoid bundle bloat.
|
|
19
19
|
*/
|
|
20
20
|
export interface DatabasePort {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Initialize the database connection and run migrations.
|
|
23
|
+
*/
|
|
24
|
+
init(options?: DatabaseInitOptions): Promise<void>;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Close the database connection and release resources.
|
|
28
|
+
*/
|
|
29
|
+
close(): Promise<void>;
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Check if the database is initialized.
|
|
33
|
+
*/
|
|
34
|
+
isInitialized(): boolean;
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Execute a SELECT query and return rows.
|
|
38
|
+
*
|
|
39
|
+
* @param sql - SQL query string with $1, $2, etc. placeholders
|
|
40
|
+
* @param params - Query parameters
|
|
41
|
+
* @returns Query result with typed rows
|
|
42
|
+
*/
|
|
43
|
+
query<T = DbRow>(sql: string, params?: DbValue[]): Promise<QueryResult<T>>;
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Execute an INSERT/UPDATE/DELETE statement.
|
|
47
|
+
*
|
|
48
|
+
* @param sql - SQL statement with $1, $2, etc. placeholders
|
|
49
|
+
* @param params - Statement parameters
|
|
50
|
+
*/
|
|
51
|
+
execute(sql: string, params?: DbValue[]): Promise<void>;
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Run a callback within a database transaction.
|
|
55
|
+
* Automatically commits on success, rolls back on error.
|
|
56
|
+
*
|
|
57
|
+
* @param callback - Function to execute within transaction
|
|
58
|
+
* @returns Result of the callback
|
|
59
|
+
*/
|
|
60
|
+
transaction<T>(callback: (ctx: TransactionContext) => Promise<T>): Promise<T>;
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Run schema migrations.
|
|
64
|
+
*
|
|
65
|
+
* @param migrations - Array of migrations to apply
|
|
66
|
+
*/
|
|
67
|
+
migrate(migrations: Migration[]): Promise<void>;
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Export the current database state as a binary blob.
|
|
71
|
+
* Useful for backup/restore in browser context.
|
|
72
|
+
*/
|
|
73
|
+
export(): Promise<Uint8Array>;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/**
|
|
@@ -78,5 +78,5 @@ export interface DatabasePort {
|
|
|
78
78
|
* Used for lazy-loading adapters.
|
|
79
79
|
*/
|
|
80
80
|
export type DatabaseAdapterFactory = (
|
|
81
|
-
|
|
81
|
+
options?: DatabaseInitOptions
|
|
82
82
|
) => Promise<DatabasePort>;
|
package/src/ports/index.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Database value types supported by the sandbox runtime
|
|
3
3
|
*/
|
|
4
4
|
export type DbValue =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
| string
|
|
6
|
+
| number
|
|
7
|
+
| boolean
|
|
8
|
+
| null
|
|
9
|
+
| Uint8Array
|
|
10
|
+
| Date
|
|
11
|
+
| undefined;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Generic row type for database results
|
|
@@ -19,37 +19,37 @@ export type DbRow = Record<string, DbValue>;
|
|
|
19
19
|
* Options for initializing a database adapter
|
|
20
20
|
*/
|
|
21
21
|
export interface DatabaseInitOptions {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Data directory for persistence (optional).
|
|
24
|
+
* - Browser: uses IndexedDB when specified
|
|
25
|
+
* - Node/Bun: uses file system when specified
|
|
26
|
+
* - If omitted, uses in-memory storage
|
|
27
|
+
*/
|
|
28
|
+
dataDir?: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Query builder result type
|
|
33
33
|
*/
|
|
34
34
|
export interface QueryResult<T = DbRow> {
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
rows: T[];
|
|
36
|
+
rowCount: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Transaction context for atomic operations
|
|
41
41
|
*/
|
|
42
42
|
export interface TransactionContext {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Execute raw SQL within the transaction
|
|
45
|
+
*/
|
|
46
|
+
execute(sql: string, params?: DbValue[]): Promise<void>;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Migration definition
|
|
51
51
|
*/
|
|
52
52
|
export interface Migration {
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
id: string;
|
|
54
|
+
sql: string;
|
|
55
55
|
}
|