@granular-software/sdk 0.4.16 → 0.4.17

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/index.mjs CHANGED
@@ -11815,6 +11815,70 @@ var Environment = class extends Session {
11815
11815
  await this._runGraphql(mutation.query, mutation.label);
11816
11816
  }
11817
11817
  }
11818
+ async _ensureWorkspaceStreamsRoot() {
11819
+ await this._runGraphql(
11820
+ `mutation { create_model(path: "workspace", label: "workspace") { model { path } } }`,
11821
+ "ensure workspace"
11822
+ ).catch((error) => {
11823
+ if (!error.message.includes("already exists")) throw error;
11824
+ });
11825
+ await this._runGraphql(
11826
+ `mutation { at(path: "workspace") { create_submodel(subpath: "streams", label: "Streams") { model { path } } } }`,
11827
+ "ensure workspace:streams"
11828
+ ).catch((error) => {
11829
+ if (!error.message.includes("already exists")) throw error;
11830
+ });
11831
+ }
11832
+ async _applyEventStreamDeclaration(stream) {
11833
+ await this._ensureWorkspaceStreamsRoot();
11834
+ const streamPath = `workspace:streams:${stream.name}`;
11835
+ await this._runGraphql(
11836
+ `mutation { at(path: "workspace:streams") { create_submodel(subpath: ${JSON.stringify(stream.name)}, label: ${JSON.stringify(stream.name)}) { model { path } } } }`,
11837
+ `create stream ${stream.name}`
11838
+ ).catch((error) => {
11839
+ if (!error.message.includes("already exists")) throw error;
11840
+ });
11841
+ if (stream.description) {
11842
+ await this._runGraphql(
11843
+ `mutation { at(path: ${JSON.stringify(streamPath)}) { set_description(description: ${JSON.stringify(stream.description)}) { done } } }`,
11844
+ `set stream description on ${streamPath}`
11845
+ );
11846
+ }
11847
+ for (const eventType of stream.eventTypes) {
11848
+ const typePath = `${streamPath}:${eventType.name}`;
11849
+ await this._runGraphql(
11850
+ `mutation { at(path: ${JSON.stringify(streamPath)}) { create_submodel(subpath: ${JSON.stringify(eventType.name)}, label: ${JSON.stringify(eventType.name)}) { model { path } } } }`,
11851
+ `create event type ${eventType.name} on ${streamPath}`
11852
+ ).catch((error) => {
11853
+ if (!error.message.includes("already exists")) throw error;
11854
+ });
11855
+ if (eventType.description) {
11856
+ await this._runGraphql(
11857
+ `mutation { at(path: ${JSON.stringify(typePath)}) { set_description(description: ${JSON.stringify(eventType.description)}) { done } } }`,
11858
+ `set event type description on ${typePath}`
11859
+ );
11860
+ }
11861
+ if (eventType.payloadSchema?.properties) {
11862
+ const fieldSpecs = {};
11863
+ for (const [propName, propSchema] of Object.entries(eventType.payloadSchema.properties)) {
11864
+ const schema = propSchema;
11865
+ fieldSpecs[propName] = {
11866
+ type: schema.type ?? "string",
11867
+ description: schema.description
11868
+ };
11869
+ }
11870
+ await this._applyFields(typePath, fieldSpecs);
11871
+ }
11872
+ if (eventType.payloadSchema?.required?.length) {
11873
+ await this._runGraphql(
11874
+ `mutation { at(path: ${JSON.stringify(typePath)}) { create_submodel(subpath: "required", label: "required") { set_string_value(value: ${JSON.stringify(JSON.stringify(eventType.payloadSchema.required))}) { done } } } }`,
11875
+ `store required fields on ${typePath}`
11876
+ ).catch((error) => {
11877
+ if (!error.message.includes("already exists")) throw error;
11878
+ });
11879
+ }
11880
+ }
11881
+ }
11818
11882
  async _applyEffectDeclaration(effect, aliasMap) {
11819
11883
  let containerPath = "workspace:tools:declared";
11820
11884
  if (effect.attachedClass) {
@@ -11902,6 +11966,9 @@ var Environment = class extends Session {
11902
11966
  if (op.withEffect) {
11903
11967
  await this._applyEffectDeclaration(op.withEffect, aliasMap);
11904
11968
  }
11969
+ if (op.defineEventStream) {
11970
+ await this._applyEventStreamDeclaration(op.defineEventStream);
11971
+ }
11905
11972
  }
11906
11973
  /**
11907
11974
  * Apply field definitions (has) to a model via GraphQL
@@ -12874,6 +12941,107 @@ var Granular = class _Granular {
12874
12941
  }
12875
12942
  };
12876
12943
  }
12944
+ /**
12945
+ * Event stream operations: query, subscribe, and acknowledge stream events
12946
+ */
12947
+ get streams() {
12948
+ return {
12949
+ getEvents: async (params) => {
12950
+ const sandbox = await this._resolveSandboxId(params.ontology);
12951
+ const query = new URLSearchParams({ sandboxId: sandbox });
12952
+ if (params.environment) query.set("environmentId", params.environment);
12953
+ if (params.session) query.set("sessionId", params.session);
12954
+ if (params.stream) query.set("streamName", params.stream);
12955
+ if (params.eventTypes && params.eventTypes.length > 0) {
12956
+ query.set("eventTypes", params.eventTypes.join(","));
12957
+ }
12958
+ if (params.since) query.set("since", params.since.toISOString());
12959
+ if (params.until) query.set("until", params.until.toISOString());
12960
+ if (params.isAcked !== void 0) query.set("isAcked", params.isAcked ? "1" : "0");
12961
+ if (params.limit) query.set("limit", String(params.limit));
12962
+ if (params.offset) query.set("offset", String(params.offset));
12963
+ const result = await this.request(`/control/stream-events?${query.toString()}`);
12964
+ return (result.items || []).map((row) => ({
12965
+ eventId: row.event_id,
12966
+ streamName: row.stream_name,
12967
+ eventType: row.event_type,
12968
+ payload: typeof row.payload === "string" ? JSON.parse(row.payload) : row.payload,
12969
+ environmentId: row.environment_id,
12970
+ sessionId: row.session_id,
12971
+ subjectId: row.subject_id,
12972
+ source: row.source,
12973
+ isAcked: Boolean(row.is_acked),
12974
+ createdAt: row.created_at
12975
+ }));
12976
+ },
12977
+ subscribe: (params) => {
12978
+ const interval = params.pollIntervalMs ?? 5e3;
12979
+ let cursor = params.since || /* @__PURE__ */ new Date();
12980
+ let running = true;
12981
+ const seenEventIds = /* @__PURE__ */ new Set();
12982
+ const poll = async () => {
12983
+ while (running) {
12984
+ try {
12985
+ const events = await this.streams.getEvents({
12986
+ ontology: params.ontology,
12987
+ stream: params.stream,
12988
+ environment: params.environment,
12989
+ session: params.session,
12990
+ eventTypes: params.eventTypes,
12991
+ since: cursor,
12992
+ limit: 100
12993
+ });
12994
+ const orderedEvents = [...events].sort((a, b) => a.createdAt - b.createdAt);
12995
+ for (const event of orderedEvents) {
12996
+ if (seenEventIds.has(event.eventId)) {
12997
+ continue;
12998
+ }
12999
+ seenEventIds.add(event.eventId);
13000
+ const eventTime = new Date(event.createdAt * 1e3);
13001
+ if (eventTime > cursor) {
13002
+ cursor = eventTime;
13003
+ }
13004
+ params.onEvent(event);
13005
+ }
13006
+ } catch (err) {
13007
+ params.onError?.(err instanceof Error ? err : new Error(String(err)));
13008
+ }
13009
+ await new Promise((resolve) => setTimeout(resolve, interval));
13010
+ }
13011
+ };
13012
+ poll();
13013
+ return { unsubscribe: () => {
13014
+ running = false;
13015
+ } };
13016
+ },
13017
+ ack: async (eventId) => {
13018
+ await this.request("/control/stream-events/ack", {
13019
+ method: "POST",
13020
+ body: JSON.stringify({ eventIds: [eventId] })
13021
+ });
13022
+ },
13023
+ ackBatch: async (eventIds) => {
13024
+ await this.request("/control/stream-events/ack", {
13025
+ method: "POST",
13026
+ body: JSON.stringify({ eventIds })
13027
+ });
13028
+ },
13029
+ getStats: async (params) => {
13030
+ const sandbox = await this._resolveSandboxId(params.ontology);
13031
+ const query = new URLSearchParams({ sandboxId: sandbox });
13032
+ if (params.environment) query.set("environmentId", params.environment);
13033
+ const result = await this.request(`/control/stream-events/stats?${query.toString()}`);
13034
+ return (result.items || []).map((row) => ({
13035
+ streamName: row.stream_name,
13036
+ eventType: row.event_type,
13037
+ total: Number(row.total),
13038
+ last1h: Number(row.last_1h),
13039
+ last24h: Number(row.last_24h),
13040
+ unacked: Number(row.unacked)
13041
+ }));
13042
+ }
13043
+ };
13044
+ }
12877
13045
  /**
12878
13046
  * Subject management
12879
13047
  */
@@ -12907,6 +13075,12 @@ var Granular = class _Granular {
12907
13075
  }
12908
13076
  };
12909
13077
  }
13078
+ async _resolveSandboxId(ontologyNameOrId) {
13079
+ if (ontologyNameOrId.startsWith("sbx_")) return ontologyNameOrId;
13080
+ const result = await this.request(`/control/sandboxes?name=${encodeURIComponent(ontologyNameOrId)}`);
13081
+ if (result.items.length === 0) throw new Error(`Ontology not found: ${ontologyNameOrId}`);
13082
+ return result.items[0].sandboxId;
13083
+ }
12910
13084
  /**
12911
13085
  * Make an authenticated API request
12912
13086
  */