@serfab/cadre-core 0.0.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.
Files changed (54) hide show
  1. package/README.md +252 -0
  2. package/dist/arachnode-stub.d.ts +60 -0
  3. package/dist/arachnode-stub.js +120 -0
  4. package/dist/arachnode-stub.js.map +1 -0
  5. package/dist/cadre-node.d.ts +252 -0
  6. package/dist/cadre-node.js +753 -0
  7. package/dist/cadre-node.js.map +1 -0
  8. package/dist/control-database.d.ts +62 -0
  9. package/dist/control-database.js +295 -0
  10. package/dist/control-database.js.map +1 -0
  11. package/dist/enrollment.d.ts +76 -0
  12. package/dist/enrollment.js +126 -0
  13. package/dist/enrollment.js.map +1 -0
  14. package/dist/hibernation-manager.d.ts +68 -0
  15. package/dist/hibernation-manager.js +212 -0
  16. package/dist/hibernation-manager.js.map +1 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.js +24 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/seed-bootstrap.d.ts +164 -0
  21. package/dist/seed-bootstrap.js +554 -0
  22. package/dist/seed-bootstrap.js.map +1 -0
  23. package/dist/strand-database.d.ts +39 -0
  24. package/dist/strand-database.js +108 -0
  25. package/dist/strand-database.js.map +1 -0
  26. package/dist/strand-formation-manager.d.ts +87 -0
  27. package/dist/strand-formation-manager.js +168 -0
  28. package/dist/strand-formation-manager.js.map +1 -0
  29. package/dist/strand-instance-manager.d.ts +60 -0
  30. package/dist/strand-instance-manager.js +209 -0
  31. package/dist/strand-instance-manager.js.map +1 -0
  32. package/dist/strand-solicitation.d.ts +145 -0
  33. package/dist/strand-solicitation.js +180 -0
  34. package/dist/strand-solicitation.js.map +1 -0
  35. package/dist/strand-watcher.d.ts +70 -0
  36. package/dist/strand-watcher.js +136 -0
  37. package/dist/strand-watcher.js.map +1 -0
  38. package/dist/types.d.ts +484 -0
  39. package/dist/types.js +26 -0
  40. package/dist/types.js.map +1 -0
  41. package/package.json +67 -0
  42. package/src/arachnode-stub.ts +160 -0
  43. package/src/cadre-node.ts +923 -0
  44. package/src/control-database.ts +376 -0
  45. package/src/enrollment.ts +199 -0
  46. package/src/hibernation-manager.ts +268 -0
  47. package/src/index.ts +71 -0
  48. package/src/seed-bootstrap.ts +753 -0
  49. package/src/strand-database.ts +168 -0
  50. package/src/strand-formation-manager.ts +277 -0
  51. package/src/strand-instance-manager.ts +281 -0
  52. package/src/strand-solicitation.ts +318 -0
  53. package/src/strand-watcher.ts +197 -0
  54. package/src/types.ts +553 -0
