@granular-software/sdk 0.4.20 → 0.4.22
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 +269 -149
- package/dist/agent-evals.d.mts +1 -1
- package/dist/agent-evals.d.ts +1 -1
- package/dist/agent-evals.js +53 -19
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +53 -19
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/agent-harness.js +5 -2
- package/dist/agent-harness.js.map +1 -1
- package/dist/agent-harness.mjs +5 -2
- package/dist/agent-harness.mjs.map +1 -1
- package/dist/cli/index.js +500 -184
- package/dist/{client-DWYdWpS-.d.mts → client-DLGC0mJk.d.mts} +7 -2
- package/dist/{client-DWYdWpS-.d.ts → client-DLGC0mJk.d.ts} +7 -2
- package/dist/index.d.mts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.js +56 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,95 +61,107 @@ Projects created with `granular init` can include Markdown for coding agents (op
|
|
|
61
61
|
## Quick Start
|
|
62
62
|
|
|
63
63
|
```typescript
|
|
64
|
-
import { Granular, type ManifestContent } from
|
|
64
|
+
import { Granular, type ManifestContent } from "@granular-software/sdk";
|
|
65
65
|
|
|
66
66
|
const granular = new Granular({ apiKey: process.env.GRANULAR_API_KEY });
|
|
67
67
|
|
|
68
68
|
// 1. Connect to the default dev environment for one of your app users
|
|
69
69
|
const env = await granular.connect({
|
|
70
|
-
ontology:
|
|
71
|
-
environment:
|
|
72
|
-
userId:
|
|
73
|
-
permissions: [
|
|
74
|
-
name:
|
|
75
|
-
email:
|
|
70
|
+
ontology: "my-ontology",
|
|
71
|
+
environment: "dev",
|
|
72
|
+
userId: "user_123",
|
|
73
|
+
permissions: ["agent"],
|
|
74
|
+
name: "Jane Doe", // optional
|
|
75
|
+
email: "jane@example.com", // optional
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
// 2. Define your domain ontology
|
|
79
79
|
const manifest: ManifestContent = {
|
|
80
80
|
schemaVersion: 2,
|
|
81
|
-
name:
|
|
82
|
-
volumes: [
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
name: "my-app",
|
|
82
|
+
volumes: [
|
|
83
|
+
{
|
|
84
|
+
name: "schema",
|
|
85
|
+
scope: "sandbox",
|
|
86
|
+
imports: [{ alias: "@std", name: "standard_modules", label: "prod" }],
|
|
87
|
+
operations: [
|
|
88
|
+
// Define classes with typed properties
|
|
89
|
+
{
|
|
90
|
+
create: "customer",
|
|
91
|
+
extends: "@std/class",
|
|
92
|
+
has: {
|
|
93
|
+
name: { type: "string", description: "Customer name" },
|
|
94
|
+
email: { type: "string", description: "Email address" },
|
|
95
|
+
tier: { type: "string", description: "Subscription tier" },
|
|
96
|
+
},
|
|
95
97
|
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
{
|
|
99
|
+
create: "order",
|
|
100
|
+
extends: "@std/class",
|
|
101
|
+
has: {
|
|
102
|
+
total: { type: "number", description: "Order total" },
|
|
103
|
+
status: { type: "string", description: "Order status" },
|
|
104
|
+
},
|
|
103
105
|
},
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
// Define relationships
|
|
107
|
+
{
|
|
108
|
+
defineRelationship: {
|
|
109
|
+
left: "customer",
|
|
110
|
+
right: "order",
|
|
111
|
+
leftSubmodel: "orders",
|
|
112
|
+
rightSubmodel: "customer",
|
|
113
|
+
leftIsMany: true,
|
|
114
|
+
rightIsMany: false,
|
|
115
|
+
},
|
|
111
116
|
},
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
],
|
|
115
120
|
};
|
|
116
121
|
|
|
117
122
|
await env.applyManifest(manifest);
|
|
118
123
|
|
|
119
124
|
// 3. Record object instances
|
|
120
125
|
await env.recordObject({
|
|
121
|
-
className:
|
|
122
|
-
id:
|
|
123
|
-
label:
|
|
124
|
-
fields: { name:
|
|
126
|
+
className: "customer",
|
|
127
|
+
id: "cust_42",
|
|
128
|
+
label: "Acme Corp",
|
|
129
|
+
fields: { name: "Acme Corp", email: "billing@acme.com", tier: "enterprise" },
|
|
125
130
|
});
|
|
126
131
|
|
|
127
132
|
// 4. Register live effect handlers for effects already declared in the ontology manifest
|
|
128
133
|
await granular.registerEffects(env.sandboxId, [
|
|
129
134
|
{
|
|
130
|
-
name:
|
|
131
|
-
description:
|
|
132
|
-
className:
|
|
135
|
+
name: "get_billing_summary",
|
|
136
|
+
description: "Get billing summary for a customer",
|
|
137
|
+
className: "customer", // Attached to Customer class
|
|
133
138
|
// static omitted → instance method (receives object ID automatically)
|
|
134
139
|
inputSchema: {
|
|
135
|
-
type:
|
|
140
|
+
type: "object",
|
|
136
141
|
properties: {
|
|
137
|
-
period: {
|
|
142
|
+
period: {
|
|
143
|
+
type: "string",
|
|
144
|
+
description: 'Billing period (e.g. "2024-Q1")',
|
|
145
|
+
},
|
|
138
146
|
},
|
|
139
147
|
},
|
|
140
148
|
outputSchema: {
|
|
141
|
-
type:
|
|
149
|
+
type: "object",
|
|
142
150
|
properties: {
|
|
143
|
-
total:
|
|
144
|
-
invoices: { type:
|
|
145
|
-
period:
|
|
151
|
+
total: { type: "number", description: "Total billed amount" },
|
|
152
|
+
invoices: { type: "number", description: "Number of invoices" },
|
|
153
|
+
period: { type: "string", description: "The billing period" },
|
|
146
154
|
},
|
|
147
|
-
required: [
|
|
155
|
+
required: ["total", "invoices"],
|
|
148
156
|
},
|
|
149
157
|
handler: async (customerId: string, params: any, ctx: any) => {
|
|
150
158
|
// customerId comes from `this.id` in sandbox code
|
|
151
|
-
console.log(
|
|
152
|
-
return {
|
|
159
|
+
console.log("Running for subject:", ctx.user.subjectId);
|
|
160
|
+
return {
|
|
161
|
+
total: 4250.0,
|
|
162
|
+
invoices: 3,
|
|
163
|
+
period: params?.period || "current",
|
|
164
|
+
};
|
|
153
165
|
},
|
|
154
166
|
},
|
|
155
167
|
]);
|
|
@@ -183,13 +195,13 @@ Effects must be declared ahead of time in the ontology version manifest with `wi
|
|
|
183
195
|
## Core Flow
|
|
184
196
|
|
|
185
197
|
```
|
|
186
|
-
declare effects in the manifest → `granular build` creates or reuses a version → `connect({ ontology, environment, userId })` opens a named environment → `recordObject()` → `registerEffects()` → `submitJob()`
|
|
198
|
+
declare effects in the manifest → `granular build` creates or reuses a version → `connect({ ontology, environment, userId })` opens a named environment → `recordObject()` or `recordObjects()` → `registerEffects()` → `submitJob()`
|
|
187
199
|
```
|
|
188
200
|
|
|
189
201
|
1. **`connect()`** — Connect to an ontology environment for a given `userId`, returning an `Environment`
|
|
190
202
|
2. **`recordUser()`** — Optional explicit user upsert when you want the returned `granularId`
|
|
191
203
|
3. **`applyManifest()`** — Define your domain ontology (classes, properties, relationships)
|
|
192
|
-
4. **`recordObject()`** —
|
|
204
|
+
4. **`recordObject()` / `recordObjects()`** — Use `recordObject()` for one targeted upsert. Use `recordObjects()` for immediate multi-record writes with chunk progress.
|
|
193
205
|
5. **`granular.registerEffects()`** — Register sandbox-scoped live handlers for effects declared in the ontology manifest
|
|
194
206
|
6. **`submitJob()`** — Execute code in the sandbox that uses the auto-generated typed classes
|
|
195
207
|
|
|
@@ -200,42 +212,47 @@ Use `applyManifest()` to declare classes, typed properties, and relationships:
|
|
|
200
212
|
```typescript
|
|
201
213
|
const manifest: ManifestContent = {
|
|
202
214
|
schemaVersion: 2,
|
|
203
|
-
name:
|
|
204
|
-
volumes: [
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
name: "library-app",
|
|
216
|
+
volumes: [
|
|
217
|
+
{
|
|
218
|
+
name: "schema",
|
|
219
|
+
scope: "sandbox",
|
|
220
|
+
imports: [{ alias: "@std", name: "standard_modules", label: "prod" }],
|
|
221
|
+
operations: [
|
|
222
|
+
// Classes with typed properties
|
|
223
|
+
{
|
|
224
|
+
create: "author",
|
|
225
|
+
extends: "@std/class",
|
|
226
|
+
has: {
|
|
227
|
+
name: { type: "string", description: "Author full name" },
|
|
228
|
+
birth_year: { type: "number", description: "Year of birth" },
|
|
229
|
+
},
|
|
216
230
|
},
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
231
|
+
{
|
|
232
|
+
create: "book",
|
|
233
|
+
extends: "@std/class",
|
|
234
|
+
has: {
|
|
235
|
+
title: { type: "string", description: "Book title" },
|
|
236
|
+
isbn: { type: "string", description: "ISBN number" },
|
|
237
|
+
published_year: { type: "number", description: "Year published" },
|
|
238
|
+
},
|
|
225
239
|
},
|
|
226
|
-
},
|
|
227
240
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
// Relationships (bidirectional, with cardinality)
|
|
242
|
+
{
|
|
243
|
+
defineRelationship: {
|
|
244
|
+
left: "author",
|
|
245
|
+
right: "book",
|
|
246
|
+
leftSubmodel: "books",
|
|
247
|
+
rightSubmodel: "author",
|
|
248
|
+
leftIsMany: true,
|
|
249
|
+
rightIsMany: false,
|
|
250
|
+
// → author.books (one-to-many), book.author (many-to-one)
|
|
251
|
+
},
|
|
235
252
|
},
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
],
|
|
239
256
|
};
|
|
240
257
|
|
|
241
258
|
await env.applyManifest(manifest);
|
|
@@ -247,32 +264,47 @@ After defining the ontology, connect as a user and populate it with data:
|
|
|
247
264
|
|
|
248
265
|
```typescript
|
|
249
266
|
const env = await granular.connect({
|
|
250
|
-
ontology:
|
|
251
|
-
environment:
|
|
252
|
-
userId:
|
|
253
|
-
permissions: [
|
|
267
|
+
ontology: "library-app",
|
|
268
|
+
environment: "dev",
|
|
269
|
+
userId: "user_123",
|
|
270
|
+
permissions: ["agent"],
|
|
254
271
|
});
|
|
255
272
|
|
|
256
|
-
const tolkien = await env.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
273
|
+
const [tolkien, lotr] = await env.recordObjects(
|
|
274
|
+
[
|
|
275
|
+
{
|
|
276
|
+
className: "author",
|
|
277
|
+
id: "tolkien", // Real-world ID (unique per class)
|
|
278
|
+
label: "J.R.R. Tolkien",
|
|
279
|
+
fields: { name: "J.R.R. Tolkien", birth_year: 1892 },
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
className: "book",
|
|
283
|
+
id: "lotr",
|
|
284
|
+
label: "The Lord of the Rings",
|
|
285
|
+
fields: {
|
|
286
|
+
title: "The Lord of the Rings",
|
|
287
|
+
isbn: "978-0-618-64015-7",
|
|
288
|
+
published_year: 1954,
|
|
289
|
+
},
|
|
290
|
+
relationships: { author: "tolkien" }, // Real-world ID — SDK resolves it automatically
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
{
|
|
294
|
+
onChunkComplete(info) {
|
|
295
|
+
console.log(`Committed chunk ${info.chunkIndex + 1}/${info.totalChunks}`);
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
);
|
|
262
299
|
// tolkien.path → 'author_tolkien' (internal graph path, unique globally)
|
|
263
300
|
// tolkien.id → 'tolkien' (real-world ID)
|
|
264
|
-
|
|
265
|
-
const lotr = await env.recordObject({
|
|
266
|
-
className: 'book',
|
|
267
|
-
id: 'lotr',
|
|
268
|
-
label: 'The Lord of the Rings',
|
|
269
|
-
fields: { title: 'The Lord of the Rings', isbn: '978-0-618-64015-7', published_year: 1954 },
|
|
270
|
-
relationships: { author: 'tolkien' }, // Real-world ID — SDK resolves it automatically
|
|
271
|
-
});
|
|
301
|
+
// lotr.created → true (false means the record already existed and was updated)
|
|
272
302
|
```
|
|
273
303
|
|
|
274
304
|
> **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.
|
|
275
305
|
|
|
306
|
+
For one-off writes, `recordObject(...)` is still the clearest choice. For large fire-and-forget imports, use `enqueueRecordImport(...)` and poll `getRecordImport(...)` / `getRecordImportSummary()` for aggregate progress.
|
|
307
|
+
|
|
276
308
|
## Effect Definitions
|
|
277
309
|
|
|
278
310
|
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:
|
|
@@ -282,44 +314,44 @@ await granular.registerEffects(env.sandboxId, [
|
|
|
282
314
|
// Instance method: called as `tolkien.get_bio({ detailed: true })`
|
|
283
315
|
// Handler receives (objectId, params)
|
|
284
316
|
{
|
|
285
|
-
name:
|
|
286
|
-
description:
|
|
287
|
-
className:
|
|
317
|
+
name: "get_bio",
|
|
318
|
+
description: "Get biography of an author",
|
|
319
|
+
className: "author", // Attached to Author class
|
|
288
320
|
// static: false (default) // Instance method
|
|
289
321
|
inputSchema: {
|
|
290
|
-
type:
|
|
322
|
+
type: "object",
|
|
291
323
|
properties: {
|
|
292
|
-
detailed: { type:
|
|
324
|
+
detailed: { type: "boolean", description: "Include full details" },
|
|
293
325
|
},
|
|
294
326
|
},
|
|
295
327
|
outputSchema: {
|
|
296
|
-
type:
|
|
328
|
+
type: "object",
|
|
297
329
|
properties: {
|
|
298
|
-
bio:
|
|
299
|
-
source: { type:
|
|
330
|
+
bio: { type: "string", description: "The biography text" },
|
|
331
|
+
source: { type: "string", description: "Source of the bio" },
|
|
300
332
|
},
|
|
301
|
-
required: [
|
|
333
|
+
required: ["bio"],
|
|
302
334
|
},
|
|
303
335
|
handler: async (id: string, params: any, ctx: any) => {
|
|
304
|
-
return { bio: `Biography of ${id}`, source:
|
|
336
|
+
return { bio: `Biography of ${id}`, source: "database" };
|
|
305
337
|
},
|
|
306
338
|
},
|
|
307
339
|
|
|
308
340
|
// Static method: called as `Author.search({ query: 'tolkien' })`
|
|
309
341
|
// Handler receives (params) — no object ID
|
|
310
342
|
{
|
|
311
|
-
name:
|
|
312
|
-
description:
|
|
313
|
-
className:
|
|
343
|
+
name: "search",
|
|
344
|
+
description: "Search for authors",
|
|
345
|
+
className: "author",
|
|
314
346
|
static: true,
|
|
315
347
|
inputSchema: {
|
|
316
|
-
type:
|
|
317
|
-
properties: { query: { type:
|
|
318
|
-
required: [
|
|
348
|
+
type: "object",
|
|
349
|
+
properties: { query: { type: "string" } },
|
|
350
|
+
required: ["query"],
|
|
319
351
|
},
|
|
320
352
|
outputSchema: {
|
|
321
|
-
type:
|
|
322
|
-
properties: { results: { type:
|
|
353
|
+
type: "object",
|
|
354
|
+
properties: { results: { type: "array" } },
|
|
323
355
|
},
|
|
324
356
|
handler: async (params: any, ctx: any) => {
|
|
325
357
|
return { results: [`Found: ${params.query}`] };
|
|
@@ -329,16 +361,16 @@ await granular.registerEffects(env.sandboxId, [
|
|
|
329
361
|
// Global tool: called as `global_search({ query: 'rings' })`
|
|
330
362
|
// No className → standalone exported function
|
|
331
363
|
{
|
|
332
|
-
name:
|
|
333
|
-
description:
|
|
364
|
+
name: "global_search",
|
|
365
|
+
description: "Search across everything",
|
|
334
366
|
inputSchema: {
|
|
335
|
-
type:
|
|
336
|
-
properties: { query: { type:
|
|
337
|
-
required: [
|
|
367
|
+
type: "object",
|
|
368
|
+
properties: { query: { type: "string" } },
|
|
369
|
+
required: ["query"],
|
|
338
370
|
},
|
|
339
371
|
outputSchema: {
|
|
340
|
-
type:
|
|
341
|
-
properties: { results: { type:
|
|
372
|
+
type: "object",
|
|
373
|
+
properties: { results: { type: "array" } },
|
|
342
374
|
},
|
|
343
375
|
handler: async (params: any, ctx: any) => {
|
|
344
376
|
return { results: [`Result for: ${params.query}`] };
|
|
@@ -353,6 +385,14 @@ After applying the manifest and publishing tools, the sandbox gets **auto-genera
|
|
|
353
385
|
|
|
354
386
|
```typescript
|
|
355
387
|
// What the sandbox sees (auto-generated):
|
|
388
|
+
export interface SandboxPageResult<T> {
|
|
389
|
+
items: T[];
|
|
390
|
+
page: number;
|
|
391
|
+
perPage: number;
|
|
392
|
+
totalCount: number;
|
|
393
|
+
hasMore: boolean;
|
|
394
|
+
}
|
|
395
|
+
|
|
356
396
|
export declare class Author {
|
|
357
397
|
readonly id: string;
|
|
358
398
|
readonly name: string;
|
|
@@ -361,13 +401,45 @@ export declare class Author {
|
|
|
361
401
|
constructor(id: string, fields?: Record<string, any>);
|
|
362
402
|
|
|
363
403
|
/** Get a cached Author by graph path, hydrating from the graph when needed */
|
|
364
|
-
static get(query: {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
404
|
+
static get(query: {
|
|
405
|
+
path: string;
|
|
406
|
+
refresh?: boolean;
|
|
407
|
+
}): Promise<Author | null>;
|
|
408
|
+
|
|
409
|
+
/** Count Author instances without loading them into the heap */
|
|
410
|
+
static count(): Promise<number>;
|
|
411
|
+
|
|
412
|
+
/** Return one page of Author instances together with pagination metadata */
|
|
413
|
+
static page(query?: {
|
|
414
|
+
page?: number;
|
|
415
|
+
perPage?: number;
|
|
416
|
+
limit?: number;
|
|
417
|
+
saveAs?: string;
|
|
418
|
+
refresh?: boolean;
|
|
419
|
+
}): Promise<SandboxPageResult<Author>>;
|
|
420
|
+
|
|
421
|
+
/** List one page of Author instances */
|
|
422
|
+
static list(query?: {
|
|
423
|
+
page?: number;
|
|
424
|
+
perPage?: number;
|
|
425
|
+
limit?: number;
|
|
426
|
+
saveAs?: string;
|
|
427
|
+
refresh?: boolean;
|
|
428
|
+
}): Promise<Author[]>;
|
|
429
|
+
|
|
430
|
+
/** Stream Author instances page by page */
|
|
431
|
+
static iterate(query?: {
|
|
432
|
+
page?: number;
|
|
433
|
+
perPage?: number;
|
|
434
|
+
limit?: number;
|
|
435
|
+
maxItems?: number;
|
|
436
|
+
refresh?: boolean;
|
|
437
|
+
}): AsyncIterable<Author>;
|
|
368
438
|
|
|
369
439
|
/** Get biography of an author */
|
|
370
|
-
get_bio(input?: {
|
|
440
|
+
get_bio(input?: {
|
|
441
|
+
detailed?: boolean;
|
|
442
|
+
}): Promise<{ bio: string; source?: string }>;
|
|
371
443
|
|
|
372
444
|
/** Search for authors (static) */
|
|
373
445
|
static search(input: { query: string }): Promise<{ results?: any[] }>;
|
|
@@ -384,36 +456,69 @@ export declare class Book {
|
|
|
384
456
|
|
|
385
457
|
static get(query: { path: string; refresh?: boolean }): Promise<Book | null>;
|
|
386
458
|
|
|
387
|
-
static
|
|
459
|
+
static count(): Promise<number>;
|
|
460
|
+
|
|
461
|
+
static page(query?: {
|
|
462
|
+
page?: number;
|
|
463
|
+
perPage?: number;
|
|
464
|
+
limit?: number;
|
|
465
|
+
saveAs?: string;
|
|
466
|
+
refresh?: boolean;
|
|
467
|
+
}): Promise<SandboxPageResult<Book>>;
|
|
468
|
+
|
|
469
|
+
static list(query?: {
|
|
470
|
+
page?: number;
|
|
471
|
+
perPage?: number;
|
|
472
|
+
limit?: number;
|
|
473
|
+
saveAs?: string;
|
|
474
|
+
refresh?: boolean;
|
|
475
|
+
}): Promise<Book[]>;
|
|
476
|
+
|
|
477
|
+
static iterate(query?: {
|
|
478
|
+
page?: number;
|
|
479
|
+
perPage?: number;
|
|
480
|
+
limit?: number;
|
|
481
|
+
maxItems?: number;
|
|
482
|
+
refresh?: boolean;
|
|
483
|
+
}): AsyncIterable<Book>;
|
|
388
484
|
|
|
389
485
|
/** Navigate to author (many_to_one) */
|
|
390
486
|
get_author(): Promise<Author | null>;
|
|
391
487
|
}
|
|
392
488
|
|
|
393
489
|
/** Search across everything */
|
|
394
|
-
export declare function global_search(input: {
|
|
490
|
+
export declare function global_search(input: {
|
|
491
|
+
query: string;
|
|
492
|
+
}): Promise<{ results?: any[] }>;
|
|
395
493
|
```
|
|
396
494
|
|
|
397
495
|
The LLM or user writes code against these typed classes:
|
|
398
496
|
|
|
399
497
|
```typescript
|
|
400
|
-
import { Author, Book, global_search } from
|
|
401
|
-
|
|
402
|
-
const
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
498
|
+
import { Author, Book, global_search } from "./sandbox-tools";
|
|
499
|
+
|
|
500
|
+
const totalAuthors = await Author.count();
|
|
501
|
+
const firstPage = await Author.page({ page: 1, perPage: 25 });
|
|
502
|
+
const authors = firstPage.items;
|
|
503
|
+
const tolkien = authors.find((author) => author.name === "J.R.R. Tolkien");
|
|
504
|
+
if (!tolkien) throw new Error("Author not found");
|
|
505
|
+
console.log(totalAuthors, firstPage.hasMore);
|
|
506
|
+
console.log(tolkien.name); // "J.R.R. Tolkien"
|
|
507
|
+
|
|
508
|
+
for await (const author of Author.iterate({ perPage: 100, maxItems: 200 })) {
|
|
509
|
+
console.log(author.id);
|
|
510
|
+
}
|
|
406
511
|
|
|
407
512
|
const bio = await tolkien.get_bio({ detailed: true });
|
|
408
|
-
console.log(bio.bio);
|
|
513
|
+
console.log(bio.bio); // typed as string
|
|
409
514
|
|
|
410
515
|
const books = await tolkien.get_books();
|
|
411
516
|
for (const book of books) {
|
|
412
|
-
console.log(book.title, book.isbn);
|
|
517
|
+
console.log(book.title, book.isbn); // typed properties
|
|
413
518
|
}
|
|
414
519
|
|
|
415
|
-
const results = await Author.search({ query:
|
|
416
|
-
const globalResults = await global_search({ query:
|
|
520
|
+
const results = await Author.search({ query: "tolkien" });
|
|
521
|
+
const globalResults = await global_search({ query: "rings" });
|
|
417
522
|
```
|
|
418
523
|
|
|
419
524
|
## GraphQL API
|
|
@@ -423,7 +528,7 @@ Every environment has a built-in GraphQL API for direct graph queries:
|
|
|
423
528
|
```typescript
|
|
424
529
|
// Authenticated automatically with your API key
|
|
425
530
|
const result = await env.graphql(
|
|
426
|
-
`query { model(path: "author") { path label submodels { path label } } }
|
|
531
|
+
`query { model(path: "author") { path label submodels { path label } } }`,
|
|
427
532
|
);
|
|
428
533
|
|
|
429
534
|
// The endpoint is also available as a URL
|
|
@@ -445,21 +550,27 @@ See [examples/](./examples/) for runnable code:
|
|
|
445
550
|
## API Reference
|
|
446
551
|
|
|
447
552
|
### `granular.recordUser(options)`
|
|
553
|
+
|
|
448
554
|
Registers a user identity and their assigned permission profiles.
|
|
449
555
|
|
|
450
556
|
### `granular.connect(options)`
|
|
557
|
+
|
|
451
558
|
Connects to an environment session for the specified user. Pass `ontology` and `environment` explicitly, for example `environment: 'dev'` or `environment: 'prod'`. Only pass `tagName` for advanced overrides.
|
|
452
559
|
|
|
453
560
|
### `environment.applyManifest(manifest)`
|
|
561
|
+
|
|
454
562
|
Defines domain ontology: classes (with typed properties), and relationships (with cardinality).
|
|
455
563
|
|
|
456
564
|
### `environment.recordObject(options)`
|
|
565
|
+
|
|
457
566
|
Creates or updates a class instance with fields and relationships. Returns `{ path, id, created }`.
|
|
458
567
|
|
|
459
568
|
### `environment.recordObjects(records, options?)`
|
|
569
|
+
|
|
460
570
|
Batch upsert for many instances. The SDK sends **chunks** (default **100** rows per HTTP `POST` to `/records/batch`) with **retries** on transient failures, so large arrays do not time out as a single oversized request.
|
|
461
571
|
|
|
462
572
|
Optional **`options`**:
|
|
573
|
+
|
|
463
574
|
- **`batchSize`** — max rows per request (default 100).
|
|
464
575
|
- **`concurrency`** — how many chunk requests may run in parallel (default 1, max 16); can reduce wall time when the server can overlap work.
|
|
465
576
|
- **`onChunkComplete`** — async-friendly hook after each chunk for progress UIs; the returned array is always ordered like `records`.
|
|
@@ -467,30 +578,39 @@ Optional **`options`**:
|
|
|
467
578
|
**Sync batch vs queued import:** use **`recordObjects`** when you need **synchronous** commits and/or per-chunk feedback. Use **`enqueueRecordImport`** + **`getRecordImport` / `getRecordImportSummary`** for **background** ingestion with aggregate counters when admission latency matters more than immediate row-by-row completion.
|
|
468
579
|
|
|
469
580
|
### `environment.getRelationships(modelPath)`
|
|
581
|
+
|
|
470
582
|
Returns relationship definitions for a given class.
|
|
471
583
|
|
|
472
584
|
### `environment.listRelated(modelPath, submodelPath)`
|
|
585
|
+
|
|
473
586
|
Lists related instances through a relationship.
|
|
474
587
|
|
|
475
588
|
### `granular.registerEffects(sandboxId, effects)`
|
|
589
|
+
|
|
476
590
|
Registers sandbox-scoped live handlers for effects declared in the ontology manifest. Effects can be instance methods (`className` set, `static` omitted), static methods (`static: true`), or global functions (no `className`).
|
|
477
591
|
|
|
478
592
|
### `environment.submitJob(code)`
|
|
593
|
+
|
|
479
594
|
Submits code to be executed in the sandbox. The code imports typed classes from `./sandbox-tools`.
|
|
480
595
|
|
|
481
596
|
### `environment.getDomainDocumentation()`
|
|
597
|
+
|
|
482
598
|
Get auto-generated TypeScript class declarations. Pass this to LLMs to help them write correct code.
|
|
483
599
|
|
|
484
600
|
### `environment.graphql(query, variables?)`
|
|
601
|
+
|
|
485
602
|
Execute a GraphQL query against the environment's graph. Authenticated automatically.
|
|
486
603
|
|
|
487
604
|
### `environment.on(event, handler)`
|
|
605
|
+
|
|
488
606
|
Listen for events: `'effect:invoke'`, `'effect:result'`, `'job:status'`, `'stdout'`, etc. Legacy `'tool:*'` aliases still exist internally but are no longer the primary model.
|
|
489
607
|
|
|
490
608
|
### `Environment.toGraphPath(className, id)`
|
|
609
|
+
|
|
491
610
|
Convert a class name + real-world ID to a unique graph path (`{className}_{id}`).
|
|
492
611
|
|
|
493
612
|
### `Environment.extractIdFromGraphPath(graphPath, className)`
|
|
613
|
+
|
|
494
614
|
Extract the real-world ID from a graph path by stripping the class prefix.
|
|
495
615
|
|
|
496
616
|
## License
|
package/dist/agent-evals.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as Prompt,
|
|
1
|
+
import { P as Prompt, d as Environment, c as SessionHeapSnapshot, a$ as ManifestContent, ax as RecordObjectOptions, T as ToolWithHandler, G as Granular, C as ConnectOptions, y as CreateEnvironmentData, i as GranularOptions } from './client-DLGC0mJk.mjs';
|
|
2
2
|
import { GeneratedJobCodeIssue, HarnessControllerBudgets } from './agent-harness.mjs';
|
|
3
3
|
import '@automerge/automerge';
|
|
4
4
|
import '@automerge/automerge/slim';
|
package/dist/agent-evals.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as Prompt,
|
|
1
|
+
import { P as Prompt, d as Environment, c as SessionHeapSnapshot, a$ as ManifestContent, ax as RecordObjectOptions, T as ToolWithHandler, G as Granular, C as ConnectOptions, y as CreateEnvironmentData, i as GranularOptions } from './client-DLGC0mJk.js';
|
|
2
2
|
import { GeneratedJobCodeIssue, HarnessControllerBudgets } from './agent-harness.js';
|
|
3
3
|
import '@automerge/automerge';
|
|
4
4
|
import '@automerge/automerge/slim';
|