@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.
- package/README.md +252 -0
- package/dist/arachnode-stub.d.ts +60 -0
- package/dist/arachnode-stub.js +120 -0
- package/dist/arachnode-stub.js.map +1 -0
- package/dist/cadre-node.d.ts +252 -0
- package/dist/cadre-node.js +753 -0
- package/dist/cadre-node.js.map +1 -0
- package/dist/control-database.d.ts +62 -0
- package/dist/control-database.js +295 -0
- package/dist/control-database.js.map +1 -0
- package/dist/enrollment.d.ts +76 -0
- package/dist/enrollment.js +126 -0
- package/dist/enrollment.js.map +1 -0
- package/dist/hibernation-manager.d.ts +68 -0
- package/dist/hibernation-manager.js +212 -0
- package/dist/hibernation-manager.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/seed-bootstrap.d.ts +164 -0
- package/dist/seed-bootstrap.js +554 -0
- package/dist/seed-bootstrap.js.map +1 -0
- package/dist/strand-database.d.ts +39 -0
- package/dist/strand-database.js +108 -0
- package/dist/strand-database.js.map +1 -0
- package/dist/strand-formation-manager.d.ts +87 -0
- package/dist/strand-formation-manager.js +168 -0
- package/dist/strand-formation-manager.js.map +1 -0
- package/dist/strand-instance-manager.d.ts +60 -0
- package/dist/strand-instance-manager.js +209 -0
- package/dist/strand-instance-manager.js.map +1 -0
- package/dist/strand-solicitation.d.ts +145 -0
- package/dist/strand-solicitation.js +180 -0
- package/dist/strand-solicitation.js.map +1 -0
- package/dist/strand-watcher.d.ts +70 -0
- package/dist/strand-watcher.js +136 -0
- package/dist/strand-watcher.js.map +1 -0
- package/dist/types.d.ts +484 -0
- package/dist/types.js +26 -0
- package/dist/types.js.map +1 -0
- package/package.json +67 -0
- package/src/arachnode-stub.ts +160 -0
- package/src/cadre-node.ts +923 -0
- package/src/control-database.ts +376 -0
- package/src/enrollment.ts +199 -0
- package/src/hibernation-manager.ts +268 -0
- package/src/index.ts +71 -0
- package/src/seed-bootstrap.ts +753 -0
- package/src/strand-database.ts +168 -0
- package/src/strand-formation-manager.ts +277 -0
- package/src/strand-instance-manager.ts +281 -0
- package/src/strand-solicitation.ts +318 -0
- package/src/strand-watcher.ts +197 -0
- 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
|
+
|