@granular-software/sdk 0.4.4 → 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
@@ -33,7 +33,7 @@ Inside this monorepo, prefer the workspace package instead of installing from np
33
33
  By default, the SDK resolves endpoints like this:
34
34
 
35
35
  - Local mode (`NODE_ENV=development`): `ws://localhost:8787/granular`
36
- - Production mode (default): `wss://api.granular.dev/v2/ws`
36
+ - Production mode (default): `wss://cf-api-gateway.arthur6084.workers.dev/granular`
37
37
 
38
38
  Overrides:
39
39
 
@@ -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
@@ -5387,7 +5387,7 @@ var import_dotenv = __toESM(require_main());
5387
5387
 
5388
5388
  // src/endpoints.ts
5389
5389
  var LOCAL_API_URL = "ws://localhost:8787/granular";
5390
- var PRODUCTION_API_URL = "wss://api.granular.dev/v2/ws";
5390
+ var PRODUCTION_API_URL = "wss://cf-api-gateway.arthur6084.workers.dev/granular";
5391
5391
  var LOCAL_AUTH_URL = "http://localhost:3000";
5392
5392
  var PRODUCTION_AUTH_URL = "https://app.granular.software";
5393
5393
  function readEnv(name) {
@@ -5775,6 +5775,9 @@ var ApiClient = class {
5775
5775
  return false;
5776
5776
  }
5777
5777
  }
5778
+ async validateKeyOrThrow() {
5779
+ await this.request("/control/sandboxes");
5780
+ }
5778
5781
  // ── Sandboxes ──
5779
5782
  async listSandboxes() {
5780
5783
  const result = await this.request("/control/sandboxes");
@@ -7768,7 +7771,31 @@ async function main() {
7768
7771
  await granular.registerEffects(SANDBOX_ID, ${effectsArray});
7769
7772
 
7770
7773
  log('Effects registered. Process kept alive for simulator. Press Ctrl+C to exit.');
7771
- 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
+ });
7772
7799
  }
7773
7800
 
