@contractspec/lib.runtime-sandbox 2.7.6 → 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.
@@ -4,26 +4,24 @@
4
4
  * Uses lazy-loading for PGLite to avoid bundle bloat.
5
5
  */
6
6
  import type { DatabasePort } from '@contractspec/lib.runtime-sandbox';
7
-
7
+ import { SANDBOX_MIGRATIONS } from '../database/migrations';
8
8
  import { LocalEventBus } from '../events/local-pubsub';
9
9
  import { LocalGraphQLClient } from '../graphql/local-client';
10
10
  import { LocalStorageService } from '../storage/indexeddb';
11
11
 
12
- import { SANDBOX_MIGRATIONS } from '../database/migrations';
13
-
14
12
  export type TemplateId = string;
15
13
 
16
14
  export interface LocalRuntimeInitOptions {
17
- /**
18
- * Data directory for IndexedDB persistence (optional).
19
- * If omitted, uses in-memory database.
20
- */
21
- dataDir?: string;
15
+ /**
16
+ * Data directory for IndexedDB persistence (optional).
17
+ * If omitted, uses in-memory database.
18
+ */
19
+ dataDir?: string;
22
20
  }
23
21
 
24
22
  export interface TemplateSeedOptions {
25
- templateId: TemplateId;
26
- projectId?: string;
23
+ templateId: TemplateId;
24
+ projectId?: string;
27
25
  }
28
26
 
29
27
  const DEFAULT_PROJECT_ID = 'local-project' as const;
@@ -34,99 +32,100 @@ const DEFAULT_PROJECT_ID = 'local-project' as const;
34
32
  * Provides lazy-loaded database access via DatabasePort interface.
35
33
  */
