@contractspec/lib.runtime-sandbox 0.11.0

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.
Files changed (63) hide show
  1. package/dist/_virtual/rolldown_runtime.js +18 -0
  2. package/dist/adapters/pglite/adapter.js +97 -0
  3. package/dist/adapters/pglite/adapter.js.map +1 -0
  4. package/dist/adapters/pglite/index.js +3 -0
  5. package/dist/index.d.ts +23 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +24 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/ports/database.port.d.ts +70 -0
  10. package/dist/ports/database.port.d.ts.map +1 -0
  11. package/dist/types/database.types.d.ts +47 -0
  12. package/dist/types/database.types.d.ts.map +1 -0
  13. package/dist/web/database/migrations.d.ts +12 -0
  14. package/dist/web/database/migrations.d.ts.map +1 -0
  15. package/dist/web/database/migrations.js +746 -0
  16. package/dist/web/database/migrations.js.map +1 -0
  17. package/dist/web/database/schema.d.ts +7349 -0
  18. package/dist/web/database/schema.d.ts.map +1 -0
  19. package/dist/web/database/schema.js +528 -0
  20. package/dist/web/database/schema.js.map +1 -0
  21. package/dist/web/events/local-pubsub.d.ts +10 -0
  22. package/dist/web/events/local-pubsub.d.ts.map +1 -0
  23. package/dist/web/events/local-pubsub.js +24 -0
  24. package/dist/web/events/local-pubsub.js.map +1 -0
  25. package/dist/web/graphql/local-client.d.ts +20 -0
  26. package/dist/web/graphql/local-client.d.ts.map +1 -0
  27. package/dist/web/graphql/local-client.js +536 -0
  28. package/dist/web/graphql/local-client.js.map +1 -0
  29. package/dist/web/index.d.ts +15 -0
  30. package/dist/web/index.d.ts.map +1 -0
  31. package/dist/web/index.js +68 -0
  32. package/dist/web/index.js.map +1 -0
  33. package/dist/web/runtime/seeders/index.js +358 -0
  34. package/dist/web/runtime/seeders/index.js.map +1 -0
  35. package/dist/web/runtime/services.d.ts +60 -0
  36. package/dist/web/runtime/services.d.ts.map +1 -0
  37. package/dist/web/runtime/services.js +80 -0
  38. package/dist/web/runtime/services.js.map +1 -0
  39. package/dist/web/storage/indexeddb.d.ts +22 -0
  40. package/dist/web/storage/indexeddb.d.ts.map +1 -0
  41. package/dist/web/storage/indexeddb.js +85 -0
  42. package/dist/web/storage/indexeddb.js.map +1 -0
  43. package/dist/web/utils/id.d.ts +5 -0
  44. package/dist/web/utils/id.d.ts.map +1 -0
  45. package/dist/web/utils/id.js +9 -0
  46. package/dist/web/utils/id.js.map +1 -0
  47. package/package.json +70 -0
  48. package/src/adapters/pglite/adapter.ts +152 -0
  49. package/src/adapters/pglite/index.ts +1 -0
  50. package/src/index.ts +41 -0
  51. package/src/ports/database.port.ts +82 -0
  52. package/src/ports/index.ts +4 -0
  53. package/src/types/database.types.ts +55 -0
  54. package/src/types/index.ts +1 -0
  55. package/src/web/database/migrations.ts +760 -0
  56. package/src/web/database/schema.ts +596 -0
  57. package/src/web/events/local-pubsub.ts +28 -0
  58. package/src/web/graphql/local-client.ts +747 -0
  59. package/src/web/index.ts +21 -0
  60. package/src/web/runtime/seeders/index.ts +449 -0
  61. package/src/web/runtime/services.ts +132 -0
  62. package/src/web/storage/indexeddb.ts +116 -0
  63. package/src/web/utils/id.ts +7 -0
