@granular-software/sdk 0.4.5 → 0.4.6

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 CHANGED
@@ -8,7 +8,7 @@ The official TypeScript SDK for Granular.
8
8
 
9
9
  - 🏗️ **Domain Ontology** — Define classes, properties, and relationships as a typed graph
10
10
  - 🔧 **Class-Based Tools** — Attach instance methods, static methods, and global tools with typed I/O
11
- - 🤖 **Domain Synthesis** — Auto-generated TypeScript classes with `find()`, relationship accessors, and typed methods
11
+ - 🤖 **Domain Synthesis** — Auto-generated TypeScript classes with `get({ path })`, `list()`, relationship accessors, and typed methods
12
12
  - 🔒 **Secure Execution** — Run AI-generated code in isolated sandboxes
13
13
  - 👥 **User Permissions** — Control what each user can access
14
14
  - ⚡ **Real-time** — WebSocket-based streaming and events
@@ -147,8 +147,10 @@ await granular.registerEffects(env.sandboxId, [
147
147
  const job = await env.submitJob(`
148
148
  import { Customer } from './sandbox-tools';
149
149
 
150
- // Find returns a typed Customer instance with loaded field values
151
- const acme = await Customer.find({ id: 'cust_42' });
150
+ // list() discovers instances; get({ path }) hydrates a specific graph object
151
+ const customers = await Customer.list();
152
+ const acme = customers.find((customer) => customer.name === 'Acme Corp');
153
+ if (!acme) throw new Error('Customer not found');
152
154
  console.log(acme.name); // "Acme Corp"
153
155
  console.log(acme.email); // "billing@acme.com"
154
156
 
@@ -340,8 +342,11 @@ export declare class Author {
340
342
 
341
343
  constructor(id: string, fields?: Record<string, any>);
342
344
 
343
- /** Find an Author by ID */
344
- static find(query: { id: string }): Promise<Author | null>;
345
+ /** Get a cached Author by graph path, hydrating from the graph when needed */
346
+ static get(query: { path: string; refresh?: boolean }): Promise<Author | null>;
347
+
348
+ /** List known Author instances */
349
+ static list(query?: { limit?: number; saveAs?: string; refresh?: boolean }): Promise<Author[]>;
345
350
 
346
351
  /** Get biography of an author */
347
352
  get_bio(input?: { detailed?: boolean }): Promise<{ bio: string; source?: string }>;
@@ -359,7 +364,9 @@ export declare class Book {
359
364
  readonly isbn: string;
360
365
  readonly published_year: number;
361
366
 
362
- static find(query: { id: string }): Promise<Book | null>;
367
+ static get(query: { path: string; refresh?: boolean }): Promise<Book | null>;
368
+
369
+ static list(query?: { limit?: number; saveAs?: string; refresh?: boolean }): Promise<Book[]>;
363
370
 
364
371
  /** Navigate to author (many_to_one) */
365
372
  get_author(): Promise<Author | null>;
@@ -374,7 +381,9 @@ The LLM or user writes code against these typed classes:
374
381
  ```typescript
375
382
  import { Author, Book, global_search } from './sandbox-tools';
376
383
 
377
- const tolkien = await Author.find({ id: 'tolkien' });
384
+ const authors = await Author.list();
385
+ const tolkien = authors.find((author) => author.name === 'J.R.R. Tolkien');
386
+ if (!tolkien) throw new Error('Author not found');
378
387
  console.log(tolkien.name); // "J.R.R. Tolkien"
379
388
 
380
389
  const bio = await tolkien.get_bio({ detailed: true });
package/dist/cli/index.js CHANGED
@@ -7771,7 +7771,31 @@ async function main() {
7771
7771
  await granular.registerEffects(SANDBOX_ID, ${effectsArray});
7772
7772
 
7773
7773
  log('Effects registered. Process kept alive for simulator. Press Ctrl+C to exit.');
7774
- await new Promise(() => {});
7774
+
7775
+ const keepAliveTimer = setInterval(() => {
7776
+ // Keep an active event-loop handle so Bun/Node does not exit immediately.
7777
+ }, 60_000);
7778
+
7779
+ await new Promise<void>((resolve) => {
7780
+ let shuttingDown = false;
7781
+
7782
+ const shutdown = async (signal: string) => {
7783
+ if (shuttingDown) return;
7784
+ shuttingDown = true;
7785
+ clearInterval(keepAliveTimer);
7786
+ log(\`Received \${signal}. Shutting down effects host...\`);
7787
+ try {
7788
+ await granular.disconnectEffects(SANDBOX_ID);
7789
+ } catch (error) {
7790
+ console.warn('[Effects] Failed to disconnect live effects cleanly:', error);
7791
+ }
7792
+ resolve();
7793
+ process.exit(0);
7794
+ };
7795
+
7796
+ process.once('SIGINT', () => { void shutdown('SIGINT'); });
7797
+ process.once('SIGTERM', () => { void shutdown('SIGTERM'); });
7798
+ });
7775
7799
  }
7776
7800
 
7777
7801
  main().catch((err) => {
@@ -8714,12 +8738,16 @@ console.log('Connected to:', env.environmentId);`);
8714
8738
  if (classes.length > 0) {
8715
8739
  const cls = classes[0];
8716
8740
  const ClassName = cls.name.charAt(0).toUpperCase() + cls.name.slice(1);
8717
- lines.push(` // 1. Find object`);
8718
- lines.push(` const item = await ${ClassName}.find({ id: 'unique_${cls.name}_id' });`);
8719
- lines.push(` console.log('Found:', item.id);`);
8741
+ lines.push(` // 1. List objects`);
8742
+ lines.push(` const items = await ${ClassName}.list({ limit: 10, saveAs: '${cls.name}_items' });`);
8743
+ lines.push(` const item = items[0] ?? null;`);
8744
+ lines.push(` console.log('Loaded:', item?.id);`);
8720
8745
  lines.push(``);
8721
8746
  lines.push(` // 2. Call instance method`);
8722
- lines.push(` const summary = await item.summarize({ maxLength: 100 });`);
8747
+ lines.push(` if (item) {`);
8748
+ lines.push(` const summary = await item.summarize({ maxLength: 100 });`);
8749
+ lines.push(` console.log(summary);`);
8750
+ lines.push(` }`);
8723
8751
  lines.push(``);
8724
8752
  lines.push(` // 3. Call static method`);
8725
8753
  lines.push(` const results = await ${ClassName}.search({ query: 'test' });`);
package/dist/index.d.mts CHANGED
@@ -904,7 +904,8 @@ declare class Session {
904
904
  * ```typescript
905
905
  * import { Author, Book, global_search } from './sandbox-tools';
906
906
  *
907
- * const tolkien = await Author.find({ id: 'tolkien' });
907
+ * const authors = await Author.list({ limit: 10, saveAs: 'recent_authors' });
908
+ * const tolkien = await Author.get({ path: 'author_tolkien' });
908
909
  * const bio = await tolkien.get_bio({ detailed: true });
909
910
  * const books = await tolkien.get_books();
910
911
  * ```
@@ -1410,6 +1411,13 @@ declare class Granular {
1410
1411
  * sandbox-scoped live catalog.
1411
1412
  */
1412
1413
  unregisterEffect(sandboxNameOrId: string, name: string): Promise<void>;
1414
+ /**
1415
+ * Disconnect one sandbox-scoped effect host, or all of them when no sandbox is provided.
1416
+ *
1417
+ * This is primarily useful for long-lived helper processes such as generated
1418
+ * `granular-effects.ts` scripts that need to shut down cleanly on SIGINT/SIGTERM.
1419
+ */
1420
+ disconnectEffects(sandboxNameOrId?: string): Promise<void>;
1413
1421
  /**
1414
1422
  * Unregister all effects for a sandbox.
1415
1423
  */
package/dist/index.d.ts CHANGED
@@ -904,7 +904,8 @@ declare class Session {
904
904
  * ```typescript
905
905
  * import { Author, Book, global_search } from './sandbox-tools';
906
906
  *
907
- * const tolkien = await Author.find({ id: 'tolkien' });
907
+ * const authors = await Author.list({ limit: 10, saveAs: 'recent_authors' });
908
+ * const tolkien = await Author.get({ path: 'author_tolkien' });
908
909
  * const bio = await tolkien.get_bio({ detailed: true });
909
910
  * const books = await tolkien.get_books();
910
911
  * ```
@@ -1410,6 +1411,13 @@ declare class Granular {
1410
1411
  * sandbox-scoped live catalog.
1411
1412
  */
1412
1413
  unregisterEffect(sandboxNameOrId: string, name: string): Promise<void>;
1414
+ /**
1415
+ * Disconnect one sandbox-scoped effect host, or all of them when no sandbox is provided.
1416
+ *
1417
+ * This is primarily useful for long-lived helper processes such as generated
1418
+ * `granular-effects.ts` scripts that need to shut down cleanly on SIGINT/SIGTERM.
1419
+ */
1420
+ disconnectEffects(sandboxNameOrId?: string): Promise<void>;
1413
1421
  /**
1414
1422
  * Unregister all effects for a sandbox.
1415
1423
  */
package/dist/index.js CHANGED
@@ -4577,7 +4577,8 @@ var Session = class {
4577
4577
  * ```typescript
4578
4578
  * import { Author, Book, global_search } from './sandbox-tools';
4579
4579
  *
4580
- * const tolkien = await Author.find({ id: 'tolkien' });
4580
+ * const authors = await Author.list({ limit: 10, saveAs: 'recent_authors' });
4581
+ * const tolkien = await Author.get({ path: 'author_tolkien' });
4581
4582
  * const bio = await tolkien.get_bio({ detailed: true });
4582
4583
  * const books = await tolkien.get_books();
4583
4584
  * ```
@@ -6149,7 +6150,21 @@ var Granular = class {
6149
6150
  const effects = Array.from(this.getSandboxEffectMap(host.sandboxId).values()).map(
6150
6151
  (effect) => this.serializeEffect(effect)
6151
6152
  );
6152
- await host.wsClient.call("effects.publishCatalog", { effects });
6153
+ const result = await host.wsClient.call("effects.publishCatalog", { effects });
6154
+ const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
6155
+ const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
6156
+ if (acceptedCount === 0 && rejected.length > 0) {
6157
+ const detail = rejected.map((entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`).join("; ");
6158
+ throw new Error(
6159
+ `Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
6160
+ );
6161
+ }
6162
+ if (rejected.length > 0) {
6163
+ console.warn(
6164
+ `[Granular] Some live effects were rejected for sandbox ${host.sandboxId}:`,
6165
+ rejected
6166
+ );
6167
+ }
6153
6168
  }
6154
6169
  async syncSandboxEffectCatalog(sandboxId) {
6155
6170
  const host = await this.ensureSandboxEffectHost(sandboxId);
@@ -6315,6 +6330,22 @@ var Granular = class {
6315
6330
  }
6316
6331
  await this.syncSandboxEffectCatalog(sandboxId);
6317
6332
  }
6333
+ /**
6334
+ * Disconnect one sandbox-scoped effect host, or all of them when no sandbox is provided.
6335
+ *
6336
+ * This is primarily useful for long-lived helper processes such as generated
6337
+ * `granular-effects.ts` scripts that need to shut down cleanly on SIGINT/SIGTERM.
6338
+ */
6339
+ async disconnectEffects(sandboxNameOrId) {
6340
+ if (sandboxNameOrId) {
6341
+ const sandbox = await this.findOrCreateSandbox(sandboxNameOrId);
6342
+ this.disconnectSandboxEffectHost(sandbox.sandboxId);
6343
+ return;
6344
+ }
6345
+ for (const sandboxId of Array.from(this.sandboxEffectHosts.keys())) {
6346
+ this.disconnectSandboxEffectHost(sandboxId);
6347
+ }
6348
+ }
6318
6349
  /**
6319
6350
  * Unregister all effects for a sandbox.
6320
6351
  */