36
34
  export class LocalRuntimeServices {
37
- readonly storage = new LocalStorageService();
38
- readonly pubsub = new LocalEventBus();
39
- #initialized = false;
40
-
41
- private _db?: DatabasePort;
42
-
43
- /**
44
- * Get the database port (must be initialized first).
45
- */
46
- get db(): DatabasePort {
47
- if (!this._db) {
48
- throw new Error(
49
- 'LocalRuntimeServices not initialized. Call init() first.'
50
- );
51
- }
52
- return this._db;
53
- }
54
-
55
- private _graphql?: LocalGraphQLClient;
56
-
57
- /**
58
- * Get the GraphQL client (must be initialized first).
59
- */
60
- get graphql(): LocalGraphQLClient {
61
- if (!this._graphql) {
62
- throw new Error(
63
- 'LocalRuntimeServices not initialized. Call init() first.'
64
- );
65
- }
66
- return this._graphql;
67
- }
68
-
69
- /**
70
- * Initialize the runtime services.
71
- *
72
- * Lazy-loads PGLite adapter to avoid bundle bloat.
73
- */
74
- async init(options: LocalRuntimeInitOptions = {}): Promise<void> {
75
- if (this.#initialized) return;
76
-
77
- // Lazy-load PGLite adapter
78
- const { createPGLiteAdapter } =
79
- await import('@contractspec/lib.runtime-sandbox');
80
- this._db = await createPGLiteAdapter();
81
- await this._db.init({ dataDir: options.dataDir });
82
-
83
- // Run migrations
84
- await this._db.migrate(SANDBOX_MIGRATIONS);
85
-
86
- // Initialize storage
87
- await this.storage.init();
88
-
89
- // Initialize GraphQL client with the new database port
90
- this._graphql = new LocalGraphQLClient({
91
- db: this._db,
92
- storage: this.storage,
93
- pubsub: this.pubsub,
94
- });
95
-
96
- this.#initialized = true;
97
- }
98
-
99
- /**
100
- * Check if runtime is initialized.
101
- */
102
- isInitialized(): boolean {
103
- return this.#initialized;
104
- }
105
-
106
- /**
107
- * Seed the database with deterministic defaults for a template.
108
- *
109
- * - No randomness
110
- * - No wall-clock timestamps
111
- * - Unknown templates are a no-op (safe default)
112
- */
113
- async seedTemplate(options: TemplateSeedOptions): Promise<void> {
114
- if (!this.#initialized) {
115
- throw new Error('Call init() before seeding templates.');
116
- }
117
-
118
- const projectId = options.projectId ?? DEFAULT_PROJECT_ID;
119
-
120
- if (!this._db) {
121
- throw new Error('Database not initialized');
122
- }
123
-
124
- // Lazy-load seeders to avoid bundle bloat
125
- const { seedTemplate } = await import('./seeders');
126
- await seedTemplate({
127
- templateId: options.templateId,
128
- projectId,
129
- db: this._db,
130
- });
131
- }
35
+ readonly storage = new LocalStorageService();
36
+ readonly pubsub = new LocalEventBus();
37
+ #initialized = false;
38
+
39
+ private _db?: DatabasePort;
40
+
41
+ /**
42
+ * Get the database port (must be initialized first).
43
+ */
44
+ get db(): DatabasePort {
45
+ if (!this._db) {
46
+ throw new Error(
47
+ 'LocalRuntimeServices not initialized. Call init() first.'
48
+ );
49
+ }
50
+ return this._db;
51
+ }
52
+
53
+ private _graphql?: LocalGraphQLClient;
54
+
55
+ /**
56
+ * Get the GraphQL client (must be initialized first).
57
+ */
58
+ get graphql(): LocalGraphQLClient {
59
+ if (!this._graphql) {
60
+ throw new Error(
61
+ 'LocalRuntimeServices not initialized. Call init() first.'
62
+ );
63
+ }
64
+ return this._graphql;
65
+ }
66
+
67
+ /**
68
+ * Initialize the runtime services.
69
+ *
70
+ * Lazy-loads PGLite adapter to avoid bundle bloat.
71
+ */
72
+ async init(options: LocalRuntimeInitOptions = {}): Promise<void> {
73
+ if (this.#initialized) return;
74
+
75
+ // Lazy-load PGLite adapter
76
+ const { createPGLiteAdapter } = await import(
77
+ '@contractspec/lib.runtime-sandbox'
78
+ );
79
+ this._db = await createPGLiteAdapter();
80
+ await this._db.init({ dataDir: options.dataDir });
81
+
82
+ // Run migrations
83
+ await this._db.migrate(SANDBOX_MIGRATIONS);
84
+
85
+ // Initialize storage
86
+ await this.storage.init();
87
+
88
+ // Initialize GraphQL client with the new database port
89
+ this._graphql = new LocalGraphQLClient({
90
+ db: this._db,
91
+ storage: this.storage,
92
+ pubsub: this.pubsub,
93
+ });
94
+
95
+ this.#initialized = true;
96
+ }
97
+
98
+ /**
99
+ * Check if runtime is initialized.
100
+ */
101
+ isInitialized(): boolean {
102
+ return this.#initialized;
103
+ }
104
+
105
+ /**
106
+ * Seed the database with deterministic defaults for a template.
107
+ *
108
+ * - No randomness
109
+ * - No wall-clock timestamps
110
+ * - Unknown templates are a no-op (safe default)
111
+ */
112
+ async seedTemplate(options: TemplateSeedOptions): Promise<void> {
113
+ if (!this.#initialized) {
114
+ throw new Error('Call init() before seeding templates.');
115
+ }
116
+
117
+ const projectId = options.projectId ?? DEFAULT_PROJECT_ID;
118
+
119
+ if (!this._db) {
120
+ throw new Error('Database not initialized');
121
+ }
122
+
123
+ // Lazy-load seeders to avoid bundle bloat
124
+ const { seedTemplate } = await import('./seeders');
125
+ await seedTemplate({
126
+ templateId: options.templateId,
127
+ projectId,
128
+ db: this._db,
129
+ });
130
+ }
132
131
  }
@@ -1,7 +1,7 @@
1
1
  export interface LocalStorageOptions {
2
- dbName?: string;
3
- storeName?: string;
4
- version?: number;
2
+ dbName?: string;
3
+ storeName?: string;
4
+ version?: number;
5
5
  }
6
6
 
7
7
  const DEFAULT_DB_NAME = 'contractspec-runtime';
@@ -9,108 +9,108 @@ const DEFAULT_STORE = 'kv';
9
9
  const FALLBACK_STORE = new Map<string, unknown>();
10
10
 
11
11
  export class LocalStorageService {
12
- private dbPromise?: Promise<IDBDatabase | null>;
12
+ private dbPromise?: Promise<IDBDatabase | null>;
13
13
 
14
- constructor(private readonly options: LocalStorageOptions = {}) {}
14
+ constructor(private readonly options: LocalStorageOptions = {}) {}
15
15
 
16
- async init(): Promise<void> {
17
- await this.getDb();
18
- }
16
+ async init(): Promise<void> {
17
+ await this.getDb();
18
+ }
19
19
 
20
- async get<TValue = unknown>(
21
- key: string,
22
- fallback?: TValue
23
- ): Promise<TValue | undefined> {
24
- const db = await this.getDb();
25
- if (!db) {
26
- return (FALLBACK_STORE.get(key) as TValue | undefined) ?? fallback;
27
- }
28
- return new Promise((resolve, reject) => {
29
- const tx = db.transaction(this.storeName, 'readonly');
30
- const store = tx.objectStore(this.storeName);
31
- const request = store.get(key);
32
- request.onsuccess = () => {
33
- resolve((request.result as TValue | undefined) ?? fallback);
34
- };
35
- request.onerror = () => reject(request.error);
36
- });
37
- }
20
+ async get<TValue = unknown>(
21
+ key: string,
22
+ fallback?: TValue
23
+ ): Promise<TValue | undefined> {
24
+ const db = await this.getDb();
25
+ if (!db) {
26
+ return (FALLBACK_STORE.get(key) as TValue | undefined) ?? fallback;
27
+ }
28
+ return new Promise((resolve, reject) => {
29
+ const tx = db.transaction(this.storeName, 'readonly');
30
+ const store = tx.objectStore(this.storeName);
31
+ const request = store.get(key);
32
+ request.onsuccess = () => {
33
+ resolve((request.result as TValue | undefined) ?? fallback);
34
+ };
35
+ request.onerror = () => reject(request.error);
36
+ });
37
+ }
38
38
 
39
- async set<TValue = unknown>(key: string, value: TValue): Promise<void> {
40
- const db = await this.getDb();
41
- if (!db) {
42
- FALLBACK_STORE.set(key, value);
43
- return;
44
- }
45
- await new Promise<void>((resolve, reject) => {
46
- const tx = db.transaction(this.storeName, 'readwrite');
47
- const store = tx.objectStore(this.storeName);
48
- const request = store.put(value, key);
49
- request.onsuccess = () => resolve();
50
- request.onerror = () => reject(request.error);
51
- });
52
- }
39
+ async set<TValue = unknown>(key: string, value: TValue): Promise<void> {
40
+ const db = await this.getDb();
41
+ if (!db) {
42
+ FALLBACK_STORE.set(key, value);
43
+ return;
44
+ }
45
+ await new Promise<void>((resolve, reject) => {
46
+ const tx = db.transaction(this.storeName, 'readwrite');
47
+ const store = tx.objectStore(this.storeName);
48
+ const request = store.put(value, key);
49
+ request.onsuccess = () => resolve();
50
+ request.onerror = () => reject(request.error);
51
+ });
52
+ }
53
53
 
54
- async delete(key: string): Promise<void> {
55
- const db = await this.getDb();
56
- if (!db) {
57
- FALLBACK_STORE.delete(key);
58
- return;
59
- }
60
- await new Promise<void>((resolve, reject) => {
61
- const tx = db.transaction(this.storeName, 'readwrite');
62
- const store = tx.objectStore(this.storeName);
63
- const request = store.delete(key);
64
- request.onsuccess = () => resolve();
65
- request.onerror = () => reject(request.error);
66
- });
67
- }
54
+ async delete(key: string): Promise<void> {
55
+ const db = await this.getDb();
56
+ if (!db) {
57
+ FALLBACK_STORE.delete(key);
58
+ return;
59
+ }
60
+ await new Promise<void>((resolve, reject) => {
61
+ const tx = db.transaction(this.storeName, 'readwrite');
62
+ const store = tx.objectStore(this.storeName);
63
+ const request = store.delete(key);
64
+ request.onsuccess = () => resolve();
65
+ request.onerror = () => reject(request.error);
66
+ });
67
+ }
68
68
 
69
- async clear(): Promise<void> {
70
- const db = await this.getDb();
71
- if (!db) {
72
- FALLBACK_STORE.clear();
73
- return;
74
- }
75
- await new Promise<void>((resolve, reject) => {
76
- const tx = db.transaction(this.storeName, 'readwrite');
77
- const store = tx.objectStore(this.storeName);
78
- const request = store.clear();
79
- request.onsuccess = () => resolve();
80
- request.onerror = () => reject(request.error);
81
- });
82
- }
69
+ async clear(): Promise<void> {
70
+ const db = await this.getDb();
71
+ if (!db) {
72
+ FALLBACK_STORE.clear();
73
+ return;
74
+ }
75
+ await new Promise<void>((resolve, reject) => {
76
+ const tx = db.transaction(this.storeName, 'readwrite');
77
+ const store = tx.objectStore(this.storeName);
78
+ const request = store.clear();
79
+ request.onsuccess = () => resolve();
80
+ request.onerror = () => reject(request.error);
81
+ });
82
+ }
83
83
 
84
- private get storeName() {
85
- return this.options.storeName ?? DEFAULT_STORE;
86
- }
84
+ private get storeName() {
85
+ return this.options.storeName ?? DEFAULT_STORE;
86
+ }
87
87
 
88
- private async getDb(): Promise<IDBDatabase | null> {
89
- if (typeof indexedDB === 'undefined') {
90
- return null;
91
- }
92
- if (!this.dbPromise) {
93
- this.dbPromise = this.openDb();
94
- }
95
- return this.dbPromise;
96
- }
88
+ private async getDb(): Promise<IDBDatabase | null> {
89
+ if (typeof indexedDB === 'undefined') {
90
+ return null;
91
+ }
92
+ if (!this.dbPromise) {
93
+ this.dbPromise = this.openDb();
94
+ }
95
+ return this.dbPromise;
96
+ }
97
97
 
98
- private openDb(): Promise<IDBDatabase> {
99
- return new Promise((resolve, reject) => {
100
- const request = indexedDB.open(
101
- this.options.dbName ?? DEFAULT_DB_NAME,
102
- this.options.version ?? 1
103
- );
104
- request.onerror = () => reject(request.error);
105
- request.onsuccess = () => {
106
- resolve(request.result);
107
- };
108
- request.onupgradeneeded = () => {
109
- const db = request.result;
110
- if (!db.objectStoreNames.contains(this.storeName)) {
111
- db.createObjectStore(this.storeName);
112
- }
113
- };
114
- });
115
- }
98
+ private openDb(): Promise<IDBDatabase> {
99
+ return new Promise((resolve, reject) => {
100
+ const request = indexedDB.open(
101
+ this.options.dbName ?? DEFAULT_DB_NAME,
102
+ this.options.version ?? 1
103
+ );
104
+ request.onerror = () => reject(request.error);
105
+ request.onsuccess = () => {
106
+ resolve(request.result);
107
+ };
108
+ request.onupgradeneeded = () => {
109
+ const db = request.result;
110
+ if (!db.objectStoreNames.contains(this.storeName)) {
111
+ db.createObjectStore(this.storeName);
112
+ }
113
+ };
114
+ });
115
+ }
116
116
  }
@@ -1,7 +1,7 @@
1
1
  export function generateId(prefix?: string): string {
2
- const base =
3
- typeof crypto !== 'undefined' && 'randomUUID' in crypto
4
- ? crypto.randomUUID()
5
- : Math.random().toString(36).slice(2, 10);
6
- return prefix ? `${prefix}_${base}` : base;
2
+ const base =
3
+ typeof crypto !== 'undefined' && 'randomUUID' in crypto
4
+ ? crypto.randomUUID()
5
+ : Math.random().toString(36).slice(2, 10);
6
+ return prefix ? `${prefix}_${base}` : base;
7
7
  }