@@ -0,0 +1,85 @@
1
+ //#region src/web/storage/indexeddb.ts
2
+ const DEFAULT_DB_NAME = "contractspec-runtime";
3
+ const DEFAULT_STORE = "kv";
4
+ const FALLBACK_STORE = /* @__PURE__ */ new Map();
5
+ var LocalStorageService = class {
6
+ dbPromise;
7
+ constructor(options = {}) {
8
+ this.options = options;
9
+ }
10
+ async init() {
11
+ await this.getDb();
12
+ }
13
+ async get(key, fallback) {
14
+ const db = await this.getDb();
15
+ if (!db) return FALLBACK_STORE.get(key) ?? fallback;
16
+ return new Promise((resolve, reject) => {
17
+ const request = db.transaction(this.storeName, "readonly").objectStore(this.storeName).get(key);
18
+ request.onsuccess = () => {
19
+ resolve(request.result ?? fallback);
20
+ };
21
+ request.onerror = () => reject(request.error);
22
+ });
23
+ }
24
+ async set(key, value) {
25
+ const db = await this.getDb();
26
+ if (!db) {
27
+ FALLBACK_STORE.set(key, value);
28
+ return;
29
+ }
30
+ await new Promise((resolve, reject) => {
31
+ const request = db.transaction(this.storeName, "readwrite").objectStore(this.storeName).put(value, key);
32
+ request.onsuccess = () => resolve();
33
+ request.onerror = () => reject(request.error);
34
+ });
35
+ }
36
+ async delete(key) {
37
+ const db = await this.getDb();
38
+ if (!db) {
39
+ FALLBACK_STORE.delete(key);
40
+ return;
41
+ }
42
+ await new Promise((resolve, reject) => {
43
+ const request = db.transaction(this.storeName, "readwrite").objectStore(this.storeName).delete(key);
44
+ request.onsuccess = () => resolve();
45
+ request.onerror = () => reject(request.error);
46
+ });
47
+ }
48
+ async clear() {
49
+ const db = await this.getDb();
50
+ if (!db) {
51
+ FALLBACK_STORE.clear();
52
+ return;
53
+ }
54
+ await new Promise((resolve, reject) => {
55
+ const request = db.transaction(this.storeName, "readwrite").objectStore(this.storeName).clear();
56
+ request.onsuccess = () => resolve();
57
+ request.onerror = () => reject(request.error);
58
+ });
59
+ }
60
+ get storeName() {
61
+ return this.options.storeName ?? DEFAULT_STORE;
62
+ }
63
+ async getDb() {
64
+ if (typeof indexedDB === "undefined") return null;
65
+ if (!this.dbPromise) this.dbPromise = this.openDb();
66
+ return this.dbPromise;
67
+ }
68
+ openDb() {
69
+ return new Promise((resolve, reject) => {
70
+ const request = indexedDB.open(this.options.dbName ?? DEFAULT_DB_NAME, this.options.version ?? 1);
71
+ request.onerror = () => reject(request.error);
72
+ request.onsuccess = () => {
73
+ resolve(request.result);
74
+ };
75
+ request.onupgradeneeded = () => {
76
+ const db = request.result;
77
+ if (!db.objectStoreNames.contains(this.storeName)) db.createObjectStore(this.storeName);
78
+ };
79
+ });
80
+ }
81
+ };
82
+
83
+ //#endregion
84
+ export { LocalStorageService };
85
+ //# sourceMappingURL=indexeddb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb.js","names":[],"sources":["../../../src/web/storage/indexeddb.ts"],"sourcesContent":["export interface LocalStorageOptions {\n dbName?: string;\n storeName?: string;\n version?: number;\n}\n\nconst DEFAULT_DB_NAME = 'contractspec-runtime';\nconst DEFAULT_STORE = 'kv';\nconst FALLBACK_STORE = new Map<string, unknown>();\n\nexport class LocalStorageService {\n private dbPromise?: Promise<IDBDatabase | null>;\n\n constructor(private readonly options: LocalStorageOptions = {}) {}\n\n async init(): Promise<void> {\n await this.getDb();\n }\n\n async get<TValue = unknown>(\n key: string,\n fallback?: TValue\n ): Promise<TValue | undefined> {\n const db = await this.getDb();\n if (!db) {\n return (FALLBACK_STORE.get(key) as TValue | undefined) ?? fallback;\n }\n return new Promise((resolve, reject) => {\n const tx = db.transaction(this.storeName, 'readonly');\n const store = tx.objectStore(this.storeName);\n const request = store.get(key);\n request.onsuccess = () => {\n resolve((request.result as TValue | undefined) ?? fallback);\n };\n request.onerror = () => reject(request.error);\n });\n }\n\n async set<TValue = unknown>(key: string, value: TValue): Promise<void> {\n const db = await this.getDb();\n if (!db) {\n FALLBACK_STORE.set(key, value);\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction(this.storeName, 'readwrite');\n const store = tx.objectStore(this.storeName);\n const request = store.put(value, key);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n }\n\n async delete(key: string): Promise<void> {\n const db = await this.getDb();\n if (!db) {\n FALLBACK_STORE.delete(key);\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction(this.storeName, 'readwrite');\n const store = tx.objectStore(this.storeName);\n const request = store.delete(key);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n }\n\n async clear(): Promise<void> {\n const db = await this.getDb();\n if (!db) {\n FALLBACK_STORE.clear();\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction(this.storeName, 'readwrite');\n const store = tx.objectStore(this.storeName);\n const request = store.clear();\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n }\n\n private get storeName() {\n return this.options.storeName ?? DEFAULT_STORE;\n }\n\n private async getDb(): Promise<IDBDatabase | null> {\n if (typeof indexedDB === 'undefined') {\n return null;\n }\n if (!this.dbPromise) {\n this.dbPromise = this.openDb();\n }\n return this.dbPromise;\n }\n\n private openDb(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(\n this.options.dbName ?? DEFAULT_DB_NAME,\n this.options.version ?? 1\n );\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n resolve(request.result);\n };\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(this.storeName)) {\n db.createObjectStore(this.storeName);\n }\n };\n });\n }\n}\n"],"mappings":";AAMA,MAAM,kBAAkB;AACxB,MAAM,gBAAgB;AACtB,MAAM,iCAAiB,IAAI,KAAsB;AAEjD,IAAa,sBAAb,MAAiC;CAC/B,AAAQ;CAER,YAAY,AAAiB,UAA+B,EAAE,EAAE;EAAnC;;CAE7B,MAAM,OAAsB;AAC1B,QAAM,KAAK,OAAO;;CAGpB,MAAM,IACJ,KACA,UAC6B;EAC7B,MAAM,KAAK,MAAM,KAAK,OAAO;AAC7B,MAAI,CAAC,GACH,QAAQ,eAAe,IAAI,IAAI,IAA2B;AAE5D,SAAO,IAAI,SAAS,SAAS,WAAW;GAGtC,MAAM,UAFK,GAAG,YAAY,KAAK,WAAW,WAAW,CACpC,YAAY,KAAK,UAAU,CACtB,IAAI,IAAI;AAC9B,WAAQ,kBAAkB;AACxB,YAAS,QAAQ,UAAiC,SAAS;;AAE7D,WAAQ,gBAAgB,OAAO,QAAQ,MAAM;IAC7C;;CAGJ,MAAM,IAAsB,KAAa,OAA8B;EACrE,MAAM,KAAK,MAAM,KAAK,OAAO;AAC7B,MAAI,CAAC,IAAI;AACP,kBAAe,IAAI,KAAK,MAAM;AAC9B;;AAEF,QAAM,IAAI,SAAe,SAAS,WAAW;GAG3C,MAAM,UAFK,GAAG,YAAY,KAAK,WAAW,YAAY,CACrC,YAAY,KAAK,UAAU,CACtB,IAAI,OAAO,IAAI;AACrC,WAAQ,kBAAkB,SAAS;AACnC,WAAQ,gBAAgB,OAAO,QAAQ,MAAM;IAC7C;;CAGJ,MAAM,OAAO,KAA4B;EACvC,MAAM,KAAK,MAAM,KAAK,OAAO;AAC7B,MAAI,CAAC,IAAI;AACP,kBAAe,OAAO,IAAI;AAC1B;;AAEF,QAAM,IAAI,SAAe,SAAS,WAAW;GAG3C,MAAM,UAFK,GAAG,YAAY,KAAK,WAAW,YAAY,CACrC,YAAY,KAAK,UAAU,CACtB,OAAO,IAAI;AACjC,WAAQ,kBAAkB,SAAS;AACnC,WAAQ,gBAAgB,OAAO,QAAQ,MAAM;IAC7C;;CAGJ,MAAM,QAAuB;EAC3B,MAAM,KAAK,MAAM,KAAK,OAAO;AAC7B,MAAI,CAAC,IAAI;AACP,kBAAe,OAAO;AACtB;;AAEF,QAAM,IAAI,SAAe,SAAS,WAAW;GAG3C,MAAM,UAFK,GAAG,YAAY,KAAK,WAAW,YAAY,CACrC,YAAY,KAAK,UAAU,CACtB,OAAO;AAC7B,WAAQ,kBAAkB,SAAS;AACnC,WAAQ,gBAAgB,OAAO,QAAQ,MAAM;IAC7C;;CAGJ,IAAY,YAAY;AACtB,SAAO,KAAK,QAAQ,aAAa;;CAGnC,MAAc,QAAqC;AACjD,MAAI,OAAO,cAAc,YACvB,QAAO;AAET,MAAI,CAAC,KAAK,UACR,MAAK,YAAY,KAAK,QAAQ;AAEhC,SAAO,KAAK;;CAGd,AAAQ,SAA+B;AACrC,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,UAAU,UAAU,KACxB,KAAK,QAAQ,UAAU,iBACvB,KAAK,QAAQ,WAAW,EACzB;AACD,WAAQ,gBAAgB,OAAO,QAAQ,MAAM;AAC7C,WAAQ,kBAAkB;AACxB,YAAQ,QAAQ,OAAO;;AAEzB,WAAQ,wBAAwB;IAC9B,MAAM,KAAK,QAAQ;AACnB,QAAI,CAAC,GAAG,iBAAiB,SAAS,KAAK,UAAU,CAC/C,IAAG,kBAAkB,KAAK,UAAU;;IAGxC"}
@@ -0,0 +1,5 @@
1
+ //#region src/web/utils/id.d.ts
2
+ declare function generateId(prefix?: string): string;
3
+ //#endregion
4
+ export { generateId };
5
+ //# sourceMappingURL=id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.d.ts","names":[],"sources":["../../../src/web/utils/id.ts"],"sourcesContent":[],"mappings":";iBAAgB,UAAA"}
@@ -0,0 +1,9 @@
1
+ //#region src/web/utils/id.ts
2
+ function generateId(prefix) {
3
+ const base = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : Math.random().toString(36).slice(2, 10);
4
+ return prefix ? `${prefix}_${base}` : base;
5
+ }
6
+
7
+ //#endregion
8
+ export { generateId };
9
+ //# sourceMappingURL=id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.js","names":[],"sources":["../../../src/web/utils/id.ts"],"sourcesContent":["export function generateId(prefix?: string): string {\n const base =\n typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? crypto.randomUUID()\n : Math.random().toString(36).slice(2, 10);\n return prefix ? `${prefix}_${base}` : base;\n}\n"],"mappings":";AAAA,SAAgB,WAAW,QAAyB;CAClD,MAAM,OACJ,OAAO,WAAW,eAAe,gBAAgB,SAC7C,OAAO,YAAY,GACnB,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,GAAG;AAC7C,QAAO,SAAS,GAAG,OAAO,GAAG,SAAS"}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@contractspec/lib.runtime-sandbox",
3
+ "version": "0.11.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
7
+ "publish:pkg:canary": "bun publish:pkg --tag canary",
8
+ "clean": "rm -rf dist",
9
+ "lint": "bun run lint:fix",
10
+ "lint:fix": "eslint src --fix",
11
+ "lint:check": "eslint src",
12
+ "build": "bun run build:bundle",
13
+ "build:bundle": "tsdown",
14
+ "build:types": "tsc --noEmit",
15
+ "dev": "bun run build:bundle --watch"
16
+ },
17
+ "peerDependencies": {
18
+ "@electric-sql/pglite": "^0.3.14",
19
+ "drizzle-orm": "^0.38.4"
20
+ },
21
+ "peerDependenciesMeta": {
22
+ "@electric-sql/pglite": {
23
+ "optional": true
24
+ },
25
+ "drizzle-orm": {
26
+ "optional": true
27
+ }
28
+ },
29
+ "devDependencies": {
30
+ "@contractspec/tool.typescript": "1.56.0",
31
+ "@contractspec/tool.tsdown": "1.56.0",
32
+ "typescript": "^5.9.3",
33
+ "@electric-sql/pglite": "^0.3.14",
34
+ "drizzle-orm": "^0.45.1",
35
+ "tsdown": "^0.19.0",
36
+ "@types/bun": "~1.3.5"
37
+ },
38
+ "types": "./dist/index.d.ts",
39
+ "exports": {
40
+ ".": "./dist/index.js",
41
+ "./*": "./*"
42
+ },
43
+ "typesVersions": {
44
+ "*": {
45
+ "*": [
46
+ "src/*"
47
+ ]
48
+ }
49
+ },
50
+ "files": [
51
+ "src",
52
+ "dist"
53
+ ],
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "registry": "https://registry.npmjs.org/",
57
+ "exports": {
58
+ ".": "./dist/index.mjs",
59
+ "./ports": "./dist/ports/index.mjs",
60
+ "./types": "./dist/types/index.mjs",
61
+ "./adapters/pglite": "./dist/adapters/pglite/index.mjs",
62
+ "./*": "./*"
63
+ }
64
+ },
65
+ "dependencies": {
66
+ "@apollo/client": "^4.0.12",
67
+ "@graphql-tools/schema": "^10.0.31",
68
+ "graphql": "^16.12.0"
69
+ }
70
+ }
@@ -0,0 +1,152 @@
1
+ import { PGlite } from '@electric-sql/pglite';
2
+
3
+ import type { DatabasePort } from '../../ports/database.port';
4
+ import type {
5
+ DatabaseInitOptions,
6
+ DbRow,
7
+ DbValue,
8
+ Migration,
9
+ QueryResult,
10
+ TransactionContext,
11
+ } from '../../types/database.types';
12
+
13
+ /**
14
+ * PGLite database adapter for browser sandbox runtime.
15
+ *
16
+ * Uses PGLite (PostgreSQL WASM) for in-browser database operations.
17
+ * Supports in-memory or IndexedDB persistence.
18
+ */
19
+ export class PGLiteDatabaseAdapter implements DatabasePort {
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
+ CREATE TABLE IF NOT EXISTS _sandbox_migrations (
96
+ id TEXT PRIMARY KEY,
97
+ applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
98
+ )
99
+ `);
100
+
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
+ }
@@ -0,0 +1 @@
1
+ export { PGLiteDatabaseAdapter } from './adapter';
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @contractspec/lib.runtime-sandbox
3
+ *
4
+ * Sandbox runtime library providing database abstraction for browser environments.
5
+ * Supports lazy-loading of PGLite for bundle optimization.
6
+ */
7
+
8
+ // Core ports (interfaces)
9
+ export { type DatabaseAdapterFactory, type DatabasePort } from './ports';
10
+
11
+ // Types
12
+ export {
13
+ type DatabaseInitOptions,
14
+ type DbRow,
15
+ type DbValue,
16
+ type Migration,
17
+ type QueryResult,
18
+ type TransactionContext,
19
+ } from './types';
20
+
21
+ /**
22
+ * Lazy-load the PGLite database adapter.
23
+ *
24
+ * This function dynamically imports PGLite and Drizzle only when called,
25
+ * avoiding bundle bloat for consumers not using the sandbox runtime.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const adapter = await createPGLiteAdapter();
30
+ * await adapter.init();
31
+ * const result = await adapter.query('SELECT * FROM users');
32
+ * ```
33
+ */
34
+ export async function createPGLiteAdapter(): Promise<
35
+ import('./ports').DatabasePort
36
+ > {
37
+ const { PGLiteDatabaseAdapter } = await import('./adapters/pglite');
38
+ return new PGLiteDatabaseAdapter();
39
+ }
40
+
41
+ export * as web from './web';
@@ -0,0 +1,82 @@
1
+ import type {
2
+ DatabaseInitOptions,
3
+ DbRow,
4
+ DbValue,
5
+ Migration,
6
+ QueryResult,
7
+ TransactionContext,
8
+ } from '../types/database.types';
9
+
10
+ /**
11
+ * Database port interface for sandbox runtime.
12
+ *
13
+ * This interface abstracts the database layer, allowing:
14
+ * - PGLite adapter for browser sandbox
15
+ * - Prisma/Drizzle adapter for production
16
+ * - Mock adapter for testing
17
+ *
18
+ * All implementations must be lazy-loadable to avoid bundle bloat.
19
+ */
20
+ export interface DatabasePort {
21
+ /**
22
+ * Initialize the database connection and run migrations.
23
+ */
24
+ init(options?: DatabaseInitOptions): Promise<void>;
25
+
26
+ /**
27
+ * Close the database connection and release resources.
28
+ */
29
+ close(): Promise<void>;
30
+
31
+ /**
32
+ * Check if the database is initialized.
33
+ */
34
+ isInitialized(): boolean;
35
+
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
+
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
+
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
+
62
+ /**
63
+ * Run schema migrations.
64
+ *
65
+ * @param migrations - Array of migrations to apply
66
+ */
67
+ migrate(migrations: Migration[]): Promise<void>;
68
+
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
+ }
75
+
76
+ /**
77
+ * Factory function type for creating database adapters.
78
+ * Used for lazy-loading adapters.
79
+ */
80
+ export type DatabaseAdapterFactory = (
81
+ options?: DatabaseInitOptions
82
+ ) => Promise<DatabasePort>;
@@ -0,0 +1,4 @@
1
+ export {
2
+ type DatabaseAdapterFactory,
3
+ type DatabasePort,
4
+ } from './database.port';
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Database value types supported by the sandbox runtime
3
+ */
4
+ export type DbValue =
5
+ | string
6
+ | number
7
+ | boolean
8
+ | null
9
+ | Uint8Array
10
+ | Date
11
+ | undefined;
12
+
13
+ /**
14
+ * Generic row type for database results
15
+ */
16
+ export type DbRow = Record<string, DbValue>;
17
+
18
+ /**
19
+ * Options for initializing a database adapter
20
+ */
21
+ export interface DatabaseInitOptions {
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
+ }
30
+
31
+ /**
32
+ * Query builder result type
33
+ */
34
+ export interface QueryResult<T = DbRow> {
35
+ rows: T[];
36
+ rowCount: number;
37
+ }
38
+
39
+ /**
40
+ * Transaction context for atomic operations
41
+ */
42
+ export interface TransactionContext {
43
+ /**
44
+ * Execute raw SQL within the transaction
45
+ */
46
+ execute(sql: string, params?: DbValue[]): Promise<void>;
47
+ }
48
+
49
+ /**
50
+ * Migration definition
51
+ */
52
+ export interface Migration {
53
+ id: string;
54
+ sql: string;
55
+ }
@@ -0,0 +1 @@
1
+ export * from './database.types';