7774
7801
  main().catch((err) => {
@@ -7847,9 +7874,11 @@ async function initCommand(projectName, options) {
7847
7874
  const apiUrl = loadApiUrl();
7848
7875
  const api = new ApiClient(apiKey, apiUrl);
7849
7876
  const validating = spinner("Validating API key...");
7850
- const isValid = await api.validateKey();
7851
- if (!isValid) {
7852
- validating.fail(" Invalid API key. Please check your key and try again.");
7877
+ try {
7878
+ await api.validateKeyOrThrow();
7879
+ } catch (err) {
7880
+ const detail = err?.message ? ` (${err.message})` : "";
7881
+ validating.fail(` API key validation failed${detail}.`);
7853
7882
  process.exit(1);
7854
7883
  }
7855
7884
  validating.succeed(" API key validated.");
@@ -8015,9 +8044,11 @@ async function loginCommand(options = {}) {
8015
8044
  }
8016
8045
  const spinner2 = spinner("Validating...");
8017
8046
  const api = new ApiClient(apiKey, apiUrl);
8018
- const valid = await api.validateKey();
8019
- if (!valid) {
8020
- spinner2.fail(" Invalid API key.");
8047
+ try {
8048
+ await api.validateKeyOrThrow();
8049
+ } catch (err) {
8050
+ const detail = err?.message ? ` (${err.message})` : "";
8051
+ spinner2.fail(` API key validation failed${detail}.`);
8021
8052
  process.exit(1);
8022
8053
  }
8023
8054
  saveApiKey(apiKey);
@@ -8707,12 +8738,16 @@ console.log('Connected to:', env.environmentId);`);
8707
8738
  if (classes.length > 0) {
8708
8739
  const cls = classes[0];
8709
8740
  const ClassName = cls.name.charAt(0).toUpperCase() + cls.name.slice(1);
8710
- lines.push(` // 1. Find object`);
8711
- lines.push(` const item = await ${ClassName}.find({ id: 'unique_${cls.name}_id' });`);
8712
- 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);`);
8713
8745
  lines.push(``);
8714
8746
  lines.push(` // 2. Call instance method`);
8715
- 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(` }`);
8716
8751
  lines.push(``);
8717
8752
  lines.push(` // 3. Call static method`);
8718
8753
  lines.push(` const results = await ${ClassName}.search({ query: 'test' });`);
package/dist/index.d.mts CHANGED
@@ -88,6 +88,11 @@ interface ConnectOptions {
88
88
  /** Optional stable client ID. Defaults to `client_${Date.now()}`. Use a fixed
89
89
  * value for long-lived effect hosts so tool catalogs don't accumulate. */
90
90
  clientId?: string;
91
+ /** Optional session heap seed. Each item is eagerly hydrated into the session heap on connect. */
92
+ initialHeap?: Array<{
93
+ className: string;
94
+ id: string;
95
+ }>;
91
96
  }
92
97
  /**
93
98
  * A sandbox container
@@ -454,6 +459,47 @@ interface Prompt {
454
459
  options?: string[];
455
460
  defaultValue?: unknown;
456
461
  }
462
+ type SessionHeapFieldType = 'string' | 'number' | 'boolean' | 'null' | 'unknown';
463
+ interface SessionHeapFieldValue {
464
+ name: string;
465
+ type: SessionHeapFieldType;
466
+ value: string | number | boolean | null;
467
+ }
468
+ interface SessionHeapEntry {
469
+ path: string;
470
+ className: string;
471
+ id: string;
472
+ label?: string | null;
473
+ description?: string | null;
474
+ prototypes: string[];
475
+ fields: SessionHeapFieldValue[];
476
+ relatedJobIds: string[];
477
+ source: string;
478
+ createdAt: number;
479
+ updatedAt: number;
480
+ }
481
+ interface SessionHeapList {
482
+ name: string;
483
+ className: string;
484
+ paths: string[];
485
+ relatedJobIds: string[];
486
+ updatedAt: number;
487
+ }
488
+ interface SessionHeapVariable {
489
+ name: string;
490
+ kind: 'entry' | 'list' | 'scalar';
491
+ entryPath?: string;
492
+ listName?: string;
493
+ value?: string | number | boolean | null;
494
+ className?: string;
495
+ updatedAt: number;
496
+ }
497
+ interface SessionHeapSnapshot {
498
+ entriesByPath: Record<string, SessionHeapEntry>;
499
+ listsByName: Record<string, SessionHeapList>;
500
+ variablesByName: Record<string, SessionHeapVariable>;
501
+ updatedAt: number;
502
+ }
457
503
  interface WSDisconnectInfo {
458
504
  code?: number;
459
505
  reason?: string;
@@ -858,7 +904,8 @@ declare class Session {
858
904
  * ```typescript
859
905
  * import { Author, Book, global_search } from './sandbox-tools';
860
906
  *
861
- * 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' });
862
909
  * const bio = await tolkien.get_bio({ detailed: true });
863
910
  * const books = await tolkien.get_books();
864
911
  * ```
@@ -974,6 +1021,13 @@ declare class Environment extends Session {
974
1021
  get permissionProfileId(): string;
975
1022
  /** The GraphQL API endpoint URL */
976
1023
  get apiEndpoint(): string;
1024
+ /**
1025
+ * Return a plain JS snapshot of the synced session heap.
1026
+ *
1027
+ * The heap lives in the Automerge document, so this method does not perform
1028
+ * any extra network roundtrip.
1029
+ */
1030
+ getHeap(): SessionHeapSnapshot;
977
1031
  private getRuntimeBaseUrl;
978
1032
  /**
979
1033
  * Close the session and disconnect from the sandbox.
@@ -1357,6 +1411,13 @@ declare class Granular {
1357
1411
  * sandbox-scoped live catalog.
1358
1412
  */
1359
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>;
1360
1421
  /**
1361
1422
  * Unregister all effects for a sandbox.
1362
1423
  */
@@ -1427,4 +1488,4 @@ declare class Granular {
1427
1488
  private request;
1428
1489
  }
1429
1490
 
1430
- export { type APIError, type AccessTokenProvider, type Assignment, type AssignmentListResponse, type Build, type BuildListResponse, type BuildPolicy, type BuildStatus, type ConnectOptions, type CreateEnvironmentData, type CreatePermissionProfileData, type CreateSandboxData, type DefineRelationshipOptions, type DeleteResponse, type DomainState, type EffectHandler, type EffectHandlerContext, type EffectInfo, type EffectSchema, type EffectWithHandler, type EffectsChangedEvent, type EndpointMode, Environment, type EnvironmentData, type EnvironmentListResponse, Granular, type GranularAuth, type GranularOptions, type GraphQLResult, type InstanceEffectHandler, type InstanceToolHandler, type Job, type JobStatus, type JobSubmitResult, type Manifest, type ManifestContent, type ManifestEffectDeclaration, type ManifestEffectSchema, type ManifestImport, type ManifestListResponse, type ManifestOperation, type ManifestPropertySpec, type ManifestRelationshipDef, type ManifestVolume, type ModelRef, type PermissionProfile, type PermissionProfileListResponse, type PermissionRules, type Prompt, type PublishEffectsResult, type PublishToolsResult, type RPCRequest, type RPCRequestFromServer, type RPCResponse, type RecordObjectOptions, type RecordObjectResult, type RecordUserOptions, type RelationshipInfo, type Sandbox, type SandboxListResponse, Session, type Subject, type SyncMessage, type ToolHandler, type ToolInfo, type ToolInvokeParams, type ToolResultParams, type ToolSchema, type ToolWithHandler, type ToolsChangedEvent, type User, WSClient, type WSClientOptions, type WSDisconnectInfo, type WSReconnectErrorInfo };
1491
+ export { type APIError, type AccessTokenProvider, type Assignment, type AssignmentListResponse, type Build, type BuildListResponse, type BuildPolicy, type BuildStatus, type ConnectOptions, type CreateEnvironmentData, type CreatePermissionProfileData, type CreateSandboxData, type DefineRelationshipOptions, type DeleteResponse, type DomainState, type EffectHandler, type EffectHandlerContext, type EffectInfo, type EffectSchema, type EffectWithHandler, type EffectsChangedEvent, type EndpointMode, Environment, type EnvironmentData, type EnvironmentListResponse, Granular, type GranularAuth, type GranularOptions, type GraphQLResult, type InstanceEffectHandler, type InstanceToolHandler, type Job, type JobStatus, type JobSubmitResult, type Manifest, type ManifestContent, type ManifestEffectDeclaration, type ManifestEffectSchema, type ManifestImport, type ManifestListResponse, type ManifestOperation, type ManifestPropertySpec, type ManifestRelationshipDef, type ManifestVolume, type ModelRef, type PermissionProfile, type PermissionProfileListResponse, type PermissionRules, type Prompt, type PublishEffectsResult, type PublishToolsResult, type RPCRequest, type RPCRequestFromServer, type RPCResponse, type RecordObjectOptions, type RecordObjectResult, type RecordUserOptions, type RelationshipInfo, type Sandbox, type SandboxListResponse, Session, type SessionHeapEntry, type SessionHeapFieldType, type SessionHeapFieldValue, type SessionHeapList, type SessionHeapSnapshot, type SessionHeapVariable, type Subject, type SyncMessage, type ToolHandler, type ToolInfo, type ToolInvokeParams, type ToolResultParams, type ToolSchema, type ToolWithHandler, type ToolsChangedEvent, type User, WSClient, type WSClientOptions, type WSDisconnectInfo, type WSReconnectErrorInfo };
package/dist/index.d.ts CHANGED
@@ -88,6 +88,11 @@ interface ConnectOptions {
88
88
  /** Optional stable client ID. Defaults to `client_${Date.now()}`. Use a fixed
89
89
  * value for long-lived effect hosts so tool catalogs don't accumulate. */
90
90
  clientId?: string;
91
+ /** Optional session heap seed. Each item is eagerly hydrated into the session heap on connect. */
92
+ initialHeap?: Array<{
93
+ className: string;
94
+ id: string;
95
+ }>;
91
96
  }
92
97
  /**
93
98
  * A sandbox container
@@ -454,6 +459,47 @@ interface Prompt {
454
459
  options?: string[];
455
460
  defaultValue?: unknown;
456
461
  }
462
+ type SessionHeapFieldType = 'string' | 'number' | 'boolean' | 'null' | 'unknown';
463
+ interface SessionHeapFieldValue {
464
+ name: string;
465
+ type: SessionHeapFieldType;
466
+ value: string | number | boolean | null;
467
+ }
468
+ interface SessionHeapEntry {
469
+ path: string;
470
+ className: string;
471
+ id: string;
472
+ label?: string | null;
473
+ description?: string | null;
474
+ prototypes: string[];
475
+ fields: SessionHeapFieldValue[];
476
+ relatedJobIds: string[];
477
+ source: string;
478
+ createdAt: number;
479
+ updatedAt: number;
480
+ }
481
+ interface SessionHeapList {
482
+ name: string;
483
+ className: string;
484
+ paths: string[];
485
+ relatedJobIds: string[];
486
+ updatedAt: number;
487
+ }
488
+ interface SessionHeapVariable {
489
+ name: string;
490
+ kind: 'entry' | 'list' | 'scalar';
491
+ entryPath?: string;
492
+ listName?: string;
493
+ value?: string | number | boolean | null;
494
+ className?: string;
495
+ updatedAt: number;
496
+ }
497
+ interface SessionHeapSnapshot {
498
+ entriesByPath: Record<string, SessionHeapEntry>;
499
+ listsByName: Record<string, SessionHeapList>;
500
+ variablesByName: Record<string, SessionHeapVariable>;
501
+ updatedAt: number;
502
+ }
457
503
  interface WSDisconnectInfo {
458
504
  code?: number;
459
505
  reason?: string;
@@ -858,7 +904,8 @@ declare class Session {
858
904
  * ```typescript
859
905
  * import { Author, Book, global_search } from './sandbox-tools';
860
906
  *
861
- * 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' });
862
909
  * const bio = await tolkien.get_bio({ detailed: true });
863
910
  * const books = await tolkien.get_books();
864
911
  * ```
@@ -974,6 +1021,13 @@ declare class Environment extends Session {
974
1021
  get permissionProfileId(): string;
975
1022
  /** The GraphQL API endpoint URL */
976
1023
  get apiEndpoint(): string;
1024
+ /**
1025
+ * Return a plain JS snapshot of the synced session heap.
1026
+ *
1027
+ * The heap lives in the Automerge document, so this method does not perform
1028
+ * any extra network roundtrip.
1029
+ */
1030
+ getHeap(): SessionHeapSnapshot;
977
1031
  private getRuntimeBaseUrl;
978
1032
  /**
979
1033
  * Close the session and disconnect from the sandbox.
@@ -1357,6 +1411,13 @@ declare class Granular {
1357
1411
  * sandbox-scoped live catalog.
1358
1412
  */
1359
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>;
1360
1421
  /**
1361
1422
  * Unregister all effects for a sandbox.
1362
1423
  */
@@ -1427,4 +1488,4 @@ declare class Granular {
1427
1488
  private request;
1428
1489
  }
1429
1490
 
1430
- export { type APIError, type AccessTokenProvider, type Assignment, type AssignmentListResponse, type Build, type BuildListResponse, type BuildPolicy, type BuildStatus, type ConnectOptions, type CreateEnvironmentData, type CreatePermissionProfileData, type CreateSandboxData, type DefineRelationshipOptions, type DeleteResponse, type DomainState, type EffectHandler, type EffectHandlerContext, type EffectInfo, type EffectSchema, type EffectWithHandler, type EffectsChangedEvent, type EndpointMode, Environment, type EnvironmentData, type EnvironmentListResponse, Granular, type GranularAuth, type GranularOptions, type GraphQLResult, type InstanceEffectHandler, type InstanceToolHandler, type Job, type JobStatus, type JobSubmitResult, type Manifest, type ManifestContent, type ManifestEffectDeclaration, type ManifestEffectSchema, type ManifestImport, type ManifestListResponse, type ManifestOperation, type ManifestPropertySpec, type ManifestRelationshipDef, type ManifestVolume, type ModelRef, type PermissionProfile, type PermissionProfileListResponse, type PermissionRules, type Prompt, type PublishEffectsResult, type PublishToolsResult, type RPCRequest, type RPCRequestFromServer, type RPCResponse, type RecordObjectOptions, type RecordObjectResult, type RecordUserOptions, type RelationshipInfo, type Sandbox, type SandboxListResponse, Session, type Subject, type SyncMessage, type ToolHandler, type ToolInfo, type ToolInvokeParams, type ToolResultParams, type ToolSchema, type ToolWithHandler, type ToolsChangedEvent, type User, WSClient, type WSClientOptions, type WSDisconnectInfo, type WSReconnectErrorInfo };
1491
+ export { type APIError, type AccessTokenProvider, type Assignment, type AssignmentListResponse, type Build, type BuildListResponse, type BuildPolicy, type BuildStatus, type ConnectOptions, type CreateEnvironmentData, type CreatePermissionProfileData, type CreateSandboxData, type DefineRelationshipOptions, type DeleteResponse, type DomainState, type EffectHandler, type EffectHandlerContext, type EffectInfo, type EffectSchema, type EffectWithHandler, type EffectsChangedEvent, type EndpointMode, Environment, type EnvironmentData, type EnvironmentListResponse, Granular, type GranularAuth, type GranularOptions, type GraphQLResult, type InstanceEffectHandler, type InstanceToolHandler, type Job, type JobStatus, type JobSubmitResult, type Manifest, type ManifestContent, type ManifestEffectDeclaration, type ManifestEffectSchema, type ManifestImport, type ManifestListResponse, type ManifestOperation, type ManifestPropertySpec, type ManifestRelationshipDef, type ManifestVolume, type ModelRef, type PermissionProfile, type PermissionProfileListResponse, type PermissionRules, type Prompt, type PublishEffectsResult, type PublishToolsResult, type RPCRequest, type RPCRequestFromServer, type RPCResponse, type RecordObjectOptions, type RecordObjectResult, type RecordUserOptions, type RelationshipInfo, type Sandbox, type SandboxListResponse, Session, type SessionHeapEntry, type SessionHeapFieldType, type SessionHeapFieldValue, type SessionHeapList, type SessionHeapSnapshot, type SessionHeapVariable, type Subject, type SyncMessage, type ToolHandler, type ToolInfo, type ToolInvokeParams, type ToolResultParams, type ToolSchema, type ToolWithHandler, type ToolsChangedEvent, type User, WSClient, type WSClientOptions, type WSDisconnectInfo, type WSReconnectErrorInfo };
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
  * ```
@@ -5089,7 +5090,7 @@ var JobImplementation = class {
5089
5090
 
5090
5091
  // src/endpoints.ts
5091
5092
  var LOCAL_API_URL = "ws://localhost:8787/granular";
5092
- var PRODUCTION_API_URL = "wss://api.granular.dev/v2/ws";
5093
+ var PRODUCTION_API_URL = "wss://cf-api-gateway.arthur6084.workers.dev/granular";
5093
5094
  function readEnv(name) {
5094
5095
  if (typeof process === "undefined" || !process.env) return void 0;
5095
5096
  return process.env[name];
@@ -5176,6 +5177,26 @@ function buildEffectHostUrl(apiUrl, sandboxId, effectClientId, clientId) {
5176
5177
  url.searchParams.set("clientId", clientId);
5177
5178
  return url.toString();
5178
5179
  }
5180
+ function createEmptyHeapSnapshot(now = Date.now()) {
5181
+ return {
5182
+ entriesByPath: {},
5183
+ listsByName: {},
5184
+ variablesByName: {},
5185
+ updatedAt: now
5186
+ };
5187
+ }
5188
+ function normalizeHeapSnapshot(raw) {
5189
+ if (!raw || typeof raw !== "object") {
5190
+ return createEmptyHeapSnapshot();
5191
+ }
5192
+ const heap = raw;
5193
+ return {
5194
+ entriesByPath: heap.entriesByPath && typeof heap.entriesByPath === "object" ? JSON.parse(JSON.stringify(heap.entriesByPath)) : {},
5195
+ listsByName: heap.listsByName && typeof heap.listsByName === "object" ? JSON.parse(JSON.stringify(heap.listsByName)) : {},
5196
+ variablesByName: heap.variablesByName && typeof heap.variablesByName === "object" ? JSON.parse(JSON.stringify(heap.variablesByName)) : {},
5197
+ updatedAt: typeof heap.updatedAt === "number" ? heap.updatedAt : Date.now()
5198
+ };
5199
+ }
5179
5200
  var Environment = class _Environment extends Session {
5180
5201
  envData;
5181
5202
  _apiKey;
@@ -5206,6 +5227,16 @@ var Environment = class _Environment extends Session {
5206
5227
  get apiEndpoint() {
5207
5228
  return this._apiEndpoint;
5208
5229
  }
5230
+ /**
5231
+ * Return a plain JS snapshot of the synced session heap.
5232
+ *
5233
+ * The heap lives in the Automerge document, so this method does not perform
5234
+ * any extra network roundtrip.
5235
+ */
5236
+ getHeap() {
5237
+ const doc = this.document;
5238
+ return normalizeHeapSnapshot(doc?.heap);
5239
+ }
5209
5240
  getRuntimeBaseUrl() {
5210
5241
  try {
5211
5242
  const endpoint = new URL(this._apiEndpoint);
@@ -6067,7 +6098,8 @@ var Granular = class {
6067
6098
  method: "POST",
6068
6099
  body: JSON.stringify({
6069
6100
  environmentId: envData.environmentId,
6070
- clientId
6101
+ clientId,
6102
+ initialHeap: options.initialHeap
6071
6103
  })
6072
6104
  });
6073
6105
  const client = new WSClient({
@@ -6118,7 +6150,21 @@ var Granular = class {
6118
6150
  const effects = Array.from(this.getSandboxEffectMap(host.sandboxId).values()).map(
6119
6151
  (effect) => this.serializeEffect(effect)
6120
6152
  );
6121
- 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
+ }
6122
6168
  }
6123
6169
  async syncSandboxEffectCatalog(sandboxId) {
6124
6170
  const host = await this.ensureSandboxEffectHost(sandboxId);
@@ -6284,6 +6330,22 @@ var Granular = class {
6284
6330
  }
6285
6331
  await this.syncSandboxEffectCatalog(sandboxId);
6286
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
+ }
6287
6349
  /**
6288
6350
  * Unregister all effects for a sandbox.
6289
6351
  */