@granular-software/sdk 0.4.1 → 0.4.2
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 +17 -14
- package/dist/adapters/anthropic.d.mts +1 -1
- package/dist/adapters/anthropic.d.ts +1 -1
- package/dist/adapters/langchain.d.mts +1 -1
- package/dist/adapters/langchain.d.ts +1 -1
- package/dist/adapters/mastra.d.mts +1 -1
- package/dist/adapters/mastra.d.ts +1 -1
- package/dist/adapters/openai.d.mts +1 -1
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/cli/index.js +123 -39
- package/dist/index.d.mts +51 -162
- package/dist/index.d.ts +51 -162
- package/dist/index.js +304 -264
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +304 -264
- package/dist/index.mjs.map +1 -1
- package/dist/{types-BOPsFZYi.d.mts → types-C0AVRsVR.d.mts} +85 -31
- package/dist/{types-BOPsFZYi.d.ts → types-C0AVRsVR.d.ts} +85 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,8 +112,8 @@ await env.recordObject({
|
|
|
112
112
|
fields: { name: 'Acme Corp', email: 'billing@acme.com', tier: 'enterprise' },
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
// 5.
|
|
116
|
-
await env.
|
|
115
|
+
// 5. Register live effect handlers for effects already declared in the build manifest
|
|
116
|
+
await granular.registerEffects(env.sandboxId, [
|
|
117
117
|
{
|
|
118
118
|
name: 'get_billing_summary',
|
|
119
119
|
description: 'Get billing summary for a customer',
|
|
@@ -134,8 +134,9 @@ await env.publishTools([
|
|
|
134
134
|
},
|
|
135
135
|
required: ['total', 'invoices'],
|
|
136
136
|
},
|
|
137
|
-
handler: async (customerId: string, params: any) => {
|
|
137
|
+
handler: async (customerId: string, params: any, ctx: any) => {
|
|
138
138
|
// customerId comes from `this.id` in sandbox code
|
|
139
|
+
console.log('Running for subject:', ctx.user.subjectId);
|
|
139
140
|
return { total: 4250.00, invoices: 3, period: params?.period || 'current' };
|
|
140
141
|
},
|
|
141
142
|
},
|
|
@@ -163,17 +164,19 @@ const job = await env.submitJob(`
|
|
|
163
164
|
const result = await job.result;
|
|
164
165
|
```
|
|
165
166
|
|
|
167
|
+
Effects must be declared ahead of time in the sandbox build manifest with `withEffect`. Live registration only makes already-declared effects available at runtime.
|
|
168
|
+
|
|
166
169
|
## Core Flow
|
|
167
170
|
|
|
168
171
|
```
|
|
169
|
-
|
|
172
|
+
declare effects in build manifest → connect() → recordObject() → registerEffects() → submitJob()
|
|
170
173
|
```
|
|
171
174
|
|
|
172
175
|
1. **`recordUser()`** — Register a user and their permission profiles
|
|
173
176
|
2. **`connect()`** — Connect to a sandbox, returning an `Environment`
|
|
174
177
|
3. **`applyManifest()`** — Define your domain ontology (classes, properties, relationships)
|
|
175
178
|
4. **`recordObject()`** — Create/update instances of your classes with fields and relationships
|
|
176
|
-
5. **`
|
|
179
|
+
5. **`granular.registerEffects()`** — Register sandbox-scoped live handlers for effects declared in the build manifest
|
|
177
180
|
6. **`submitJob()`** — Execute code in the sandbox that uses the auto-generated typed classes
|
|
178
181
|
|
|
179
182
|
## Defining the Domain Ontology
|
|
@@ -249,12 +252,12 @@ const lotr = await env.recordObject({
|
|
|
249
252
|
|
|
250
253
|
> **Cross-class ID uniqueness**: Two objects of different classes can share the same real-world ID (e.g., an `author` "tolkien" and a `publisher` "tolkien"). The SDK derives unique graph paths internally (`author_tolkien`, `publisher_tolkien`) so they never collide.
|
|
251
254
|
|
|
252
|
-
##
|
|
255
|
+
## Effect Definitions
|
|
253
256
|
|
|
254
|
-
|
|
257
|
+
Effects are declared in the manifest with `withEffect`, then their live handlers are registered at sandbox scope. Effects can be **instance methods**, **static methods**, or **global functions**. Both `inputSchema` and `outputSchema` use JSON Schema:
|
|
255
258
|
|
|
256
259
|
```typescript
|
|
257
|
-
await env.
|
|
260
|
+
await granular.registerEffects(env.sandboxId, [
|
|
258
261
|
// Instance method: called as `tolkien.get_bio({ detailed: true })`
|
|
259
262
|
// Handler receives (objectId, params)
|
|
260
263
|
{
|
|
@@ -276,7 +279,7 @@ await env.publishTools([
|
|
|
276
279
|
},
|
|
277
280
|
required: ['bio'],
|
|
278
281
|
},
|
|
279
|
-
handler: async (id: string, params: any) => {
|
|
282
|
+
handler: async (id: string, params: any, ctx: any) => {
|
|
280
283
|
return { bio: `Biography of ${id}`, source: 'database' };
|
|
281
284
|
},
|
|
282
285
|
},
|
|
@@ -297,7 +300,7 @@ await env.publishTools([
|
|
|
297
300
|
type: 'object',
|
|
298
301
|
properties: { results: { type: 'array' } },
|
|
299
302
|
},
|
|
300
|
-
handler: async (params: any) => {
|
|
303
|
+
handler: async (params: any, ctx: any) => {
|
|
301
304
|
return { results: [`Found: ${params.query}`] };
|
|
302
305
|
},
|
|
303
306
|
},
|
|
@@ -316,7 +319,7 @@ await env.publishTools([
|
|
|
316
319
|
type: 'object',
|
|
317
320
|
properties: { results: { type: 'array' } },
|
|
318
321
|
},
|
|
319
|
-
handler: async (params: any) => {
|
|
322
|
+
handler: async (params: any, ctx: any) => {
|
|
320
323
|
return { results: [`Result for: ${params.query}`] };
|
|
321
324
|
},
|
|
322
325
|
},
|
|
@@ -470,8 +473,8 @@ Returns relationship definitions for a given class.
|
|
|
470
473
|
### `environment.listRelated(modelPath, submodelPath)`
|
|
471
474
|
Lists related instances through a relationship.
|
|
472
475
|
|
|
473
|
-
### `
|
|
474
|
-
|
|
476
|
+
### `granular.registerEffects(sandboxId, effects)`
|
|
477
|
+
Registers sandbox-scoped live handlers for effects declared in the sandbox build manifest. Effects can be instance methods (`className` set, `static` omitted), static methods (`static: true`), or global functions (no `className`).
|
|
475
478
|
|
|
476
479
|
### `environment.submitJob(code)`
|
|
477
480
|
Submits code to be executed in the sandbox. The code imports typed classes from `./sandbox-tools`.
|
|
@@ -483,7 +486,7 @@ Get auto-generated TypeScript class declarations. Pass this to LLMs to help them
|
|
|
483
486
|
Execute a GraphQL query against the environment's graph. Authenticated automatically.
|
|
484
487
|
|
|
485
488
|
### `environment.on(event, handler)`
|
|
486
|
-
Listen for events: `'
|
|
489
|
+
Listen for events: `'effect:invoke'`, `'effect:result'`, `'job:status'`, `'stdout'`, etc. Legacy `'tool:*'` aliases still exist internally but are no longer the primary model.
|
|
487
490
|
|
|
488
491
|
### `Environment.toGraphPath(className, id)`
|
|
489
492
|
Convert a class name + real-world ID to a unique graph path (`{className}_{id}`).
|
package/dist/cli/index.js
CHANGED
|
@@ -5552,7 +5552,105 @@ function ensureGitignore() {
|
|
|
5552
5552
|
fs__namespace.writeFileSync(gitignorePath, content + section, "utf-8");
|
|
5553
5553
|
}
|
|
5554
5554
|
}
|
|
5555
|
+
function createDefaultEffectOperations(classNames) {
|
|
5556
|
+
const operations = classNames.flatMap((className) => {
|
|
5557
|
+
const searchName = `search_${className}s`;
|
|
5558
|
+
return [
|
|
5559
|
+
{
|
|
5560
|
+
withEffect: {
|
|
5561
|
+
name: "get_info",
|
|
5562
|
+
description: `Get details of a ${className} by ID`,
|
|
5563
|
+
attachedClass: className,
|
|
5564
|
+
isStatic: false,
|
|
5565
|
+
inputSchema: {
|
|
5566
|
+
type: "object",
|
|
5567
|
+
properties: {
|
|
5568
|
+
include_related: { type: "boolean", description: "Include related items" }
|
|
5569
|
+
}
|
|
5570
|
+
},
|
|
5571
|
+
outputSchema: {
|
|
5572
|
+
type: "object",
|
|
5573
|
+
properties: {
|
|
5574
|
+
id: { type: "string" },
|
|
5575
|
+
name: { type: "string" },
|
|
5576
|
+
related: { type: "array", items: { type: "object" } }
|
|
5577
|
+
}
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5580
|
+
},
|
|
5581
|
+
{
|
|
5582
|
+
withEffect: {
|
|
5583
|
+
name: searchName,
|
|
5584
|
+
description: `Search ${className}s by keyword`,
|
|
5585
|
+
attachedClass: className,
|
|
5586
|
+
isStatic: true,
|
|
5587
|
+
inputSchema: {
|
|
5588
|
+
type: "object",
|
|
5589
|
+
properties: {
|
|
5590
|
+
query: { type: "string", description: "Search keyword" },
|
|
5591
|
+
limit: { type: "number", description: "Max results" }
|
|
5592
|
+
},
|
|
5593
|
+
required: ["query"]
|
|
5594
|
+
},
|
|
5595
|
+
outputSchema: {
|
|
5596
|
+
type: "object",
|
|
5597
|
+
properties: {
|
|
5598
|
+
query: { type: "string" },
|
|
5599
|
+
results: {
|
|
5600
|
+
type: "array",
|
|
5601
|
+
items: {
|
|
5602
|
+
type: "object",
|
|
5603
|
+
properties: {
|
|
5604
|
+
id: { type: "string" },
|
|
5605
|
+
name: { type: "string" }
|
|
5606
|
+
}
|
|
5607
|
+
}
|
|
5608
|
+
},
|
|
5609
|
+
total: { type: "number" }
|
|
5610
|
+
}
|
|
5611
|
+
}
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
];
|
|
5615
|
+
});
|
|
5616
|
+
operations.push({
|
|
5617
|
+
withEffect: {
|
|
5618
|
+
name: "full_text_search",
|
|
5619
|
+
description: "Search across all types",
|
|
5620
|
+
inputSchema: {
|
|
5621
|
+
type: "object",
|
|
5622
|
+
properties: {
|
|
5623
|
+
query: { type: "string", description: "Search query" },
|
|
5624
|
+
types: { type: "array", items: { type: "string" }, description: "Filter by type" }
|
|
5625
|
+
},
|
|
5626
|
+
required: ["query"]
|
|
5627
|
+
},
|
|
5628
|
+
outputSchema: {
|
|
5629
|
+
type: "object",
|
|
5630
|
+
properties: {
|
|
5631
|
+
query: { type: "string" },
|
|
5632
|
+
types: { type: "array", items: { type: "string" } },
|
|
5633
|
+
matches: {
|
|
5634
|
+
type: "array",
|
|
5635
|
+
items: {
|
|
5636
|
+
type: "object",
|
|
5637
|
+
properties: {
|
|
5638
|
+
type: { type: "string" },
|
|
5639
|
+
id: { type: "string" },
|
|
5640
|
+
label: { type: "string" },
|
|
5641
|
+
snippet: { type: "string" }
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5644
|
+
},
|
|
5645
|
+
total: { type: "number" }
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
}
|
|
5649
|
+
});
|
|
5650
|
+
return operations;
|
|
5651
|
+
}
|
|
5555
5652
|
function createDefaultManifest(name) {
|
|
5653
|
+
const classNames = ["note", "tag"];
|
|
5556
5654
|
return {
|
|
5557
5655
|
manifest: {
|
|
5558
5656
|
schemaVersion: 2,
|
|
@@ -5591,7 +5689,8 @@ function createDefaultManifest(name) {
|
|
|
5591
5689
|
leftIsMany: true,
|
|
5592
5690
|
rightIsMany: true
|
|
5593
5691
|
}
|
|
5594
|
-
}
|
|
5692
|
+
},
|
|
5693
|
+
...createDefaultEffectOperations(classNames)
|
|
5595
5694
|
]
|
|
5596
5695
|
}]
|
|
5597
5696
|
}
|
|
@@ -5762,7 +5861,7 @@ var ApiClient = class {
|
|
|
5762
5861
|
return await this.createPermissionProfile(sandboxId, {
|
|
5763
5862
|
name: "default",
|
|
5764
5863
|
rules: {
|
|
5765
|
-
|
|
5864
|
+
effects: { allow: ["*"] },
|
|
5766
5865
|
resources: { allow: ["*"] }
|
|
5767
5866
|
}
|
|
5768
5867
|
});
|
|
@@ -7663,37 +7762,12 @@ async function main() {
|
|
|
7663
7762
|
apiUrl: process.env.GRANULAR_API_URL ?? API_URL,
|
|
7664
7763
|
});
|
|
7665
7764
|
|
|
7666
|
-
|
|
7667
|
-
log(\`Recording user \${userId}\`);
|
|
7668
|
-
const user = await granular.recordUser({
|
|
7669
|
-
userId,
|
|
7670
|
-
name: 'Effects Host',
|
|
7671
|
-
permissions: ['default'],
|
|
7672
|
-
});
|
|
7673
|
-
|
|
7674
|
-
log(\`Connecting to sandbox \${SANDBOX_ID}\`);
|
|
7675
|
-
const env = await granular.connect({ sandbox: SANDBOX_ID, user, clientId: 'effects-host' });
|
|
7676
|
-
|
|
7677
|
-
log('Waiting for graph container to be ready...');
|
|
7678
|
-
for (let i = 0; i < 6; i++) {
|
|
7679
|
-
await new Promise((r) => setTimeout(r, 5000));
|
|
7680
|
-
try {
|
|
7681
|
-
const probe = await env.graphql(\`query { model(path: "class") { path } }\`);
|
|
7682
|
-
if ((probe as any)?.data?.model?.path) {
|
|
7683
|
-
log(\`Graph ready after \${(i + 1) * 5}s\`);
|
|
7684
|
-
break;
|
|
7685
|
-
}
|
|
7686
|
-
} catch {
|
|
7687
|
-
log(\`Not ready yet (\${(i + 1) * 5}s)...\`);
|
|
7688
|
-
}
|
|
7689
|
-
}
|
|
7690
|
-
|
|
7691
|
-
log('Publishing effects...');
|
|
7765
|
+
log(\`Registering live effects for sandbox \${SANDBOX_ID}\`);
|
|
7692
7766
|
${mockDataBlock}
|
|
7693
7767
|
|
|
7694
|
-
await
|
|
7768
|
+
await granular.registerEffects(SANDBOX_ID, ${effectsArray});
|
|
7695
7769
|
|
|
7696
|
-
log('Effects
|
|
7770
|
+
log('Effects registered. Process kept alive for simulator. Press Ctrl+C to exit.');
|
|
7697
7771
|
await new Promise(() => {});
|
|
7698
7772
|
}
|
|
7699
7773
|
|
|
@@ -7858,6 +7932,7 @@ async function initCommand(projectName, options) {
|
|
|
7858
7932
|
hint("granular simulate", "opens app.granular.software/simulator for this sandbox");
|
|
7859
7933
|
console.log();
|
|
7860
7934
|
}
|
|
7935
|
+
var DEFAULT_LOCAL_API_KEY = "gn_sk_tenant_default_principal_local_e2e_00000000";
|
|
7861
7936
|
function promptSecret(question) {
|
|
7862
7937
|
const rl = readline__namespace.createInterface({ input: process.stdin, output: process.stdout });
|
|
7863
7938
|
return new Promise((resolve) => {
|
|
@@ -7883,6 +7958,9 @@ function maskApiKey(apiKey) {
|
|
|
7883
7958
|
if (apiKey.length < 10) return `${apiKey}...`;
|
|
7884
7959
|
return `${apiKey.substring(0, 10)}...`;
|
|
7885
7960
|
}
|
|
7961
|
+
function isLocalApiUrl(apiUrl) {
|
|
7962
|
+
return apiUrl.startsWith("ws://localhost:") || apiUrl.startsWith("wss://localhost:") || apiUrl.startsWith("ws://127.0.0.1:") || apiUrl.startsWith("wss://127.0.0.1:") || apiUrl.startsWith("http://localhost:") || apiUrl.startsWith("https://localhost:") || apiUrl.startsWith("http://127.0.0.1:") || apiUrl.startsWith("https://127.0.0.1:");
|
|
7963
|
+
}
|
|
7886
7964
|
async function loginCommand(options = {}) {
|
|
7887
7965
|
printHeader();
|
|
7888
7966
|
const existing = loadApiKey();
|
|
@@ -7894,7 +7972,10 @@ async function loginCommand(options = {}) {
|
|
|
7894
7972
|
const apiUrl = loadApiUrl();
|
|
7895
7973
|
let apiKey = options.apiKey?.trim();
|
|
7896
7974
|
if (!apiKey) {
|
|
7897
|
-
if (options.manual) {
|
|
7975
|
+
if (!options.manual && isLocalApiUrl(apiUrl)) {
|
|
7976
|
+
apiKey = existing?.trim() || process.env.GRANULAR_LOCAL_API_KEY?.trim() || DEFAULT_LOCAL_API_KEY;
|
|
7977
|
+
info("Using local Granular API key for localhost development.");
|
|
7978
|
+
} else if (options.manual) {
|
|
7898
7979
|
dim("Get your API key at https://app.granular.software/w/default/api-keys");
|
|
7899
7980
|
console.log();
|
|
7900
7981
|
apiKey = await promptSecret("Enter your API key");
|
|
@@ -8448,7 +8529,7 @@ ${manifest.description}`);
|
|
|
8448
8529
|
lines.push(`- [Integration Guide](#integration-guide)`);
|
|
8449
8530
|
lines.push(` - [1. Initialize & Connect](#1-initialize--connect)`);
|
|
8450
8531
|
lines.push(` - [2. Record Objects](#2-record-objects)`);
|
|
8451
|
-
lines.push(` - [3.
|
|
8532
|
+
lines.push(` - [3. Declare And Register Effects](#3-declare-and-register-effects)`);
|
|
8452
8533
|
lines.push(` - [4. Submit Jobs](#4-submit-jobs)`);
|
|
8453
8534
|
lines.push(`
|
|
8454
8535
|
## Getting Started`);
|
|
@@ -8564,11 +8645,11 @@ console.log('Connected to:', env.environmentId);`);
|
|
|
8564
8645
|
lines.push(`*(No classes defined in manifest)*`);
|
|
8565
8646
|
}
|
|
8566
8647
|
lines.push(`
|
|
8567
|
-
### 3.
|
|
8568
|
-
lines.push(`
|
|
8648
|
+
### 3. Declare And Register Effects`);
|
|
8649
|
+
lines.push(`Declare effects in your manifest with \`withEffect\`, then register live handlers at sandbox scope.`);
|
|
8569
8650
|
lines.push(`
|
|
8570
8651
|
\`\`\`typescript`);
|
|
8571
|
-
lines.push(`await
|
|
8652
|
+
lines.push(`await granular.registerEffects('your-sandbox-id', [`);
|
|
8572
8653
|
if (classes.length > 0) {
|
|
8573
8654
|
const cls = classes[0];
|
|
8574
8655
|
lines.push(` // Instance method on ${cls.name}`);
|
|
@@ -8578,8 +8659,9 @@ console.log('Connected to:', env.environmentId);`);
|
|
|
8578
8659
|
lines.push(` className: '${cls.name}',`);
|
|
8579
8660
|
lines.push(` inputSchema: { type: 'object', properties: { maxLength: { type: 'number' } } },`);
|
|
8580
8661
|
lines.push(` outputSchema: { type: 'object', properties: { summary: { type: 'string' } }, required: ['summary'] },`);
|
|
8581
|
-
lines.push(` handler: async (id, params) => {`);
|
|
8662
|
+
lines.push(` handler: async (id, params, ctx) => {`);
|
|
8582
8663
|
lines.push(` // 'id' is the real-world ID of the ${cls.name}`);
|
|
8664
|
+
lines.push(` console.log('Invoked for user', ctx.user.subjectId);`);
|
|
8583
8665
|
lines.push(` return { summary: \`Summary for \${id}\` };`);
|
|
8584
8666
|
lines.push(` },`);
|
|
8585
8667
|
lines.push(` },`);
|
|
@@ -8591,17 +8673,19 @@ console.log('Connected to:', env.environmentId);`);
|
|
|
8591
8673
|
lines.push(` static: true,`);
|
|
8592
8674
|
lines.push(` inputSchema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },`);
|
|
8593
8675
|
lines.push(` outputSchema: { type: 'object', properties: { ids: { type: 'array' } }, required: ['ids'] },`);
|
|
8594
|
-
lines.push(` handler: async (params) => {`);
|
|
8676
|
+
lines.push(` handler: async (params, ctx) => {`);
|
|
8677
|
+
lines.push(` console.log('Invoked for user', ctx.user.subjectId);`);
|
|
8595
8678
|
lines.push(` return { ids: [] };`);
|
|
8596
8679
|
lines.push(` },`);
|
|
8597
8680
|
lines.push(` },`);
|
|
8598
8681
|
}
|
|
8599
|
-
lines.push(` // Global
|
|
8682
|
+
lines.push(` // Global effect`);
|
|
8600
8683
|
lines.push(` {`);
|
|
8601
8684
|
lines.push(` name: 'notify',`);
|
|
8602
8685
|
lines.push(` description: 'Send notification',`);
|
|
8603
8686
|
lines.push(` inputSchema: { type: 'object', properties: { msg: { type: 'string' } }, required: ['msg'] },`);
|
|
8604
|
-
lines.push(` handler: async (params) => {`);
|
|
8687
|
+
lines.push(` handler: async (params, ctx) => {`);
|
|
8688
|
+
lines.push(` console.log('Invoked for user', ctx.user.subjectId);`);
|
|
8605
8689
|
lines.push(` console.log('Notification:', params.msg);`);
|
|
8606
8690
|
lines.push(` },`);
|
|
8607
8691
|
lines.push(` },`);
|