@granular-software/sdk 0.4.29 → 0.4.31
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 +25 -20
- package/dist/agent-evals.d.mts +7 -6
- package/dist/agent-evals.d.ts +7 -6
- package/dist/agent-evals.js +1002 -525
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +1002 -525
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/agent-harness.d.mts +11 -2
- package/dist/agent-harness.d.ts +11 -2
- package/dist/agent-harness.js +248 -125
- package/dist/agent-harness.js.map +1 -1
- package/dist/agent-harness.mjs +246 -126
- package/dist/agent-harness.mjs.map +1 -1
- package/dist/cli/index.js +773 -394
- package/dist/{client-CigjaeO8.d.mts → client-CTH1hfwe.d.mts} +255 -117
- package/dist/{client-CigjaeO8.d.ts → client-CTH1hfwe.d.ts} +255 -117
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +938 -435
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +934 -436
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -6597,9 +6597,9 @@ async function main() {
|
|
|
6597
6597
|
});
|
|
6598
6598
|
|
|
6599
6599
|
// Connect as one app user so the SDK can create an environment for the seed run.
|
|
6600
|
-
const env = await granular.
|
|
6600
|
+
const env = await granular.openEnvironment({
|
|
6601
6601
|
ontology: SANDBOX_ID,
|
|
6602
|
-
|
|
6602
|
+
tag: 'dev',
|
|
6603
6603
|
userId: '${template.seedUser.userId}',
|
|
6604
6604
|
name: '${template.seedUser.name}',
|
|
6605
6605
|
email: '${template.seedUser.email}',
|
|
@@ -6621,7 +6621,7 @@ async function main() {
|
|
|
6621
6621
|
const updatedCount = results.length - createdCount;
|
|
6622
6622
|
console.log(\`[Seed] Created \${createdCount} records, updated \${updatedCount} records.\`);
|
|
6623
6623
|
} finally {
|
|
6624
|
-
await
|
|
6624
|
+
await session.disconnect();
|
|
6625
6625
|
}
|
|
6626
6626
|
|
|
6627
6627
|
console.log(\`Seeded \${records.length} records into \${SANDBOX_ID}\`);
|
|
@@ -6670,7 +6670,7 @@ async function main() {
|
|
|
6670
6670
|
apiUrl: process.env.GRANULAR_API_URL ?? API_URL,
|
|
6671
6671
|
});
|
|
6672
6672
|
|
|
6673
|
-
await granular.
|
|
6673
|
+
await granular.ontology(SANDBOX_ID).effects.registerMany([
|
|
6674
6674
|
${registrations}
|
|
6675
6675
|
]);
|
|
6676
6676
|
|
|
@@ -6688,7 +6688,7 @@ ${registrations}
|
|
|
6688
6688
|
shuttingDown = true;
|
|
6689
6689
|
clearInterval(keepAliveTimer);
|
|
6690
6690
|
console.log(\`Shutting down after \${signal}...\`);
|
|
6691
|
-
await granular.
|
|
6691
|
+
await granular.ontology(SANDBOX_ID).effects.disconnect();
|
|
6692
6692
|
resolve();
|
|
6693
6693
|
process.exit(0);
|
|
6694
6694
|
};
|
|
@@ -6738,9 +6738,9 @@ test('granular connect + submitJob smoke test', {
|
|
|
6738
6738
|
apiUrl: process.env.GRANULAR_API_URL ?? rc.apiUrl ?? DEFAULT_API_URL,
|
|
6739
6739
|
});
|
|
6740
6740
|
|
|
6741
|
-
const environment = await granular.
|
|
6741
|
+
const environment = await granular.openEnvironment({
|
|
6742
6742
|
ontology: rc.sandboxId,
|
|
6743
|
-
|
|
6743
|
+
tag: 'dev',
|
|
6744
6744
|
userId: '${template.seedUser.userId}',
|
|
6745
6745
|
name: '${template.seedUser.name}',
|
|
6746
6746
|
email: '${template.seedUser.email}',
|
|
@@ -6751,7 +6751,8 @@ test('granular connect + submitJob smoke test', {
|
|
|
6751
6751
|
assert.ok(environment.environmentId);
|
|
6752
6752
|
assert.ok(environment.sessionId);
|
|
6753
6753
|
|
|
6754
|
-
const
|
|
6754
|
+
const session = await environment.sessions.create();
|
|
6755
|
+
const job = await session.submitJob(\`
|
|
6755
6756
|
return {
|
|
6756
6757
|
ok: true,
|
|
6757
6758
|
template: '${template.id}',
|
|
@@ -6762,7 +6763,7 @@ return {
|
|
|
6762
6763
|
assert.equal(result.ok, true);
|
|
6763
6764
|
assert.equal(result.template, '${template.id}');
|
|
6764
6765
|
} finally {
|
|
6765
|
-
await
|
|
6766
|
+
await session.disconnect();
|
|
6766
6767
|
}
|
|
6767
6768
|
});
|
|
6768
6769
|
`;
|
|
@@ -8960,7 +8961,7 @@ function manifestGuideGranularProductSection() {
|
|
|
8960
8961
|
2. **Build** that declaration from \`granular.json\` so the service compiles types and tooling for a **sandbox** (a workspace identified by \`sbx_\u2026\`).
|
|
8961
8962
|
3. **Store** **records** (instances of your classes) and **run jobs** (code executed in Granular\u2019s **sandbox runtime**) that call generated helpers and effects.
|
|
8962
8963
|
|
|
8963
|
-
**Split of responsibility:** Data and domain rules live in Granular; **secrets, payment providers, email, and arbitrary HTTP** stay in **your** server via **effect handlers** you register with \`
|
|
8964
|
+
**Split of responsibility:** Data and domain rules live in Granular; **secrets, payment providers, email, and arbitrary HTTP** stay in **your** server via **effect handlers** you register with \`granular.ontology(sandboxId).effects.registerMany\`.
|
|
8964
8965
|
|
|
8965
8966
|
---
|
|
8966
8967
|
|
|
@@ -8985,10 +8986,10 @@ The rest of this guide assumes these terms.
|
|
|
8985
8986
|
| **Manifest** | \`granular.json\` (definition under \`manifest\`). **Source** in the repo until you create a new ontology **version** from it. |
|
|
8986
8987
|
| **Version** | Immutable ontology state derived from one manifest revision. Tags like \`dev\` and \`prod\` point to versions. |
|
|
8987
8988
|
| **Build run** | The CI-style compilation process. \`granular build\` creates or reuses the ontology version, then runs a build for it. |
|
|
8988
|
-
| **Environment** | Return value of \`granular.
|
|
8989
|
-
| **Session** |
|
|
8990
|
-
| **Job** | Code string passed to \`
|
|
8991
|
-
| **Effect** | Declared with \`withEffect\`; **handler** registered
|
|
8989
|
+
| **Environment** | Return value of \`granular.openEnvironment({ ontology, tag, userId, \u2026 })\`. It is your **server-side** handle for \`recordObject\`, \`recordObjects\`, \`graphql\`, and \`sessions.create()\`. |
|
|
8990
|
+
| **Session** | Live runtime connection opened from an environment with \`environment.sessions.create()\` or \`environment.sessions.connect()\`. Job, prompt, and streaming APIs live here. |
|
|
8991
|
+
| **Job** | Code string passed to \`session.submitJob(code)\`. Runs in Granular\u2019s **sandbox runtime** with access to \`./sandbox-tools\` (generated classes + effect entrypoints). |
|
|
8992
|
+
| **Effect** | Declared with \`withEffect\`; **handler** registered in **your** process with \`granular.ontology(sandboxId).effects.registerMany(...)\`. Jobs invoke effects; handlers do IO outside Granular. |
|
|
8992
8993
|
| **Class** | Entity **kind** in the domain (e.g. \`book\`) with \`has\` fields in the manifest. |
|
|
8993
8994
|
| **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. |
|
|
8994
8995
|
| **Relationship** | Declared link between two classes; defines **property names** and cardinality. Keys appear in \`recordObject({ relationships })\`. |
|
|
@@ -9007,10 +9008,10 @@ function manifestGuideEndToEndSection() {
|
|
|
9007
9008
|
|------|--------|---------|
|
|
9008
9009
|
| 1 | Edit \`granular.json\` (\`manifest\` \u2192 \`volumes\` \u2192 \`operations\`) | Domain **source** in the repo |
|
|
9009
9010
|
| 2 | \`granular build\` | Manifest **uploaded**; ontology version created or reused; build run compiles it; errors surface here |
|
|
9010
|
-
| 3 | (Optional) Run your **effects host** \u2014 e.g. \`npx tsx granular-effects.ts\` \u2014 calling \`
|
|
9011
|
-
| 4 | \`new Granular({ apiKey })\` then \`
|
|
9011
|
+
| 3 | (Optional) Run your **effects host** \u2014 e.g. \`npx tsx granular-effects.ts\` \u2014 calling \`granular.ontology(sandboxId).effects.registerMany(...)\` | Effect **handlers** attached in **your** process |
|
|
9012
|
+
| 4 | \`new Granular({ apiKey })\` then \`openEnvironment({ ontology, tag, userId, permissions })\` | **\`Environment\`** handle for that ontology/user pair |
|
|
9012
9013
|
| 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. |
|
|
9013
|
-
| 6 | \`submitJob(\`\u2026\`)\`
|
|
9014
|
+
| 6 | \`const session = await environment.sessions.create();\` then \`session.submitJob(\`\u2026\`)\` | **Jobs** run; may call **effects** \u2192 your handlers execute and return results |
|
|
9014
9015
|
|
|
9015
9016
|
**Accuracy tip:** After changing the manifest, always **re-build** before assuming generated types, \`./sandbox-tools\` names, or effect signatures match the file on disk.`;
|
|
9016
9017
|
}
|
|
@@ -9024,9 +9025,9 @@ function sandboxDocMainConceptsSection() {
|
|
|
9024
9025
|
| **Manifest** | \`granular.json\` \u2014 **source**; edit here, then \`granular build\`. |
|
|
9025
9026
|
| **Version** | Immutable ontology state created from one manifest revision. |
|
|
9026
9027
|
| **Build run** | Compilation run that validates and materializes a version. |
|
|
9027
|
-
| **Environment** | \`
|
|
9028
|
-
| **Session** |
|
|
9029
|
-
| **Job** | \`submitJob\` code using \`./sandbox-tools\`. |
|
|
9028
|
+
| **Environment** | \`openEnvironment()\` result \u2014 **record**, **recordObjects**, **graphql**, and **session lifecycle** for this sandbox. |
|
|
9029
|
+
| **Session** | Live runtime connection created from an environment; job and prompt APIs live here. |
|
|
9030
|
+
| **Job** | \`session.submitJob(...)\` code using \`./sandbox-tools\`. |
|
|
9030
9031
|
| **Effect** | Declared in manifest; **handler** in your process. |
|
|
9031
9032
|
|
|
9032
9033
|
**Full product + manifest how-to:** [docs/granular-manifest.md](docs/granular-manifest.md).`;
|
|
@@ -14230,17 +14231,8 @@ var searchableMetamodelPackage = defineMetamodelPackage({
|
|
|
14230
14231
|
}
|
|
14231
14232
|
},
|
|
14232
14233
|
domain: {
|
|
14233
|
-
applyToPropertyIR(propertyIR,
|
|
14234
|
-
|
|
14235
|
-
return propertyIR;
|
|
14236
|
-
}
|
|
14237
|
-
return {
|
|
14238
|
-
...propertyIR,
|
|
14239
|
-
docs: [
|
|
14240
|
-
...propertyIR.docs,
|
|
14241
|
-
propertySummary.searchablePhonetic ? "Searchable via `search` using FalkorDB full-text query syntax with phonetic matching enabled." : "Searchable via `search` using FalkorDB full-text query syntax."
|
|
14242
|
-
]
|
|
14243
|
-
};
|
|
14234
|
+
applyToPropertyIR(propertyIR, _propertySummary) {
|
|
14235
|
+
return propertyIR;
|
|
14244
14236
|
}
|
|
14245
14237
|
}
|
|
14246
14238
|
});
|
|
@@ -14869,7 +14861,7 @@ function generateManifestAgentGuide(options) {
|
|
|
14869
14861
|
);
|
|
14870
14862
|
return `# Granular manifest guide (for coding agents)
|
|
14871
14863
|
|
|
14872
|
-
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 (\`
|
|
14864
|
+
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 (\`openEnvironment\` \u2192 **environment**, \`environment.sessions.create()\` \u2192 **session**, \`recordObject\`, **effects**). For **this repository\u2019s** exact class names, relationship keys, and effect payloads only, see [GRANULAR_SANDBOX.md](../GRANULAR_SANDBOX.md).
|
|
14873
14865
|
|
|
14874
14866
|
**Precedence:** User instructions in chat override this file. When in doubt, read \`granular.json\` and [AGENTS.md](../AGENTS.md).
|
|
14875
14867
|
|
|
@@ -15127,14 +15119,14 @@ Example:
|
|
|
15127
15119
|
|
|
15128
15120
|
| Field | Meaning |
|
|
15129
15121
|
|-------|---------|
|
|
15130
|
-
| \`name\` | Must match the
|
|
15122
|
+
| \`name\` | Must match the handler name you register from your effect host. |
|
|
15131
15123
|
| \`attachedClass\` | Omit for **global** effects. Set for class-bound effects. |
|
|
15132
15124
|
| \`isStatic\` | \`true\` \u2192 static method; \`false\` or omit \u2192 **instance** method (handler receives the object **id** first). |
|
|
15133
15125
|
| \`inputSchema\` / \`outputSchema\` | JSON Schema; used for codegen and validation. |
|
|
15134
15126
|
|
|
15135
|
-
**Lifecycle:** declare in manifest \u2192 \`granular build\` creates or reuses a version and runs the build \u2192 implement handlers \u2192 \`granular.
|
|
15127
|
+
**Lifecycle:** declare in manifest \u2192 \`granular build\` creates or reuses a version and runs the build \u2192 implement handlers \u2192 \`granular.ontology(sandboxId).effects.registerMany([...])\` \u2192 jobs call generated methods \u2192 your handler runs and returns the result to the job.
|
|
15136
15128
|
|
|
15137
|
-
**Permissions:** \`
|
|
15129
|
+
**Permissions:** \`openEnvironment({ permissions: [...] })\` assigns profiles (e.g. \`default\`) that can allow or deny which effects a user may call.
|
|
15138
15130
|
|
|
15139
15131
|
### Effect metamodels
|
|
15140
15132
|
|
|
@@ -15200,30 +15192,38 @@ ${effectMetamodelTable}
|
|
|
15200
15192
|
| API | Purpose |
|
|
15201
15193
|
|-----|---------|
|
|
15202
15194
|
| \`new Granular({ apiKey, apiUrl?, endpointMode?, token?, tokenProvider?, \u2026 })\` | Auth. Env: \`GRANULAR_API_KEY\`, \`GRANULAR_API_URL\`, \`GRANULAR_ENDPOINT_MODE\`. |
|
|
15203
|
-
| \`recordUser({ userId, name?, email?, permissions? })\` | Upsert user for later \`
|
|
15204
|
-
| \`
|
|
15205
|
-
| \`
|
|
15206
|
-
| \`
|
|
15195
|
+
| \`recordUser({ userId, name?, email?, permissions? })\` | Upsert user for later \`openEnvironment\`. |
|
|
15196
|
+
| \`openEnvironment({ ontology, tag?, userId?, granularId?, user?, permissions, createFreshIfOutdated?, ... })\` | Resolves or creates an **\`Environment\`** handle. |
|
|
15197
|
+
| \`granular.ontology(sandboxId).effects.registerMany(effects)\` / \`.register(effect)\` | Register handlers for manifest \`withEffect\` declarations. |
|
|
15198
|
+
| \`granular.ontology(sandboxId).effects.unregister(name)\` / \`.clear()\` / \`.disconnect()\` | Stop effect handlers for a sandbox. |
|
|
15207
15199
|
| \`granular.sandboxes\` | \`.list()\`, \`.get\`, \`.create\`, \`.update\`, \`.delete\` |
|
|
15208
15200
|
| \`granular.permissionProfiles\` | Permission profiles per sandbox. |
|
|
15209
|
-
| \`granular.environments\` | List/create/delete environments (
|
|
15201
|
+
| \`granular.environments\` | List/create/delete environments (advanced control-plane API). |
|
|
15210
15202
|
| \`granular.subjects\` | Subjects / assignments (see typings). |
|
|
15211
15203
|
|
|
15212
|
-
### \`Environment\` (
|
|
15204
|
+
### \`Environment\` (sessionless environment handle)
|
|
15213
15205
|
|
|
15214
15206
|
| API | Purpose |
|
|
15215
15207
|
|-----|---------|
|
|
15216
|
-
| \`environmentId\`, \`sandboxId\`, \`apiEndpoint\`, \u2026 |
|
|
15208
|
+
| \`environmentId\`, \`sandboxId\`, \`apiEndpoint\`, \u2026 | Environment context. |
|
|
15217
15209
|
| \`applyManifest(manifest)\` | Apply manifest operations at runtime (alternative to CLI build for dynamic ontologies). |
|
|
15218
15210
|
| \`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\`. |
|
|
15219
15211
|
| \`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. |
|
|
15220
15212
|
| \`graphql(query, variables?)\` | **GraphQL** \u2014 see dedicated subsection below. |
|
|
15221
15213
|
| \`defineRelationship\`, \`getRelationships\`, \`attach\`, \`detach\`, \`listRelated\` | Imperative relationship operations (same ideas as manifest \`defineRelationship\`). |
|
|
15214
|
+
| \`sessions.list()\`, \`sessions.create()\`, \`sessions.connect()\`, \`sessions.reopen()\`, \`sessions.close()\` | Session lifecycle for this environment. |
|
|
15215
|
+
|
|
15216
|
+
### \`Session\` (live runtime connection)
|
|
15217
|
+
|
|
15218
|
+
| API | Purpose |
|
|
15219
|
+
|-----|---------|
|
|
15222
15220
|
| \`submitJob(code)\` | Run code in the sandbox; import from \`./sandbox-tools\`. |
|
|
15221
|
+
| \`answerPrompt(...)\`, \`appendMessage(...)\` | Human-in-the-loop and conversation APIs. |
|
|
15223
15222
|
| \`getDomain()\`, \`getDomainTypes()\`, \`getDomainDocs()\`, \`getDomainDocumentation()\` | Domain summary and generated TypeScript / docs. |
|
|
15224
|
-
| \`getEffects()\` / \`getTools()\`, \`
|
|
15225
|
-
| \`checkReadiness()\`, \`on('readiness',
|
|
15223
|
+
| \`getEffects()\` / \`getTools()\`, \`session.on("effects:changed", ...)\` | Effect catalog and live updates. |
|
|
15224
|
+
| \`checkReadiness()\`, \`on('readiness', ...)\` | Runtime readiness. |
|
|
15226
15225
|
| \`getHeap()\` | Session state snapshot (advanced). |
|
|
15226
|
+
| \`disconnect()\` | Close the live runtime session. |
|
|
15227
15227
|
| \`rpc(method, params)\` | Low-level session RPC (advanced). |
|
|
15228
15228
|
| \`disconnect()\` | End the session. |
|
|
15229
15229
|
|
|
@@ -15423,9 +15423,9 @@ function generateSandboxAgentDoc(manifest, meta) {
|
|
|
15423
15423
|
lines.push(` // apiUrl: process.env.GRANULAR_API_URL, // optional`);
|
|
15424
15424
|
lines.push(`});`);
|
|
15425
15425
|
lines.push("");
|
|
15426
|
-
lines.push(`const env = await granular.
|
|
15426
|
+
lines.push(`const env = await granular.openEnvironment({`);
|
|
15427
15427
|
lines.push(` ontology: '${meta.sandboxId}',`);
|
|
15428
|
-
lines.push(`
|
|
15428
|
+
lines.push(` tag: 'dev',`);
|
|
15429
15429
|
lines.push(` userId: 'your_app_user_id',`);
|
|
15430
15430
|
lines.push(` permissions: ['default'],`);
|
|
15431
15431
|
lines.push(`});`);
|
|
@@ -15544,7 +15544,7 @@ function generateSandboxAgentDoc(manifest, meta) {
|
|
|
15544
15544
|
"1. **Build** the manifest so the domain package includes the effect (`granular build`)."
|
|
15545
15545
|
);
|
|
15546
15546
|
lines.push(
|
|
15547
|
-
"2. **Register** a handler whose `name` (and `className` / `static` when applicable) matches \u2014 use `granular.
|
|
15547
|
+
"2. **Register** a handler whose `name` (and `className` / `static` when applicable) matches \u2014 use `granular.ontology('" + meta.sandboxId + "').effects.registerMany([...])` in your host process (see `granular-effects.ts` in starter projects)."
|
|
15548
15548
|
);
|
|
15549
15549
|
lines.push(
|
|
15550
15550
|
"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."
|
|
@@ -15612,7 +15612,8 @@ function generateSandboxAgentDoc(manifest, meta) {
|
|
|
15612
15612
|
const globalEffects = effects2.filter((e) => !e.attachedClass);
|
|
15613
15613
|
const firstGlobal = globalEffects[0]?.name;
|
|
15614
15614
|
lines.push("```typescript");
|
|
15615
|
-
lines.push(`const
|
|
15615
|
+
lines.push(`const session = await env.sessions.create();
|
|
15616
|
+
const job = await session.submitJob(\``);
|
|
15616
15617
|
const importList = [...pascalImports, ...effectNames].filter(
|
|
15617
15618
|
(v, i, a) => a.indexOf(v) === i
|
|
15618
15619
|
);
|
|
@@ -15691,7 +15692,7 @@ function generateSandboxAgentDoc(manifest, meta) {
|
|
|
15691
15692
|
"| Domain | `getDomain`, `getDomainTypes`, `getDomainDocumentation` |"
|
|
15692
15693
|
);
|
|
15693
15694
|
lines.push(
|
|
15694
|
-
|
|
15695
|
+
'| Effects | `granular.ontology(...).effects.registerMany`, `session.getEffects()`, `session.on("effects:changed", ...)` |'
|
|
15695
15696
|
);
|
|
15696
15697
|
lines.push("| Ops | `checkReadiness`, `getHeap`, `rpc` |");
|
|
15697
15698
|
lines.push("");
|
|
@@ -18442,27 +18443,27 @@ var Session = class {
|
|
|
18442
18443
|
}
|
|
18443
18444
|
async publishTools(tools, revision = "1.0.0") {
|
|
18444
18445
|
throw new Error(
|
|
18445
|
-
"Environment-scoped effect publication was removed. Declare effects in the manifest and register live handlers with granular.
|
|
18446
|
+
"Environment-scoped effect publication was removed. Declare effects in the manifest and register live handlers with granular.ontology(environment.sandboxId).effects.registerMany(effects)."
|
|
18446
18447
|
);
|
|
18447
18448
|
}
|
|
18448
18449
|
async publishEffect(effect) {
|
|
18449
18450
|
throw new Error(
|
|
18450
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
18451
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.register(effect)."
|
|
18451
18452
|
);
|
|
18452
18453
|
}
|
|
18453
18454
|
async publishEffects(effects2) {
|
|
18454
18455
|
throw new Error(
|
|
18455
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
18456
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.registerMany(effects)."
|
|
18456
18457
|
);
|
|
18457
18458
|
}
|
|
18458
18459
|
async unpublishEffect(name) {
|
|
18459
18460
|
throw new Error(
|
|
18460
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
18461
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.unregister(effectName)."
|
|
18461
18462
|
);
|
|
18462
18463
|
}
|
|
18463
18464
|
async unpublishAllEffects() {
|
|
18464
18465
|
throw new Error(
|
|
18465
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
18466
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.clear()."
|
|
18466
18467
|
);
|
|
18467
18468
|
}
|
|
18468
18469
|
/**
|
|
@@ -19520,17 +19521,40 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
19520
19521
|
|
|
19521
19522
|
// src/client.ts
|
|
19522
19523
|
var STANDARD_MODULES_OPERATIONS = [
|
|
19523
|
-
{
|
|
19524
|
+
{
|
|
19525
|
+
create: "entity",
|
|
19526
|
+
has: { id: { value: "auto-generated" }, createdAt: { value: void 0 } }
|
|
19527
|
+
},
|
|
19524
19528
|
{ create: "class", extends: "entity", has: {} },
|
|
19525
|
-
{
|
|
19526
|
-
|
|
19529
|
+
{
|
|
19530
|
+
create: "user",
|
|
19531
|
+
extends: "entity",
|
|
19532
|
+
has: {
|
|
19533
|
+
email: { value: void 0 },
|
|
19534
|
+
firstName: { value: void 0 },
|
|
19535
|
+
lastName: { value: void 0 }
|
|
19536
|
+
}
|
|
19537
|
+
},
|
|
19538
|
+
{
|
|
19539
|
+
create: "company",
|
|
19540
|
+
extends: "entity",
|
|
19541
|
+
has: { name: { value: void 0 }, website: { value: void 0 } }
|
|
19542
|
+
},
|
|
19527
19543
|
{ create: "string", has: {} },
|
|
19528
19544
|
{ create: "number", has: {} },
|
|
19529
19545
|
{ create: "boolean", has: {} },
|
|
19530
|
-
{
|
|
19546
|
+
{
|
|
19547
|
+
create: "tool_parameter",
|
|
19548
|
+
has: {
|
|
19549
|
+
name: { value: void 0 },
|
|
19550
|
+
type: { value: "string" },
|
|
19551
|
+
description: { value: void 0 },
|
|
19552
|
+
required: { value: false }
|
|
19553
|
+
}
|
|
19554
|
+
}
|
|
19531
19555
|
];
|
|
19532
19556
|
var BUILTIN_MODULES = {
|
|
19533
|
-
|
|
19557
|
+
standard_modules: STANDARD_MODULES_OPERATIONS
|
|
19534
19558
|
};
|
|
19535
19559
|
var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
|
|
19536
19560
|
var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
|
|
@@ -19565,7 +19589,9 @@ function isRetryableLocalWorkerRestart(status, body, url) {
|
|
|
19565
19589
|
}
|
|
19566
19590
|
function isRetryableRecordObjectsError(error2) {
|
|
19567
19591
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19568
|
-
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
19592
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
19593
|
+
message
|
|
19594
|
+
);
|
|
19569
19595
|
}
|
|
19570
19596
|
function computeEffectKey2(effect) {
|
|
19571
19597
|
const attachedClass = effect.className?.trim();
|
|
@@ -19618,6 +19644,22 @@ function normalizeHeapSnapshot(raw) {
|
|
|
19618
19644
|
updatedAt: typeof heap.updatedAt === "number" ? heap.updatedAt : Date.now()
|
|
19619
19645
|
};
|
|
19620
19646
|
}
|
|
19647
|
+
function deriveRuntimeBaseUrl(apiEndpoint) {
|
|
19648
|
+
try {
|
|
19649
|
+
const endpoint = new URL(apiEndpoint);
|
|
19650
|
+
const graphqlSuffix = "/orchestrator/graphql";
|
|
19651
|
+
if (endpoint.pathname.endsWith(graphqlSuffix)) {
|
|
19652
|
+
endpoint.pathname = endpoint.pathname.slice(0, -graphqlSuffix.length);
|
|
19653
|
+
} else if (endpoint.pathname.endsWith("/graphql")) {
|
|
19654
|
+
endpoint.pathname = endpoint.pathname.slice(0, -"/graphql".length);
|
|
19655
|
+
}
|
|
19656
|
+
endpoint.search = "";
|
|
19657
|
+
endpoint.hash = "";
|
|
19658
|
+
return endpoint.toString().replace(/\/$/, "");
|
|
19659
|
+
} catch {
|
|
19660
|
+
return apiEndpoint.replace(/\/orchestrator\/graphql$/, "").replace(/\/$/, "");
|
|
19661
|
+
}
|
|
19662
|
+
}
|
|
19621
19663
|
function normalizeSubject(subject) {
|
|
19622
19664
|
const granularId = subject.granularId || subject.subjectId;
|
|
19623
19665
|
const userId = subject.userId || subject.identityId || granularId;
|
|
@@ -19642,7 +19684,10 @@ function normalizeUser(user) {
|
|
|
19642
19684
|
};
|
|
19643
19685
|
}
|
|
19644
19686
|
function normalizeEnvironmentData(environment) {
|
|
19645
|
-
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
19687
|
+
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
19688
|
+
mode: "pinned",
|
|
19689
|
+
versionId: environment.versionId || environment.buildId
|
|
19690
|
+
});
|
|
19646
19691
|
const environmentName = environment.environment || environment.envName || "prod";
|
|
19647
19692
|
return {
|
|
19648
19693
|
...environment,
|
|
@@ -19654,12 +19699,13 @@ function normalizeEnvironmentData(environment) {
|
|
|
19654
19699
|
tracking: environment.tracking || buildPolicy
|
|
19655
19700
|
};
|
|
19656
19701
|
}
|
|
19657
|
-
var Environment = class
|
|
19702
|
+
var Environment = class {
|
|
19703
|
+
granular;
|
|
19658
19704
|
envData;
|
|
19659
19705
|
_apiKey;
|
|
19660
19706
|
_apiEndpoint;
|
|
19661
|
-
constructor(
|
|
19662
|
-
|
|
19707
|
+
constructor(granular, envData, apiKey, apiEndpoint) {
|
|
19708
|
+
this.granular = granular;
|
|
19663
19709
|
this.envData = envData;
|
|
19664
19710
|
this._apiKey = apiKey;
|
|
19665
19711
|
this._apiEndpoint = apiEndpoint;
|
|
@@ -19700,35 +19746,126 @@ var Environment = class extends Session {
|
|
|
19700
19746
|
get permissionProfileId() {
|
|
19701
19747
|
return this.envData.permissionProfileId;
|
|
19702
19748
|
}
|
|
19749
|
+
/** The current build policy backing this environment */
|
|
19750
|
+
get buildPolicy() {
|
|
19751
|
+
return this.envData.buildPolicy;
|
|
19752
|
+
}
|
|
19753
|
+
/** The current update state relative to the followed tag */
|
|
19754
|
+
get updateState() {
|
|
19755
|
+
return this.envData.updateState;
|
|
19756
|
+
}
|
|
19757
|
+
/** Convenience flag for whether this environment trails the current tag target */
|
|
19758
|
+
get isOutdated() {
|
|
19759
|
+
return this.envData.updateState === "update_available";
|
|
19760
|
+
}
|
|
19761
|
+
/** The followed tag name when this environment is tag-tracked */
|
|
19762
|
+
get tag() {
|
|
19763
|
+
return this.envData.tag?.name || this.envData.buildPolicy.tagName || null;
|
|
19764
|
+
}
|
|
19703
19765
|
/** The GraphQL API endpoint URL */
|
|
19704
19766
|
get apiEndpoint() {
|
|
19705
19767
|
return this._apiEndpoint;
|
|
19706
19768
|
}
|
|
19769
|
+
/** Internal auth token used for control-plane and runtime fallback requests */
|
|
19770
|
+
get authToken() {
|
|
19771
|
+
return this._apiKey;
|
|
19772
|
+
}
|
|
19773
|
+
/** Base runtime URL derived from the GraphQL endpoint */
|
|
19774
|
+
get runtimeBaseUrl() {
|
|
19775
|
+
return this.getRuntimeBaseUrl();
|
|
19776
|
+
}
|
|
19777
|
+
get sessions() {
|
|
19778
|
+
return {
|
|
19779
|
+
list: async (options) => this.listSessions(options?.status || "active"),
|
|
19780
|
+
create: async (options) => this.createSession(options),
|
|
19781
|
+
connect: async (sessionId, options) => this.connectSession(sessionId, options),
|
|
19782
|
+
reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
|
|
19783
|
+
close: async (sessionId, session2) => this.closeSession(sessionId, session2)
|
|
19784
|
+
};
|
|
19785
|
+
}
|
|
19786
|
+
get data() {
|
|
19787
|
+
return {
|
|
19788
|
+
record: async (record) => this.recordObject(record),
|
|
19789
|
+
recordMany: async (records, options) => this.recordObjects(records, options),
|
|
19790
|
+
import: async (records, options) => this.enqueueRecordImport(records, options),
|
|
19791
|
+
listImports: async (status) => this.listRecordImports(status),
|
|
19792
|
+
getImport: async (importId) => this.getRecordImport(importId),
|
|
19793
|
+
getImportSummary: async () => this.getRecordImportSummary(),
|
|
19794
|
+
cancelImport: async (importId) => this.cancelRecordImport(importId),
|
|
19795
|
+
getAwaitingCount: async () => this.getAwaitingRecordCount()
|
|
19796
|
+
};
|
|
19797
|
+
}
|
|
19798
|
+
get feedback() {
|
|
19799
|
+
return {
|
|
19800
|
+
list: async () => this.listFeedback()
|
|
19801
|
+
};
|
|
19802
|
+
}
|
|
19707
19803
|
/**
|
|
19708
|
-
*
|
|
19709
|
-
*
|
|
19710
|
-
*
|
|
19711
|
-
*
|
|
19804
|
+
* Sessionless environments do not own a live transport, so disconnecting the
|
|
19805
|
+
* environment handle itself is a no-op. This keeps the public surface
|
|
19806
|
+
* symmetric with `EnvironmentSession.disconnect()` and lets callers always
|
|
19807
|
+
* clean up safely without tracking whether they currently hold an environment
|
|
19808
|
+
* or a session.
|
|
19712
19809
|
*/
|
|
19713
|
-
|
|
19714
|
-
const doc = this.document;
|
|
19715
|
-
return normalizeHeapSnapshot(doc?.heap);
|
|
19810
|
+
async disconnect() {
|
|
19716
19811
|
}
|
|
19717
|
-
|
|
19718
|
-
|
|
19719
|
-
const
|
|
19720
|
-
|
|
19721
|
-
|
|
19722
|
-
|
|
19723
|
-
|
|
19724
|
-
|
|
19725
|
-
|
|
19726
|
-
|
|
19727
|
-
|
|
19728
|
-
|
|
19729
|
-
|
|
19730
|
-
|
|
19812
|
+
async listSessions(status = "active") {
|
|
19813
|
+
if (status === "all") {
|
|
19814
|
+
const [active, closed] = await Promise.all([
|
|
19815
|
+
this.granular.listOpenSessions({ environmentId: this.environmentId }),
|
|
19816
|
+
this.granular.listClosedSessions({ environmentId: this.environmentId })
|
|
19817
|
+
]);
|
|
19818
|
+
return [...active, ...closed].sort(
|
|
19819
|
+
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
|
|
19820
|
+
);
|
|
19821
|
+
}
|
|
19822
|
+
return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
|
|
19823
|
+
}
|
|
19824
|
+
async createSession(options) {
|
|
19825
|
+
return this.granular.createSession({
|
|
19826
|
+
environmentId: this.environmentId,
|
|
19827
|
+
clientId: options?.clientId,
|
|
19828
|
+
initialHeap: options?.initialHeap
|
|
19829
|
+
});
|
|
19830
|
+
}
|
|
19831
|
+
async connectSession(sessionId, options) {
|
|
19832
|
+
const session2 = await this.granular["connectSession"]({
|
|
19833
|
+
sessionId,
|
|
19834
|
+
clientId: options?.clientId
|
|
19835
|
+
});
|
|
19836
|
+
if (session2.environmentId !== this.environmentId) {
|
|
19837
|
+
await session2.disconnect().catch(() => {
|
|
19838
|
+
session2.disconnectTransport();
|
|
19839
|
+
});
|
|
19840
|
+
throw new Error(
|
|
19841
|
+
`Session ${sessionId} belongs to environment ${session2.environmentId}, not ${this.environmentId}.`
|
|
19842
|
+
);
|
|
19843
|
+
}
|
|
19844
|
+
return session2;
|
|
19845
|
+
}
|
|
19846
|
+
async reopenSession(sessionId, options) {
|
|
19847
|
+
const session2 = await this.granular.reopenSession(sessionId, {
|
|
19848
|
+
clientId: options?.clientId
|
|
19849
|
+
});
|
|
19850
|
+
if (session2.environmentId !== this.environmentId) {
|
|
19851
|
+
await session2.disconnect().catch(() => {
|
|
19852
|
+
session2.disconnectTransport();
|
|
19853
|
+
});
|
|
19854
|
+
throw new Error(
|
|
19855
|
+
`Session ${sessionId} belongs to environment ${session2.environmentId}, not ${this.environmentId}.`
|
|
19856
|
+
);
|
|
19731
19857
|
}
|
|
19858
|
+
return session2;
|
|
19859
|
+
}
|
|
19860
|
+
async closeSession(sessionId, session2) {
|
|
19861
|
+
await this.granular.closeSession(sessionId, session2);
|
|
19862
|
+
}
|
|
19863
|
+
async listFeedback() {
|
|
19864
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
|
|
19865
|
+
return Array.isArray(response.items) ? response.items : [];
|
|
19866
|
+
}
|
|
19867
|
+
getRuntimeBaseUrl() {
|
|
19868
|
+
return deriveRuntimeBaseUrl(this._apiEndpoint);
|
|
19732
19869
|
}
|
|
19733
19870
|
async controlPlaneRequest(path6, options = {}) {
|
|
19734
19871
|
const runtimeBase = this.getRuntimeBaseUrl();
|
|
@@ -19736,94 +19873,19 @@ var Environment = class extends Session {
|
|
|
19736
19873
|
const response = await fetch(url, {
|
|
19737
19874
|
...options,
|
|
19738
19875
|
headers: {
|
|
19739
|
-
|
|
19876
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
19740
19877
|
"Content-Type": "application/json",
|
|
19741
|
-
|
|
19878
|
+
Connection: "close",
|
|
19742
19879
|
...options.headers
|
|
19743
19880
|
}
|
|
19744
19881
|
});
|
|
19745
19882
|
if (!response.ok) {
|
|
19746
|
-
throw new Error(
|
|
19883
|
+
throw new Error(
|
|
19884
|
+
`Control Plane API Error (${response.status}): ${await response.text()}`
|
|
19885
|
+
);
|
|
19747
19886
|
}
|
|
19748
19887
|
return response.json();
|
|
19749
19888
|
}
|
|
19750
|
-
/**
|
|
19751
|
-
* Close the session and disconnect from the sandbox.
|
|
19752
|
-
*
|
|
19753
|
-
* Sends `client.goodbye` over WebSocket first, then issues an HTTP fallback
|
|
19754
|
-
* to the runtime goodbye endpoint if no definitive WS-side runtime notify
|
|
19755
|
-
* acknowledgement was observed.
|
|
19756
|
-
*/
|
|
19757
|
-
async disconnect() {
|
|
19758
|
-
let wsNotifiedRuntime = false;
|
|
19759
|
-
try {
|
|
19760
|
-
const goodbye = await this.rpc("client.goodbye", {
|
|
19761
|
-
timestamp: Date.now()
|
|
19762
|
-
});
|
|
19763
|
-
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
19764
|
-
} catch {
|
|
19765
|
-
wsNotifiedRuntime = false;
|
|
19766
|
-
}
|
|
19767
|
-
if (!wsNotifiedRuntime) {
|
|
19768
|
-
try {
|
|
19769
|
-
const runtimeBase = this.getRuntimeBaseUrl();
|
|
19770
|
-
await fetch(
|
|
19771
|
-
`${runtimeBase}/orchestrator/runtime/environments/${this.environmentId}/session-goodbye`,
|
|
19772
|
-
{
|
|
19773
|
-
method: "POST",
|
|
19774
|
-
headers: {
|
|
19775
|
-
"Content-Type": "application/json",
|
|
19776
|
-
"Authorization": `Bearer ${this._apiKey}`,
|
|
19777
|
-
"Connection": "close"
|
|
19778
|
-
},
|
|
19779
|
-
body: JSON.stringify({
|
|
19780
|
-
reason: "sdk_disconnect_http_fallback",
|
|
19781
|
-
sessionId: this.client.currentSessionId
|
|
19782
|
-
})
|
|
19783
|
-
}
|
|
19784
|
-
);
|
|
19785
|
-
} catch {
|
|
19786
|
-
}
|
|
19787
|
-
}
|
|
19788
|
-
this.client.disconnect();
|
|
19789
|
-
}
|
|
19790
|
-
// ==================== GRAPH CONTAINER READINESS ====================
|
|
19791
|
-
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
19792
|
-
graphContainerStatus = null;
|
|
19793
|
-
/**
|
|
19794
|
-
* Check if the graph container is ready and warm.
|
|
19795
|
-
*
|
|
19796
|
-
* Sends a lightweight heartbeat RPC to the Session DO which internally
|
|
19797
|
-
* pings the FalkorDB container. The response includes `graphContainerStatus`,
|
|
19798
|
-
* which is stored locally and emitted as a `readiness` event.
|
|
19799
|
-
*
|
|
19800
|
-
* Use this method to proactively warm the graph container before any
|
|
19801
|
-
* GraphQL query that requires it, or to poll the container's state in
|
|
19802
|
-
* the background.
|
|
19803
|
-
*
|
|
19804
|
-
* @returns The current graph container status object
|
|
19805
|
-
*
|
|
19806
|
-
* @example
|
|
19807
|
-
* ```typescript
|
|
19808
|
-
* const status = await env.checkReadiness();
|
|
19809
|
-
* console.log(status.status); // 'hot' | 'warming' | 'unknown'
|
|
19810
|
-
*
|
|
19811
|
-
* // Or listen for live updates
|
|
19812
|
-
* env.on('readiness', (status) => {
|
|
19813
|
-
* console.log('Graph is now:', status.status);
|
|
19814
|
-
* });
|
|
19815
|
-
* ```
|
|
19816
|
-
*/
|
|
19817
|
-
async checkReadiness() {
|
|
19818
|
-
const result = await this.client.call("client.heartbeat", {});
|
|
19819
|
-
const containerStatus = result?.graphContainerStatus ?? {
|
|
19820
|
-
lastKeepAliveAt: Date.now(),
|
|
19821
|
-
status: "unknown"
|
|
19822
|
-
};
|
|
19823
|
-
this.graphContainerStatus = containerStatus;
|
|
19824
|
-
this.emit("readiness", containerStatus);
|
|
19825
|
-
return containerStatus;
|
|
19826
|
-
}
|
|
19827
19889
|
// ==================== ID ↔ GRAPH PATH MAPPING ====================
|
|
19828
19890
|
/**
|
|
19829
19891
|
* Convert a class name + real-world ID into a unique graph path.
|
|
@@ -19852,14 +19914,14 @@ var Environment = class extends Session {
|
|
|
19852
19914
|
}
|
|
19853
19915
|
/**
|
|
19854
19916
|
* Execute a GraphQL query against the environment's graph.
|
|
19855
|
-
*
|
|
19917
|
+
*
|
|
19856
19918
|
* The query uses the Granular graph query language (based on Cypher/GraphQL).
|
|
19857
19919
|
* Authentication is handled automatically using the SDK's API key.
|
|
19858
|
-
*
|
|
19920
|
+
*
|
|
19859
19921
|
* @param query - The GraphQL query string
|
|
19860
19922
|
* @param variables - Optional variables for the query
|
|
19861
19923
|
* @returns The query result data
|
|
19862
|
-
*
|
|
19924
|
+
*
|
|
19863
19925
|
* @example
|
|
19864
19926
|
* ```typescript
|
|
19865
19927
|
* // Read the workspace
|
|
@@ -19867,7 +19929,7 @@ var Environment = class extends Session {
|
|
|
19867
19929
|
* `query { model(path: "workspace") { path label submodels { path label } } }`
|
|
19868
19930
|
* );
|
|
19869
19931
|
* console.log(result.data);
|
|
19870
|
-
*
|
|
19932
|
+
*
|
|
19871
19933
|
* // Create a model
|
|
19872
19934
|
* const created = await env.graphql(
|
|
19873
19935
|
* `mutation { at(path: "workspace") { create_submodel(subpath: "my_node", label: "My Node", prototype: "Model") { model { path label } } } }`
|
|
@@ -19879,7 +19941,7 @@ var Environment = class extends Session {
|
|
|
19879
19941
|
method: "POST",
|
|
19880
19942
|
headers: {
|
|
19881
19943
|
"Content-Type": "application/json",
|
|
19882
|
-
|
|
19944
|
+
Authorization: `Bearer ${this._apiKey}`
|
|
19883
19945
|
},
|
|
19884
19946
|
body: JSON.stringify({
|
|
19885
19947
|
environmentId: this.environmentId,
|
|
@@ -19896,10 +19958,10 @@ var Environment = class extends Session {
|
|
|
19896
19958
|
// ==================== RELATIONSHIP METHODS ====================
|
|
19897
19959
|
/**
|
|
19898
19960
|
* Define a relationship between two model types.
|
|
19899
|
-
*
|
|
19961
|
+
*
|
|
19900
19962
|
* Creates both submodels (if they don't exist) and links them with
|
|
19901
19963
|
* a RelationshipDef node that encodes cardinality.
|
|
19902
|
-
*
|
|
19964
|
+
*
|
|
19903
19965
|
* @example
|
|
19904
19966
|
* ```typescript
|
|
19905
19967
|
* // Author has many Books, Book has one Author
|
|
@@ -19959,10 +20021,10 @@ var Environment = class extends Session {
|
|
|
19959
20021
|
}
|
|
19960
20022
|
/**
|
|
19961
20023
|
* Get all relationships for a model type.
|
|
19962
|
-
*
|
|
20024
|
+
*
|
|
19963
20025
|
* @param modelPath - The model type path (e.g., "author")
|
|
19964
20026
|
* @returns Array of relationships from this model's perspective
|
|
19965
|
-
*
|
|
20027
|
+
*
|
|
19966
20028
|
* @example
|
|
19967
20029
|
* ```typescript
|
|
19968
20030
|
* const rels = await env.getRelationships('author');
|
|
@@ -19995,18 +20057,18 @@ var Environment = class extends Session {
|
|
|
19995
20057
|
}
|
|
19996
20058
|
/**
|
|
19997
20059
|
* Attach a target model to a relationship submodel.
|
|
19998
|
-
*
|
|
20060
|
+
*
|
|
19999
20061
|
* Handles cardinality automatically:
|
|
20000
20062
|
* - "One" side: sets/replaces the reference
|
|
20001
20063
|
* - "Many" side: adds the target to the collection
|
|
20002
|
-
*
|
|
20064
|
+
*
|
|
20003
20065
|
* If the target model doesn't exist, it's created as an instance of the foreign type.
|
|
20004
20066
|
* Bidirectional sync is automatic.
|
|
20005
|
-
*
|
|
20067
|
+
*
|
|
20006
20068
|
* @param modelPath - The model instance path (e.g., "tolkien")
|
|
20007
20069
|
* @param submodelPath - The relationship submodel (e.g., "books")
|
|
20008
20070
|
* @param targetPath - The target model to attach (e.g., "lord_of_the_rings")
|
|
20009
|
-
*
|
|
20071
|
+
*
|
|
20010
20072
|
* @example
|
|
20011
20073
|
* ```typescript
|
|
20012
20074
|
* // Attach a book to an author (many side)
|
|
@@ -20033,18 +20095,18 @@ var Environment = class extends Session {
|
|
|
20033
20095
|
}
|
|
20034
20096
|
/**
|
|
20035
20097
|
* Detach a target model from a relationship submodel.
|
|
20036
|
-
*
|
|
20098
|
+
*
|
|
20037
20099
|
* Handles bidirectional cleanup automatically.
|
|
20038
|
-
*
|
|
20100
|
+
*
|
|
20039
20101
|
* @param modelPath - The model instance path
|
|
20040
20102
|
* @param submodelPath - The relationship submodel
|
|
20041
20103
|
* @param targetPath - The target to detach (optional for "one" side; omit on "many" side to detach all)
|
|
20042
|
-
*
|
|
20104
|
+
*
|
|
20043
20105
|
* @example
|
|
20044
20106
|
* ```typescript
|
|
20045
20107
|
* // Detach a specific book
|
|
20046
20108
|
* await env.detach('tolkien', 'books', 'lord_of_the_rings');
|
|
20047
|
-
*
|
|
20109
|
+
*
|
|
20048
20110
|
* // Detach all books
|
|
20049
20111
|
* await env.detach('tolkien', 'books');
|
|
20050
20112
|
* ```
|
|
@@ -20068,11 +20130,11 @@ var Environment = class extends Session {
|
|
|
20068
20130
|
}
|
|
20069
20131
|
/**
|
|
20070
20132
|
* List all related models through a relationship submodel.
|
|
20071
|
-
*
|
|
20133
|
+
*
|
|
20072
20134
|
* @param modelPath - The model instance path
|
|
20073
20135
|
* @param submodelPath - The relationship submodel
|
|
20074
20136
|
* @returns Array of related model references
|
|
20075
|
-
*
|
|
20137
|
+
*
|
|
20076
20138
|
* @example
|
|
20077
20139
|
* ```typescript
|
|
20078
20140
|
* const books = await env.listRelated('tolkien', 'books');
|
|
@@ -20096,14 +20158,14 @@ var Environment = class extends Session {
|
|
|
20096
20158
|
}
|
|
20097
20159
|
/**
|
|
20098
20160
|
* Apply a manifest to the current environment's graph.
|
|
20099
|
-
*
|
|
20161
|
+
*
|
|
20100
20162
|
* Translates each manifest operation into GraphQL mutations and executes them
|
|
20101
20163
|
* in order. This is the core mechanism for creating classes, fields, and
|
|
20102
20164
|
* relationships from a declarative manifest.
|
|
20103
|
-
*
|
|
20165
|
+
*
|
|
20104
20166
|
* @param manifest - The manifest content to apply
|
|
20105
20167
|
* @returns Summary of applied operations
|
|
20106
|
-
*
|
|
20168
|
+
*
|
|
20107
20169
|
* @example
|
|
20108
20170
|
* ```typescript
|
|
20109
20171
|
* await environment.applyManifest({
|
|
@@ -20141,12 +20203,16 @@ var Environment = class extends Session {
|
|
|
20141
20203
|
applied++;
|
|
20142
20204
|
} catch (err) {
|
|
20143
20205
|
if (!err.message?.includes("already exists")) {
|
|
20144
|
-
errors.push(
|
|
20206
|
+
errors.push(
|
|
20207
|
+
`Import ${imp.name} operation failed: ${err.message}`
|
|
20208
|
+
);
|
|
20145
20209
|
}
|
|
20146
20210
|
}
|
|
20147
20211
|
}
|
|
20148
20212
|
} else {
|
|
20149
|
-
errors.push(
|
|
20213
|
+
errors.push(
|
|
20214
|
+
`Unknown module: "${imp.name}" (only built-in modules are supported)`
|
|
20215
|
+
);
|
|
20150
20216
|
}
|
|
20151
20217
|
}
|
|
20152
20218
|
}
|
|
@@ -20230,7 +20296,10 @@ var Environment = class extends Session {
|
|
|
20230
20296
|
}
|
|
20231
20297
|
}
|
|
20232
20298
|
async _applyEffectMetamodels(toolPath, metamodels) {
|
|
20233
|
-
for (const mutation of buildEffectMetamodelMutations(
|
|
20299
|
+
for (const mutation of buildEffectMetamodelMutations(
|
|
20300
|
+
toolPath,
|
|
20301
|
+
metamodels
|
|
20302
|
+
)) {
|
|
20234
20303
|
await this._runGraphql(mutation.query, mutation.label);
|
|
20235
20304
|
}
|
|
20236
20305
|
}
|
|
@@ -20279,7 +20348,9 @@ var Environment = class extends Session {
|
|
|
20279
20348
|
}
|
|
20280
20349
|
if (eventType.payloadSchema?.properties) {
|
|
20281
20350
|
const fieldSpecs = {};
|
|
20282
|
-
for (const [propName, propSchema] of Object.entries(
|
|
20351
|
+
for (const [propName, propSchema] of Object.entries(
|
|
20352
|
+
eventType.payloadSchema.properties
|
|
20353
|
+
)) {
|
|
20283
20354
|
const schema = propSchema;
|
|
20284
20355
|
fieldSpecs[propName] = {
|
|
20285
20356
|
type: schema.type ?? "string",
|
|
@@ -20562,7 +20633,9 @@ var Environment = class extends Session {
|
|
|
20562
20633
|
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
20563
20634
|
await Promise.all(
|
|
20564
20635
|
wave.map(async (plan) => {
|
|
20565
|
-
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
20636
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
20637
|
+
plan.slice
|
|
20638
|
+
);
|
|
20566
20639
|
if (items.length !== plan.slice.length) {
|
|
20567
20640
|
throw new Error(
|
|
20568
20641
|
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
@@ -20592,13 +20665,10 @@ var Environment = class extends Session {
|
|
|
20592
20665
|
let lastError;
|
|
20593
20666
|
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
20594
20667
|
try {
|
|
20595
|
-
const response = await this.controlPlaneRequest(
|
|
20596
|
-
|
|
20597
|
-
{
|
|
20598
|
-
|
|
20599
|
-
body: JSON.stringify({ records: chunk })
|
|
20600
|
-
}
|
|
20601
|
-
);
|
|
20668
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/records/batch`, {
|
|
20669
|
+
method: "POST",
|
|
20670
|
+
body: JSON.stringify({ records: chunk })
|
|
20671
|
+
});
|
|
20602
20672
|
const items = Array.isArray(response.items) ? response.items : [];
|
|
20603
20673
|
return { items, durationMs: Date.now() - wallStart };
|
|
20604
20674
|
} catch (error2) {
|
|
@@ -20661,7 +20731,9 @@ var Environment = class extends Session {
|
|
|
20661
20731
|
* Fetch a single record import by id.
|
|
20662
20732
|
*/
|
|
20663
20733
|
async getRecordImport(importId) {
|
|
20664
|
-
return this.controlPlaneRequest(
|
|
20734
|
+
return this.controlPlaneRequest(
|
|
20735
|
+
`/control/record-imports/${importId}`
|
|
20736
|
+
);
|
|
20665
20737
|
}
|
|
20666
20738
|
/**
|
|
20667
20739
|
* Cancel a queued/background record import.
|
|
@@ -20674,36 +20746,186 @@ var Environment = class extends Session {
|
|
|
20674
20746
|
}
|
|
20675
20747
|
);
|
|
20676
20748
|
}
|
|
20677
|
-
|
|
20749
|
+
};
|
|
20750
|
+
var EnvironmentSession = class extends Session {
|
|
20751
|
+
environment;
|
|
20752
|
+
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
20753
|
+
graphContainerStatus = null;
|
|
20754
|
+
constructor(client, environment, clientId) {
|
|
20755
|
+
super(client, clientId);
|
|
20756
|
+
this.environment = environment;
|
|
20757
|
+
}
|
|
20758
|
+
get environmentId() {
|
|
20759
|
+
return this.environment.environmentId;
|
|
20760
|
+
}
|
|
20761
|
+
get sandboxId() {
|
|
20762
|
+
return this.environment.sandboxId;
|
|
20763
|
+
}
|
|
20764
|
+
get ontologyId() {
|
|
20765
|
+
return this.environment.ontologyId;
|
|
20766
|
+
}
|
|
20767
|
+
get subjectId() {
|
|
20768
|
+
return this.environment.subjectId;
|
|
20769
|
+
}
|
|
20770
|
+
get envName() {
|
|
20771
|
+
return this.environment.envName;
|
|
20772
|
+
}
|
|
20773
|
+
get versionId() {
|
|
20774
|
+
return this.environment.versionId;
|
|
20775
|
+
}
|
|
20776
|
+
get granularId() {
|
|
20777
|
+
return this.environment.granularId;
|
|
20778
|
+
}
|
|
20779
|
+
get permissionProfileId() {
|
|
20780
|
+
return this.environment.permissionProfileId;
|
|
20781
|
+
}
|
|
20782
|
+
get apiEndpoint() {
|
|
20783
|
+
return this.environment.apiEndpoint;
|
|
20784
|
+
}
|
|
20785
|
+
get data() {
|
|
20786
|
+
return this.environment.data;
|
|
20787
|
+
}
|
|
20788
|
+
get feedback() {
|
|
20789
|
+
return this.environment.feedback;
|
|
20790
|
+
}
|
|
20678
20791
|
/**
|
|
20679
|
-
*
|
|
20792
|
+
* Return a plain JS snapshot of the synced session heap.
|
|
20680
20793
|
*/
|
|
20681
|
-
|
|
20682
|
-
|
|
20794
|
+
getHeap() {
|
|
20795
|
+
const doc = this.document;
|
|
20796
|
+
return normalizeHeapSnapshot(doc?.heap);
|
|
20797
|
+
}
|
|
20798
|
+
async graphql(query, variables) {
|
|
20799
|
+
return this.environment.graphql(query, variables);
|
|
20800
|
+
}
|
|
20801
|
+
async defineRelationship(options) {
|
|
20802
|
+
return this.environment.defineRelationship(options);
|
|
20803
|
+
}
|
|
20804
|
+
async getRelationships(modelPath) {
|
|
20805
|
+
return this.environment.getRelationships(modelPath);
|
|
20806
|
+
}
|
|
20807
|
+
async attach(modelPath, submodelPath, targetPath) {
|
|
20808
|
+
return this.environment.attach(modelPath, submodelPath, targetPath);
|
|
20809
|
+
}
|
|
20810
|
+
async detach(modelPath, submodelPath, targetPath) {
|
|
20811
|
+
return this.environment.detach(modelPath, submodelPath, targetPath);
|
|
20812
|
+
}
|
|
20813
|
+
async listRelated(modelPath, submodelPath) {
|
|
20814
|
+
return this.environment.listRelated(modelPath, submodelPath);
|
|
20815
|
+
}
|
|
20816
|
+
async applyManifest(manifest) {
|
|
20817
|
+
return this.environment.applyManifest(manifest);
|
|
20818
|
+
}
|
|
20819
|
+
async recordObject(options) {
|
|
20820
|
+
return this.environment.recordObject(options);
|
|
20821
|
+
}
|
|
20822
|
+
async recordObjects(records, options) {
|
|
20823
|
+
return this.environment.recordObjects(records, options);
|
|
20824
|
+
}
|
|
20825
|
+
async enqueueRecordImport(records, options = {}) {
|
|
20826
|
+
return this.environment.enqueueRecordImport(records, options);
|
|
20827
|
+
}
|
|
20828
|
+
async listRecordImports(status) {
|
|
20829
|
+
return this.environment.listRecordImports(status);
|
|
20830
|
+
}
|
|
20831
|
+
async getRecordImportSummary() {
|
|
20832
|
+
return this.environment.getRecordImportSummary();
|
|
20833
|
+
}
|
|
20834
|
+
async getAwaitingRecordCount() {
|
|
20835
|
+
return this.environment.getAwaitingRecordCount();
|
|
20836
|
+
}
|
|
20837
|
+
async getRecordImport(importId) {
|
|
20838
|
+
return this.environment.getRecordImport(importId);
|
|
20839
|
+
}
|
|
20840
|
+
async cancelRecordImport(importId) {
|
|
20841
|
+
return this.environment.cancelRecordImport(importId);
|
|
20842
|
+
}
|
|
20843
|
+
async listFeedback() {
|
|
20844
|
+
return this.environment.listFeedback();
|
|
20683
20845
|
}
|
|
20684
20846
|
/**
|
|
20685
|
-
*
|
|
20847
|
+
* Close the session and disconnect from the sandbox.
|
|
20848
|
+
*
|
|
20849
|
+
* Sends `client.goodbye` over WebSocket first, then issues an HTTP fallback
|
|
20850
|
+
* to the runtime goodbye endpoint if no definitive WS-side runtime notify
|
|
20851
|
+
* acknowledgement was observed.
|
|
20686
20852
|
*/
|
|
20687
|
-
async
|
|
20688
|
-
|
|
20853
|
+
async disconnect() {
|
|
20854
|
+
let wsNotifiedRuntime = false;
|
|
20855
|
+
try {
|
|
20856
|
+
const goodbye = await this.rpc(
|
|
20857
|
+
"client.goodbye",
|
|
20858
|
+
{
|
|
20859
|
+
timestamp: Date.now()
|
|
20860
|
+
}
|
|
20861
|
+
);
|
|
20862
|
+
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
20863
|
+
} catch {
|
|
20864
|
+
wsNotifiedRuntime = false;
|
|
20865
|
+
}
|
|
20866
|
+
if (!wsNotifiedRuntime) {
|
|
20867
|
+
try {
|
|
20868
|
+
await fetch(
|
|
20869
|
+
`${this.environment.runtimeBaseUrl}/orchestrator/runtime/environments/${this.environmentId}/session-goodbye`,
|
|
20870
|
+
{
|
|
20871
|
+
method: "POST",
|
|
20872
|
+
headers: {
|
|
20873
|
+
"Content-Type": "application/json",
|
|
20874
|
+
Authorization: `Bearer ${this.environment.authToken}`,
|
|
20875
|
+
Connection: "close"
|
|
20876
|
+
},
|
|
20877
|
+
body: JSON.stringify({
|
|
20878
|
+
reason: "sdk_disconnect_http_fallback",
|
|
20879
|
+
sessionId: this.client.currentSessionId
|
|
20880
|
+
})
|
|
20881
|
+
}
|
|
20882
|
+
);
|
|
20883
|
+
} catch {
|
|
20884
|
+
}
|
|
20885
|
+
}
|
|
20886
|
+
this.client.disconnect();
|
|
20689
20887
|
}
|
|
20690
20888
|
/**
|
|
20691
|
-
*
|
|
20889
|
+
* Close only the socket transport without sending `client.goodbye`.
|
|
20692
20890
|
*/
|
|
20693
|
-
|
|
20694
|
-
|
|
20891
|
+
disconnectTransport() {
|
|
20892
|
+
this.client.disconnect();
|
|
20695
20893
|
}
|
|
20696
20894
|
/**
|
|
20697
|
-
*
|
|
20895
|
+
* Backwards-compatible alias for `disconnect()`.
|
|
20698
20896
|
*/
|
|
20699
|
-
async
|
|
20700
|
-
|
|
20897
|
+
async close() {
|
|
20898
|
+
await this.disconnect();
|
|
20701
20899
|
}
|
|
20702
20900
|
/**
|
|
20703
|
-
*
|
|
20901
|
+
* Check if the graph container is ready and warm.
|
|
20704
20902
|
*/
|
|
20705
|
-
async
|
|
20706
|
-
|
|
20903
|
+
async checkReadiness() {
|
|
20904
|
+
const result = await this.client.call("client.heartbeat", {});
|
|
20905
|
+
const containerStatus = result?.graphContainerStatus ?? {
|
|
20906
|
+
lastKeepAliveAt: Date.now(),
|
|
20907
|
+
status: "unknown"
|
|
20908
|
+
};
|
|
20909
|
+
this.graphContainerStatus = containerStatus;
|
|
20910
|
+
this.emit("readiness", containerStatus);
|
|
20911
|
+
return containerStatus;
|
|
20912
|
+
}
|
|
20913
|
+
};
|
|
20914
|
+
var OntologyHandle = class {
|
|
20915
|
+
granular;
|
|
20916
|
+
ontologyNameOrId;
|
|
20917
|
+
constructor(granular, ontologyNameOrId) {
|
|
20918
|
+
this.granular = granular;
|
|
20919
|
+
this.ontologyNameOrId = ontologyNameOrId;
|
|
20920
|
+
}
|
|
20921
|
+
get effects() {
|
|
20922
|
+
return {
|
|
20923
|
+
register: async (effect) => this.granular.registerEffect(this.ontologyNameOrId, effect),
|
|
20924
|
+
registerMany: async (effects2) => this.granular.registerEffects(this.ontologyNameOrId, effects2),
|
|
20925
|
+
unregister: async (name) => this.granular.unregisterEffect(this.ontologyNameOrId, name),
|
|
20926
|
+
clear: async () => this.granular.unregisterAllEffects(this.ontologyNameOrId),
|
|
20927
|
+
disconnect: async () => this.granular.disconnectEffects(this.ontologyNameOrId)
|
|
20928
|
+
};
|
|
20707
20929
|
}
|
|
20708
20930
|
};
|
|
20709
20931
|
var Granular = class _Granular {
|
|
@@ -20728,7 +20950,9 @@ var Granular = class _Granular {
|
|
|
20728
20950
|
constructor(options) {
|
|
20729
20951
|
const auth = options.token ?? options.apiKey;
|
|
20730
20952
|
if (!auth) {
|
|
20731
|
-
throw new Error(
|
|
20953
|
+
throw new Error(
|
|
20954
|
+
"Granular client requires either apiKey or token. Set GRANULAR_API_KEY or GRANULAR_TOKEN, or pass one in options."
|
|
20955
|
+
);
|
|
20732
20956
|
}
|
|
20733
20957
|
this.apiUrl = resolveApiUrl(options.apiUrl, options.endpointMode);
|
|
20734
20958
|
this.apiKey = resolveAuthTokenForApiUrl(auth, this.apiUrl);
|
|
@@ -20738,12 +20962,18 @@ var Granular = class _Granular {
|
|
|
20738
20962
|
this.onReconnectError = options.onReconnectError;
|
|
20739
20963
|
this.httpUrl = this.apiUrl.replace(/^wss:\/\//, "https://").replace(/^ws:\/\//, "http://").replace(/\/ws$/, "");
|
|
20740
20964
|
}
|
|
20965
|
+
/**
|
|
20966
|
+
* Return an ontology-scoped handle for effects and other ontology-level APIs.
|
|
20967
|
+
*/
|
|
20968
|
+
ontology(ontologyNameOrId) {
|
|
20969
|
+
return new OntologyHandle(this, ontologyNameOrId);
|
|
20970
|
+
}
|
|
20741
20971
|
/**
|
|
20742
20972
|
* Records/upserts a user and prepares them for sandbox connections
|
|
20743
|
-
*
|
|
20973
|
+
*
|
|
20744
20974
|
* @param options - User options
|
|
20745
20975
|
* @returns The recorded user with both `userId` and `granularId`
|
|
20746
|
-
*
|
|
20976
|
+
*
|
|
20747
20977
|
* @example
|
|
20748
20978
|
* ```typescript
|
|
20749
20979
|
* const user = await granular.recordUser({
|
|
@@ -20754,14 +20984,16 @@ var Granular = class _Granular {
|
|
|
20754
20984
|
* ```
|
|
20755
20985
|
*/
|
|
20756
20986
|
async recordUser(options) {
|
|
20757
|
-
const subject = normalizeSubject(
|
|
20758
|
-
|
|
20759
|
-
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
|
|
20987
|
+
const subject = normalizeSubject(
|
|
20988
|
+
await this.request("/control/subjects", {
|
|
20989
|
+
method: "POST",
|
|
20990
|
+
body: JSON.stringify({
|
|
20991
|
+
identityId: options.userId,
|
|
20992
|
+
name: options.name,
|
|
20993
|
+
email: options.email
|
|
20994
|
+
})
|
|
20763
20995
|
})
|
|
20764
|
-
|
|
20996
|
+
);
|
|
20765
20997
|
return normalizeUser({
|
|
20766
20998
|
granularId: subject.granularId,
|
|
20767
20999
|
userId: options.userId,
|
|
@@ -20772,7 +21004,23 @@ var Granular = class _Granular {
|
|
|
20772
21004
|
permissions: options.permissions || []
|
|
20773
21005
|
});
|
|
20774
21006
|
}
|
|
21007
|
+
/**
|
|
21008
|
+
* Alias for `recordUser()` with user-facing naming that matches upsert semantics.
|
|
21009
|
+
*/
|
|
21010
|
+
async upsertUser(options) {
|
|
21011
|
+
return this.recordUser(options);
|
|
21012
|
+
}
|
|
20775
21013
|
async resolveConnectUser(options) {
|
|
21014
|
+
const providedIdentityCount = [
|
|
21015
|
+
Boolean(options.user),
|
|
21016
|
+
Boolean(options.userId),
|
|
21017
|
+
Boolean(options.granularId)
|
|
21018
|
+
].filter(Boolean).length;
|
|
21019
|
+
if (providedIdentityCount !== 1) {
|
|
21020
|
+
throw new Error(
|
|
21021
|
+
"openEnvironment() requires exactly one of userId, granularId, or a user object returned by recordUser()."
|
|
21022
|
+
);
|
|
21023
|
+
}
|
|
20776
21024
|
if (options.user) {
|
|
20777
21025
|
const user = normalizeUser(options.user);
|
|
20778
21026
|
return {
|
|
@@ -20808,76 +21056,141 @@ var Granular = class _Granular {
|
|
|
20808
21056
|
permissions: options.permissions || []
|
|
20809
21057
|
};
|
|
20810
21058
|
}
|
|
20811
|
-
throw new Error(
|
|
21059
|
+
throw new Error(
|
|
21060
|
+
"openEnvironment() requires either userId, granularId, or a user object returned by recordUser()."
|
|
21061
|
+
);
|
|
20812
21062
|
}
|
|
20813
21063
|
/**
|
|
20814
|
-
*
|
|
20815
|
-
*
|
|
20816
|
-
* Effects are registered at the sandbox level via `granular.registerEffect()`
|
|
20817
|
-
* or `granular.registerEffects()`. Sessions pick up live availability from
|
|
20818
|
-
* the sandbox registry automatically.
|
|
20819
|
-
*
|
|
20820
|
-
* @param options - Connection options
|
|
20821
|
-
* @returns An active environment session
|
|
20822
|
-
*
|
|
21064
|
+
* Open or resolve an ontology environment for one user without opening a session.
|
|
21065
|
+
*
|
|
20823
21066
|
* @example
|
|
20824
21067
|
* ```typescript
|
|
20825
|
-
* const environment = await granular.
|
|
21068
|
+
* const environment = await granular.openEnvironment({
|
|
20826
21069
|
* ontology: 'my-ontology',
|
|
20827
|
-
*
|
|
21070
|
+
* tag: 'dev',
|
|
20828
21071
|
* userId: 'user_123',
|
|
20829
21072
|
* permissions: ['agent'],
|
|
20830
21073
|
* });
|
|
20831
|
-
*
|
|
20832
|
-
* await
|
|
20833
|
-
*
|
|
20834
|
-
*
|
|
20835
|
-
*
|
|
20836
|
-
* handler: async () => 'Hello!',
|
|
21074
|
+
*
|
|
21075
|
+
* await environment.data.record({
|
|
21076
|
+
* className: 'customer',
|
|
21077
|
+
* id: 'acme',
|
|
21078
|
+
* fields: { name: 'Acme' },
|
|
20837
21079
|
* });
|
|
20838
|
-
*
|
|
20839
|
-
*
|
|
20840
|
-
* const job = await
|
|
20841
|
-
*
|
|
20842
|
-
* return await tools.greet({});
|
|
20843
|
-
* `);
|
|
20844
|
-
*
|
|
20845
|
-
* console.log(await job.result); // 'Hello!'
|
|
21080
|
+
*
|
|
21081
|
+
* const session = await environment.sessions.create();
|
|
21082
|
+
* const job = await session.submitJob(`return "hello";`);
|
|
21083
|
+
* console.log(await job.result);
|
|
20846
21084
|
* ```
|
|
20847
|
-
|
|
21085
|
+
*/
|
|
21086
|
+
async openEnvironment(options) {
|
|
21087
|
+
const envData = await this.resolveOpenEnvironmentData(
|
|
21088
|
+
options,
|
|
21089
|
+
"openEnvironment"
|
|
21090
|
+
);
|
|
21091
|
+
return this.bindEnvironmentHandle(envData);
|
|
21092
|
+
}
|
|
21093
|
+
/**
|
|
21094
|
+
* Deprecated compatibility alias for `openEnvironment()`.
|
|
21095
|
+
*
|
|
21096
|
+
* `connect()` no longer opens a runtime session automatically.
|
|
21097
|
+
*/
|
|
20848
21098
|
async connect(options) {
|
|
20849
|
-
|
|
21099
|
+
return this.openEnvironment({
|
|
21100
|
+
...options,
|
|
21101
|
+
tag: this.resolveRequestedTag(options, "connect"),
|
|
21102
|
+
permissions: options.permissions || options.user?.permissions || []
|
|
21103
|
+
});
|
|
21104
|
+
}
|
|
21105
|
+
resolveRequestedTag(options, methodName) {
|
|
21106
|
+
const tag2 = options.tag?.trim() || options.tagName?.trim() || options.environment?.trim();
|
|
21107
|
+
if (!tag2) {
|
|
21108
|
+
throw new Error(`${methodName}() requires \`tag\`.`);
|
|
21109
|
+
}
|
|
21110
|
+
return tag2;
|
|
21111
|
+
}
|
|
21112
|
+
buildManagedEnvironmentName(tag2, versionId) {
|
|
21113
|
+
return `__sdk__${tag2}__${versionId}`;
|
|
21114
|
+
}
|
|
21115
|
+
matchesTagTrackedEnvironment(environment, tagName, tagId) {
|
|
21116
|
+
const environmentTagName = environment.tag?.name || environment.buildPolicy.tagName || null;
|
|
21117
|
+
return environment.tagId === tagId || environmentTagName === tagName || environment.environment === tagName || environment.envName === tagName || environment.environment === this.buildManagedEnvironmentName(tagName, environment.versionId) || environment.envName === this.buildManagedEnvironmentName(tagName, environment.versionId);
|
|
21118
|
+
}
|
|
21119
|
+
sortEnvironmentsByRecency(environments) {
|
|
21120
|
+
return [...environments].sort(
|
|
21121
|
+
(left, right) => right.updatedAt - left.updatedAt
|
|
21122
|
+
);
|
|
21123
|
+
}
|
|
21124
|
+
async resolveOpenEnvironmentData(options, methodName) {
|
|
20850
21125
|
const ontology = options.ontology;
|
|
20851
21126
|
if (!ontology) {
|
|
20852
|
-
throw new Error(
|
|
21127
|
+
throw new Error(`${methodName}() requires \`ontology\`.`);
|
|
20853
21128
|
}
|
|
20854
|
-
const
|
|
20855
|
-
if (!
|
|
20856
|
-
throw new Error(
|
|
21129
|
+
const tagName = options.tag?.trim();
|
|
21130
|
+
if (!tagName) {
|
|
21131
|
+
throw new Error(`${methodName}() requires \`tag\`.`);
|
|
20857
21132
|
}
|
|
20858
|
-
const tagName = options.tagName?.trim() || void 0;
|
|
20859
21133
|
const user = await this.resolveConnectUser(options);
|
|
21134
|
+
if (!Array.isArray(user.permissions) || user.permissions.length === 0) {
|
|
21135
|
+
throw new Error(
|
|
21136
|
+
`${methodName}() requires at least one permission so the SDK can ensure assignments for new users.`
|
|
21137
|
+
);
|
|
21138
|
+
}
|
|
20860
21139
|
const sandbox = await this.findOrCreateSandbox(ontology);
|
|
20861
21140
|
for (const profileName of user.permissions) {
|
|
20862
|
-
const profileId = await this.ensurePermissionProfile(
|
|
20863
|
-
|
|
21141
|
+
const profileId = await this.ensurePermissionProfile(
|
|
21142
|
+
sandbox.sandboxId,
|
|
21143
|
+
profileName
|
|
21144
|
+
);
|
|
21145
|
+
await this.ensureAssignment(
|
|
21146
|
+
user.granularId,
|
|
21147
|
+
sandbox.sandboxId,
|
|
21148
|
+
profileId
|
|
21149
|
+
);
|
|
21150
|
+
}
|
|
21151
|
+
const tags = await this.request(
|
|
21152
|
+
`/control/sandboxes/${sandbox.sandboxId}/tags`
|
|
21153
|
+
);
|
|
21154
|
+
const tag2 = (tags.items || []).find(
|
|
21155
|
+
(candidate) => Boolean(candidate?.name === tagName)
|
|
21156
|
+
);
|
|
21157
|
+
if (!tag2) {
|
|
21158
|
+
throw new Error(
|
|
21159
|
+
`Tag "${tagName}" was not found for ontology ${sandbox.sandboxId}.`
|
|
21160
|
+
);
|
|
21161
|
+
}
|
|
21162
|
+
const targetVersionId = tag2.targetVersionId || tag2.targetBuildId;
|
|
21163
|
+
if (!targetVersionId) {
|
|
21164
|
+
throw new Error(
|
|
21165
|
+
`Tag "${tagName}" does not currently point to a build/version.`
|
|
21166
|
+
);
|
|
21167
|
+
}
|
|
21168
|
+
const allEnvironments = await this.environments.list(sandbox.sandboxId);
|
|
21169
|
+
const userEnvironments = allEnvironments.filter(
|
|
21170
|
+
(environment) => environment.subjectId === user.granularId
|
|
21171
|
+
);
|
|
21172
|
+
const currentMatches = this.sortEnvironmentsByRecency(
|
|
21173
|
+
userEnvironments.filter(
|
|
21174
|
+
(environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag2.tagId) && environment.versionId === targetVersionId
|
|
21175
|
+
)
|
|
21176
|
+
);
|
|
21177
|
+
if (currentMatches.length > 0) {
|
|
21178
|
+
return currentMatches[0];
|
|
20864
21179
|
}
|
|
20865
|
-
const
|
|
21180
|
+
const outdatedMatches = this.sortEnvironmentsByRecency(
|
|
21181
|
+
userEnvironments.filter(
|
|
21182
|
+
(environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag2.tagId)
|
|
21183
|
+
)
|
|
21184
|
+
);
|
|
21185
|
+
if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
|
|
21186
|
+
return outdatedMatches[0];
|
|
21187
|
+
}
|
|
21188
|
+
return this.environments.create(sandbox.sandboxId, {
|
|
20866
21189
|
subjectId: user.granularId,
|
|
20867
|
-
environment:
|
|
20868
|
-
|
|
21190
|
+
environment: this.buildManagedEnvironmentName(tagName, targetVersionId),
|
|
21191
|
+
tagId: tag2.tagId,
|
|
20869
21192
|
permissionProfileId: null
|
|
20870
21193
|
});
|
|
20871
|
-
await this.activateEnvironment(envData.environmentId);
|
|
20872
|
-
const session2 = await this.request("/ws/sessions", {
|
|
20873
|
-
method: "POST",
|
|
20874
|
-
body: JSON.stringify({
|
|
20875
|
-
environmentId: envData.environmentId,
|
|
20876
|
-
clientId,
|
|
20877
|
-
initialHeap: options.initialHeap
|
|
20878
|
-
})
|
|
20879
|
-
});
|
|
20880
|
-
return this.bindWebSocketEnvironment(envData, clientId, session2);
|
|
20881
21194
|
}
|
|
20882
21195
|
/**
|
|
20883
21196
|
* List active (open) sessions for an environment — each session is one agent conversation thread.
|
|
@@ -20912,7 +21225,9 @@ var Granular = class _Granular {
|
|
|
20912
21225
|
createdAt: _Granular.coerceIsoDate(row.createdAt ?? row.created_at),
|
|
20913
21226
|
lastSeenAt: _Granular.coerceIsoDate(row.lastSeenAt ?? row.last_seen_at),
|
|
20914
21227
|
summary: row.summary != null ? String(row.summary) : null,
|
|
20915
|
-
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
21228
|
+
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
21229
|
+
row.summaryUpdatedAt ?? row.summary_updated_at
|
|
21230
|
+
) : null,
|
|
20916
21231
|
subjectId: row.subjectId != null ? String(row.subjectId) : null,
|
|
20917
21232
|
jobCount: typeof row.jobCount === "number" ? row.jobCount : void 0,
|
|
20918
21233
|
toolCallCount: typeof row.toolCallCount === "number" ? row.toolCallCount : void 0
|
|
@@ -20938,6 +21253,7 @@ var Granular = class _Granular {
|
|
|
20938
21253
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
20939
21254
|
await this.activateEnvironment(options.environmentId);
|
|
20940
21255
|
const envData = await this.environments.get(options.environmentId);
|
|
21256
|
+
const environment = this.bindEnvironmentHandle(envData);
|
|
20941
21257
|
const session2 = await this.request("/ws/sessions", {
|
|
20942
21258
|
method: "POST",
|
|
20943
21259
|
body: JSON.stringify({
|
|
@@ -20946,7 +21262,7 @@ var Granular = class _Granular {
|
|
|
20946
21262
|
initialHeap: options.initialHeap
|
|
20947
21263
|
})
|
|
20948
21264
|
});
|
|
20949
|
-
return this.
|
|
21265
|
+
return this.bindWebSocketEnvironmentSession(environment, clientId, session2);
|
|
20950
21266
|
}
|
|
20951
21267
|
/**
|
|
20952
21268
|
* Connect to an existing open session (same conversation thread) using a freshly minted WebSocket token.
|
|
@@ -20958,7 +21274,8 @@ var Granular = class _Granular {
|
|
|
20958
21274
|
body: JSON.stringify({})
|
|
20959
21275
|
});
|
|
20960
21276
|
const envData = await this.environments.get(minted.environmentId);
|
|
20961
|
-
|
|
21277
|
+
const environment = this.bindEnvironmentHandle(envData);
|
|
21278
|
+
return this.bindWebSocketEnvironmentSession(environment, clientId, minted);
|
|
20962
21279
|
}
|
|
20963
21280
|
/**
|
|
20964
21281
|
* Mark a session closed in the control plane. If `environment` is the connected handle for that
|
|
@@ -20990,7 +21307,11 @@ var Granular = class _Granular {
|
|
|
20990
21307
|
});
|
|
20991
21308
|
return this.connectSession({ sessionId, clientId: options?.clientId });
|
|
20992
21309
|
}
|
|
20993
|
-
|
|
21310
|
+
bindEnvironmentHandle(envData) {
|
|
21311
|
+
const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
|
|
21312
|
+
return new Environment(this, envData, this.apiKey, graphqlEndpoint);
|
|
21313
|
+
}
|
|
21314
|
+
async bindWebSocketEnvironmentSession(environment, clientId, session2) {
|
|
20994
21315
|
const client = new WSClient({
|
|
20995
21316
|
url: session2.wsUrl,
|
|
20996
21317
|
sessionId: session2.sessionId,
|
|
@@ -21001,10 +21322,13 @@ var Granular = class _Granular {
|
|
|
21001
21322
|
onReconnectError: this.onReconnectError
|
|
21002
21323
|
});
|
|
21003
21324
|
await client.connect();
|
|
21004
|
-
const
|
|
21005
|
-
|
|
21006
|
-
|
|
21007
|
-
|
|
21325
|
+
const environmentSession = new EnvironmentSession(
|
|
21326
|
+
client,
|
|
21327
|
+
environment,
|
|
21328
|
+
clientId
|
|
21329
|
+
);
|
|
21330
|
+
await environmentSession.hello();
|
|
21331
|
+
return environmentSession;
|
|
21008
21332
|
}
|
|
21009
21333
|
async activateEnvironment(environmentId) {
|
|
21010
21334
|
await this.request(`/orchestrator/runtime/environments/${environmentId}/activate`, {
|
|
@@ -21036,14 +21360,18 @@ var Granular = class _Granular {
|
|
|
21036
21360
|
};
|
|
21037
21361
|
}
|
|
21038
21362
|
async publishSandboxEffectCatalog(host) {
|
|
21039
|
-
const effects2 = Array.from(
|
|
21040
|
-
(
|
|
21041
|
-
);
|
|
21042
|
-
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
21363
|
+
const effects2 = Array.from(
|
|
21364
|
+
this.getSandboxEffectMap(host.sandboxId).values()
|
|
21365
|
+
).map((effect) => this.serializeEffect(effect));
|
|
21366
|
+
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
21367
|
+
effects: effects2
|
|
21368
|
+
});
|
|
21043
21369
|
const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
21044
21370
|
const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
|
|
21045
21371
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
21046
|
-
const detail = rejected.map(
|
|
21372
|
+
const detail = rejected.map(
|
|
21373
|
+
(entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
|
|
21374
|
+
).join("; ");
|
|
21047
21375
|
throw new Error(
|
|
21048
21376
|
`Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
|
|
21049
21377
|
);
|
|
@@ -21077,13 +21405,15 @@ var Granular = class _Granular {
|
|
|
21077
21405
|
disconnectError
|
|
21078
21406
|
);
|
|
21079
21407
|
}
|
|
21080
|
-
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
21081
|
-
|
|
21082
|
-
|
|
21083
|
-
|
|
21084
|
-
|
|
21085
|
-
|
|
21086
|
-
|
|
21408
|
+
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
21409
|
+
(reconnectError) => {
|
|
21410
|
+
console.error(
|
|
21411
|
+
`[Granular] Failed to recover effect host for sandbox ${host.sandboxId} after heartbeat failure:`,
|
|
21412
|
+
reconnectError
|
|
21413
|
+
);
|
|
21414
|
+
console.error("[Granular] Original heartbeat failure:", error2);
|
|
21415
|
+
}
|
|
21416
|
+
);
|
|
21087
21417
|
}
|
|
21088
21418
|
startEffectHostHeartbeat(host) {
|
|
21089
21419
|
if (host.heartbeatTimer) {
|
|
@@ -21104,9 +21434,15 @@ var Granular = class _Granular {
|
|
|
21104
21434
|
host.heartbeatInFlight = false;
|
|
21105
21435
|
});
|
|
21106
21436
|
};
|
|
21107
|
-
sendHeartbeat(
|
|
21437
|
+
sendHeartbeat(
|
|
21438
|
+
"[Granular] Initial effect host heartbeat failed for sandbox",
|
|
21439
|
+
false
|
|
21440
|
+
);
|
|
21108
21441
|
host.heartbeatTimer = setInterval(() => {
|
|
21109
|
-
sendHeartbeat(
|
|
21442
|
+
sendHeartbeat(
|
|
21443
|
+
"[Granular] Effect host heartbeat failed for sandbox",
|
|
21444
|
+
true
|
|
21445
|
+
);
|
|
21110
21446
|
}, 1e4);
|
|
21111
21447
|
}
|
|
21112
21448
|
stopEffectHostHeartbeat(host) {
|
|
@@ -21138,7 +21474,12 @@ var Granular = class _Granular {
|
|
|
21138
21474
|
const effectClientId = crypto.randomUUID();
|
|
21139
21475
|
const clientId = `effect-host:${sandboxId}:${effectClientId}`;
|
|
21140
21476
|
const wsClient = new WSClient({
|
|
21141
|
-
url: buildEffectHostUrl(
|
|
21477
|
+
url: buildEffectHostUrl(
|
|
21478
|
+
this.apiUrl,
|
|
21479
|
+
sandboxId,
|
|
21480
|
+
effectClientId,
|
|
21481
|
+
clientId
|
|
21482
|
+
),
|
|
21142
21483
|
sessionId: `effect-host:${effectClientId}`,
|
|
21143
21484
|
token: this.apiKey,
|
|
21144
21485
|
tokenProvider: this.tokenProvider,
|
|
@@ -21157,7 +21498,10 @@ var Granular = class _Granular {
|
|
|
21157
21498
|
};
|
|
21158
21499
|
wsClient.registerRpcHandler("effect.invoke", async (params) => {
|
|
21159
21500
|
const request = params;
|
|
21160
|
-
return invokeRegisteredEffect(
|
|
21501
|
+
return invokeRegisteredEffect(
|
|
21502
|
+
this.getSandboxEffectMap(sandboxId),
|
|
21503
|
+
request
|
|
21504
|
+
);
|
|
21161
21505
|
});
|
|
21162
21506
|
wsClient.on("open", () => {
|
|
21163
21507
|
void this.synchronizeEffectHost(host).catch((error2) => {
|
|
@@ -21205,7 +21549,7 @@ var Granular = class _Granular {
|
|
|
21205
21549
|
}
|
|
21206
21550
|
/**
|
|
21207
21551
|
* Register multiple effects (tools) for a specific sandbox.
|
|
21208
|
-
*
|
|
21552
|
+
*
|
|
21209
21553
|
* batch version of `registerEffect`.
|
|
21210
21554
|
*/
|
|
21211
21555
|
async registerEffects(sandboxNameOrId, effects2) {
|
|
@@ -21219,7 +21563,7 @@ var Granular = class _Granular {
|
|
|
21219
21563
|
}
|
|
21220
21564
|
/**
|
|
21221
21565
|
* Unregister an effect from a sandbox.
|
|
21222
|
-
*
|
|
21566
|
+
*
|
|
21223
21567
|
* Removes it from the local sandbox registry and updates the
|
|
21224
21568
|
* sandbox-scoped live catalog.
|
|
21225
21569
|
*/
|
|
@@ -21318,27 +21662,31 @@ var Granular = class _Granular {
|
|
|
21318
21662
|
const assignments = await this.request(
|
|
21319
21663
|
`/control/subjects/${subjectId}/assignments`
|
|
21320
21664
|
);
|
|
21321
|
-
const existing = assignments.items.find(
|
|
21322
|
-
(a) => a.sandboxId === sandboxId
|
|
21323
|
-
);
|
|
21665
|
+
const existing = assignments.items.find((a) => a.sandboxId === sandboxId);
|
|
21324
21666
|
if (existing) {
|
|
21325
21667
|
if (existing.permissionProfileId === permissionProfileId) {
|
|
21326
21668
|
return;
|
|
21327
21669
|
}
|
|
21328
|
-
await this.request(
|
|
21329
|
-
|
|
21330
|
-
|
|
21670
|
+
await this.request(
|
|
21671
|
+
`/control/assignments/${existing.assignmentId}`,
|
|
21672
|
+
{
|
|
21673
|
+
method: "DELETE"
|
|
21674
|
+
}
|
|
21675
|
+
);
|
|
21331
21676
|
}
|
|
21332
21677
|
} catch {
|
|
21333
21678
|
}
|
|
21334
|
-
await this.request(
|
|
21335
|
-
|
|
21336
|
-
|
|
21337
|
-
|
|
21338
|
-
|
|
21339
|
-
|
|
21340
|
-
|
|
21341
|
-
|
|
21679
|
+
await this.request(
|
|
21680
|
+
`/control/subjects/${subjectId}/assignments`,
|
|
21681
|
+
{
|
|
21682
|
+
method: "POST",
|
|
21683
|
+
body: JSON.stringify({
|
|
21684
|
+
sandboxId,
|
|
21685
|
+
subjectId,
|
|
21686
|
+
permissionProfileId
|
|
21687
|
+
})
|
|
21688
|
+
}
|
|
21689
|
+
);
|
|
21342
21690
|
}
|
|
21343
21691
|
/**
|
|
21344
21692
|
* Sandbox management API
|
|
@@ -21418,23 +21766,33 @@ var Granular = class _Granular {
|
|
|
21418
21766
|
},
|
|
21419
21767
|
get: async (environmentId) => {
|
|
21420
21768
|
return normalizeEnvironmentData(
|
|
21421
|
-
await this.request(
|
|
21769
|
+
await this.request(
|
|
21770
|
+
`/control/environments/${environmentId}`
|
|
21771
|
+
)
|
|
21422
21772
|
);
|
|
21423
21773
|
},
|
|
21424
21774
|
create: async (sandboxId, data) => {
|
|
21425
21775
|
const environmentName = data.environment || data.envName;
|
|
21426
|
-
return normalizeEnvironmentData(
|
|
21427
|
-
|
|
21428
|
-
|
|
21429
|
-
|
|
21430
|
-
|
|
21431
|
-
|
|
21432
|
-
|
|
21776
|
+
return normalizeEnvironmentData(
|
|
21777
|
+
await this.request(
|
|
21778
|
+
`/control/sandboxes/${sandboxId}/environments`,
|
|
21779
|
+
{
|
|
21780
|
+
method: "POST",
|
|
21781
|
+
body: JSON.stringify({
|
|
21782
|
+
...data,
|
|
21783
|
+
envName: environmentName
|
|
21784
|
+
})
|
|
21785
|
+
}
|
|
21786
|
+
)
|
|
21787
|
+
);
|
|
21433
21788
|
},
|
|
21434
21789
|
delete: async (environmentId) => {
|
|
21435
|
-
return this.request(
|
|
21436
|
-
|
|
21437
|
-
|
|
21790
|
+
return this.request(
|
|
21791
|
+
`/control/environments/${environmentId}`,
|
|
21792
|
+
{
|
|
21793
|
+
method: "DELETE"
|
|
21794
|
+
}
|
|
21795
|
+
);
|
|
21438
21796
|
}
|
|
21439
21797
|
};
|
|
21440
21798
|
}
|
|
@@ -21454,10 +21812,13 @@ var Granular = class _Granular {
|
|
|
21454
21812
|
}
|
|
21455
21813
|
if (params.since) query.set("since", params.since.toISOString());
|
|
21456
21814
|
if (params.until) query.set("until", params.until.toISOString());
|
|
21457
|
-
if (params.isAcked !== void 0)
|
|
21815
|
+
if (params.isAcked !== void 0)
|
|
21816
|
+
query.set("isAcked", params.isAcked ? "1" : "0");
|
|
21458
21817
|
if (params.limit) query.set("limit", String(params.limit));
|
|
21459
21818
|
if (params.offset) query.set("offset", String(params.offset));
|
|
21460
|
-
const result = await this.request(
|
|
21819
|
+
const result = await this.request(
|
|
21820
|
+
`/control/stream-events?${query.toString()}`
|
|
21821
|
+
);
|
|
21461
21822
|
return (result.items || []).map((row) => ({
|
|
21462
21823
|
eventId: row.event_id,
|
|
21463
21824
|
streamName: row.stream_name,
|
|
@@ -21488,7 +21849,9 @@ var Granular = class _Granular {
|
|
|
21488
21849
|
since: cursor,
|
|
21489
21850
|
limit: 100
|
|
21490
21851
|
});
|
|
21491
|
-
const orderedEvents = [...events].sort(
|
|
21852
|
+
const orderedEvents = [...events].sort(
|
|
21853
|
+
(a, b) => a.createdAt - b.createdAt
|
|
21854
|
+
);
|
|
21492
21855
|
for (const event of orderedEvents) {
|
|
21493
21856
|
if (seenEventIds.has(event.eventId)) {
|
|
21494
21857
|
continue;
|
|
@@ -21501,15 +21864,19 @@ var Granular = class _Granular {
|
|
|
21501
21864
|
params.onEvent(event);
|
|
21502
21865
|
}
|
|
21503
21866
|
} catch (err) {
|
|
21504
|
-
params.onError?.(
|
|
21867
|
+
params.onError?.(
|
|
21868
|
+
err instanceof Error ? err : new Error(String(err))
|
|
21869
|
+
);
|
|
21505
21870
|
}
|
|
21506
21871
|
await new Promise((resolve2) => setTimeout(resolve2, interval));
|
|
21507
21872
|
}
|
|
21508
21873
|
};
|
|
21509
21874
|
poll();
|
|
21510
|
-
return {
|
|
21511
|
-
|
|
21512
|
-
|
|
21875
|
+
return {
|
|
21876
|
+
unsubscribe: () => {
|
|
21877
|
+
running = false;
|
|
21878
|
+
}
|
|
21879
|
+
};
|
|
21513
21880
|
},
|
|
21514
21881
|
ack: async (eventId) => {
|
|
21515
21882
|
await this.request("/control/stream-events/ack", {
|
|
@@ -21527,7 +21894,9 @@ var Granular = class _Granular {
|
|
|
21527
21894
|
const sandbox = await this._resolveSandboxId(params.ontology);
|
|
21528
21895
|
const query = new URLSearchParams({ sandboxId: sandbox });
|
|
21529
21896
|
if (params.environment) query.set("environmentId", params.environment);
|
|
21530
|
-
const result = await this.request(
|
|
21897
|
+
const result = await this.request(
|
|
21898
|
+
`/control/stream-events/stats?${query.toString()}`
|
|
21899
|
+
);
|
|
21531
21900
|
return (result.items || []).map((row) => ({
|
|
21532
21901
|
streamName: row.stream_name,
|
|
21533
21902
|
eventType: row.event_type,
|
|
@@ -21545,10 +21914,14 @@ var Granular = class _Granular {
|
|
|
21545
21914
|
get subjects() {
|
|
21546
21915
|
return {
|
|
21547
21916
|
get: async (subjectId) => {
|
|
21548
|
-
return normalizeSubject(
|
|
21917
|
+
return normalizeSubject(
|
|
21918
|
+
await this.request(`/control/subjects/${subjectId}`)
|
|
21919
|
+
);
|
|
21549
21920
|
},
|
|
21550
21921
|
listAssignments: async (subjectId) => {
|
|
21551
|
-
return this.request(
|
|
21922
|
+
return this.request(
|
|
21923
|
+
`/control/subjects/${subjectId}/assignments`
|
|
21924
|
+
);
|
|
21552
21925
|
}
|
|
21553
21926
|
};
|
|
21554
21927
|
}
|
|
@@ -21558,24 +21931,31 @@ var Granular = class _Granular {
|
|
|
21558
21931
|
get users() {
|
|
21559
21932
|
return {
|
|
21560
21933
|
create: async (data) => {
|
|
21561
|
-
return normalizeSubject(
|
|
21562
|
-
|
|
21563
|
-
|
|
21564
|
-
|
|
21565
|
-
|
|
21566
|
-
|
|
21934
|
+
return normalizeSubject(
|
|
21935
|
+
await this.request("/control/subjects", {
|
|
21936
|
+
method: "POST",
|
|
21937
|
+
body: JSON.stringify({
|
|
21938
|
+
identityId: data.id,
|
|
21939
|
+
name: data.name,
|
|
21940
|
+
email: data.email
|
|
21941
|
+
})
|
|
21567
21942
|
})
|
|
21568
|
-
|
|
21943
|
+
);
|
|
21569
21944
|
},
|
|
21570
21945
|
get: async (id) => {
|
|
21571
|
-
return normalizeSubject(
|
|
21946
|
+
return normalizeSubject(
|
|
21947
|
+
await this.request(`/control/subjects/${id}`)
|
|
21948
|
+
);
|
|
21572
21949
|
}
|
|
21573
21950
|
};
|
|
21574
21951
|
}
|
|
21575
21952
|
async _resolveSandboxId(ontologyNameOrId) {
|
|
21576
21953
|
if (ontologyNameOrId.startsWith("sbx_")) return ontologyNameOrId;
|
|
21577
|
-
const result = await this.request(
|
|
21578
|
-
|
|
21954
|
+
const result = await this.request(
|
|
21955
|
+
`/control/sandboxes?name=${encodeURIComponent(ontologyNameOrId)}`
|
|
21956
|
+
);
|
|
21957
|
+
if (result.items.length === 0)
|
|
21958
|
+
throw new Error(`Ontology not found: ${ontologyNameOrId}`);
|
|
21579
21959
|
return result.items[0].sandboxId;
|
|
21580
21960
|
}
|
|
21581
21961
|
/**
|
|
@@ -21590,9 +21970,9 @@ var Granular = class _Granular {
|
|
|
21590
21970
|
const response = await fetch(url, {
|
|
21591
21971
|
...options,
|
|
21592
21972
|
headers: {
|
|
21593
|
-
|
|
21973
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
21594
21974
|
"Content-Type": "application/json",
|
|
21595
|
-
|
|
21975
|
+
Connection: "close",
|
|
21596
21976
|
...options.headers
|
|
21597
21977
|
}
|
|
21598
21978
|
});
|
|
@@ -21603,7 +21983,11 @@ var Granular = class _Granular {
|
|
|
21603
21983
|
return response.json();
|
|
21604
21984
|
}
|
|
21605
21985
|
const errorText = await response.text();
|
|
21606
|
-
const retryable = isRetryableLocalWorkerRestart(
|
|
21986
|
+
const retryable = isRetryableLocalWorkerRestart(
|
|
21987
|
+
response.status,
|
|
21988
|
+
errorText,
|
|
21989
|
+
url
|
|
21990
|
+
);
|
|
21607
21991
|
if (retryable && attempt < LOCAL_CONTROL_REQUEST_RETRY_COUNT) {
|
|
21608
21992
|
if (this.debugHttp) {
|
|
21609
21993
|
console.warn(
|
|
@@ -21662,7 +22046,7 @@ async function resolveEnvironmentData(granular, options) {
|
|
|
21662
22046
|
const ontologyId = await resolveOntologyId(granular, options.ontology);
|
|
21663
22047
|
const environmentName = options.environment ?? "dev";
|
|
21664
22048
|
const environments = await granular.environments.list(ontologyId);
|
|
21665
|
-
const existing = environments.find((
|
|
22049
|
+
const existing = environments.find((environment2) => matchesEnvironmentName(environment2, environmentName));
|
|
21666
22050
|
if (existing) {
|
|
21667
22051
|
return existing;
|
|
21668
22052
|
}
|
|
@@ -21671,21 +22055,13 @@ async function resolveEnvironmentData(granular, options) {
|
|
|
21671
22055
|
`No environment named \`${environmentName}\` found for ontology \`${ontologyId}\`. Run \`granular connect test\` or \`granular session create\` first, or pass \`--environment-id\`.`
|
|
21672
22056
|
);
|
|
21673
22057
|
}
|
|
21674
|
-
const
|
|
22058
|
+
const environment = await granular.openEnvironment({
|
|
21675
22059
|
ontology: ontologyId,
|
|
21676
|
-
|
|
22060
|
+
tag: environmentName,
|
|
21677
22061
|
userId: options.userId ?? "granular-cli",
|
|
21678
22062
|
permissions: options.permissions ?? ["default"]
|
|
21679
22063
|
});
|
|
21680
|
-
|
|
21681
|
-
return await granular.environments.get(connection.environmentId);
|
|
21682
|
-
} finally {
|
|
21683
|
-
try {
|
|
21684
|
-
await connection.disconnect();
|
|
21685
|
-
} catch {
|
|
21686
|
-
connection?.client?.disconnect?.();
|
|
21687
|
-
}
|
|
21688
|
-
}
|
|
22064
|
+
return await granular.environments.get(environment.environmentId);
|
|
21689
22065
|
}
|
|
21690
22066
|
async function listSessionsForEnvironment(granular, environmentId, status) {
|
|
21691
22067
|
if (status === "all") {
|
|
@@ -21702,11 +22078,12 @@ async function listSessionsForEnvironment(granular, environmentId, status) {
|
|
|
21702
22078
|
async function connectRuntime(options) {
|
|
21703
22079
|
if (options.sessionId) {
|
|
21704
22080
|
const { granular: granular2 } = createGranularClient();
|
|
21705
|
-
const
|
|
22081
|
+
const session3 = await granular2["connectSession"]({ sessionId: options.sessionId });
|
|
21706
22082
|
return {
|
|
21707
22083
|
granular: granular2,
|
|
21708
|
-
environment:
|
|
21709
|
-
|
|
22084
|
+
environment: session3.environment,
|
|
22085
|
+
session: session3,
|
|
22086
|
+
ontologyId: session3.ontologyId || session3.sandboxId
|
|
21710
22087
|
};
|
|
21711
22088
|
}
|
|
21712
22089
|
const { granular, config } = createGranularClient();
|
|
@@ -21714,15 +22091,17 @@ async function connectRuntime(options) {
|
|
|
21714
22091
|
if (!ontologyId) {
|
|
21715
22092
|
throw new Error("No ontology configured. Run `granular init` first or pass `--ontology`.");
|
|
21716
22093
|
}
|
|
21717
|
-
const environment = await granular.
|
|
22094
|
+
const environment = await granular.openEnvironment({
|
|
21718
22095
|
ontology: ontologyId,
|
|
21719
|
-
|
|
22096
|
+
tag: options.environment ?? "dev",
|
|
21720
22097
|
userId: options.userId ?? "granular-cli",
|
|
21721
22098
|
permissions: options.permissions ?? ["default"]
|
|
21722
22099
|
});
|
|
22100
|
+
const session2 = await environment.sessions.create();
|
|
21723
22101
|
return {
|
|
21724
22102
|
granular,
|
|
21725
22103
|
environment,
|
|
22104
|
+
session: session2,
|
|
21726
22105
|
ontologyId
|
|
21727
22106
|
};
|
|
21728
22107
|
}
|
|
@@ -21732,9 +22111,9 @@ async function withRuntimeConnection(options, callback) {
|
|
|
21732
22111
|
return await callback(connection);
|
|
21733
22112
|
} finally {
|
|
21734
22113
|
try {
|
|
21735
|
-
await connection.
|
|
22114
|
+
await connection.session.disconnect();
|
|
21736
22115
|
} catch {
|
|
21737
|
-
connection.
|
|
22116
|
+
connection.session.disconnectTransport();
|
|
21738
22117
|
}
|
|
21739
22118
|
}
|
|
21740
22119
|
}
|
|
@@ -21771,14 +22150,14 @@ async function connectTestCommand(options) {
|
|
|
21771
22150
|
userId: options.user,
|
|
21772
22151
|
permissions: normalizePermissions(options.permissions)
|
|
21773
22152
|
},
|
|
21774
|
-
async ({ environment, ontologyId }) => {
|
|
22153
|
+
async ({ environment, session: session2, ontologyId }) => {
|
|
21775
22154
|
const payload = {
|
|
21776
22155
|
ok: true,
|
|
21777
22156
|
ontologyId,
|
|
21778
22157
|
sandboxId: environment.sandboxId,
|
|
21779
22158
|
environmentId: environment.environmentId,
|
|
21780
22159
|
environment: environment.environment,
|
|
21781
|
-
sessionId:
|
|
22160
|
+
sessionId: session2.sessionId,
|
|
21782
22161
|
subjectId: environment.subjectId,
|
|
21783
22162
|
versionId: environment.versionId
|
|
21784
22163
|
};
|
|
@@ -21824,12 +22203,12 @@ async function sessionHeapCommand(options) {
|
|
|
21824
22203
|
permissions: normalizePermissions(options.permissions),
|
|
21825
22204
|
sessionId: options.session
|
|
21826
22205
|
},
|
|
21827
|
-
async ({ environment, ontologyId }) => {
|
|
22206
|
+
async ({ environment, session: session2, ontologyId }) => {
|
|
21828
22207
|
const payload = {
|
|
21829
22208
|
ontologyId,
|
|
21830
|
-
sessionId:
|
|
22209
|
+
sessionId: session2.sessionId,
|
|
21831
22210
|
environmentId: environment.environmentId,
|
|
21832
|
-
heap:
|
|
22211
|
+
heap: session2.getHeap()
|
|
21833
22212
|
};
|
|
21834
22213
|
if (!emitJson) {
|
|
21835
22214
|
step("Session heap");
|
|
@@ -21852,12 +22231,12 @@ async function sessionDocCommand(options) {
|
|
|
21852
22231
|
permissions: normalizePermissions(options.permissions),
|
|
21853
22232
|
sessionId: options.session
|
|
21854
22233
|
},
|
|
21855
|
-
async ({ environment, ontologyId }) => {
|
|
22234
|
+
async ({ environment, session: session2, ontologyId }) => {
|
|
21856
22235
|
const payload = {
|
|
21857
22236
|
ontologyId,
|
|
21858
|
-
sessionId:
|
|
22237
|
+
sessionId: session2.sessionId,
|
|
21859
22238
|
environmentId: environment.environmentId,
|
|
21860
|
-
document: documentToJson(
|
|
22239
|
+
document: documentToJson(session2.document)
|
|
21861
22240
|
};
|
|
21862
22241
|
if (!emitJson) {
|
|
21863
22242
|
step("Session document");
|
|
@@ -21882,18 +22261,18 @@ async function sessionCreateCommand(options) {
|
|
|
21882
22261
|
permissions,
|
|
21883
22262
|
createIfMissing: true
|
|
21884
22263
|
});
|
|
21885
|
-
const
|
|
22264
|
+
const session2 = await granular.createSession({
|
|
21886
22265
|
environmentId: envData.environmentId
|
|
21887
22266
|
});
|
|
21888
22267
|
try {
|
|
21889
22268
|
const payload = {
|
|
21890
22269
|
ok: true,
|
|
21891
22270
|
ontologyId: envData.sandboxId,
|
|
21892
|
-
environmentId:
|
|
21893
|
-
environment: environment.environment,
|
|
21894
|
-
sessionId:
|
|
21895
|
-
subjectId:
|
|
21896
|
-
versionId:
|
|
22271
|
+
environmentId: session2.environmentId,
|
|
22272
|
+
environment: session2.environment.environment,
|
|
22273
|
+
sessionId: session2.sessionId,
|
|
22274
|
+
subjectId: session2.subjectId,
|
|
22275
|
+
versionId: session2.versionId
|
|
21897
22276
|
};
|
|
21898
22277
|
if (emitJson) {
|
|
21899
22278
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -21912,9 +22291,9 @@ async function sessionCreateCommand(options) {
|
|
|
21912
22291
|
console.log();
|
|
21913
22292
|
} finally {
|
|
21914
22293
|
try {
|
|
21915
|
-
await
|
|
22294
|
+
await session2.disconnect();
|
|
21916
22295
|
} catch {
|
|
21917
|
-
|
|
22296
|
+
session2.disconnectTransport();
|
|
21918
22297
|
}
|
|
21919
22298
|
}
|
|
21920
22299
|
}
|
|
@@ -21992,13 +22371,13 @@ async function graphqlCommand(options) {
|
|
|
21992
22371
|
permissions: normalizePermissions(options.permissions),
|
|
21993
22372
|
sessionId: options.session
|
|
21994
22373
|
},
|
|
21995
|
-
async ({ environment, ontologyId }) => {
|
|
22374
|
+
async ({ environment, session: session2, ontologyId }) => {
|
|
21996
22375
|
const result = await environment.graphql(options.query, variables);
|
|
21997
22376
|
const payload = {
|
|
21998
22377
|
ok: !result.errors || result.errors.length === 0,
|
|
21999
22378
|
ontologyId,
|
|
22000
22379
|
environmentId: environment.environmentId,
|
|
22001
|
-
sessionId:
|
|
22380
|
+
sessionId: session2.sessionId,
|
|
22002
22381
|
query: options.query,
|
|
22003
22382
|
variables: variables ?? null,
|
|
22004
22383
|
result
|
|
@@ -22039,12 +22418,12 @@ async function effectsListCommand(options) {
|
|
|
22039
22418
|
permissions: normalizePermissions(options.permissions),
|
|
22040
22419
|
sessionId: options.session
|
|
22041
22420
|
},
|
|
22042
|
-
async ({ environment, ontologyId }) => {
|
|
22043
|
-
const effects2 = sortEffects(
|
|
22421
|
+
async ({ environment, session: session2, ontologyId }) => {
|
|
22422
|
+
const effects2 = sortEffects(session2.getEffects());
|
|
22044
22423
|
const payload = {
|
|
22045
22424
|
ontologyId,
|
|
22046
22425
|
environmentId: environment.environmentId,
|
|
22047
|
-
sessionId:
|
|
22426
|
+
sessionId: session2.sessionId,
|
|
22048
22427
|
items: effects2
|
|
22049
22428
|
};
|
|
22050
22429
|
if (emitJson) {
|
|
@@ -22084,8 +22463,8 @@ async function effectsDiffCommand(options) {
|
|
|
22084
22463
|
permissions: normalizePermissions(options.permissions),
|
|
22085
22464
|
sessionId: options.session
|
|
22086
22465
|
},
|
|
22087
|
-
async ({ environment, ontologyId }) => {
|
|
22088
|
-
const effects2 = sortEffects(
|
|
22466
|
+
async ({ environment, session: session2, ontologyId }) => {
|
|
22467
|
+
const effects2 = sortEffects(session2.getEffects());
|
|
22089
22468
|
const declared = effects2.map((effect) => effect.name);
|
|
22090
22469
|
const liveReady = effects2.filter((effect) => effect.ready).map((effect) => effect.name);
|
|
22091
22470
|
const declaredOnly = effects2.filter((effect) => !effect.ready).map((effect) => effect.name);
|
|
@@ -22093,7 +22472,7 @@ async function effectsDiffCommand(options) {
|
|
|
22093
22472
|
const payload = {
|
|
22094
22473
|
ontologyId,
|
|
22095
22474
|
environmentId: environment.environmentId,
|
|
22096
|
-
sessionId:
|
|
22475
|
+
sessionId: session2.sessionId,
|
|
22097
22476
|
summary: {
|
|
22098
22477
|
declaredCount: declared.length,
|
|
22099
22478
|
liveReadyCount: liveReady.length,
|
|
@@ -22147,8 +22526,8 @@ async function jobRunCommand(options) {
|
|
|
22147
22526
|
permissions: normalizePermissions(options.permissions),
|
|
22148
22527
|
sessionId: options.session
|
|
22149
22528
|
},
|
|
22150
|
-
async ({ environment, ontologyId }) => {
|
|
22151
|
-
const job2 = await
|
|
22529
|
+
async ({ environment, session: session2, ontologyId }) => {
|
|
22530
|
+
const job2 = await session2.submitJob(code);
|
|
22152
22531
|
const stdout = [];
|
|
22153
22532
|
const stderr = [];
|
|
22154
22533
|
job2.on("stdout", (line) => {
|
|
@@ -22172,7 +22551,7 @@ async function jobRunCommand(options) {
|
|
|
22172
22551
|
ok: true,
|
|
22173
22552
|
ontologyId,
|
|
22174
22553
|
environmentId: environment.environmentId,
|
|
22175
|
-
sessionId:
|
|
22554
|
+
sessionId: session2.sessionId,
|
|
22176
22555
|
jobId: job2.id,
|
|
22177
22556
|
status: job2.status,
|
|
22178
22557
|
stdout,
|