@@ -0,0 +1,197 @@
1
+ import debug from 'debug';
2
+ import type { StrandFilter, StrandRow } from './types.js';
3
+
4
+ const log = debug('sereus:cadre:strand-watcher');
5
+
6
+ /**
7
+ * Extended strand row that includes the sAppId for filtering purposes.
8
+ * The sAppId is provided by the hosting application, not from the control network.
9
+ */
10
+ export interface StrandRowWithApp extends StrandRow {
11
+ /** sApp ID if known (for filtering) */
12
+ sAppId?: string;
13
+ }
14
+
15
+ /**
16
+ * Callback for strand changes
17
+ */
18
+ export interface StrandWatcherCallbacks {
19
+ onStrandAdded: (strand: StrandRow) => Promise<void>;
20
+ onStrandRemoved: (strandId: string) => Promise<void>;
21
+ }
22
+
23
+ /**
24
+ * Interface for querying strands from control network
25
+ */
26
+ export interface StrandQueryable {
27
+ queryStrands(): Promise<StrandRow[]>;
28
+ }
29
+
30
+ /**
31
+ * Interface for looking up sAppId for a strand (for filtering)
32
+ */
33
+ export interface SAppIdLookup {
34
+ /** Get the sAppId for a strand, if known */
35
+ getSAppId(strandId: string): string | undefined;
36
+ }
37
+
38
+ /**
39
+ * Watches the control network's Strand table for changes and triggers
40
+ * strand instance start/stop via callbacks.
41
+ *
42
+ * Uses polling until Optimystic supports reactive subscriptions.
43
+ */
44
+ export class StrandWatcher {
45
+ private readonly filter: StrandFilter;
46
+ private readonly pollInterval: number;
47
+ private readonly callbacks: StrandWatcherCallbacks;
48
+ private readonly queryable: StrandQueryable;
49
+ private readonly sAppIdLookup?: SAppIdLookup;
50
+
51
+ private knownStrands: Map<string, StrandRow> = new Map();
52
+ private pollTimer: ReturnType<typeof setInterval> | null = null;
53
+ private running = false;
54
+
55
+ constructor(
56
+ queryable: StrandQueryable,
57
+ callbacks: StrandWatcherCallbacks,
58
+ filter: StrandFilter = { mode: 'all' },
59
+ pollInterval: number = 5000,
60
+ sAppIdLookup?: SAppIdLookup
61
+ ) {
62
+ this.queryable = queryable;
63
+ this.callbacks = callbacks;
64
+ this.filter = filter;
65
+ this.pollInterval = pollInterval;
66
+ this.sAppIdLookup = sAppIdLookup;
67
+ log('StrandWatcher created with filter: %o, interval: %dms', filter, pollInterval);
68
+ }
69
+
70
+ /**
71
+ * Check if a strand passes the current filter
72
+ */
73
+ private passesFilter(strand: StrandRow): boolean {
74
+ switch (this.filter.mode) {
75
+ case 'all':
76
+ return true;
77
+ case 'none':
78
+ return false;
79
+ case 'strandId':
80
+ return strand.Id === this.filter.strandId;
81
+ case 'sAppId': {
82
+ // Look up the sAppId for this strand
83
+ const sAppId = this.sAppIdLookup?.getSAppId(strand.Id);
84
+ if (sAppId === undefined) {
85
+ // If no sAppId lookup configured or unknown strand, pass through
86
+ // The strand will need to be re-evaluated when sApp info is available
87
+ log('sAppId unknown for strand %s - deferring filter decision', strand.Id);
88
+ return true;
89
+ }
90
+ const matches = sAppId === this.filter.sAppId;
91
+ log('sAppId filter: strand %s has sAppId %s, filter wants %s, match=%s',
92
+ strand.Id, sAppId, this.filter.sAppId, matches);
93
+ return matches;
94
+ }
95
+ default:
96
+ return true;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Poll for strand changes
102
+ */
103
+ private async poll(): Promise<void> {
104
+ if (!this.running) return;
105
+
106
+ try {
107
+ const currentStrands = await this.queryable.queryStrands();
108
+ const currentMap = new Map(currentStrands.map(s => [s.Id, s]));
109
+
110
+ // Find added strands
111
+ for (const strand of currentStrands) {
112
+ if (!this.knownStrands.has(strand.Id) && this.passesFilter(strand)) {
113
+ log('Strand added: %s', strand.Id);
114
+ this.knownStrands.set(strand.Id, strand);
115
+ try {
116
+ await this.callbacks.onStrandAdded(strand);
117
+ } catch (error) {
118
+ log('Error handling strand add for %s: %o', strand.Id, error);
119
+ }
120
+ }
121
+ }
122
+
123
+ // Find removed strands
124
+ for (const [strandId] of this.knownStrands) {
125
+ if (!currentMap.has(strandId)) {
126
+ log('Strand removed: %s', strandId);
127
+ this.knownStrands.delete(strandId);
128
+ try {
129
+ await this.callbacks.onStrandRemoved(strandId);
130
+ } catch (error) {
131
+ log('Error handling strand remove for %s: %o', strandId, error);
132
+ }
133
+ }
134
+ }
135
+ } catch (error) {
136
+ log('Error polling strands: %o', error);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Start watching for strand changes
142
+ */
143
+ async start(): Promise<void> {
144
+ if (this.running) {
145
+ log('StrandWatcher already running');
146
+ return;
147
+ }
148
+
149
+ log('Starting StrandWatcher');
150
+ this.running = true;
151
+
152
+ // Do an initial poll immediately
153
+ await this.poll();
154
+
155
+ // Set up periodic polling
156
+ this.pollTimer = setInterval(() => {
157
+ void this.poll();
158
+ }, this.pollInterval);
159
+
160
+ log('StrandWatcher started');
161
+ }
162
+
163
+ /**
164
+ * Stop watching for strand changes
165
+ */
166
+ async stop(): Promise<void> {
167
+ if (!this.running) {
168
+ return;
169
+ }
170
+
171
+ log('Stopping StrandWatcher');
172
+ this.running = false;
173
+
174
+ if (this.pollTimer) {
175
+ clearInterval(this.pollTimer);
176
+ this.pollTimer = null;
177
+ }
178
+
179
+ this.knownStrands.clear();
180
+ log('StrandWatcher stopped');
181
+ }
182
+
183
+ /**
184
+ * Get currently known strands
185
+ */
186
+ getKnownStrands(): Map<string, StrandRow> {
187
+ return new Map(this.knownStrands);
188
+ }
189
+
190
+ /**
191
+ * Force an immediate poll (useful for testing)
192
+ */
193
+ async forcePoll(): Promise<void> {
194
+ await this.poll();
195
+ }
196
+ }
197
+