@ironflow/browser 0.30.0 → 0.32.0
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/LICENSE +1 -3
- package/README.md +289 -45
- package/dist/client.d.ts +51 -49
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +355 -151
- package/dist/client.js.map +1 -1
- package/dist/config-client.d.ts +3 -14
- package/dist/config-client.d.ts.map +1 -1
- package/dist/config-client.js +6 -26
- package/dist/config-client.js.map +1 -1
- package/dist/config.d.ts +4 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kv.d.ts.map +1 -1
- package/dist/kv.js +16 -7
- package/dist/kv.js.map +1 -1
- package/dist/subscription.d.ts +1 -0
- package/dist/subscription.d.ts.map +1 -1
- package/dist/subscription.js +64 -13
- package/dist/subscription.js.map +1 -1
- package/dist/transport/connectrpc.d.ts +1 -0
- package/dist/transport/connectrpc.d.ts.map +1 -1
- package/dist/transport/connectrpc.js +51 -30
- package/dist/transport/connectrpc.js.map +1 -1
- package/dist/transport/types.d.ts +4 -4
- package/dist/transport/types.d.ts.map +1 -1
- package/dist/transport/websocket.d.ts +7 -0
- package/dist/transport/websocket.d.ts.map +1 -1
- package/dist/transport/websocket.js +158 -37
- package/dist/transport/websocket.js.map +1 -1
- package/dist/websocket-auth.d.ts +11 -0
- package/dist/websocket-auth.d.ts.map +1 -0
- package/dist/websocket-auth.js +29 -0
- package/dist/websocket-auth.js.map +1 -0
- package/package.json +2 -2
package/dist/client.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Singleton client for browser-based real-time interactions with Ironflow.
|
|
5
5
|
*/
|
|
6
|
-
import { NotConfiguredError, IronflowError, RunFailedError, RunCancelledError, UnauthenticatedError, EnterpriseRequiredError, UnauthorizedError, ValidationError, createLogger, createNoopLogger, DEFAULT_TIMEOUTS, HEADERS, TriggerResponseSchema, TriggerSyncResponseSchema, RunResponseSchema, ListRunsResponseSchema, RunStatusSchema, ErrorResponseSchema, safeJsonParse, patterns, peelProjectionEnvelope, webhookVerifyConfigToWire, webhookGraceToWire, webhookSourceFromWire, webhookDeliveryFromWire, } from "@ironflow/core";
|
|
7
|
-
import { mergeConfig } from "./config.js";
|
|
6
|
+
import { NotConfiguredError, IronflowError, RunFailedError, RunCancelledError, UnauthenticatedError, EnterpriseRequiredError, UnauthorizedError, ValidationError, createLogger, createNoopLogger, DEFAULT_TIMEOUTS, HEADERS, TriggerResponseSchema, TriggerSyncResponseSchema, RunResponseSchema, ListRunsResponseSchema, RunStatusSchema, ErrorResponseSchema, safeJsonParse, patterns, peelProjectionEnvelope, webhookVerifyConfigToWire, webhookGraceToWire, webhookSourceFromWire, webhookDeliveryFromWire, registeredFunctionFromWire, functionHistoryEntryFromWire, storedEventFromWire, runStepFromWire, consumerGroupFromWire, } from "@ironflow/core";
|
|
7
|
+
import { mergeConfig, warnAboutLongLivedBrowserKey } from "./config.js";
|
|
8
8
|
import { SubscriptionManager, } from "./subscription.js";
|
|
9
9
|
import { createWebSocketTransport } from "./transport/websocket.js";
|
|
10
10
|
import { createConnectRPCTransport } from "./transport/connectrpc.js";
|
|
@@ -12,6 +12,18 @@ import { filterWaitStreamFrames } from "./projection-stream.js";
|
|
|
12
12
|
import { BrowserKVClient } from "./kv.js";
|
|
13
13
|
import { BrowserConfigClient } from "./config-client.js";
|
|
14
14
|
import { createAgentsNamespace } from "./agents/index.js";
|
|
15
|
+
function auditEventFromWire(raw) {
|
|
16
|
+
return {
|
|
17
|
+
id: String(raw.id ?? ""),
|
|
18
|
+
runId: String(raw.run_id ?? raw.runId ?? ""),
|
|
19
|
+
functionId: String(raw.function_id ?? raw.functionId ?? ""),
|
|
20
|
+
stepId: (raw.step_id ?? raw.stepId),
|
|
21
|
+
eventType: String(raw.event_type ?? raw.eventType ?? ""),
|
|
22
|
+
payload: raw.payload ?? {},
|
|
23
|
+
metadata: raw.metadata,
|
|
24
|
+
createdAt: String(raw.created_at ?? raw.createdAt ?? ""),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
15
27
|
/**
|
|
16
28
|
* Key for the offline queue's replay entry point.
|
|
17
29
|
*
|
|
@@ -71,6 +83,7 @@ class IronflowClient {
|
|
|
71
83
|
*/
|
|
72
84
|
configure(options = {}) {
|
|
73
85
|
this.config = mergeConfig(options);
|
|
86
|
+
warnAboutLongLivedBrowserKey(this.config.auth);
|
|
74
87
|
// Set up logger
|
|
75
88
|
if (this.config.logger === false) {
|
|
76
89
|
this.logger = createNoopLogger();
|
|
@@ -326,6 +339,21 @@ class IronflowClient {
|
|
|
326
339
|
const response = await this.request(RunResponseSchema, "POST", "/ironflow.v1.IronflowService/GetRun", { id: runId });
|
|
327
340
|
return this.mapRunResponse(response);
|
|
328
341
|
}
|
|
342
|
+
/** Get the durable steps recorded for a run. */
|
|
343
|
+
async getRunSteps(runId) {
|
|
344
|
+
this.ensureConfigured();
|
|
345
|
+
const response = await this.restRequest("GET", `/api/v1/runs/${encodeURIComponent(runId)}/steps`);
|
|
346
|
+
return {
|
|
347
|
+
steps: (response.steps ?? []).map(runStepFromWire),
|
|
348
|
+
count: response.count ?? 0,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
/** Get the entity stream IDs touched by a run. */
|
|
352
|
+
async getRunStreams(runId) {
|
|
353
|
+
this.ensureConfigured();
|
|
354
|
+
const response = await this.restRequest("GET", `/api/v1/runs/${encodeURIComponent(runId)}/streams`);
|
|
355
|
+
return { entityIds: response.entity_ids ?? [] };
|
|
356
|
+
}
|
|
329
357
|
/**
|
|
330
358
|
* List runs with filtering
|
|
331
359
|
*/
|
|
@@ -365,9 +393,7 @@ class IronflowClient {
|
|
|
365
393
|
"Content-Type": "application/json",
|
|
366
394
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
367
395
|
};
|
|
368
|
-
|
|
369
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
370
|
-
}
|
|
396
|
+
this.applyAuthHeader(headers);
|
|
371
397
|
const response = await fetch(url, {
|
|
372
398
|
method: "POST",
|
|
373
399
|
headers,
|
|
@@ -397,9 +423,7 @@ class IronflowClient {
|
|
|
397
423
|
"Content-Type": "application/json",
|
|
398
424
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
399
425
|
};
|
|
400
|
-
|
|
401
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
402
|
-
}
|
|
426
|
+
this.applyAuthHeader(headers);
|
|
403
427
|
const response = await fetch(url, {
|
|
404
428
|
method: "POST",
|
|
405
429
|
headers,
|
|
@@ -430,9 +454,7 @@ class IronflowClient {
|
|
|
430
454
|
"Content-Type": "application/json",
|
|
431
455
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
432
456
|
};
|
|
433
|
-
|
|
434
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
435
|
-
}
|
|
457
|
+
this.applyAuthHeader(headers);
|
|
436
458
|
const response = await fetch(url, {
|
|
437
459
|
method: "POST",
|
|
438
460
|
headers,
|
|
@@ -463,9 +485,7 @@ class IronflowClient {
|
|
|
463
485
|
"Content-Type": "application/json",
|
|
464
486
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
465
487
|
};
|
|
466
|
-
|
|
467
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
468
|
-
}
|
|
488
|
+
this.applyAuthHeader(headers);
|
|
469
489
|
const response = await fetch(url, {
|
|
470
490
|
method: "POST",
|
|
471
491
|
headers,
|
|
@@ -507,9 +527,7 @@ class IronflowClient {
|
|
|
507
527
|
"Content-Type": "application/json",
|
|
508
528
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
509
529
|
};
|
|
510
|
-
|
|
511
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
512
|
-
}
|
|
530
|
+
this.applyAuthHeader(headers);
|
|
513
531
|
const response = await fetch(url, {
|
|
514
532
|
method: "POST",
|
|
515
533
|
headers,
|
|
@@ -554,9 +572,7 @@ class IronflowClient {
|
|
|
554
572
|
"Content-Type": "application/json",
|
|
555
573
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
556
574
|
};
|
|
557
|
-
|
|
558
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
559
|
-
}
|
|
575
|
+
this.applyAuthHeader(headers);
|
|
560
576
|
const response = await fetch(url, {
|
|
561
577
|
method: "POST",
|
|
562
578
|
headers,
|
|
@@ -614,9 +630,7 @@ class IronflowClient {
|
|
|
614
630
|
"Content-Type": "application/json",
|
|
615
631
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
616
632
|
};
|
|
617
|
-
|
|
618
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
619
|
-
}
|
|
633
|
+
this.applyAuthHeader(headers);
|
|
620
634
|
const response = await fetch(url, {
|
|
621
635
|
method: "POST",
|
|
622
636
|
headers,
|
|
@@ -656,9 +670,7 @@ class IronflowClient {
|
|
|
656
670
|
"Content-Type": "application/json",
|
|
657
671
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
658
672
|
};
|
|
659
|
-
|
|
660
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
661
|
-
}
|
|
673
|
+
this.applyAuthHeader(headers);
|
|
662
674
|
const response = await fetch(url, {
|
|
663
675
|
method: "POST",
|
|
664
676
|
headers,
|
|
@@ -687,9 +699,61 @@ class IronflowClient {
|
|
|
687
699
|
clearTimeout(timeoutId);
|
|
688
700
|
}
|
|
689
701
|
}
|
|
690
|
-
/**
|
|
691
|
-
|
|
692
|
-
|
|
702
|
+
/** Get a registered function by ID. */
|
|
703
|
+
async getFunction(functionId) {
|
|
704
|
+
this.ensureConfigured();
|
|
705
|
+
const response = await this.streamRequest("/ironflow.v1.IronflowService/GetFunction", { id: functionId });
|
|
706
|
+
return registeredFunctionFromWire(response);
|
|
707
|
+
}
|
|
708
|
+
/** Change a function's lifecycle status. */
|
|
709
|
+
async updateFunctionStatus(functionId, status) {
|
|
710
|
+
this.ensureConfigured();
|
|
711
|
+
const response = await this.streamRequest("/ironflow.v1.IronflowService/UpdateFunctionStatus", {
|
|
712
|
+
id: functionId,
|
|
713
|
+
status: `FUNCTION_STATUS_${status.toUpperCase()}`,
|
|
714
|
+
});
|
|
715
|
+
return registeredFunctionFromWire(response);
|
|
716
|
+
}
|
|
717
|
+
/** Permanently delete a registered function. */
|
|
718
|
+
async deleteFunction(functionId) {
|
|
719
|
+
this.ensureConfigured();
|
|
720
|
+
await this.streamRequest("/ironflow.v1.IronflowService/DeleteFunction", { id: functionId });
|
|
721
|
+
}
|
|
722
|
+
/** List immutable configuration snapshots, newest first. */
|
|
723
|
+
async listFunctionHistory(functionId, options = {}) {
|
|
724
|
+
this.ensureConfigured();
|
|
725
|
+
const response = await this.streamRequest("/ironflow.v1.IronflowService/ListFunctionHistory", {
|
|
726
|
+
functionId,
|
|
727
|
+
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
728
|
+
...(options.fromVersion !== undefined
|
|
729
|
+
? { fromVersion: String(options.fromVersion) }
|
|
730
|
+
: {}),
|
|
731
|
+
});
|
|
732
|
+
return {
|
|
733
|
+
entries: (response.entries ?? []).map(functionHistoryEntryFromWire),
|
|
734
|
+
hasMore: response.hasMore ?? false,
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
/** Get one historical configuration snapshot by entity version. */
|
|
738
|
+
async getFunctionAtVersion(functionId, version) {
|
|
739
|
+
this.ensureConfigured();
|
|
740
|
+
const response = await this.streamRequest("/ironflow.v1.IronflowService/GetFunctionAtVersion", {
|
|
741
|
+
functionId,
|
|
742
|
+
version: String(version),
|
|
743
|
+
});
|
|
744
|
+
return functionHistoryEntryFromWire(response.entry);
|
|
745
|
+
}
|
|
746
|
+
/** Restore a function's configuration from a historical version. */
|
|
747
|
+
async rollbackFunction(functionId, version, changeReason) {
|
|
748
|
+
this.ensureConfigured();
|
|
749
|
+
const response = await this.streamRequest("/ironflow.v1.IronflowService/RollbackFunction", {
|
|
750
|
+
functionId,
|
|
751
|
+
version: String(version),
|
|
752
|
+
...(changeReason ? { changeReason } : {}),
|
|
753
|
+
});
|
|
754
|
+
return registeredFunctionFromWire(response.function);
|
|
755
|
+
}
|
|
756
|
+
/** List registered functions. */
|
|
693
757
|
async listFunctions() {
|
|
694
758
|
this.ensureConfigured();
|
|
695
759
|
const url = `${this.config.serverUrl}/api/v1/functions`;
|
|
@@ -700,9 +764,7 @@ class IronflowClient {
|
|
|
700
764
|
const headers = {
|
|
701
765
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
702
766
|
};
|
|
703
|
-
|
|
704
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
705
|
-
}
|
|
767
|
+
this.applyAuthHeader(headers);
|
|
706
768
|
const response = await fetch(url, {
|
|
707
769
|
method: "GET",
|
|
708
770
|
headers,
|
|
@@ -731,9 +793,7 @@ class IronflowClient {
|
|
|
731
793
|
const headers = {
|
|
732
794
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
733
795
|
};
|
|
734
|
-
|
|
735
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
736
|
-
}
|
|
796
|
+
this.applyAuthHeader(headers);
|
|
737
797
|
const response = await fetch(url, {
|
|
738
798
|
method: "GET",
|
|
739
799
|
headers,
|
|
@@ -824,6 +884,104 @@ class IronflowClient {
|
|
|
824
884
|
eventId: response.eventId,
|
|
825
885
|
};
|
|
826
886
|
}
|
|
887
|
+
/**
|
|
888
|
+
* Publish a message to a developer pub/sub topic.
|
|
889
|
+
* Unlike emit(), this does not trigger workflow functions.
|
|
890
|
+
*/
|
|
891
|
+
async publish(topic, data, options) {
|
|
892
|
+
this.ensureConfigured();
|
|
893
|
+
const response = await this.streamRequest("/ironflow.v1.PubSubService/Publish", {
|
|
894
|
+
topic,
|
|
895
|
+
data: data ?? {},
|
|
896
|
+
...(options?.idempotencyKey
|
|
897
|
+
? { idempotencyKey: options.idempotencyKey }
|
|
898
|
+
: {}),
|
|
899
|
+
});
|
|
900
|
+
return {
|
|
901
|
+
eventId: response.eventId,
|
|
902
|
+
sequence: Number(response.sequence) || 0,
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
/** Trigger multiple events in one request. */
|
|
906
|
+
async triggerBatch(events) {
|
|
907
|
+
this.ensureConfigured();
|
|
908
|
+
const response = await this.streamRequest("/ironflow.v1.IronflowService/TriggerBatch", {
|
|
909
|
+
events: events.map((event) => ({
|
|
910
|
+
event: event.event,
|
|
911
|
+
data: event.data,
|
|
912
|
+
...(event.version !== undefined ? { version: event.version } : {}),
|
|
913
|
+
...(event.idempotencyKey
|
|
914
|
+
? { idempotencyKey: event.idempotencyKey }
|
|
915
|
+
: {}),
|
|
916
|
+
...(event.metadata ? { metadata: event.metadata } : {}),
|
|
917
|
+
})),
|
|
918
|
+
});
|
|
919
|
+
return (response.results ?? []).map((result) => ({
|
|
920
|
+
runIds: result.runIds ?? [],
|
|
921
|
+
eventId: result.eventId,
|
|
922
|
+
}));
|
|
923
|
+
}
|
|
924
|
+
/** Read a keyset-paginated page from the event log. */
|
|
925
|
+
async listEvents(options = {}) {
|
|
926
|
+
this.ensureConfigured();
|
|
927
|
+
const query = new URLSearchParams();
|
|
928
|
+
if (options.name)
|
|
929
|
+
query.set("name", options.name);
|
|
930
|
+
if (options.names?.length)
|
|
931
|
+
query.set("names", options.names.join(","));
|
|
932
|
+
if (options.sources?.length)
|
|
933
|
+
query.set("source", options.sources.join(","));
|
|
934
|
+
if (options.search)
|
|
935
|
+
query.set("search", options.search);
|
|
936
|
+
if (options.since)
|
|
937
|
+
query.set("since", options.since);
|
|
938
|
+
if (options.until)
|
|
939
|
+
query.set("until", options.until);
|
|
940
|
+
if (options.limit !== undefined)
|
|
941
|
+
query.set("limit", String(options.limit));
|
|
942
|
+
if (options.cursor)
|
|
943
|
+
query.set("cursor", options.cursor);
|
|
944
|
+
if (options.before)
|
|
945
|
+
query.set("before", options.before);
|
|
946
|
+
const suffix = query.size ? `?${query}` : "";
|
|
947
|
+
const response = await this.restRequest("GET", `/api/v1/events${suffix}`);
|
|
948
|
+
return {
|
|
949
|
+
events: (response.events ?? []).map(storedEventFromWire),
|
|
950
|
+
count: response.count ?? 0,
|
|
951
|
+
limit: response.limit ?? options.limit ?? 20,
|
|
952
|
+
nextCursor: response.next_cursor,
|
|
953
|
+
prevCursor: response.prev_cursor,
|
|
954
|
+
hasNext: response.has_next ?? false,
|
|
955
|
+
hasPrev: response.has_prev ?? false,
|
|
956
|
+
approxTotal: response.approx_total,
|
|
957
|
+
approxTotalCapped: response.approx_total_capped ?? false,
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
/** Get one persisted event by ID. */
|
|
961
|
+
async getEvent(eventId) {
|
|
962
|
+
this.ensureConfigured();
|
|
963
|
+
const response = await this.restRequest("GET", `/api/v1/events/${encodeURIComponent(eventId)}`);
|
|
964
|
+
return storedEventFromWire(response);
|
|
965
|
+
}
|
|
966
|
+
/** List event names and counts for filter UIs. */
|
|
967
|
+
async listEventNames(options = {}) {
|
|
968
|
+
this.ensureConfigured();
|
|
969
|
+
const query = new URLSearchParams();
|
|
970
|
+
if (options.sources?.length)
|
|
971
|
+
query.set("source", options.sources.join(","));
|
|
972
|
+
if (options.since)
|
|
973
|
+
query.set("since", options.since);
|
|
974
|
+
if (options.until)
|
|
975
|
+
query.set("until", options.until);
|
|
976
|
+
const suffix = query.size ? `?${query}` : "";
|
|
977
|
+
const response = await this.restRequest("GET", `/api/v1/events/names${suffix}`);
|
|
978
|
+
return {
|
|
979
|
+
names: response.names ?? [],
|
|
980
|
+
scanned: response.scanned ?? 0,
|
|
981
|
+
truncated: response.truncated ?? false,
|
|
982
|
+
scanCap: response.scan_cap ?? 0,
|
|
983
|
+
};
|
|
984
|
+
}
|
|
827
985
|
/**
|
|
828
986
|
* Emit an event synchronously — waits for the triggered run to complete and returns the result.
|
|
829
987
|
*
|
|
@@ -877,6 +1035,84 @@ class IronflowClient {
|
|
|
877
1035
|
});
|
|
878
1036
|
return sub;
|
|
879
1037
|
}
|
|
1038
|
+
/** Consumer-group lifecycle management. */
|
|
1039
|
+
consumerGroups = {
|
|
1040
|
+
create: async (config) => {
|
|
1041
|
+
this.ensureConfigured();
|
|
1042
|
+
const response = await this.streamRequest("/ironflow.v1.PubSubService/CreateConsumerGroup", {
|
|
1043
|
+
name: config.name,
|
|
1044
|
+
pattern: config.pattern,
|
|
1045
|
+
namespace: config.namespace ?? "default",
|
|
1046
|
+
...(config.filterExpr !== undefined ? { filter_expr: config.filterExpr } : {}),
|
|
1047
|
+
...(config.ackMode ? { ack_mode: `ACK_MODE_${config.ackMode.toUpperCase()}` } : {}),
|
|
1048
|
+
...(config.backpressure
|
|
1049
|
+
? { backpressure: `BACKPRESSURE_MODE_${config.backpressure.toUpperCase()}` }
|
|
1050
|
+
: {}),
|
|
1051
|
+
...(config.maxInflight !== undefined ? { max_inflight: config.maxInflight } : {}),
|
|
1052
|
+
...(config.maxRedeliveries !== undefined
|
|
1053
|
+
? { max_redeliveries: config.maxRedeliveries }
|
|
1054
|
+
: {}),
|
|
1055
|
+
...(config.redeliverDelayMs !== undefined
|
|
1056
|
+
? { redeliver_delay_ms: config.redeliverDelayMs }
|
|
1057
|
+
: {}),
|
|
1058
|
+
...(config.metadata !== undefined ? { metadata: config.metadata } : {}),
|
|
1059
|
+
});
|
|
1060
|
+
return consumerGroupFromWire(response);
|
|
1061
|
+
},
|
|
1062
|
+
get: async (name, namespace = "default") => {
|
|
1063
|
+
this.ensureConfigured();
|
|
1064
|
+
const response = await this.streamRequest("/ironflow.v1.PubSubService/GetConsumerGroup", { name, namespace });
|
|
1065
|
+
return consumerGroupFromWire(response);
|
|
1066
|
+
},
|
|
1067
|
+
list: async (namespace) => {
|
|
1068
|
+
this.ensureConfigured();
|
|
1069
|
+
const groups = [];
|
|
1070
|
+
let cursor;
|
|
1071
|
+
do {
|
|
1072
|
+
const response = await this.streamRequest("/ironflow.v1.PubSubService/ListConsumerGroups", {
|
|
1073
|
+
...(namespace ? { namespace } : {}),
|
|
1074
|
+
limit: 100,
|
|
1075
|
+
...(cursor ? { cursor } : {}),
|
|
1076
|
+
});
|
|
1077
|
+
groups.push(...(response.groups ?? []).map(consumerGroupFromWire));
|
|
1078
|
+
cursor = response.nextCursor || response.next_cursor || undefined;
|
|
1079
|
+
} while (cursor);
|
|
1080
|
+
return groups;
|
|
1081
|
+
},
|
|
1082
|
+
update: async (name, input, namespace = "default") => {
|
|
1083
|
+
this.ensureConfigured();
|
|
1084
|
+
const group = { name, namespace };
|
|
1085
|
+
const paths = [];
|
|
1086
|
+
const set = (path, value) => {
|
|
1087
|
+
if (value !== undefined) {
|
|
1088
|
+
group[path] = value;
|
|
1089
|
+
paths.push(path);
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
set("pattern", input.pattern);
|
|
1093
|
+
set("filter_expr", input.filterExpr);
|
|
1094
|
+
set("ack_mode", input.ackMode ? `ACK_MODE_${input.ackMode.toUpperCase()}` : undefined);
|
|
1095
|
+
set("backpressure", input.backpressure
|
|
1096
|
+
? `BACKPRESSURE_MODE_${input.backpressure.toUpperCase()}`
|
|
1097
|
+
: undefined);
|
|
1098
|
+
set("max_inflight", input.maxInflight);
|
|
1099
|
+
set("max_redeliveries", input.maxRedeliveries);
|
|
1100
|
+
set("redeliver_delay_ms", input.redeliverDelayMs);
|
|
1101
|
+
set("metadata", input.metadata);
|
|
1102
|
+
set("status", input.status
|
|
1103
|
+
? `CONSUMER_GROUP_STATUS_${input.status.toUpperCase()}`
|
|
1104
|
+
: undefined);
|
|
1105
|
+
if (paths.length === 0) {
|
|
1106
|
+
throw new ValidationError("Consumer group update requires at least one field");
|
|
1107
|
+
}
|
|
1108
|
+
const response = await this.streamRequest("/ironflow.v1.PubSubService/UpdateConsumerGroup", { group, update_mask: { paths } });
|
|
1109
|
+
return consumerGroupFromWire(response);
|
|
1110
|
+
},
|
|
1111
|
+
delete: async (name, namespace = "default") => {
|
|
1112
|
+
this.ensureConfigured();
|
|
1113
|
+
await this.streamRequest("/ironflow.v1.PubSubService/DeleteConsumerGroup", { name, namespace });
|
|
1114
|
+
},
|
|
1115
|
+
};
|
|
880
1116
|
// ============================================================================
|
|
881
1117
|
// Entity Streams
|
|
882
1118
|
// ============================================================================
|
|
@@ -1015,6 +1251,29 @@ class IronflowClient {
|
|
|
1015
1251
|
createdAt: response.createdAt ?? "",
|
|
1016
1252
|
};
|
|
1017
1253
|
},
|
|
1254
|
+
/** List entity streams visible in the current environment. */
|
|
1255
|
+
listStreams: async () => {
|
|
1256
|
+
this.ensureConfigured();
|
|
1257
|
+
const response = await this.restRequest("GET", "/api/v1/streams");
|
|
1258
|
+
return (response.streams ?? []).map((stream) => ({
|
|
1259
|
+
entityId: String(stream.entity_id ?? stream.entityId ?? ""),
|
|
1260
|
+
entityType: String(stream.entity_type ?? stream.entityType ?? ""),
|
|
1261
|
+
version: Number(stream.version ?? 0),
|
|
1262
|
+
eventCount: Number(stream.event_count ?? stream.eventCount ?? 0),
|
|
1263
|
+
lastEventAt: String(stream.last_event_at ?? stream.lastEventAt ?? stream.updated_at ?? ""),
|
|
1264
|
+
}));
|
|
1265
|
+
},
|
|
1266
|
+
/** Get the unified event history for an entity. */
|
|
1267
|
+
getEntityHistory: async (entityId) => {
|
|
1268
|
+
this.ensureConfigured();
|
|
1269
|
+
const response = await this.restRequest("GET", `/api/v1/streams/${encodeURIComponent(entityId)}/history`);
|
|
1270
|
+
return (response.entries ?? []).map((entry) => ({
|
|
1271
|
+
eventName: String(entry.event_name ?? entry.eventName ?? ""),
|
|
1272
|
+
data: entry.event_data ?? entry.data,
|
|
1273
|
+
version: Number(entry.entity_version ?? entry.version ?? 0),
|
|
1274
|
+
timestamp: String(entry.timestamp ?? ""),
|
|
1275
|
+
}));
|
|
1276
|
+
},
|
|
1018
1277
|
/**
|
|
1019
1278
|
* Subscribe to real-time events for an entity stream
|
|
1020
1279
|
*
|
|
@@ -1087,9 +1346,7 @@ class IronflowClient {
|
|
|
1087
1346
|
const headers = {
|
|
1088
1347
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1089
1348
|
};
|
|
1090
|
-
|
|
1091
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1092
|
-
}
|
|
1349
|
+
this.applyAuthHeader(headers);
|
|
1093
1350
|
const response = await fetch(url, {
|
|
1094
1351
|
method: "GET",
|
|
1095
1352
|
headers,
|
|
@@ -1153,9 +1410,7 @@ class IronflowClient {
|
|
|
1153
1410
|
const headers = {
|
|
1154
1411
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1155
1412
|
};
|
|
1156
|
-
|
|
1157
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1158
|
-
}
|
|
1413
|
+
this.applyAuthHeader(headers);
|
|
1159
1414
|
const response = await fetch(url, {
|
|
1160
1415
|
method: "GET",
|
|
1161
1416
|
headers,
|
|
@@ -1180,48 +1435,6 @@ class IronflowClient {
|
|
|
1180
1435
|
clearTimeout(timeoutId);
|
|
1181
1436
|
}
|
|
1182
1437
|
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Trigger a rebuild of a projection
|
|
1185
|
-
*
|
|
1186
|
-
* @example
|
|
1187
|
-
* ```typescript
|
|
1188
|
-
* const result = await ironflow.rebuildProjection('order-stats');
|
|
1189
|
-
* ```
|
|
1190
|
-
*/
|
|
1191
|
-
async rebuildProjection(name, options) {
|
|
1192
|
-
this.ensureConfigured();
|
|
1193
|
-
const url = `${this.config.serverUrl}/api/v1/projections/${encodeURIComponent(name)}/rebuild`;
|
|
1194
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
1195
|
-
const controller = new AbortController();
|
|
1196
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1197
|
-
try {
|
|
1198
|
-
const headers = {
|
|
1199
|
-
"Content-Type": "application/json",
|
|
1200
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1201
|
-
};
|
|
1202
|
-
if (this.config.auth?.apiKey) {
|
|
1203
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1204
|
-
}
|
|
1205
|
-
const response = await fetch(url, {
|
|
1206
|
-
method: "POST",
|
|
1207
|
-
headers,
|
|
1208
|
-
body: JSON.stringify({
|
|
1209
|
-
partition: options?.partition,
|
|
1210
|
-
from_event_id: options?.fromEventId,
|
|
1211
|
-
dry_run: options?.dryRun,
|
|
1212
|
-
}),
|
|
1213
|
-
signal: controller.signal,
|
|
1214
|
-
});
|
|
1215
|
-
if (!response.ok) {
|
|
1216
|
-
const error = safeJsonParse(await response.text());
|
|
1217
|
-
throw new IronflowError(error?.message || `Rebuild projection failed: ${response.status}`, { code: error?.code || "REBUILD_PROJECTION_FAILED" });
|
|
1218
|
-
}
|
|
1219
|
-
return response.json();
|
|
1220
|
-
}
|
|
1221
|
-
finally {
|
|
1222
|
-
clearTimeout(timeoutId);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
1438
|
/**
|
|
1226
1439
|
* Wait until the named projection has processed events up to `minSeq`,
|
|
1227
1440
|
* or the timeout elapses. Read-your-writes primitive for CQRS UIs:
|
|
@@ -1248,9 +1461,7 @@ class IronflowClient {
|
|
|
1248
1461
|
const headers = {
|
|
1249
1462
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1250
1463
|
};
|
|
1251
|
-
|
|
1252
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1253
|
-
}
|
|
1464
|
+
this.applyAuthHeader(headers);
|
|
1254
1465
|
const response = await fetch(url, {
|
|
1255
1466
|
method: "GET",
|
|
1256
1467
|
headers,
|
|
@@ -1305,7 +1516,7 @@ class IronflowClient {
|
|
|
1305
1516
|
interceptors: [
|
|
1306
1517
|
(next) => async (req) => {
|
|
1307
1518
|
req.header.set(HEADERS.ENVIRONMENT, this.config.environment);
|
|
1308
|
-
const token = this.
|
|
1519
|
+
const token = this.authCredential();
|
|
1309
1520
|
if (token)
|
|
1310
1521
|
req.header.set("Authorization", `Bearer ${token}`);
|
|
1311
1522
|
return next(req);
|
|
@@ -1345,9 +1556,7 @@ class IronflowClient {
|
|
|
1345
1556
|
"Content-Type": "application/json",
|
|
1346
1557
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1347
1558
|
};
|
|
1348
|
-
|
|
1349
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1350
|
-
}
|
|
1559
|
+
this.applyAuthHeader(headers);
|
|
1351
1560
|
const body = { eventId, projection };
|
|
1352
1561
|
if (opts.timeoutMs !== undefined)
|
|
1353
1562
|
body.timeoutMs = opts.timeoutMs;
|
|
@@ -1388,9 +1597,7 @@ class IronflowClient {
|
|
|
1388
1597
|
const headers = {
|
|
1389
1598
|
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1390
1599
|
};
|
|
1391
|
-
|
|
1392
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1393
|
-
}
|
|
1600
|
+
this.applyAuthHeader(headers);
|
|
1394
1601
|
const response = await fetch(url, {
|
|
1395
1602
|
method: "GET",
|
|
1396
1603
|
headers,
|
|
@@ -1493,11 +1700,8 @@ class IronflowClient {
|
|
|
1493
1700
|
* @example
|
|
1494
1701
|
* ```typescript
|
|
1495
1702
|
* const cfg = ironflow.configManager();
|
|
1496
|
-
* await cfg.set("app", { featureX: true, maxRetries: 3 });
|
|
1497
1703
|
* const { data, revision } = await cfg.get("app");
|
|
1498
|
-
* await cfg.patch("app", { maxRetries: 5 });
|
|
1499
1704
|
* const configs = await cfg.list();
|
|
1500
|
-
* await cfg.delete("app");
|
|
1501
1705
|
*
|
|
1502
1706
|
* // Watch for changes
|
|
1503
1707
|
* const sub = await cfg.watch("app", {
|
|
@@ -1684,6 +1888,12 @@ class IronflowClient {
|
|
|
1684
1888
|
this.ensureConfigured();
|
|
1685
1889
|
await this.restRequest("DELETE", `/api/v1/roles/${encodeURIComponent(roleId)}/policies/${encodeURIComponent(policyId)}`);
|
|
1686
1890
|
},
|
|
1891
|
+
/** List policies assigned to a role. */
|
|
1892
|
+
listPolicies: async (roleId) => {
|
|
1893
|
+
this.ensureConfigured();
|
|
1894
|
+
const response = await this.restRequest("GET", `/api/v1/roles/${encodeURIComponent(roleId)}/policies`);
|
|
1895
|
+
return response.policies ?? [];
|
|
1896
|
+
},
|
|
1687
1897
|
};
|
|
1688
1898
|
/**
|
|
1689
1899
|
* Policy management operations (enterprise-only)
|
|
@@ -1848,6 +2058,32 @@ class IronflowClient {
|
|
|
1848
2058
|
nextCursor: response.next_cursor,
|
|
1849
2059
|
};
|
|
1850
2060
|
}
|
|
2061
|
+
/** Query the environment-wide audit stream. */
|
|
2062
|
+
async listAuditEvents(options = {}) {
|
|
2063
|
+
this.ensureConfigured();
|
|
2064
|
+
const query = new URLSearchParams();
|
|
2065
|
+
if (options.runId)
|
|
2066
|
+
query.set("run_id", options.runId);
|
|
2067
|
+
if (options.functionId)
|
|
2068
|
+
query.set("function_id", options.functionId);
|
|
2069
|
+
if (options.eventType)
|
|
2070
|
+
query.set("event_type", options.eventType);
|
|
2071
|
+
if (options.fromTimestamp)
|
|
2072
|
+
query.set("from", options.fromTimestamp);
|
|
2073
|
+
if (options.toTimestamp)
|
|
2074
|
+
query.set("to", options.toTimestamp);
|
|
2075
|
+
if (options.limit !== undefined)
|
|
2076
|
+
query.set("limit", String(options.limit));
|
|
2077
|
+
if (options.cursor)
|
|
2078
|
+
query.set("cursor", options.cursor);
|
|
2079
|
+
const suffix = query.size ? `?${query}` : "";
|
|
2080
|
+
const response = await this.restRequest("GET", `/api/v1/audit${suffix}`);
|
|
2081
|
+
return {
|
|
2082
|
+
events: (response.events ?? []).map(auditEventFromWire),
|
|
2083
|
+
totalCount: response.total_count ?? 0,
|
|
2084
|
+
nextCursor: response.next_cursor || undefined,
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
1851
2087
|
// ============================================================================
|
|
1852
2088
|
// Webhook Management
|
|
1853
2089
|
// ============================================================================
|
|
@@ -1995,55 +2231,6 @@ class IronflowClient {
|
|
|
1995
2231
|
};
|
|
1996
2232
|
},
|
|
1997
2233
|
};
|
|
1998
|
-
// ============================================================================
|
|
1999
|
-
// User Management
|
|
2000
|
-
// ============================================================================
|
|
2001
|
-
/**
|
|
2002
|
-
* User management operations
|
|
2003
|
-
*
|
|
2004
|
-
* @example
|
|
2005
|
-
* ```typescript
|
|
2006
|
-
* // List all users
|
|
2007
|
-
* const users = await ironflow.users.list();
|
|
2008
|
-
*
|
|
2009
|
-
* // Create a user
|
|
2010
|
-
* const user = await ironflow.users.create({
|
|
2011
|
-
* email: "alice@example.com",
|
|
2012
|
-
* password: "secret",
|
|
2013
|
-
* roles: ["admin"],
|
|
2014
|
-
* });
|
|
2015
|
-
*
|
|
2016
|
-
* // Update a user
|
|
2017
|
-
* await ironflow.users.update(user.id, { name: "Alice" });
|
|
2018
|
-
* ```
|
|
2019
|
-
*/
|
|
2020
|
-
users = {
|
|
2021
|
-
/** Create a new user (admin only) */
|
|
2022
|
-
create: async (input) => {
|
|
2023
|
-
this.ensureConfigured();
|
|
2024
|
-
return this.restRequest("POST", "/api/v1/users", input);
|
|
2025
|
-
},
|
|
2026
|
-
/** List all users in the current organization (admin only) */
|
|
2027
|
-
list: async () => {
|
|
2028
|
-
this.ensureConfigured();
|
|
2029
|
-
return this.restRequest("GET", "/api/v1/users");
|
|
2030
|
-
},
|
|
2031
|
-
/** Get a user by ID */
|
|
2032
|
-
get: async (id) => {
|
|
2033
|
-
this.ensureConfigured();
|
|
2034
|
-
return this.restRequest("GET", `/api/v1/users/${encodeURIComponent(id)}`);
|
|
2035
|
-
},
|
|
2036
|
-
/** Update a user's profile (admin only) */
|
|
2037
|
-
update: async (id, input) => {
|
|
2038
|
-
this.ensureConfigured();
|
|
2039
|
-
return this.restRequest("PATCH", `/api/v1/users/${encodeURIComponent(id)}`, input);
|
|
2040
|
-
},
|
|
2041
|
-
/** Delete a user (admin only) */
|
|
2042
|
-
delete: async (id) => {
|
|
2043
|
-
this.ensureConfigured();
|
|
2044
|
-
await this.restRequest("DELETE", `/api/v1/users/${encodeURIComponent(id)}`);
|
|
2045
|
-
},
|
|
2046
|
-
};
|
|
2047
2234
|
/**
|
|
2048
2235
|
* Tenant management operations (enterprise-only)
|
|
2049
2236
|
*
|
|
@@ -2059,6 +2246,19 @@ class IronflowClient {
|
|
|
2059
2246
|
this.ensureConfigured();
|
|
2060
2247
|
return this.restRequest("GET", "/api/v1/tenants");
|
|
2061
2248
|
},
|
|
2249
|
+
/** Provision an organization, environment, and initial administrator key. */
|
|
2250
|
+
provision: async (input) => {
|
|
2251
|
+
this.ensureConfigured();
|
|
2252
|
+
const response = await this.restRequest("POST", "/api/v1/tenants/provision", {
|
|
2253
|
+
org_name: input.orgName,
|
|
2254
|
+
env_name: input.envName ?? "production",
|
|
2255
|
+
});
|
|
2256
|
+
return {
|
|
2257
|
+
org: response.org,
|
|
2258
|
+
environment: response.environment,
|
|
2259
|
+
apiKey: { key: response.api_key.key, roles: response.api_key.roles ?? [] },
|
|
2260
|
+
};
|
|
2261
|
+
},
|
|
2062
2262
|
};
|
|
2063
2263
|
// ============================================================================
|
|
2064
2264
|
// Pattern Helpers (static)
|
|
@@ -2075,6 +2275,15 @@ class IronflowClient {
|
|
|
2075
2275
|
throw new NotConfiguredError();
|
|
2076
2276
|
}
|
|
2077
2277
|
}
|
|
2278
|
+
authCredential() {
|
|
2279
|
+
return this.config.auth?.apiKey || this.config.auth?.token;
|
|
2280
|
+
}
|
|
2281
|
+
applyAuthHeader(headers) {
|
|
2282
|
+
const credential = this.authCredential();
|
|
2283
|
+
if (credential) {
|
|
2284
|
+
headers["Authorization"] = `Bearer ${credential}`;
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2078
2287
|
cleanup() {
|
|
2079
2288
|
if (this.visibilityHandler) {
|
|
2080
2289
|
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
@@ -2151,12 +2360,7 @@ class IronflowClient {
|
|
|
2151
2360
|
if (body !== undefined) {
|
|
2152
2361
|
headers["Content-Type"] = "application/json";
|
|
2153
2362
|
}
|
|
2154
|
-
|
|
2155
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
2156
|
-
}
|
|
2157
|
-
else if (this.config.auth?.token) {
|
|
2158
|
-
headers["Authorization"] = `Bearer ${this.config.auth.token}`;
|
|
2159
|
-
}
|
|
2363
|
+
this.applyAuthHeader(headers);
|
|
2160
2364
|
const fetchOptions = {
|
|
2161
2365
|
method,
|
|
2162
2366
|
headers,
|