@granular-software/sdk 0.4.21 → 0.4.23

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/dist/cli/index.js CHANGED
@@ -5496,13 +5496,19 @@ var LIBRARY_TEMPLATE = {
5496
5496
  operations: [
5497
5497
  createClassOperation("author", {
5498
5498
  name: { type: "string", description: "Author full name" },
5499
- nationality: { type: "string", description: "Primary nationality or home region" },
5499
+ nationality: {
5500
+ type: "string",
5501
+ description: "Primary nationality or home region"
5502
+ },
5500
5503
  birth_year: { type: "number", description: "Year of birth" }
5501
5504
  }),
5502
5505
  createClassOperation("book", {
5503
5506
  title: { type: "string", description: "Book title" },
5504
5507
  isbn: { type: "string", description: "ISBN identifier" },
5505
- publication_year: { type: "number", description: "Year the book was first published" },
5508
+ publication_year: {
5509
+ type: "number",
5510
+ description: "Year the book was first published"
5511
+ },
5506
5512
  price_eur: { type: "number", description: "Current list price in euros" }
5507
5513
  }),
5508
5514
  createClassOperation("publisher", {
@@ -5512,7 +5518,10 @@ var LIBRARY_TEMPLATE = {
5512
5518
  }),
5513
5519
  createClassOperation("editor", {
5514
5520
  name: { type: "string", description: "Editor full name" },
5515
- specialty: { type: "string", description: "Editorial focus or genre specialty" },
5521
+ specialty: {
5522
+ type: "string",
5523
+ description: "Editorial focus or genre specialty"
5524
+ },
5516
5525
  email: { type: "email", description: "Work email address" }
5517
5526
  }),
5518
5527
  createRelationshipOperation({
@@ -5710,8 +5719,14 @@ var LIBRARY_TEMPLATE = {
5710
5719
  inputSchema: {
5711
5720
  type: "object",
5712
5721
  properties: {
5713
- collection: { type: "string", description: "Destination collection name" },
5714
- note: { type: "string", description: "Optional note attached to the bookmark" }
5722
+ collection: {
5723
+ type: "string",
5724
+ description: "Destination collection name"
5725
+ },
5726
+ note: {
5727
+ type: "string",
5728
+ description: "Optional note attached to the bookmark"
5729
+ }
5715
5730
  }
5716
5731
  },
5717
5732
  outputSchema: {
@@ -5745,7 +5760,10 @@ var LIBRARY_TEMPLATE = {
5745
5760
  inputSchema: {
5746
5761
  type: "object",
5747
5762
  properties: {
5748
- format: { type: "string", description: "Requested format such as paperback, ebook, or audio" },
5763
+ format: {
5764
+ type: "string",
5765
+ description: "Requested format such as paperback, ebook, or audio"
5766
+ },
5749
5767
  quantity: { type: "number", description: "How many copies to order" }
5750
5768
  },
5751
5769
  required: ["format"]
@@ -5782,7 +5800,10 @@ var LIBRARY_TEMPLATE = {
5782
5800
  inputSchema: {
5783
5801
  type: "object",
5784
5802
  properties: {
5785
- channel: { type: "string", description: "Notification channel such as email or push" }
5803
+ channel: {
5804
+ type: "string",
5805
+ description: "Notification channel such as email or push"
5806
+ }
5786
5807
  },
5787
5808
  required: ["channel"]
5788
5809
  },
@@ -5813,8 +5834,15 @@ var LIBRARY_TEMPLATE = {
5813
5834
  type: "object",
5814
5835
  properties: {
5815
5836
  email: { type: "string", description: "Destination email address" },
5816
- bookIds: { type: "array", items: { type: "string" }, description: "Books to include in the list" },
5817
- message: { type: "string", description: "Optional note for the recipient" }
5837
+ bookIds: {
5838
+ type: "array",
5839
+ items: { type: "string" },
5840
+ description: "Books to include in the list"
5841
+ },
5842
+ message: {
5843
+ type: "string",
5844
+ description: "Optional note for the recipient"
5845
+ }
5818
5846
  },
5819
5847
  required: ["email", "bookIds"]
5820
5848
  },
@@ -5849,10 +5877,16 @@ var SUPPORT_TEMPLATE = {
5849
5877
  createClassOperation("customer", {
5850
5878
  name: { type: "string", description: "Customer display name" },
5851
5879
  email: { type: "email", description: "Customer email address" },
5852
- segment: { type: "string", description: "Customer segment such as self-serve or enterprise" }
5880
+ segment: {
5881
+ type: "string",
5882
+ description: "Customer segment such as self-serve or enterprise"
5883
+ }
5853
5884
  }),
5854
5885
  createClassOperation("order", {
5855
- number: { type: "string", description: "Order number shown to the customer" },
5886
+ number: {
5887
+ type: "string",
5888
+ description: "Order number shown to the customer"
5889
+ },
5856
5890
  total_eur: { type: "number", description: "Order total in euros" },
5857
5891
  status: { type: "string", description: "Current fulfillment status" }
5858
5892
  }),
@@ -6017,7 +6051,10 @@ var SUPPORT_TEMPLATE = {
6017
6051
  inputSchema: {
6018
6052
  type: "object",
6019
6053
  properties: {
6020
- message: { type: "string", description: "Reply body sent to the customer" }
6054
+ message: {
6055
+ type: "string",
6056
+ description: "Reply body sent to the customer"
6057
+ }
6021
6058
  },
6022
6059
  required: ["message"]
6023
6060
  },
@@ -6049,7 +6086,10 @@ var SUPPORT_TEMPLATE = {
6049
6086
  type: "object",
6050
6087
  properties: {
6051
6088
  amount: { type: "number", description: "Refund amount in euros" },
6052
- reason: { type: "string", description: "Reason shown in the payment platform" }
6089
+ reason: {
6090
+ type: "string",
6091
+ description: "Reason shown in the payment platform"
6092
+ }
6053
6093
  },
6054
6094
  required: ["amount"]
6055
6095
  },
@@ -6153,7 +6193,10 @@ var DELIVERY_TEMPLATE = {
6153
6193
  createClassOperation("project", {
6154
6194
  name: { type: "string", description: "Project name" },
6155
6195
  status: { type: "string", description: "Current delivery status" },
6156
- budget_k_eur: { type: "number", description: "Budget in thousands of euros" }
6196
+ budget_k_eur: {
6197
+ type: "number",
6198
+ description: "Budget in thousands of euros"
6199
+ }
6157
6200
  }),
6158
6201
  createClassOperation("milestone", {
6159
6202
  name: { type: "string", description: "Milestone title" },
@@ -6163,7 +6206,10 @@ var DELIVERY_TEMPLATE = {
6163
6206
  createClassOperation("task", {
6164
6207
  title: { type: "string", description: "Task title" },
6165
6208
  status: { type: "string", description: "Task execution status" },
6166
- estimate_days: { type: "number", description: "Estimated effort in days" }
6209
+ estimate_days: {
6210
+ type: "number",
6211
+ description: "Estimated effort in days"
6212
+ }
6167
6213
  }),
6168
6214
  createClassOperation("owner", {
6169
6215
  name: { type: "string", description: "Owner full name" },
@@ -6354,7 +6400,10 @@ var DELIVERY_TEMPLATE = {
6354
6400
  inputSchema: {
6355
6401
  type: "object",
6356
6402
  properties: {
6357
- environment: { type: "string", description: "Target environment such as staging or production" }
6403
+ environment: {
6404
+ type: "string",
6405
+ description: "Target environment such as staging or production"
6406
+ }
6358
6407
  },
6359
6408
  required: ["environment"]
6360
6409
  },
@@ -6385,7 +6434,10 @@ var DELIVERY_TEMPLATE = {
6385
6434
  inputSchema: {
6386
6435
  type: "object",
6387
6436
  properties: {
6388
- channel: { type: "string", description: "Destination channel such as slack or email" },
6437
+ channel: {
6438
+ type: "string",
6439
+ description: "Destination channel such as slack or email"
6440
+ },
6389
6441
  message: { type: "string", description: "Message body" }
6390
6442
  },
6391
6443
  required: ["channel", "message"]
@@ -6417,7 +6469,10 @@ var DELIVERY_TEMPLATE = {
6417
6469
  type: "object",
6418
6470
  properties: {
6419
6471
  email: { type: "string", description: "Destination email address" },
6420
- projectId: { type: "string", description: "Project referenced by the report" },
6472
+ projectId: {
6473
+ type: "string",
6474
+ description: "Project referenced by the report"
6475
+ },
6421
6476
  summary: { type: "string", description: "Status report summary" }
6422
6477
  },
6423
6478
  required: ["email", "projectId", "summary"]
@@ -6491,17 +6546,17 @@ function createStarterManifest(name, templateId) {
6491
6546
  schemaVersion: 2,
6492
6547
  name,
6493
6548
  description: `${name} \u2014 ${template.manifestDescription}`,
6494
- volumes: [{
6495
- name: "schema",
6496
- scope: "sandbox",
6497
- imports: [
6498
- { alias: "@std", name: "standard_modules", label: "prod" }
6499
- ],
6500
- operations: [
6501
- ...template.operations,
6502
- ...template.effects.map(createEffectOperation)
6503
- ]
6504
- }]
6549
+ volumes: [
6550
+ {
6551
+ name: "schema",
6552
+ scope: "sandbox",
6553
+ imports: [{ alias: "@std", name: "standard_modules", label: "prod" }],
6554
+ operations: [
6555
+ ...template.operations,
6556
+ ...template.effects.map(createEffectOperation)
6557
+ ]
6558
+ }
6559
+ ]
6505
6560
  };
6506
6561
  }
6507
6562
  function generateSeedScript(sandboxId, apiUrl, templateId) {
@@ -6512,8 +6567,12 @@ function generateSeedScript(sandboxId, apiUrl, templateId) {
6512
6567
  * Seed script for the "${template.label}" starter template.
6513
6568
  *
6514
6569
  * Safe to re-run:
6515
- * - \`recordObject()\` upserts by \`{ className, id }\`
6570
+ * - \`recordObject()\` / \`recordObjects()\` upsert by \`{ className, id }\`
6516
6571
  * - relationships are re-applied on each run
6572
+ *
6573
+ * Guidance:
6574
+ * - Use \`recordObject()\` for one record or one targeted update
6575
+ * - Use \`recordObjects()\` for starter datasets like this one so you get chunked writes + progress
6517
6576
  */
6518
6577
 
6519
6578
  import { Granular } from '@granular-software/sdk';
@@ -6548,10 +6607,19 @@ async function main() {
6548
6607
  });
6549
6608
 
6550
6609
  try {
6551
- for (const record of records) {
6552
- await env.recordObject(record);
6553
- console.log(\`Upserted \${record.className}:\${record.id}\`);
6554
- }
6610
+ let completed = 0;
6611
+ const results = await env.recordObjects(records, {
6612
+ onChunkComplete(info) {
6613
+ completed += info.recordCount;
6614
+ console.log(
6615
+ \`[Seed] Chunk \${info.chunkIndex + 1}/\${info.totalChunks}: committed \${completed}/\${records.length} records (\${info.durationMs}ms)\`,
6616
+ );
6617
+ },
6618
+ });
6619
+
6620
+ const createdCount = results.filter((result) => result.created).length;
6621
+ const updatedCount = results.length - createdCount;
6622
+ console.log(\`[Seed] Created \${createdCount} records, updated \${updatedCount} records.\`);
6555
6623
  } finally {
6556
6624
  await env.disconnect();
6557
6625
  }
@@ -8922,7 +8990,7 @@ The rest of this guide assumes these terms.
8922
8990
  | **Job** | Code string passed to \`environment.submitJob(code)\`. Runs in Granular\u2019s **sandbox runtime** with access to \`./sandbox-tools\` (generated classes + effect entrypoints). |
8923
8991
  | **Effect** | Declared with \`withEffect\`; **handler** registered with \`registerEffects\` in **your** process. Jobs invoke effects; handlers do IO outside Granular. |
8924
8992
  | **Class** | Entity **kind** in the domain (e.g. \`book\`) with \`has\` fields in the manifest. |
8925
- | **Record / object** | One **instance**: \`className\`, \`id\`, \`fields\`, \`relationships\`. Upsert with \`recordObject\`. |
8993
+ | **Record / object** | One **instance**: \`className\`, \`id\`, \`fields\`, \`relationships\`. Use \`recordObject\` for one upsert, \`recordObjects\` for several immediate upserts, and record-import APIs for async bulk loads. |
8926
8994
  | **Relationship** | Declared link between two classes; defines **property names** and cardinality. Keys appear in \`recordObject({ relationships })\`. |
8927
8995
 
8928
8996
  ### Data vs actions (short)
@@ -8941,7 +9009,7 @@ function manifestGuideEndToEndSection() {
8941
9009
  | 2 | \`granular build\` | Manifest **uploaded**; ontology version created or reused; build run compiles it; errors surface here |
8942
9010
  | 3 | (Optional) Run your **effects host** \u2014 e.g. \`npx tsx granular-effects.ts\` \u2014 calling \`registerEffects(sandboxId, \u2026)\` | Effect **handlers** attached in **your** process |
8943
9011
  | 4 | \`new Granular({ apiKey })\` then \`connect({ ontology, environment, userId, permissions })\` | **\`Environment\`** for that ontology environment slot |
8944
- | 5 | \`recordObject\` / \`recordObjects\` | **Records** stored with correct fields and relationship keys. \`recordObjects\` chunks to the control plane (default 100 rows/request); pass \`onChunkComplete\` / \`concurrency\` for UI progress. For async bulk loads use \`enqueueRecordImport\` + import status APIs. |
9012
+ | 5 | \`recordObject\` / \`recordObjects\` | **Records** stored with correct fields and relationship keys. Use \`recordObject\` for one targeted write. Use \`recordObjects\` for one synchronous multi-record write with chunking (default 100 rows/request), retries, and optional \`onChunkComplete\` / \`concurrency\` progress. For async bulk loads use \`enqueueRecordImport\` + import status APIs. |
8945
9013
  | 6 | \`submitJob(\`\u2026\`)\` with imports from \`./sandbox-tools\` | **Jobs** run; may call **effects** \u2192 your handlers execute and return results |
8946
9014
 
8947
9015
  **Accuracy tip:** After changing the manifest, always **re-build** before assuming generated types, \`./sandbox-tools\` names, or effect signatures match the file on disk.`;
@@ -14629,9 +14697,15 @@ function renderMetamodelDocTable(rows) {
14629
14697
  // src/cli/agent-docs/manifest-guide.ts
14630
14698
  function generateManifestAgentGuide(options) {
14631
14699
  const { projectName } = options;
14632
- const fieldMetamodelTable = renderMetamodelDocTable(getFieldMetamodelDocRows());
14633
- const modelMetamodelTable = renderMetamodelDocTable(getModelMetamodelDocRows());
14634
- const effectMetamodelTable = renderMetamodelDocTable(getEffectMetamodelDocRows());
14700
+ const fieldMetamodelTable = renderMetamodelDocTable(
14701
+ getFieldMetamodelDocRows()
14702
+ );
14703
+ const modelMetamodelTable = renderMetamodelDocTable(
14704
+ getModelMetamodelDocRows()
14705
+ );
14706
+ const effectMetamodelTable = renderMetamodelDocTable(
14707
+ getEffectMetamodelDocRows()
14708
+ );
14635
14709
  return `# Granular manifest guide (for coding agents)
14636
14710
 
14637
14711
  This guide is for **coding agents** who may have **no prior context on Granular**. It explains the product, core terms, how to **author** \`granular.json\`, **build** the domain, and use the SDK (\`connect\` \u2192 **environment**, \`recordObject\`, \`submitJob\`, **effects**). For **this repository\u2019s** exact class names, relationship keys, and effect payloads only, see [GRANULAR_SANDBOX.md](../GRANULAR_SANDBOX.md).
@@ -14961,8 +15035,8 @@ ${effectMetamodelTable}
14961
15035
  |-----|---------|
14962
15036
  | \`environmentId\`, \`sandboxId\`, \`apiEndpoint\`, \u2026 | Session context. |
14963
15037
  | \`applyManifest(manifest)\` | Apply manifest operations at runtime (alternative to CLI build for dynamic ontologies). |
14964
- | \`recordObject\` / \`recordObjects\` | Upsert instances and relationships. \`recordObjects\` uses chunked HTTP batch writes (default 100 rows/chunk); optional \`onChunkComplete\`, \`batchSize\`, \`concurrency\`. |
14965
- | \`enqueueRecordImport\`, \`listRecordImports\`, \`getRecordImportSummary\`, \u2026 | **Async** bulk import (worker queue + aggregate progress). Prefer when loads are huge and fire-and-forget is OK. |
15038
+ | \`recordObject\` / \`recordObjects\` | Upsert instances and relationships. Use \`recordObject\` for one object or a targeted patch. Use \`recordObjects\` for synchronous multi-record ingestion with chunked HTTP writes (default 100 rows/chunk), retries, and optional \`onChunkComplete\`, \`batchSize\`, \`concurrency\`. |
15039
+ | \`enqueueRecordImport\`, \`listRecordImports\`, \`getRecordImport\`, \`getRecordImportSummary\`, \u2026 | **Async** bulk import (worker queue + aggregate progress). Prefer when loads are huge, returning an \`importId\` is enough up front, and background processing is acceptable. |
14966
15040
  | \`graphql(query, variables?)\` | **GraphQL** \u2014 see dedicated subsection below. |
14967
15041
  | \`defineRelationship\`, \`getRelationships\`, \`attach\`, \`detach\`, \`listRelated\` | Imperative relationship operations (same ideas as manifest \`defineRelationship\`). |
14968
15042
  | \`submitJob(code)\` | Run code in the sandbox; import from \`./sandbox-tools\`. |
@@ -15099,13 +15173,18 @@ function generateSandboxAgentDoc(manifest, meta) {
15099
15173
  lines.push("|-----|-------|");
15100
15174
  lines.push(`| Sandbox id | \`${meta.sandboxId}\` |`);
15101
15175
  if (meta.apiUrl) lines.push(`| API / WS base | \`${meta.apiUrl}\` |`);
15102
- if (meta.manifestId) lines.push(`| Last uploaded manifest id | \`${meta.manifestId}\` |`);
15176
+ if (meta.manifestId)
15177
+ lines.push(`| Last uploaded manifest id | \`${meta.manifestId}\` |`);
15103
15178
  if (meta.buildId) lines.push(`| Last version id | \`${meta.buildId}\` |`);
15104
15179
  if (meta.buildPending) {
15105
- lines.push("| Version status | *No successful version/build recorded in this doc run \u2014 run `granular build`.* |");
15180
+ lines.push(
15181
+ "| Version status | *No successful version/build recorded in this doc run \u2014 run `granular build`.* |"
15182
+ );
15106
15183
  }
15107
15184
  lines.push("");
15108
- lines.push("Set `GRANULAR_API_KEY` (e.g. in `.env.local`). Optional: `GRANULAR_API_URL` overrides the WebSocket URL.");
15185
+ lines.push(
15186
+ "Set `GRANULAR_API_KEY` (e.g. in `.env.local`). Optional: `GRANULAR_API_URL` overrides the WebSocket URL."
15187
+ );
15109
15188
  lines.push("");
15110
15189
  for (const line of sandboxDocMainConceptsSection().split("\n")) {
15111
15190
  lines.push(line);
@@ -15119,18 +15198,40 @@ function generateSandboxAgentDoc(manifest, meta) {
15119
15198
  lines.push("");
15120
15199
  lines.push("| Command | Purpose |");
15121
15200
  lines.push("|---------|---------|");
15122
- lines.push("| `granular build` | Create or rebuild the current ontology version |");
15123
- lines.push("| `granular version current --json` | Resolve the latest/current ontology version |");
15124
- lines.push("| `granular version diff <version> [base]` | Show the semantic diff between versions |");
15125
- lines.push("| `granular tag move <tag> <version>` | Move a release tag such as `prod` |");
15126
- lines.push("| `granular document` | Refresh this file from `granular.json` |");
15127
- lines.push("| `granular connect test --json` | Verify auth and environment connectivity |");
15128
- lines.push("| `granular session heap --json` | Inspect the normalized session heap |");
15129
- lines.push("| `granular graphql --query '...' --json` | Query the live graph |");
15130
- lines.push("| `granular effects list --json` | Inspect declared and live effects |");
15131
- lines.push("| `granular job run --file ./job.ts --json` | Execute a real runtime job |");
15201
+ lines.push(
15202
+ "| `granular build` | Create or rebuild the current ontology version |"
15203
+ );
15204
+ lines.push(
15205
+ "| `granular version current --json` | Resolve the latest/current ontology version |"
15206
+ );
15207
+ lines.push(
15208
+ "| `granular version diff <version> [base]` | Show the semantic diff between versions |"
15209
+ );
15210
+ lines.push(
15211
+ "| `granular tag move <tag> <version>` | Move a release tag such as `prod` |"
15212
+ );
15213
+ lines.push(
15214
+ "| `granular document` | Refresh this file from `granular.json` |"
15215
+ );
15216
+ lines.push(
15217
+ "| `granular connect test --json` | Verify auth and environment connectivity |"
15218
+ );
15219
+ lines.push(
15220
+ "| `granular session heap --json` | Inspect the normalized session heap |"
15221
+ );
15222
+ lines.push(
15223
+ "| `granular graphql --query '...' --json` | Query the live graph |"
15224
+ );
15225
+ lines.push(
15226
+ "| `granular effects list --json` | Inspect declared and live effects |"
15227
+ );
15228
+ lines.push(
15229
+ "| `granular job run --file ./job.ts --json` | Execute a real runtime job |"
15230
+ );
15132
15231
  lines.push("| `npx tsx " + seed + "` | Seed sample records (if present) |");
15133
- lines.push("| `npx tsx " + eff + "` | Run effect handler host (if present) |");
15232
+ lines.push(
15233
+ "| `npx tsx " + eff + "` | Run effect handler host (if present) |"
15234
+ );
15134
15235
  lines.push("");
15135
15236
  lines.push("## Connect");
15136
15237
  lines.push("");
@@ -15153,7 +15254,9 @@ function generateSandboxAgentDoc(manifest, meta) {
15153
15254
  lines.push("## Classes and fields");
15154
15255
  lines.push("");
15155
15256
  if (classes.length === 0) {
15156
- lines.push("*No `create` + `@std/class` operations found in the manifest.*");
15257
+ lines.push(
15258
+ "*No `create` + `@std/class` operations found in the manifest.*"
15259
+ );
15157
15260
  lines.push("");
15158
15261
  } else {
15159
15262
  for (const cls of classes) {
@@ -15179,14 +15282,20 @@ function generateSandboxAgentDoc(manifest, meta) {
15179
15282
  }
15180
15283
  lines.push("## Relationships");
15181
15284
  lines.push("");
15182
- lines.push("Declared with `defineRelationship`. Submodel names are the **keys** on each class for `recordObject({ relationships: { ... } })`. Values are **foreign class ids** (single string or `string[]` when \u201Cmany\u201D).");
15285
+ lines.push(
15286
+ "Declared with `defineRelationship`. Submodel names are the **keys** on each class for `recordObject({ relationships: { ... } })`. Values are **foreign class ids** (single string or `string[]` when \u201Cmany\u201D)."
15287
+ );
15183
15288
  lines.push("");
15184
15289
  if (relationships.length === 0) {
15185
15290
  lines.push("*No relationships defined.*");
15186
15291
  lines.push("");
15187
15292
  } else {
15188
- lines.push("| Left | Right | Cardinality | Left key (`leftSubmodel`) | Right key (`rightSubmodel`) |");
15189
- lines.push("|------|-------|-------------|---------------------------|----------------------------|");
15293
+ lines.push(
15294
+ "| Left | Right | Cardinality | Left key (`leftSubmodel`) | Right key (`rightSubmodel`) |"
15295
+ );
15296
+ lines.push(
15297
+ "|------|-------|-------------|---------------------------|----------------------------|"
15298
+ );
15190
15299
  for (const r of relationships) {
15191
15300
  lines.push(
15192
15301
  `| \`${r.left}\` | \`${r.right}\` | ${r.cardinalityLabel} | \`${r.leftSubmodel}\` | \`${r.rightSubmodel}\` |`
@@ -15196,7 +15305,9 @@ function generateSandboxAgentDoc(manifest, meta) {
15196
15305
  }
15197
15306
  lines.push("## `recordObject` \u2014 examples per class");
15198
15307
  lines.push("");
15199
- lines.push("`recordObject` upserts by `{ className, id }`. In `relationships`, use **only the target object\u2019s id** for its class (see keys below).");
15308
+ lines.push(
15309
+ "`recordObject` upserts by `{ className, id }`. In `relationships`, use **only the target object\u2019s id** for its class (see keys below)."
15310
+ );
15200
15311
  lines.push("");
15201
15312
  for (const cls of classes) {
15202
15313
  const edges = relationshipEdgesForClass(cls.name, relationships);
@@ -15222,9 +15333,13 @@ function generateSandboxAgentDoc(manifest, meta) {
15222
15333
  lines.push(` relationships: {`);
15223
15334
  for (const e of edges) {
15224
15335
  if (e.isMany) {
15225
- lines.push(` ${e.submodelKey}: ['${e.foreignClass}_id_1'], // ${e.foreignClass} ids`);
15336
+ lines.push(
15337
+ ` ${e.submodelKey}: ['${e.foreignClass}_id_1'], // ${e.foreignClass} ids`
15338
+ );
15226
15339
  } else {
15227
- lines.push(` ${e.submodelKey}: '${e.foreignClass}_id', // one ${e.foreignClass} id`);
15340
+ lines.push(
15341
+ ` ${e.submodelKey}: '${e.foreignClass}_id', // one ${e.foreignClass} id`
15342
+ );
15228
15343
  }
15229
15344
  }
15230
15345
  lines.push(` },`);
@@ -15237,15 +15352,27 @@ function generateSandboxAgentDoc(manifest, meta) {
15237
15352
  lines.push("*No classes \u2014 no examples.*");
15238
15353
  lines.push("");
15239
15354
  }
15240
- lines.push("## Effects declared in this manifest (`withEffect` + live handlers)");
15355
+ lines.push(
15356
+ "## Effects declared in this manifest (`withEffect` + live handlers)"
15357
+ );
15241
15358
  lines.push("");
15242
- lines.push("Each entry below matches one `withEffect` operation. For every declaration you must:");
15359
+ lines.push(
15360
+ "Each entry below matches one `withEffect` operation. For every declaration you must:"
15361
+ );
15243
15362
  lines.push("");
15244
- lines.push("1. **Build** the manifest so the domain package includes the effect (`granular build`).");
15245
- lines.push("2. **Register** a handler whose `name` (and `className` / `static` when applicable) matches \u2014 use `granular.registerEffects('" + meta.sandboxId + "', [...])` in your host process (see `granular-effects.ts` in starter projects).");
15246
- lines.push("3. **Invoke** from sandbox code via `./sandbox-tools` inside `submitJob`; the handler you registered runs in your process and the result returns to the job.");
15363
+ lines.push(
15364
+ "1. **Build** the manifest so the domain package includes the effect (`granular build`)."
15365
+ );
15366
+ lines.push(
15367
+ "2. **Register** a handler whose `name` (and `className` / `static` when applicable) matches \u2014 use `granular.registerEffects('" + meta.sandboxId + "', [...])` in your host process (see `granular-effects.ts` in starter projects)."
15368
+ );
15369
+ lines.push(
15370
+ "3. **Invoke** from sandbox code via `./sandbox-tools` inside `submitJob`; the handler you registered runs in your process and the result returns to the job."
15371
+ );
15247
15372
  lines.push("");
15248
- lines.push("**Handler shapes:** global \u2192 `(params, ctx) => \u2026`; static class effect \u2192 `(params, ctx) => \u2026`; instance effect \u2192 `(objectId, params, ctx) => \u2026` where `objectId` is the real-world id for `attachedClass`.");
15373
+ lines.push(
15374
+ "**Handler shapes:** global \u2192 `(params, ctx) => \u2026`; static class effect \u2192 `(params, ctx) => \u2026`; instance effect \u2192 `(objectId, params, ctx) => \u2026` where `objectId` is the real-world id for `attachedClass`."
15375
+ );
15249
15376
  lines.push("");
15250
15377
  if (effects2.length === 0) {
15251
15378
  lines.push("*No `withEffect` operations in this manifest.*");
@@ -15286,7 +15413,9 @@ function generateSandboxAgentDoc(manifest, meta) {
15286
15413
  } else {
15287
15414
  lines.push("```typescript");
15288
15415
  lines.push(`// Instance: (objectId, params, ctx) => Promise<output>`);
15289
- lines.push(`handler: async (objectId, params, ctx) => { /* objectId is the ${ef.attachedClass} id */ }`);
15416
+ lines.push(
15417
+ `handler: async (objectId, params, ctx) => { /* objectId is the ${ef.attachedClass} id */ }`
15418
+ );
15290
15419
  lines.push("```");
15291
15420
  }
15292
15421
  lines.push("");
@@ -15294,7 +15423,9 @@ function generateSandboxAgentDoc(manifest, meta) {
15294
15423
  }
15295
15424
  lines.push("## `submitJob` / sandbox code");
15296
15425
  lines.push("");
15297
- lines.push("Jobs run in the sandbox with generated classes from `./sandbox-tools`. Class names are PascalCase.");
15426
+ lines.push(
15427
+ "Jobs run in the sandbox with generated classes from `./sandbox-tools`. Class names are PascalCase."
15428
+ );
15298
15429
  lines.push("");
15299
15430
  const pascalImports = classes.map((c) => toPascalCase(c.name));
15300
15431
  const effectNames = effects2.map((e) => e.name).filter(Boolean);
@@ -15302,7 +15433,9 @@ function generateSandboxAgentDoc(manifest, meta) {
15302
15433
  const firstGlobal = globalEffects[0]?.name;
15303
15434
  lines.push("```typescript");
15304
15435
  lines.push(`const job = await env.submitJob(\``);
15305
- const importList = [...pascalImports, ...effectNames].filter((v, i, a) => a.indexOf(v) === i);
15436
+ const importList = [...pascalImports, ...effectNames].filter(
15437
+ (v, i, a) => a.indexOf(v) === i
15438
+ );
15306
15439
  if (importList.length > 0) {
15307
15440
  lines.push(` import { ${importList.join(", ")} } from './sandbox-tools';`);
15308
15441
  } else {
@@ -15316,10 +15449,14 @@ function generateSandboxAgentDoc(manifest, meta) {
15316
15449
  const target = classWithInstanceEffect ?? classes[0];
15317
15450
  const C = toPascalCase(target.name);
15318
15451
  const c = target.name;
15319
- lines.push(` const rows = await ${C}.list({ limit: 10, saveAs: '${c}_rows' });`);
15452
+ lines.push(
15453
+ ` const rows = await ${C}.list({ limit: 10, saveAs: '${c}_rows' });`
15454
+ );
15320
15455
  lines.push(` const row = rows[0] ?? null;`);
15321
15456
  lines.push("");
15322
- const instEffects = effects2.filter((e) => e.attachedClass === c && !e.isStatic);
15457
+ const instEffects = effects2.filter(
15458
+ (e) => e.attachedClass === c && !e.isStatic
15459
+ );
15323
15460
  if (instEffects.length > 0) {
15324
15461
  const n = instEffects[0].name;
15325
15462
  lines.push(` if (row) {`);
@@ -15343,28 +15480,50 @@ function generateSandboxAgentDoc(manifest, meta) {
15343
15480
  lines.push("");
15344
15481
  lines.push("## Companion scripts");
15345
15482
  lines.push("");
15346
- lines.push(`- **\`${seed}\`** \u2014 example \`recordObject\` batch for starter data.`);
15347
- lines.push(`- **\`${eff}\`** \u2014 long-running process that registers effect handlers for this sandbox.`);
15483
+ lines.push(
15484
+ `- **\`${seed}\`** \u2014 example starter-data script using \`recordObjects(...)\` with progress updates.`
15485
+ );
15486
+ lines.push(
15487
+ `- **\`${eff}\`** \u2014 long-running process that registers effect handlers for this sandbox.`
15488
+ );
15348
15489
  lines.push("");
15349
15490
  lines.push("## SDK surface (reminder)");
15350
15491
  lines.push("");
15351
15492
  lines.push("| Area | APIs |");
15352
15493
  lines.push("|------|------|");
15353
- lines.push("| Auth / session | `Granular`, `connect`, `disconnect`, `recordUser` |");
15354
- lines.push("| Data | `recordObject`, `recordObjects`, record import queue APIs |");
15355
- lines.push("| GraphQL API | `graphql` \u2014 query/mutate the underlying graph when you need it |");
15356
- lines.push("| Relationships (API) | `defineRelationship`, `getRelationships`, `attach`, `detach`, `listRelated` |");
15357
- lines.push("| Ontology | `applyManifest` (runtime), or CLI `granular build` |");
15494
+ lines.push(
15495
+ "| Auth / session | `Granular`, `connect`, `disconnect`, `recordUser` |"
15496
+ );
15497
+ lines.push(
15498
+ "| Data | `recordObject` (one write), `recordObjects` (immediate multi-write), record import queue APIs (async bulk load) |"
15499
+ );
15500
+ lines.push(
15501
+ "| GraphQL API | `graphql` \u2014 query/mutate the underlying graph when you need it |"
15502
+ );
15503
+ lines.push(
15504
+ "| Relationships (API) | `defineRelationship`, `getRelationships`, `attach`, `detach`, `listRelated` |"
15505
+ );
15506
+ lines.push(
15507
+ "| Ontology | `applyManifest` (runtime), or CLI `granular build` |"
15508
+ );
15358
15509
  lines.push("| Jobs | `submitJob`, `answerPrompt` |");
15359
- lines.push("| Domain | `getDomain`, `getDomainTypes`, `getDomainDocumentation` |");
15360
- lines.push("| Effects | `registerEffects`, `getEffects`, `onEffectsChanged` |");
15510
+ lines.push(
15511
+ "| Domain | `getDomain`, `getDomainTypes`, `getDomainDocumentation` |"
15512
+ );
15513
+ lines.push(
15514
+ "| Effects | `registerEffects`, `getEffects`, `onEffectsChanged` |"
15515
+ );
15361
15516
  lines.push("| Ops | `checkReadiness`, `getHeap`, `rpc` |");
15362
15517
  lines.push("");
15363
- lines.push("Full list: [docs/granular-manifest.md](docs/granular-manifest.md).");
15518
+ lines.push(
15519
+ "Full list: [docs/granular-manifest.md](docs/granular-manifest.md)."
15520
+ );
15364
15521
  lines.push("");
15365
15522
  lines.push("---");
15366
15523
  lines.push("");
15367
- lines.push(`*Generated at ${(/* @__PURE__ */ new Date()).toISOString()} by @granular-software/sdk*`);
15524
+ lines.push(
15525
+ `*Generated at ${(/* @__PURE__ */ new Date()).toISOString()} by @granular-software/sdk*`
15526
+ );
15368
15527
  lines.push("");
15369
15528
  return lines.join("\n");
15370
15529
  }
@@ -15501,8 +15660,10 @@ edit granular.json -> granular build -> granular document -> run tests -> option
15501
15660
  # Quick reminders
15502
15661
 
15503
15662
  - Default auth story: \`granular login\`. Manual \`.env.local\` editing is fallback guidance only.
15504
- - Read \`GRANULAR_SANDBOX.md\` before writing \`recordObject\` payloads or relationship keys.
15505
- - Large ingests: use \`recordObjects(rows, { onChunkComplete, concurrency })\` for synchronous chunked writes with progress, or \`enqueueRecordImport\` + \`getRecordImport\` for async queue-based bulk loads.
15663
+ - Read \`GRANULAR_SANDBOX.md\` before writing \`recordObject\` or \`recordObjects\` payloads, especially relationship keys.
15664
+ - Use \`recordObject(...)\` for one object or one targeted update.
15665
+ - Use one \`recordObjects(rows, { onChunkComplete, concurrency })\` call when you need to write several records now and want chunk-level progress.
15666
+ - Use \`enqueueRecordImport(...)\` with \`getRecordImport(...)\` / \`getRecordImportSummary()\` only for large async imports where returning an \`importId\` is better than waiting for every row to finish.
15506
15667
  - Build failures should be mapped to the exact class, relationship, or effect declaration that caused them.
15507
15668
  - If the user starts from the control plane UI, converge back to the same repo state and the same CLI commands.
15508
15669
  `
@@ -15815,8 +15976,12 @@ function renderControlPlaneSection(controlPlaneSessionId) {
15815
15976
  `;
15816
15977
  }
15817
15978
  function renderOnboardingStateTemplate(options = {}) {
15818
- const controlPlaneSection = renderControlPlaneSection(options.controlPlaneSessionId);
15819
- const controlPlaneSetupSection = renderControlPlaneSetupSection(options.onboardingContext);
15979
+ const controlPlaneSection = renderControlPlaneSection(
15980
+ options.controlPlaneSessionId
15981
+ );
15982
+ const controlPlaneSetupSection = renderControlPlaneSetupSection(
15983
+ options.onboardingContext
15984
+ );
15820
15985
  return `# Granular onboarding state
15821
15986
 
15822
15987
  Use this file to track the first-run flow. After the first successful loop, switch to the short daily loop:
@@ -15902,8 +16067,12 @@ ${authHint}
15902
16067
  `;
15903
16068
  }
15904
16069
  function renderStartHereTemplate(options = {}) {
15905
- const controlPlaneSection = renderControlPlaneSection(options.controlPlaneSessionId);
15906
- const controlPlaneSetupSection = renderControlPlaneSetupSection(options.onboardingContext);
16070
+ const controlPlaneSection = renderControlPlaneSection(
16071
+ options.controlPlaneSessionId
16072
+ );
16073
+ const controlPlaneSetupSection = renderControlPlaneSetupSection(
16074
+ options.onboardingContext
16075
+ );
15907
16076
  return `# Granular start here
15908
16077
 
15909
16078
  This file is generated by \`granular init\`. Use it as the shared kickoff file for Codex, Cursor, Claude Code, and humans.
@@ -15976,7 +16145,10 @@ function mergeReadmeSnippet(existingContent) {
15976
16145
  `;
15977
16146
  }
15978
16147
  if (existingContent.includes(README_BEGIN) && existingContent.includes(README_END)) {
15979
- const re = new RegExp(`${escapeRegex2(README_BEGIN)}[\\s\\S]*?${escapeRegex2(README_END)}`, "m");
16148
+ const re = new RegExp(
16149
+ `${escapeRegex2(README_BEGIN)}[\\s\\S]*?${escapeRegex2(README_END)}`,
16150
+ "m"
16151
+ );
15980
16152
  return existingContent.replace(re, block.trim());
15981
16153
  }
15982
16154
  return `${existingContent.trimEnd()}
@@ -16009,27 +16181,52 @@ ${endMarker}`;
16009
16181
  return;
16010
16182
  }
16011
16183
  if (previous.includes(beginMarker) && previous.includes(endMarker)) {
16012
- const re = new RegExp(`${escapeRegex2(beginMarker)}[\\s\\S]*?${escapeRegex2(endMarker)}`, "m");
16013
- fs__namespace.writeFileSync(filePath, previous.replace(re, nextContent.trim()), "utf-8");
16184
+ const re = new RegExp(
16185
+ `${escapeRegex2(beginMarker)}[\\s\\S]*?${escapeRegex2(endMarker)}`,
16186
+ "m"
16187
+ );
16188
+ fs__namespace.writeFileSync(
16189
+ filePath,
16190
+ previous.replace(re, nextContent.trim()),
16191
+ "utf-8"
16192
+ );
16014
16193
  return;
16015
16194
  }
16016
- fs__namespace.writeFileSync(filePath, `${previous.trimEnd()}
16195
+ fs__namespace.writeFileSync(
16196
+ filePath,
16197
+ `${previous.trimEnd()}
16017
16198
 
16018
16199
  ${nextContent}
16019
- `, "utf-8");
16200
+ `,
16201
+ "utf-8"
16202
+ );
16020
16203
  }
16021
16204
  function writeCursorHooks(projectRoot) {
16022
16205
  const hooksConfigPath = path3__namespace.join(projectRoot, ".cursor", "hooks.json");
16023
- const afterShellPath = path3__namespace.join(projectRoot, ".cursor", "hooks", "after-shell.mjs");
16024
- const afterFileEditPath = path3__namespace.join(projectRoot, ".cursor", "hooks", "after-file-edit.mjs");
16206
+ const afterShellPath = path3__namespace.join(
16207
+ projectRoot,
16208
+ ".cursor",
16209
+ "hooks",
16210
+ "after-shell.mjs"
16211
+ );
16212
+ const afterFileEditPath = path3__namespace.join(
16213
+ projectRoot,
16214
+ ".cursor",
16215
+ "hooks",
16216
+ "after-file-edit.mjs"
16217
+ );
16025
16218
  upsertFile(
16026
16219
  hooksConfigPath,
16027
16220
  JSON.stringify(
16028
16221
  {
16029
16222
  version: 1,
16030
16223
  hooks: {
16031
- afterShellExecution: [{ command: "node .cursor/hooks/after-shell.mjs" }],
16032
- afterFileEdit: [{ command: "node .cursor/hooks/after-file-edit.mjs" }]
16224
+ afterShellExecution: [
16225
+ { command: "node .cursor/hooks/after-shell.mjs" }
16226
+ ],
16227
+ afterFileEdit: [
16228
+ { command: "node .cursor/hooks/after-file-edit.mjs" }
16229
+ ]
16033
16230
  }
16034
16231
  },
16035
16232
  null,
@@ -16140,7 +16337,33 @@ policy:
16140
16337
  `;
16141
16338
  }
16142
16339
  function writeGranularAgentSkills(options) {
16143
- const { projectRoot, includeCursorHooks, includeCursorSubagents, controlPlaneSessionId, onboardingContext } = options;
16340
+ const {
16341
+ projectRoot,
16342
+ includeCursorHooks,
16343
+ includeCursorSubagents,
16344
+ controlPlaneSessionId,
16345
+ onboardingContext
16346
+ } = options;
16347
+ const readmePath = path3__namespace.join(projectRoot, "README.md");
16348
+ const readmeContent = fs__namespace.existsSync(readmePath) ? fs__namespace.readFileSync(readmePath, "utf-8") : "";
16349
+ fs__namespace.writeFileSync(readmePath, mergeReadmeSnippet(readmeContent), "utf-8");
16350
+ maybeWriteFile(
16351
+ path3__namespace.join(projectRoot, ONTOLOGY_BRIEF_NAME),
16352
+ renderOntologyBriefTemplate(onboardingContext)
16353
+ );
16354
+ upsertFile(
16355
+ path3__namespace.join(projectRoot, GRANULAR_START_HERE_NAME),
16356
+ renderStartHereTemplate({ controlPlaneSessionId, onboardingContext })
16357
+ );
16358
+ maybeWriteFile(
16359
+ path3__namespace.join(projectRoot, "ONBOARDING_STATE.md"),
16360
+ renderOnboardingStateTemplate({ controlPlaneSessionId, onboardingContext })
16361
+ );
16362
+ maybeWriteFile(
16363
+ path3__namespace.join(projectRoot, ".agents", "openai.yaml"),
16364
+ renderOpenAiAgentMetadata()
16365
+ );
16366
+ writeClaudeIndex(projectRoot);
16144
16367
  for (const definition of GRANULAR_SKILLS) {
16145
16368
  upsertFile(
16146
16369
  path3__namespace.join(projectRoot, ".agents", "skills", definition.name, "SKILL.md"),
@@ -16164,25 +16387,14 @@ function writeGranularAgentSkills(options) {
16164
16387
  if (includeCursorHooks) {
16165
16388
  writeCursorHooks(projectRoot);
16166
16389
  }
16167
- maybeWriteFile(path3__namespace.join(projectRoot, ONTOLOGY_BRIEF_NAME), renderOntologyBriefTemplate(onboardingContext));
16168
- upsertFile(
16169
- path3__namespace.join(projectRoot, GRANULAR_START_HERE_NAME),
16170
- renderStartHereTemplate({ controlPlaneSessionId, onboardingContext })
16171
- );
16172
- maybeWriteFile(
16173
- path3__namespace.join(projectRoot, "ONBOARDING_STATE.md"),
16174
- renderOnboardingStateTemplate({ controlPlaneSessionId, onboardingContext })
16175
- );
16176
- maybeWriteFile(path3__namespace.join(projectRoot, ".agents", "openai.yaml"), renderOpenAiAgentMetadata());
16177
- writeClaudeIndex(projectRoot);
16178
- const readmePath = path3__namespace.join(projectRoot, "README.md");
16179
- const readmeContent = fs__namespace.existsSync(readmePath) ? fs__namespace.readFileSync(readmePath, "utf-8") : "";
16180
- fs__namespace.writeFileSync(readmePath, mergeReadmeSnippet(readmeContent), "utf-8");
16181
16390
  }
16182
16391
 
16183
16392
  // src/cli/commands/init.ts
16184
16393
  function prompt(question, defaultValue) {
16185
- const rl = readline__namespace.createInterface({ input: process.stdin, output: process.stdout });
16394
+ const rl = readline__namespace.createInterface({
16395
+ input: process.stdin,
16396
+ output: process.stdout
16397
+ });
16186
16398
  const suffix = defaultValue ? ` ${brand.muted(`(${defaultValue})`)}` : "";
16187
16399
  return new Promise((resolve2) => {
16188
16400
  rl.question(` ${question}${suffix}: `, (answer) => {
@@ -16195,7 +16407,10 @@ function waitForEnter(message) {
16195
16407
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
16196
16408
  return Promise.resolve();
16197
16409
  }
16198
- const rl = readline__namespace.createInterface({ input: process.stdin, output: process.stdout });
16410
+ const rl = readline__namespace.createInterface({
16411
+ input: process.stdin,
16412
+ output: process.stdout
16413
+ });
16199
16414
  return new Promise((resolve2) => {
16200
16415
  rl.question(` ${message}`, () => {
16201
16416
  rl.close();
@@ -16223,12 +16438,16 @@ async function selectStarterTemplate(requestedTemplate) {
16223
16438
  return DEFAULT_STARTER_TEMPLATE_ID;
16224
16439
  }
16225
16440
  const templates = listStarterTemplates();
16226
- const defaultIndex = templates.findIndex((template) => template.id === DEFAULT_STARTER_TEMPLATE_ID);
16441
+ const defaultIndex = templates.findIndex(
16442
+ (template) => template.id === DEFAULT_STARTER_TEMPLATE_ID
16443
+ );
16227
16444
  step("Choose a starter ontology template");
16228
16445
  for (const [index, template] of templates.entries()) {
16229
16446
  const isDefault = template.id === DEFAULT_STARTER_TEMPLATE_ID;
16230
16447
  const defaultLabel = isDefault ? ` ${brand.muted("(default)")}` : "";
16231
- console.log(` ${index + 1}. ${brand.bold(template.label)}${defaultLabel}`);
16448
+ console.log(
16449
+ ` ${index + 1}. ${brand.bold(template.label)}${defaultLabel}`
16450
+ );
16232
16451
  dim(` ${template.id} \u2014 ${template.summary}`);
16233
16452
  }
16234
16453
  console.log();
@@ -16241,7 +16460,9 @@ async function selectStarterTemplate(requestedTemplate) {
16241
16460
  if (isStarterTemplateId(normalizedChoice)) {
16242
16461
  return normalizedChoice;
16243
16462
  }
16244
- warn(`Unknown template "${rawChoice}". Falling back to ${DEFAULT_STARTER_TEMPLATE_ID}.`);
16463
+ warn(
16464
+ `Unknown template "${rawChoice}". Falling back to ${DEFAULT_STARTER_TEMPLATE_ID}.`
16465
+ );
16245
16466
  return DEFAULT_STARTER_TEMPLATE_ID;
16246
16467
  }
16247
16468
  async function initCommand(projectName, options) {
@@ -16263,7 +16484,9 @@ async function initCommand(projectName, options) {
16263
16484
  info("No API key found. Starting browser login...");
16264
16485
  await waitForEnter("Press Enter to open the login page in your browser...");
16265
16486
  console.log();
16266
- const waiting = spinner("Opening browser and waiting for authentication...");
16487
+ const waiting = spinner(
16488
+ "Opening browser and waiting for authentication..."
16489
+ );
16267
16490
  try {
16268
16491
  apiKey = await loginWithBrowser({
16269
16492
  authBaseUrl: loadAuthUrl(),
@@ -16310,7 +16533,9 @@ async function initCommand(projectName, options) {
16310
16533
  try {
16311
16534
  sandbox = await api.findOrCreateSandbox(name);
16312
16535
  if (sandbox.name === name) {
16313
- creating.succeed(` Sandbox ready: ${brand.secondary(sandbox.sandboxId)}`);
16536
+ creating.succeed(
16537
+ ` Sandbox ready: ${brand.secondary(sandbox.sandboxId)}`
16538
+ );
16314
16539
  }
16315
16540
  } catch (err) {
16316
16541
  creating.fail(` Failed to create sandbox: ${err.message}`);
@@ -16318,7 +16543,9 @@ async function initCommand(projectName, options) {
16318
16543
  }
16319
16544
  const project = createDefaultManifest(name, templateId);
16320
16545
  writeManifestFile(project);
16321
- success(`Created ${brand.bold("granular.json")} with the ${template.label} starter ontology`);
16546
+ success(
16547
+ `Created ${brand.bold("granular.json")} with the ${template.label} starter ontology`
16548
+ );
16322
16549
  writeRcFile({
16323
16550
  sandboxId: sandbox.sandboxId,
16324
16551
  sandboxName: sandbox.name,
@@ -16354,6 +16581,37 @@ async function initCommand(projectName, options) {
16354
16581
  writeAgentsMdFile(getProjectRoot());
16355
16582
  success(`Updated ${brand.bold("AGENTS.md")}`);
16356
16583
  }
16584
+ const effectsPath = path3__namespace.join(getProjectRoot(), EFFECTS_SCRIPT_NAME);
16585
+ const seedPath = path3__namespace.join(getProjectRoot(), SEED_SCRIPT_NAME);
16586
+ const integrationTestPath = path3__namespace.join(
16587
+ getProjectRoot(),
16588
+ INTEGRATION_TEST_NAME
16589
+ );
16590
+ fs__namespace.writeFileSync(
16591
+ effectsPath,
16592
+ generateEffectsScript(sandbox.sandboxId, apiUrl, templateId),
16593
+ "utf-8"
16594
+ );
16595
+ success(
16596
+ `Created ${brand.bold(EFFECTS_SCRIPT_NAME)} (external effect host)`
16597
+ );
16598
+ fs__namespace.writeFileSync(
16599
+ seedPath,
16600
+ generateSeedScript(sandbox.sandboxId, apiUrl, templateId),
16601
+ "utf-8"
16602
+ );
16603
+ success(
16604
+ `Created ${brand.bold(SEED_SCRIPT_NAME)} (sample record seeding script)`
16605
+ );
16606
+ fs__namespace.mkdirSync(path3__namespace.dirname(integrationTestPath), { recursive: true });
16607
+ fs__namespace.writeFileSync(
16608
+ integrationTestPath,
16609
+ generateIntegrationTest(sandbox.sandboxId, apiUrl, templateId),
16610
+ "utf-8"
16611
+ );
16612
+ success(
16613
+ `Created ${brand.bold(INTEGRATION_TEST_NAME)} (real integration smoke test)`
16614
+ );
16357
16615
  writeGranularAgentSkills({
16358
16616
  projectRoot: getProjectRoot(),
16359
16617
  includeCursorHooks: options?.cursorHooks,
@@ -16368,7 +16626,9 @@ async function initCommand(projectName, options) {
16368
16626
  rolesSummary: options?.rolesSummary
16369
16627
  }
16370
16628
  });
16371
- success("Generated Granular agent skills for Codex, Cursor, and Claude Code");
16629
+ success(
16630
+ "Generated Granular agent skills for Codex, Cursor, and Claude Code"
16631
+ );
16372
16632
  success(
16373
16633
  `Created ${brand.bold(ONTOLOGY_BRIEF_NAME)}, ${brand.bold(GRANULAR_START_HERE_NAME)}, and ${brand.bold("ONBOARDING_STATE.md")}; updated ${brand.bold("README.md")}`
16374
16634
  );
@@ -16388,41 +16648,24 @@ async function initCommand(projectName, options) {
16388
16648
  );
16389
16649
  lastManifestId = manifest.manifestId;
16390
16650
  building.text = " Triggering build...";
16391
- const build = await api.triggerBuild(sandbox.sandboxId, manifest.manifestId);
16651
+ const build = await api.triggerBuild(
16652
+ sandbox.sandboxId,
16653
+ manifest.manifestId
16654
+ );
16392
16655
  building.text = " Building...";
16393
16656
  const completed = await api.waitForBuild(build.buildId, (status) => {
16394
16657
  building.text = ` Building... ${brand.muted(status)}`;
16395
16658
  });
16396
16659
  lastBuildId = completed.buildId;
16397
16660
  initialBuildSucceeded = true;
16398
- building.succeed(` Build completed: ${brand.secondary(completed.buildId)}`);
16661
+ building.succeed(
16662
+ ` Build completed: ${brand.secondary(completed.buildId)}`
16663
+ );
16399
16664
  } catch (err) {
16400
16665
  building.fail(` Build failed: ${err.message}`);
16401
16666
  dim("You can retry with `granular build`");
16402
16667
  }
16403
16668
  }
16404
- const effectsPath = path3__namespace.join(getProjectRoot(), EFFECTS_SCRIPT_NAME);
16405
- const seedPath = path3__namespace.join(getProjectRoot(), SEED_SCRIPT_NAME);
16406
- const integrationTestPath = path3__namespace.join(getProjectRoot(), INTEGRATION_TEST_NAME);
16407
- fs__namespace.writeFileSync(
16408
- effectsPath,
16409
- generateEffectsScript(sandbox.sandboxId, apiUrl, templateId),
16410
- "utf-8"
16411
- );
16412
- success(`Created ${brand.bold(EFFECTS_SCRIPT_NAME)} (external effect host)`);
16413
- fs__namespace.writeFileSync(
16414
- seedPath,
16415
- generateSeedScript(sandbox.sandboxId, apiUrl, templateId),
16416
- "utf-8"
16417
- );
16418
- success(`Created ${brand.bold(SEED_SCRIPT_NAME)} (sample record seeding script)`);
16419
- fs__namespace.mkdirSync(path3__namespace.dirname(integrationTestPath), { recursive: true });
16420
- fs__namespace.writeFileSync(
16421
- integrationTestPath,
16422
- generateIntegrationTest(sandbox.sandboxId, apiUrl, templateId),
16423
- "utf-8"
16424
- );
16425
- success(`Created ${brand.bold(INTEGRATION_TEST_NAME)} (real integration smoke test)`);
16426
16669
  writeSandboxAgentDocFile(getProjectRoot(), project.manifest, {
16427
16670
  sandboxId: sandbox.sandboxId,
16428
16671
  apiUrl,
@@ -16430,18 +16673,20 @@ async function initCommand(projectName, options) {
16430
16673
  buildId: lastBuildId,
16431
16674
  buildPending: !initialBuildSucceeded
16432
16675
  });
16433
- success(`Wrote ${brand.bold("GRANULAR_SANDBOX.md")} (agent reference for this sandbox)`);
16676
+ success(
16677
+ `Wrote ${brand.bold("GRANULAR_SANDBOX.md")} (agent reference for this sandbox)`
16678
+ );
16434
16679
  console.log();
16435
16680
  divider();
16436
16681
  console.log();
16437
16682
  success(`Project ${brand.bold(name)} initialized!`);
16438
16683
  console.log();
16439
16684
  const kv = {
16440
- "Ontology": sandbox.sandboxId,
16441
- "Template": template.label,
16442
- "Manifest": "granular.json",
16443
- "Config": ".granularrc",
16444
- "Auth": "granular login / .env.local",
16685
+ Ontology: sandbox.sandboxId,
16686
+ Template: template.label,
16687
+ Manifest: "granular.json",
16688
+ Config: ".granularrc",
16689
+ Auth: "granular login / .env.local",
16445
16690
  "Start here": GRANULAR_START_HERE_NAME,
16446
16691
  "Ontology brief": ONTOLOGY_BRIEF_NAME,
16447
16692
  "Seed script": SEED_SCRIPT_NAME,
@@ -16477,25 +16722,65 @@ async function initCommand(projectName, options) {
16477
16722
  }
16478
16723
  keyValue(kv);
16479
16724
  const nextSteps2 = options?.skipBuild ? [
16480
- { command: "granular login", description: "Ensure local auth is ready before building or testing" },
16481
- { command: `open ${GRANULAR_START_HERE_NAME}`, description: "Read the happy path and file map shared by humans and agents" },
16482
- { command: `open ${ONTOLOGY_BRIEF_NAME}`, description: "Fill the product brief before editing granular.json" },
16483
- { command: "granular build", description: "Build the starter ontology before you seed or simulate" },
16484
- { command: `node --test ${INTEGRATION_TEST_NAME}`, description: "Run the generated real Granular smoke test against your dev ontology" },
16485
- { command: "granular simulate", description: "Open the simulator for this sandbox" }
16725
+ {
16726
+ command: "granular login",
16727
+ description: "Ensure local auth is ready before building or testing"
16728
+ },
16729
+ {
16730
+ command: `open ${GRANULAR_START_HERE_NAME}`,
16731
+ description: "Read the happy path and file map shared by humans and agents"
16732
+ },
16733
+ {
16734
+ command: `open ${ONTOLOGY_BRIEF_NAME}`,
16735
+ description: "Fill the product brief before editing granular.json"
16736
+ },
16737
+ {
16738
+ command: "granular build",
16739
+ description: "Build the starter ontology before you seed or simulate"
16740
+ },
16741
+ {
16742
+ command: `node --test ${INTEGRATION_TEST_NAME}`,
16743
+ description: "Run the generated real Granular smoke test against your dev ontology"
16744
+ },
16745
+ {
16746
+ command: "granular simulate",
16747
+ description: "Open the simulator for this sandbox"
16748
+ }
16486
16749
  ] : [
16487
- { command: `open ${GRANULAR_START_HERE_NAME}`, description: "Read the happy path and file map shared by humans and agents" },
16488
- { command: `open ${ONTOLOGY_BRIEF_NAME}`, description: "Fill the brief, then use it to guide your first manifest edits" },
16489
- { command: "granular build", description: "Compile the ontology after updating granular.json" },
16490
- { command: "granular document", description: "Refresh GRANULAR_SANDBOX.md so agents and tests use exact keys" },
16491
- { command: `node --test ${INTEGRATION_TEST_NAME}`, description: "Run the generated real Granular smoke test against your dev ontology" },
16492
- { command: "granular simulate", description: "Open the simulator for this sandbox" }
16750
+ {
16751
+ command: `open ${GRANULAR_START_HERE_NAME}`,
16752
+ description: "Read the happy path and file map shared by humans and agents"
16753
+ },
16754
+ {
16755
+ command: `open ${ONTOLOGY_BRIEF_NAME}`,
16756
+ description: "Fill the brief, then use it to guide your first manifest edits"
16757
+ },
16758
+ {
16759
+ command: "granular build",
16760
+ description: "Compile the ontology after updating granular.json"
16761
+ },
16762
+ {
16763
+ command: "granular document",
16764
+ description: "Refresh GRANULAR_SANDBOX.md so agents and tests use exact keys"
16765
+ },
16766
+ {
16767
+ command: `node --test ${INTEGRATION_TEST_NAME}`,
16768
+ description: "Run the generated real Granular smoke test against your dev ontology"
16769
+ },
16770
+ {
16771
+ command: "granular simulate",
16772
+ description: "Open the simulator for this sandbox"
16773
+ }
16493
16774
  ];
16494
16775
  nextSteps(nextSteps2);
16495
16776
  console.log();
16496
16777
  dim("Mental model:");
16497
- dim(" Query and navigate data through the ontology. Use effects only for actions that leave the sandbox.");
16498
- dim(" The seed script shows how records enter the graph. The effects script shows how external systems plug in.");
16778
+ dim(
16779
+ " Query and navigate data through the ontology. Use effects only for actions that leave the sandbox."
16780
+ );
16781
+ dim(
16782
+ " The seed script shows synchronous batch recording with progress. The effects script shows how external systems plug in."
16783
+ );
16499
16784
  console.log();
16500
16785
  }
16501
16786
  var DEFAULT_LOCAL_API_KEY2 = "gn_sk_tenant_default_principal_local_e2e_00000000";
@@ -18015,7 +18300,9 @@ var Session = class {
18015
18300
  }
18016
18301
  }
18017
18302
  if (!revision) {
18018
- throw new Error("No domain revision available. Register live effects or ensure the build schema is activated.");
18303
+ throw new Error(
18304
+ "No domain revision available. Register live effects or ensure the build schema is activated."
18305
+ );
18019
18306
  }
18020
18307
  const result = await this.client.call("job.submit", {
18021
18308
  domainRevision: revision,
@@ -18051,7 +18338,11 @@ var Session = class {
18051
18338
  const prompt3 = this.promptCache.get(promptId);
18052
18339
  const resolvedAnswer = resolvePromptAnswer(prompt3, answer);
18053
18340
  this.promptCache.delete(promptId);
18054
- await this.client.call("prompt.answer", { promptId, answer: resolvedAnswer, value: resolvedAnswer });
18341
+ await this.client.call("prompt.answer", {
18342
+ promptId,
18343
+ answer: resolvedAnswer,
18344
+ value: resolvedAnswer
18345
+ });
18055
18346
  }
18056
18347
  /**
18057
18348
  * Get the current list of available effects.
@@ -18083,7 +18374,8 @@ var Session = class {
18083
18374
  for (const tool of cat.tools) {
18084
18375
  if (!tool?.name) continue;
18085
18376
  const existing = toolMap.get(tool.name);
18086
- if (existing?.publishedAt && cat.publishedAt && existing.publishedAt > cat.publishedAt) continue;
18377
+ if (existing?.publishedAt && cat.publishedAt && existing.publishedAt > cat.publishedAt)
18378
+ continue;
18087
18379
  const isLocal = clientId === this.clientId;
18088
18380
  const ready = isLocal ? this.effects.has(tool.name) || this.toolHandlers.has(tool.name) || this.instanceTools.has(tool.name) : true;
18089
18381
  toolMap.set(tool.name, {
@@ -18121,7 +18413,10 @@ var Session = class {
18121
18413
  return () => {
18122
18414
  const listeners = this.eventListeners.get("effects:changed");
18123
18415
  if (listeners) {
18124
- this.eventListeners.set("effects:changed", listeners.filter((h) => h !== handler));
18416
+ this.eventListeners.set(
18417
+ "effects:changed",
18418
+ listeners.filter((h) => h !== handler)
18419
+ );
18125
18420
  }
18126
18421
  };
18127
18422
  }
@@ -18137,7 +18432,10 @@ var Session = class {
18137
18432
  return () => {
18138
18433
  const listeners = this.eventListeners.get("tools:changed");
18139
18434
  if (listeners) {
18140
- this.eventListeners.set("tools:changed", listeners.filter((h) => h !== handler));
18435
+ this.eventListeners.set(
18436
+ "tools:changed",
18437
+ listeners.filter((h) => h !== handler)
18438
+ );
18141
18439
  }
18142
18440
  };
18143
18441
  }
@@ -18145,11 +18443,16 @@ var Session = class {
18145
18443
  * Get the current domain state and available tools
18146
18444
  */
18147
18445
  async getDomain() {
18148
- const summary = await this.client.call("domain.getSummary", {});
18446
+ const summary = await this.client.call(
18447
+ "domain.getSummary",
18448
+ {}
18449
+ );
18149
18450
  if (summary.activeDomainRevision) {
18150
18451
  this.currentDomainRevision = summary.activeDomainRevision;
18151
18452
  } else {
18152
- this.currentDomainRevision = this.extractDomainRevisionFromDoc(this.client.doc);
18453
+ this.currentDomainRevision = this.extractDomainRevisionFromDoc(
18454
+ this.client.doc
18455
+ );
18153
18456
  }
18154
18457
  return summary;
18155
18458
  }
@@ -18324,20 +18627,31 @@ import { ${allImports} } from "./sandbox-tools";
18324
18627
  off(event, handler) {
18325
18628
  const handlers = this.eventListeners.get(event);
18326
18629
  if (handlers) {
18327
- this.eventListeners.set(event, handlers.filter((h) => h !== handler));
18630
+ this.eventListeners.set(
18631
+ event,
18632
+ handlers.filter((h) => h !== handler)
18633
+ );
18328
18634
  }
18329
18635
  }
18330
18636
  // --- Internal ---
18331
18637
  setupToolInvokeHandler() {
18332
18638
  this.client.registerRpcHandler("tool.invoke", async (params) => {
18333
18639
  const { callId, toolName, input } = params;
18334
- this.emit("effect:invoke", { callId, effectKey: toolName, toolName, input });
18640
+ this.emit("effect:invoke", {
18641
+ callId,
18642
+ effectKey: toolName,
18643
+ toolName,
18644
+ input
18645
+ });
18335
18646
  this.emit("tool:invoke", { callId, toolName, input });
18336
18647
  const handler = this.toolHandlers.get(toolName);
18337
18648
  if (!handler) {
18338
18649
  await this.client.call("tool.result", {
18339
18650
  callId,
18340
- error: { code: "TOOL_NOT_FOUND", message: `Tool handler not found: ${toolName}` }
18651
+ error: {
18652
+ code: "TOOL_NOT_FOUND",
18653
+ message: `Tool handler not found: ${toolName}`
18654
+ }
18341
18655
  });
18342
18656
  return;
18343
18657
  }
@@ -18346,7 +18660,11 @@ import { ${allImports} } from "./sandbox-tools";
18346
18660
  const invocationContext = this.buildLegacyEffectContext();
18347
18661
  if (this.instanceTools.has(toolName) && input && typeof input === "object" && "_objectId" in input) {
18348
18662
  const { _objectId, ...restParams } = input;
18349
- result = await handler(_objectId, restParams, invocationContext);
18663
+ result = await handler(
18664
+ _objectId,
18665
+ restParams,
18666
+ invocationContext
18667
+ );
18350
18668
  } else {
18351
18669
  result = await handler(input, invocationContext);
18352
18670
  }
@@ -18358,7 +18676,11 @@ import { ${allImports} } from "./sandbox-tools";
18358
18676
  });
18359
18677
  } catch (error2) {
18360
18678
  const errorMessage = error2 instanceof Error ? error2.message : String(error2);
18361
- this.emit("effect:result", { callId, effectKey: toolName, error: errorMessage });
18679
+ this.emit("effect:result", {
18680
+ callId,
18681
+ effectKey: toolName,
18682
+ error: errorMessage
18683
+ });
18362
18684
  this.emit("tool:result", { callId, error: errorMessage });
18363
18685
  await this.client.call("tool.result", {
18364
18686
  callId,
@@ -18368,7 +18690,10 @@ import { ${allImports} } from "./sandbox-tools";
18368
18690
  });
18369
18691
  }
18370
18692
  setupEventHandlers() {
18371
- this.client.on("open", (payload) => this.emit("open", payload || {}));
18693
+ this.client.on(
18694
+ "open",
18695
+ (payload) => this.emit("open", payload || {})
18696
+ );
18372
18697
  this.client.on("sync", (doc) => {
18373
18698
  this.currentDomainRevision = this.extractDomainRevisionFromDoc(doc);
18374
18699
  this.emit("sync", doc);
@@ -18382,8 +18707,14 @@ import { ${allImports} } from "./sandbox-tools";
18382
18707
  };
18383
18708
  this.client.on("prompt", emitPrompt);
18384
18709
  this.client.on("prompt.request", emitPrompt);
18385
- this.client.on("disconnect", (payload) => this.emit("disconnect", payload || {}));
18386
- this.client.on("reconnect_error", (payload) => this.emit("reconnect_error", payload || {}));
18710
+ this.client.on(
18711
+ "disconnect",
18712
+ (payload) => this.emit("disconnect", payload || {})
18713
+ );
18714
+ this.client.on(
18715
+ "reconnect_error",
18716
+ (payload) => this.emit("reconnect_error", payload || {})
18717
+ );
18387
18718
  this.client.on("job.status", (data) => {
18388
18719
  this.emit("job:status", data);
18389
18720
  });
@@ -18499,7 +18830,10 @@ function sanitizeFeedbackValue(value, depth = 0, seen = /* @__PURE__ */ new Weak
18499
18830
  return "[Circular]";
18500
18831
  }
18501
18832
  seen.add(value);
18502
- const entries = Object.entries(value).slice(0, MAX_FEEDBACK_OBJECT_KEYS);
18833
+ const entries = Object.entries(value).slice(
18834
+ 0,
18835
+ MAX_FEEDBACK_OBJECT_KEYS
18836
+ );
18503
18837
  const sanitized = {};
18504
18838
  for (const [key, entryValue] of entries) {
18505
18839
  sanitized[key] = sanitizeFeedbackValue(entryValue, depth + 1, seen);
@@ -18550,12 +18884,18 @@ var JobImplementation = class {
18550
18884
  if (progressData.execId === id || progressData.jobId === id) {
18551
18885
  if (progressData.stdout) {
18552
18886
  this.markStarted();
18553
- this.metadata.stdout = [...this.metadata.stdout, truncateFeedbackString(progressData.stdout)].slice(-100);
18887
+ this.metadata.stdout = [
18888
+ ...this.metadata.stdout,
18889
+ truncateFeedbackString(progressData.stdout)
18890
+ ].slice(-100);
18554
18891
  this.emit("stdout", progressData.stdout);
18555
18892
  }
18556
18893
  if (progressData.stderr) {
18557
18894
  this.markStarted();
18558
- this.metadata.stderr = [...this.metadata.stderr, truncateFeedbackString(progressData.stderr)].slice(-100);
18895
+ this.metadata.stderr = [
18896
+ ...this.metadata.stderr,
18897
+ truncateFeedbackString(progressData.stderr)
18898
+ ].slice(-100);
18559
18899
  this.emit("stderr", progressData.stderr);
18560
18900
  }
18561
18901
  }
@@ -18577,12 +18917,18 @@ var JobImplementation = class {
18577
18917
  });
18578
18918
  this.client.on(`job.${id}.stdout`, (line) => {
18579
18919
  this.markStarted();
18580
- this.metadata.stdout = [...this.metadata.stdout, truncateFeedbackString(String(line))].slice(-100);
18920
+ this.metadata.stdout = [
18921
+ ...this.metadata.stdout,
18922
+ truncateFeedbackString(String(line))
18923
+ ].slice(-100);
18581
18924
  this.emit("stdout", line);
18582
18925
  });
18583
18926
  this.client.on(`job.${id}.stderr`, (line) => {
18584
18927
  this.markStarted();
18585
- this.metadata.stderr = [...this.metadata.stderr, truncateFeedbackString(String(line))].slice(-100);
18928
+ this.metadata.stderr = [
18929
+ ...this.metadata.stderr,
18930
+ truncateFeedbackString(String(line))
18931
+ ].slice(-100);
18586
18932
  this.emit("stderr", line);
18587
18933
  });
18588
18934
  this.client.on(`job.${id}.result`, (result) => {
@@ -18614,7 +18960,11 @@ var JobImplementation = class {
18614
18960
  this.client.on("job.failed", (data) => {
18615
18961
  const jobData = data;
18616
18962
  if (jobData.jobId === id) {
18617
- this.finalize("failed", void 0, jobData.error || new Error("Job failed"));
18963
+ this.finalize(
18964
+ "failed",
18965
+ void 0,
18966
+ jobData.error || new Error("Job failed")
18967
+ );
18618
18968
  this.emit("status", this.status);
18619
18969
  }
18620
18970
  });
@@ -18627,7 +18977,12 @@ var JobImplementation = class {
18627
18977
  input: sanitizeFeedbackValue(d.input),
18628
18978
  startedAt: d.timestamp || Date.now()
18629
18979
  });
18630
- this.emit("toolCallStart", { callId: d.callId, toolName: d.toolName, input: d.input, timestamp: d.timestamp });
18980
+ this.emit("toolCallStart", {
18981
+ callId: d.callId,
18982
+ toolName: d.toolName,
18983
+ input: d.input,
18984
+ timestamp: d.timestamp
18985
+ });
18631
18986
  }
18632
18987
  });
18633
18988
  this.client.on("tool.call.end", (data) => {
@@ -18642,7 +18997,25 @@ var JobImplementation = class {
18642
18997
  completedAt,
18643
18998
  durationMs: typeof d.durationMs === "number" ? d.durationMs : void 0
18644
18999
  });
18645
- this.emit("toolCallEnd", { callId: d.callId, toolName: d.toolName, result: d.result, error: d.error, durationMs: d.durationMs, timestamp: d.timestamp });
19000
+ this.emit("toolCallEnd", {
19001
+ callId: d.callId,
19002
+ toolName: d.toolName,
19003
+ result: d.result,
19004
+ error: d.error,
19005
+ durationMs: d.durationMs,
19006
+ timestamp: d.timestamp
19007
+ });
19008
+ }
19009
+ });
19010
+ this.client.on("job.agent_message", (data) => {
19011
+ const d = data;
19012
+ if (d.jobId === id) {
19013
+ this.emit("agentMessage", {
19014
+ messageId: d.messageId,
19015
+ reply: typeof d.reply === "string" ? d.reply : "",
19016
+ show: d.show,
19017
+ timestamp: d.timestamp || Date.now()
19018
+ });
18646
19019
  }
18647
19020
  });
18648
19021
  }
@@ -18712,7 +19085,9 @@ var JobImplementation = class {
18712
19085
  }
18713
19086
  upsertToolCall(next) {
18714
19087
  const callId = next.callId || `tool-call-${Date.now()}`;
18715
- const existingIndex = this.metadata.toolCalls.findIndex((entry) => entry.callId === callId);
19088
+ const existingIndex = this.metadata.toolCalls.findIndex(
19089
+ (entry) => entry.callId === callId
19090
+ );
18716
19091
  const existing = existingIndex >= 0 ? this.metadata.toolCalls[existingIndex] : void 0;
18717
19092
  const merged = {
18718
19093
  ...existing,