@granular-software/sdk 0.4.16 → 0.4.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +5567 -305
- package/dist/index.d.mts +81 -2
- package/dist/index.d.ts +81 -2
- package/dist/index.js +197 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +197 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/dist/index.mjs
CHANGED
|
@@ -3928,6 +3928,12 @@ var READY_STATE_OPEN = 1;
|
|
|
3928
3928
|
var TOKEN_REFRESH_LEEWAY_MS = 2 * 60 * 1e3;
|
|
3929
3929
|
var TOKEN_REFRESH_RETRY_MS = 30 * 1e3;
|
|
3930
3930
|
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
3931
|
+
var DEBUG_WS = process.env.GRANULAR_DEBUG_WS === "1";
|
|
3932
|
+
function debugWs(...args) {
|
|
3933
|
+
if (DEBUG_WS) {
|
|
3934
|
+
console.log(...args);
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3931
3937
|
var WSClient = class {
|
|
3932
3938
|
ws = null;
|
|
3933
3939
|
url;
|
|
@@ -4227,7 +4233,7 @@ var WSClient = class {
|
|
|
4227
4233
|
}
|
|
4228
4234
|
handleMessage(message) {
|
|
4229
4235
|
if (typeof message !== "object" || message === null) return;
|
|
4230
|
-
|
|
4236
|
+
debugWs("[Granular DEBUG] Received message:", JSON.stringify(message).slice(0, 500));
|
|
4231
4237
|
if ("type" in message && message.type === "sync") {
|
|
4232
4238
|
const syncMessage = message;
|
|
4233
4239
|
let bytes;
|
|
@@ -4247,7 +4253,7 @@ var WSClient = class {
|
|
|
4247
4253
|
} else {
|
|
4248
4254
|
return;
|
|
4249
4255
|
}
|
|
4250
|
-
|
|
4256
|
+
debugWs("[Granular DEBUG] Applying sync bytes:", bytes.length);
|
|
4251
4257
|
const [newDoc, newSyncState] = Automerge.receiveSyncMessage(
|
|
4252
4258
|
this.doc,
|
|
4253
4259
|
this.syncState,
|
|
@@ -4257,19 +4263,19 @@ var WSClient = class {
|
|
|
4257
4263
|
this.syncState = newSyncState;
|
|
4258
4264
|
const docAny = this.doc;
|
|
4259
4265
|
if (docAny.catalog) {
|
|
4260
|
-
|
|
4261
|
-
|
|
4266
|
+
debugWs("[Granular DEBUG] Doc catalog sync applied. Keys in catalog:", Object.keys(docAny.catalog || {}));
|
|
4267
|
+
debugWs("[Granular DEBUG] RawToolCatalogs:", Object.keys(docAny.catalog.rawToolCatalogs || {}));
|
|
4262
4268
|
} else {
|
|
4263
|
-
|
|
4269
|
+
debugWs("[Granular DEBUG] Doc synced but no catalog yet. Keys in doc:", Object.keys(docAny));
|
|
4264
4270
|
}
|
|
4265
4271
|
this.emit("sync", this.doc);
|
|
4266
4272
|
} catch (e) {
|
|
4267
4273
|
try {
|
|
4268
|
-
|
|
4274
|
+
debugWs("[Granular DEBUG] receiveSyncMessage failed, trying applyChanges...");
|
|
4269
4275
|
const [newDoc] = Automerge.applyChanges(this.doc, [bytes]);
|
|
4270
4276
|
this.doc = newDoc;
|
|
4271
4277
|
this.emit("sync", this.doc);
|
|
4272
|
-
|
|
4278
|
+
debugWs("[Granular DEBUG] applyChanges succeeded. Doc:", JSON.stringify(Automerge.toJS(this.doc)));
|
|
4273
4279
|
} catch (applyError) {
|
|
4274
4280
|
console.warn("[Granular] Failed to apply sync message (both sync & applyChanges)", e, applyError);
|
|
4275
4281
|
}
|
|
@@ -4280,10 +4286,10 @@ var WSClient = class {
|
|
|
4280
4286
|
const snapshotMessage = message;
|
|
4281
4287
|
try {
|
|
4282
4288
|
const bytes = new Uint8Array(snapshotMessage.data);
|
|
4283
|
-
|
|
4289
|
+
debugWs("[Granular DEBUG] Loading Automerge session snapshot bytes:", bytes.length);
|
|
4284
4290
|
this.doc = Automerge.load(bytes);
|
|
4285
4291
|
this.emit("sync", this.doc);
|
|
4286
|
-
|
|
4292
|
+
debugWs("[Granular DEBUG] Automerge session snapshot loaded. Doc:", JSON.stringify(Automerge.toJS(this.doc)));
|
|
4287
4293
|
} catch (e) {
|
|
4288
4294
|
console.warn("[Granular] Failed to load snapshot message", e);
|
|
4289
4295
|
}
|
|
@@ -11321,6 +11327,7 @@ var Environment = class extends Session {
|
|
|
11321
11327
|
headers: {
|
|
11322
11328
|
"Authorization": `Bearer ${this._apiKey}`,
|
|
11323
11329
|
"Content-Type": "application/json",
|
|
11330
|
+
"Connection": "close",
|
|
11324
11331
|
...options.headers
|
|
11325
11332
|
}
|
|
11326
11333
|
});
|
|
@@ -11355,7 +11362,8 @@ var Environment = class extends Session {
|
|
|
11355
11362
|
method: "POST",
|
|
11356
11363
|
headers: {
|
|
11357
11364
|
"Content-Type": "application/json",
|
|
11358
|
-
"Authorization": `Bearer ${this._apiKey}
|
|
11365
|
+
"Authorization": `Bearer ${this._apiKey}`,
|
|
11366
|
+
"Connection": "close"
|
|
11359
11367
|
},
|
|
11360
11368
|
body: JSON.stringify({
|
|
11361
11369
|
reason: "sdk_disconnect_http_fallback",
|
|
@@ -11815,6 +11823,70 @@ var Environment = class extends Session {
|
|
|
11815
11823
|
await this._runGraphql(mutation.query, mutation.label);
|
|
11816
11824
|
}
|
|
11817
11825
|
}
|
|
11826
|
+
async _ensureWorkspaceStreamsRoot() {
|
|
11827
|
+
await this._runGraphql(
|
|
11828
|
+
`mutation { create_model(path: "workspace", label: "workspace") { model { path } } }`,
|
|
11829
|
+
"ensure workspace"
|
|
11830
|
+
).catch((error) => {
|
|
11831
|
+
if (!error.message.includes("already exists")) throw error;
|
|
11832
|
+
});
|
|
11833
|
+
await this._runGraphql(
|
|
11834
|
+
`mutation { at(path: "workspace") { create_submodel(subpath: "streams", label: "Streams") { model { path } } } }`,
|
|
11835
|
+
"ensure workspace:streams"
|
|
11836
|
+
).catch((error) => {
|
|
11837
|
+
if (!error.message.includes("already exists")) throw error;
|
|
11838
|
+
});
|
|
11839
|
+
}
|
|
11840
|
+
async _applyEventStreamDeclaration(stream) {
|
|
11841
|
+
await this._ensureWorkspaceStreamsRoot();
|
|
11842
|
+
const streamPath = `workspace:streams:${stream.name}`;
|
|
11843
|
+
await this._runGraphql(
|
|
11844
|
+
`mutation { at(path: "workspace:streams") { create_submodel(subpath: ${JSON.stringify(stream.name)}, label: ${JSON.stringify(stream.name)}) { model { path } } } }`,
|
|
11845
|
+
`create stream ${stream.name}`
|
|
11846
|
+
).catch((error) => {
|
|
11847
|
+
if (!error.message.includes("already exists")) throw error;
|
|
11848
|
+
});
|
|
11849
|
+
if (stream.description) {
|
|
11850
|
+
await this._runGraphql(
|
|
11851
|
+
`mutation { at(path: ${JSON.stringify(streamPath)}) { set_description(description: ${JSON.stringify(stream.description)}) { done } } }`,
|
|
11852
|
+
`set stream description on ${streamPath}`
|
|
11853
|
+
);
|
|
11854
|
+
}
|
|
11855
|
+
for (const eventType of stream.eventTypes) {
|
|
11856
|
+
const typePath = `${streamPath}:${eventType.name}`;
|
|
11857
|
+
await this._runGraphql(
|
|
11858
|
+
`mutation { at(path: ${JSON.stringify(streamPath)}) { create_submodel(subpath: ${JSON.stringify(eventType.name)}, label: ${JSON.stringify(eventType.name)}) { model { path } } } }`,
|
|
11859
|
+
`create event type ${eventType.name} on ${streamPath}`
|
|
11860
|
+
).catch((error) => {
|
|
11861
|
+
if (!error.message.includes("already exists")) throw error;
|
|
11862
|
+
});
|
|
11863
|
+
if (eventType.description) {
|
|
11864
|
+
await this._runGraphql(
|
|
11865
|
+
`mutation { at(path: ${JSON.stringify(typePath)}) { set_description(description: ${JSON.stringify(eventType.description)}) { done } } }`,
|
|
11866
|
+
`set event type description on ${typePath}`
|
|
11867
|
+
);
|
|
11868
|
+
}
|
|
11869
|
+
if (eventType.payloadSchema?.properties) {
|
|
11870
|
+
const fieldSpecs = {};
|
|
11871
|
+
for (const [propName, propSchema] of Object.entries(eventType.payloadSchema.properties)) {
|
|
11872
|
+
const schema = propSchema;
|
|
11873
|
+
fieldSpecs[propName] = {
|
|
11874
|
+
type: schema.type ?? "string",
|
|
11875
|
+
description: schema.description
|
|
11876
|
+
};
|
|
11877
|
+
}
|
|
11878
|
+
await this._applyFields(typePath, fieldSpecs);
|
|
11879
|
+
}
|
|
11880
|
+
if (eventType.payloadSchema?.required?.length) {
|
|
11881
|
+
await this._runGraphql(
|
|
11882
|
+
`mutation { at(path: ${JSON.stringify(typePath)}) { create_submodel(subpath: "required", label: "required") { set_string_value(value: ${JSON.stringify(JSON.stringify(eventType.payloadSchema.required))}) { done } } } }`,
|
|
11883
|
+
`store required fields on ${typePath}`
|
|
11884
|
+
).catch((error) => {
|
|
11885
|
+
if (!error.message.includes("already exists")) throw error;
|
|
11886
|
+
});
|
|
11887
|
+
}
|
|
11888
|
+
}
|
|
11889
|
+
}
|
|
11818
11890
|
async _applyEffectDeclaration(effect, aliasMap) {
|
|
11819
11891
|
let containerPath = "workspace:tools:declared";
|
|
11820
11892
|
if (effect.attachedClass) {
|
|
@@ -11902,6 +11974,9 @@ var Environment = class extends Session {
|
|
|
11902
11974
|
if (op.withEffect) {
|
|
11903
11975
|
await this._applyEffectDeclaration(op.withEffect, aliasMap);
|
|
11904
11976
|
}
|
|
11977
|
+
if (op.defineEventStream) {
|
|
11978
|
+
await this._applyEventStreamDeclaration(op.defineEventStream);
|
|
11979
|
+
}
|
|
11905
11980
|
}
|
|
11906
11981
|
/**
|
|
11907
11982
|
* Apply field definitions (has) to a model via GraphQL
|
|
@@ -12151,6 +12226,7 @@ var Granular = class _Granular {
|
|
|
12151
12226
|
WebSocketCtor;
|
|
12152
12227
|
onUnexpectedClose;
|
|
12153
12228
|
onReconnectError;
|
|
12229
|
+
debugHttp = process.env.GRANULAR_DEBUG_HTTP === "1";
|
|
12154
12230
|
/** Sandbox-level effect registry: sandboxId → (effectKey → ToolWithHandler) */
|
|
12155
12231
|
sandboxEffects = /* @__PURE__ */ new Map();
|
|
12156
12232
|
/** Live sandbox-scoped effect hosts keyed by sandboxId */
|
|
@@ -12874,6 +12950,107 @@ var Granular = class _Granular {
|
|
|
12874
12950
|
}
|
|
12875
12951
|
};
|
|
12876
12952
|
}
|
|
12953
|
+
/**
|
|
12954
|
+
* Event stream operations: query, subscribe, and acknowledge stream events
|
|
12955
|
+
*/
|
|
12956
|
+
get streams() {
|
|
12957
|
+
return {
|
|
12958
|
+
getEvents: async (params) => {
|
|
12959
|
+
const sandbox = await this._resolveSandboxId(params.ontology);
|
|
12960
|
+
const query = new URLSearchParams({ sandboxId: sandbox });
|
|
12961
|
+
if (params.environment) query.set("environmentId", params.environment);
|
|
12962
|
+
if (params.session) query.set("sessionId", params.session);
|
|
12963
|
+
if (params.stream) query.set("streamName", params.stream);
|
|
12964
|
+
if (params.eventTypes && params.eventTypes.length > 0) {
|
|
12965
|
+
query.set("eventTypes", params.eventTypes.join(","));
|
|
12966
|
+
}
|
|
12967
|
+
if (params.since) query.set("since", params.since.toISOString());
|
|
12968
|
+
if (params.until) query.set("until", params.until.toISOString());
|
|
12969
|
+
if (params.isAcked !== void 0) query.set("isAcked", params.isAcked ? "1" : "0");
|
|
12970
|
+
if (params.limit) query.set("limit", String(params.limit));
|
|
12971
|
+
if (params.offset) query.set("offset", String(params.offset));
|
|
12972
|
+
const result = await this.request(`/control/stream-events?${query.toString()}`);
|
|
12973
|
+
return (result.items || []).map((row) => ({
|
|
12974
|
+
eventId: row.event_id,
|
|
12975
|
+
streamName: row.stream_name,
|
|
12976
|
+
eventType: row.event_type,
|
|
12977
|
+
payload: typeof row.payload === "string" ? JSON.parse(row.payload) : row.payload,
|
|
12978
|
+
environmentId: row.environment_id,
|
|
12979
|
+
sessionId: row.session_id,
|
|
12980
|
+
subjectId: row.subject_id,
|
|
12981
|
+
source: row.source,
|
|
12982
|
+
isAcked: Boolean(row.is_acked),
|
|
12983
|
+
createdAt: row.created_at
|
|
12984
|
+
}));
|
|
12985
|
+
},
|
|
12986
|
+
subscribe: (params) => {
|
|
12987
|
+
const interval = params.pollIntervalMs ?? 5e3;
|
|
12988
|
+
let cursor = params.since || /* @__PURE__ */ new Date();
|
|
12989
|
+
let running = true;
|
|
12990
|
+
const seenEventIds = /* @__PURE__ */ new Set();
|
|
12991
|
+
const poll = async () => {
|
|
12992
|
+
while (running) {
|
|
12993
|
+
try {
|
|
12994
|
+
const events = await this.streams.getEvents({
|
|
12995
|
+
ontology: params.ontology,
|
|
12996
|
+
stream: params.stream,
|
|
12997
|
+
environment: params.environment,
|
|
12998
|
+
session: params.session,
|
|
12999
|
+
eventTypes: params.eventTypes,
|
|
13000
|
+
since: cursor,
|
|
13001
|
+
limit: 100
|
|
13002
|
+
});
|
|
13003
|
+
const orderedEvents = [...events].sort((a, b) => a.createdAt - b.createdAt);
|
|
13004
|
+
for (const event of orderedEvents) {
|
|
13005
|
+
if (seenEventIds.has(event.eventId)) {
|
|
13006
|
+
continue;
|
|
13007
|
+
}
|
|
13008
|
+
seenEventIds.add(event.eventId);
|
|
13009
|
+
const eventTime = new Date(event.createdAt * 1e3);
|
|
13010
|
+
if (eventTime > cursor) {
|
|
13011
|
+
cursor = eventTime;
|
|
13012
|
+
}
|
|
13013
|
+
params.onEvent(event);
|
|
13014
|
+
}
|
|
13015
|
+
} catch (err) {
|
|
13016
|
+
params.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
13017
|
+
}
|
|
13018
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
13019
|
+
}
|
|
13020
|
+
};
|
|
13021
|
+
poll();
|
|
13022
|
+
return { unsubscribe: () => {
|
|
13023
|
+
running = false;
|
|
13024
|
+
} };
|
|
13025
|
+
},
|
|
13026
|
+
ack: async (eventId) => {
|
|
13027
|
+
await this.request("/control/stream-events/ack", {
|
|
13028
|
+
method: "POST",
|
|
13029
|
+
body: JSON.stringify({ eventIds: [eventId] })
|
|
13030
|
+
});
|
|
13031
|
+
},
|
|
13032
|
+
ackBatch: async (eventIds) => {
|
|
13033
|
+
await this.request("/control/stream-events/ack", {
|
|
13034
|
+
method: "POST",
|
|
13035
|
+
body: JSON.stringify({ eventIds })
|
|
13036
|
+
});
|
|
13037
|
+
},
|
|
13038
|
+
getStats: async (params) => {
|
|
13039
|
+
const sandbox = await this._resolveSandboxId(params.ontology);
|
|
13040
|
+
const query = new URLSearchParams({ sandboxId: sandbox });
|
|
13041
|
+
if (params.environment) query.set("environmentId", params.environment);
|
|
13042
|
+
const result = await this.request(`/control/stream-events/stats?${query.toString()}`);
|
|
13043
|
+
return (result.items || []).map((row) => ({
|
|
13044
|
+
streamName: row.stream_name,
|
|
13045
|
+
eventType: row.event_type,
|
|
13046
|
+
total: Number(row.total),
|
|
13047
|
+
last1h: Number(row.last_1h),
|
|
13048
|
+
last24h: Number(row.last_24h),
|
|
13049
|
+
unacked: Number(row.unacked)
|
|
13050
|
+
}));
|
|
13051
|
+
}
|
|
13052
|
+
};
|
|
13053
|
+
}
|
|
12877
13054
|
/**
|
|
12878
13055
|
* Subject management
|
|
12879
13056
|
*/
|
|
@@ -12907,17 +13084,26 @@ var Granular = class _Granular {
|
|
|
12907
13084
|
}
|
|
12908
13085
|
};
|
|
12909
13086
|
}
|
|
13087
|
+
async _resolveSandboxId(ontologyNameOrId) {
|
|
13088
|
+
if (ontologyNameOrId.startsWith("sbx_")) return ontologyNameOrId;
|
|
13089
|
+
const result = await this.request(`/control/sandboxes?name=${encodeURIComponent(ontologyNameOrId)}`);
|
|
13090
|
+
if (result.items.length === 0) throw new Error(`Ontology not found: ${ontologyNameOrId}`);
|
|
13091
|
+
return result.items[0].sandboxId;
|
|
13092
|
+
}
|
|
12910
13093
|
/**
|
|
12911
13094
|
* Make an authenticated API request
|
|
12912
13095
|
*/
|
|
12913
13096
|
async request(path, options = {}) {
|
|
12914
13097
|
const url = `${this.httpUrl}${path}`;
|
|
12915
|
-
|
|
13098
|
+
if (this.debugHttp) {
|
|
13099
|
+
console.log(`[SDK] Requesting: ${url}`);
|
|
13100
|
+
}
|
|
12916
13101
|
const response = await fetch(url, {
|
|
12917
13102
|
...options,
|
|
12918
13103
|
headers: {
|
|
12919
13104
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
12920
13105
|
"Content-Type": "application/json",
|
|
13106
|
+
"Connection": "close",
|
|
12921
13107
|
...options.headers
|
|
12922
13108
|
}
|
|
12923
13109
|
});
|