@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,108 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import { Database, registerPlugin } from '@quereus/quereus';
|
|
3
|
+
import cryptoPlugin from '@optimystic/quereus-plugin-crypto/plugin';
|
|
4
|
+
import optimysticPlugin from '@optimystic/quereus-plugin-optimystic/plugin';
|
|
5
|
+
const log = debug('sereus:cadre:strand-db');
|
|
6
|
+
/**
|
|
7
|
+
* StrandDatabase manages the sApp schema for a strand using Quereus with Optimystic backend.
|
|
8
|
+
* Each strand instance has its own isolated database with the sApp's schema applied.
|
|
9
|
+
*/
|
|
10
|
+
export class StrandDatabase {
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.db = null;
|
|
13
|
+
this.collectionFactory = null;
|
|
14
|
+
this.initialized = false;
|
|
15
|
+
this.config = config;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the database - register plugins and execute sApp schema
|
|
19
|
+
*/
|
|
20
|
+
async initialize() {
|
|
21
|
+
if (this.initialized) {
|
|
22
|
+
log('StrandDatabase for strand %s already initialized', this.config.strandId);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
log('Initializing StrandDatabase for strand: %s (sApp: %s v%s)', this.config.strandId, this.config.sAppConfig.id, this.config.sAppConfig.version);
|
|
26
|
+
// Create database instance
|
|
27
|
+
this.db = new Database();
|
|
28
|
+
// Register crypto plugin (provides digest, sign, verify functions)
|
|
29
|
+
await registerPlugin(this.db, cryptoPlugin);
|
|
30
|
+
log('Registered crypto plugin');
|
|
31
|
+
// Register optimystic plugin with network transactor as default
|
|
32
|
+
const networkName = `strand-${this.config.strandId}`;
|
|
33
|
+
const pluginResult = optimysticPlugin(this.db, {
|
|
34
|
+
default_transactor: 'network',
|
|
35
|
+
default_key_network: 'libp2p',
|
|
36
|
+
default_network_name: networkName,
|
|
37
|
+
enable_cache: true,
|
|
38
|
+
});
|
|
39
|
+
// Register vtables and functions manually since we need access to collectionFactory
|
|
40
|
+
for (const vtable of pluginResult.vtables) {
|
|
41
|
+
this.db.registerModule(vtable.name, vtable.module, vtable.auxData);
|
|
42
|
+
}
|
|
43
|
+
for (const func of pluginResult.functions) {
|
|
44
|
+
this.db.registerFunction(func.schema);
|
|
45
|
+
}
|
|
46
|
+
this.collectionFactory = pluginResult.collectionFactory;
|
|
47
|
+
// Inject the libp2p node into the collection factory
|
|
48
|
+
this.collectionFactory.registerLibp2pNode(networkName, this.config.libp2pNode, this.config.coordinatedRepo);
|
|
49
|
+
log('Registered libp2p node with collection factory');
|
|
50
|
+
// Set optimystic as the default virtual table module so that
|
|
51
|
+
// `declare schema` tables (which omit USING) are backed by optimystic
|
|
52
|
+
// instead of the built-in memory module.
|
|
53
|
+
this.db.setDefaultVtabName('optimystic');
|
|
54
|
+
this.db.setDefaultVtabArgs({
|
|
55
|
+
networkName,
|
|
56
|
+
transactor: 'network',
|
|
57
|
+
keyNetwork: 'libp2p',
|
|
58
|
+
});
|
|
59
|
+
log('Set default vtab to optimystic (networkName=%s)', networkName);
|
|
60
|
+
// Execute the sApp schema DDL
|
|
61
|
+
await this.executeSchema();
|
|
62
|
+
this.initialized = true;
|
|
63
|
+
log('StrandDatabase for strand %s initialized successfully', this.config.strandId);
|
|
64
|
+
}
|
|
65
|
+
async executeSchema() {
|
|
66
|
+
const rawSchema = this.config.sAppConfig.schema;
|
|
67
|
+
log('Applying sApp schema for strand %s', this.config.strandId);
|
|
68
|
+
// Wrap the raw schema DDL in a declarative schema block and apply it.
|
|
69
|
+
// This ensures proper schema management with diff/apply semantics.
|
|
70
|
+
// The schema is applied to a named schema 'App' to keep it isolated.
|
|
71
|
+
const wrappedSchema = `
|
|
72
|
+
declare schema App {
|
|
73
|
+
${rawSchema}
|
|
74
|
+
}
|
|
75
|
+
apply schema App;
|
|
76
|
+
`;
|
|
77
|
+
await this.db.exec(wrappedSchema);
|
|
78
|
+
log('sApp schema applied to strand %s', this.config.strandId);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get the underlying database for queries
|
|
82
|
+
*/
|
|
83
|
+
getDatabase() {
|
|
84
|
+
this.ensureInitialized();
|
|
85
|
+
return this.db;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Close the database and cleanup resources
|
|
89
|
+
*/
|
|
90
|
+
async close() {
|
|
91
|
+
if (this.collectionFactory) {
|
|
92
|
+
await this.collectionFactory.shutdown();
|
|
93
|
+
this.collectionFactory = null;
|
|
94
|
+
}
|
|
95
|
+
if (this.db) {
|
|
96
|
+
this.db.close();
|
|
97
|
+
this.db = null;
|
|
98
|
+
}
|
|
99
|
+
this.initialized = false;
|
|
100
|
+
log('StrandDatabase for strand %s closed', this.config.strandId);
|
|
101
|
+
}
|
|
102
|
+
ensureInitialized() {
|
|
103
|
+
if (!this.initialized || !this.db) {
|
|
104
|
+
throw new Error(`StrandDatabase for strand ${this.config.strandId} not initialized. Call initialize() first.`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=strand-database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strand-database.js","sourceRoot":"","sources":["../src/strand-database.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Libp2p } from '@libp2p/interface';
|
|
2
|
+
import type { OpenInvitation, FormStrandResult, StrandFormationDisclosure } from './types.js';
|
|
3
|
+
import type { DisclosureValidator, FormationUsageRecorder, StrandProvisioner } from './strand-solicitation.js';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for StrandFormationManager
|
|
6
|
+
*/
|
|
7
|
+
export interface StrandFormationManagerConfig {
|
|
8
|
+
/** Session timeout in milliseconds */
|
|
9
|
+
sessionTimeoutMs?: number;
|
|
10
|
+
/** Step timeout in milliseconds */
|
|
11
|
+
stepTimeoutMs?: number;
|
|
12
|
+
/** Maximum concurrent sessions */
|
|
13
|
+
maxConcurrentSessions?: number;
|
|
14
|
+
/** Enable debug logging */
|
|
15
|
+
enableDebugLogging?: boolean;
|
|
16
|
+
/** Protocol ID override */
|
|
17
|
+
protocolId?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Options for creating a StrandFormationManager
|
|
21
|
+
*/
|
|
22
|
+
export interface StrandFormationManagerOptions {
|
|
23
|
+
/** Validates disclosures from initiators */
|
|
24
|
+
disclosureValidator?: DisclosureValidator;
|
|
25
|
+
/** Records and validates token usage */
|
|
26
|
+
formationUsageRecorder?: FormationUsageRecorder;
|
|
27
|
+
/** Provisions strands after validation */
|
|
28
|
+
strandProvisioner?: StrandProvisioner;
|
|
29
|
+
/** This party's ID for identification */
|
|
30
|
+
partyId: string;
|
|
31
|
+
/** This party's cadre peer addresses */
|
|
32
|
+
cadrePeerAddrs?: string[];
|
|
33
|
+
/** Configuration options */
|
|
34
|
+
config?: StrandFormationManagerConfig;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* StrandFormationManager bridges cadre-core's strand solicitation interfaces
|
|
38
|
+
* with strand-proto's SessionManager for actual protocol handling over libp2p.
|
|
39
|
+
*
|
|
40
|
+
* It implements SessionHooks by delegating to the existing interfaces:
|
|
41
|
+
* - DisclosureValidator -> validateIdentity
|
|
42
|
+
* - FormationUsageRecorder -> validateToken
|
|
43
|
+
* - StrandProvisioner -> provisionStrand
|
|
44
|
+
*/
|
|
45
|
+
export declare class StrandFormationManager {
|
|
46
|
+
private readonly sessionManager;
|
|
47
|
+
private readonly disclosureValidator?;
|
|
48
|
+
private readonly formationUsageRecorder?;
|
|
49
|
+
private readonly strandProvisioner?;
|
|
50
|
+
private readonly partyId;
|
|
51
|
+
private readonly cadrePeerAddrs;
|
|
52
|
+
private registeredNodes;
|
|
53
|
+
constructor(options: StrandFormationManagerOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Register this manager as a protocol handler on a libp2p node.
|
|
56
|
+
* Call this on the control network node to handle incoming formation requests.
|
|
57
|
+
*/
|
|
58
|
+
registerResponder(node: Libp2p, protocolId?: string): void;
|
|
59
|
+
/**
|
|
60
|
+
* Unregister the protocol handler from a libp2p node.
|
|
61
|
+
*/
|
|
62
|
+
unregisterResponder(node: Libp2p, protocolId?: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* Form a strand with a responder via an open invitation.
|
|
65
|
+
*
|
|
66
|
+
* This is the initiator-side operation. It:
|
|
67
|
+
* 1. Converts the OpenInvitation to a BootstrapLink
|
|
68
|
+
* 2. Calls SessionManager.initiateBootstrap()
|
|
69
|
+
* 3. Returns the result as a FormStrandResult
|
|
70
|
+
*/
|
|
71
|
+
formStrand(invitation: OpenInvitation, disclosure: StrandFormationDisclosure, node: Libp2p): Promise<FormStrandResult>;
|
|
72
|
+
/**
|
|
73
|
+
* Get the number of active sessions
|
|
74
|
+
*/
|
|
75
|
+
getActiveSessionCounts(): {
|
|
76
|
+
listeners: number;
|
|
77
|
+
dialers: number;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Create SessionHooks that delegate to our interfaces
|
|
81
|
+
*/
|
|
82
|
+
private createSessionHooks;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create a StrandFormationManager with the given options
|
|
86
|
+
*/
|
|
87
|
+
export declare function createStrandFormationManager(options: StrandFormationManagerOptions): StrandFormationManager;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import { createBootstrapManager, DEFAULT_PROTOCOL_ID } from '@serfab/strand-proto';
|
|
3
|
+
const log = debug('sereus:cadre:formation-manager');
|
|
4
|
+
/**
|
|
5
|
+
* StrandFormationManager bridges cadre-core's strand solicitation interfaces
|
|
6
|
+
* with strand-proto's SessionManager for actual protocol handling over libp2p.
|
|
7
|
+
*
|
|
8
|
+
* It implements SessionHooks by delegating to the existing interfaces:
|
|
9
|
+
* - DisclosureValidator -> validateIdentity
|
|
10
|
+
* - FormationUsageRecorder -> validateToken
|
|
11
|
+
* - StrandProvisioner -> provisionStrand
|
|
12
|
+
*/
|
|
13
|
+
export class StrandFormationManager {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.registeredNodes = new Set();
|
|
16
|
+
this.disclosureValidator = options.disclosureValidator;
|
|
17
|
+
this.formationUsageRecorder = options.formationUsageRecorder;
|
|
18
|
+
this.strandProvisioner = options.strandProvisioner;
|
|
19
|
+
this.partyId = options.partyId;
|
|
20
|
+
this.cadrePeerAddrs = options.cadrePeerAddrs ?? [];
|
|
21
|
+
// Create SessionHooks that delegate to our interfaces
|
|
22
|
+
const hooks = this.createSessionHooks();
|
|
23
|
+
// Create the underlying SessionManager
|
|
24
|
+
const config = {
|
|
25
|
+
sessionTimeoutMs: options.config?.sessionTimeoutMs ?? 30000,
|
|
26
|
+
stepTimeoutMs: options.config?.stepTimeoutMs ?? 5000,
|
|
27
|
+
maxConcurrentSessions: options.config?.maxConcurrentSessions ?? 100,
|
|
28
|
+
enableDebugLogging: options.config?.enableDebugLogging ?? false,
|
|
29
|
+
protocolId: options.config?.protocolId ?? DEFAULT_PROTOCOL_ID
|
|
30
|
+
};
|
|
31
|
+
this.sessionManager = createBootstrapManager(hooks, config);
|
|
32
|
+
log('StrandFormationManager created for party: %s', this.partyId);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Register this manager as a protocol handler on a libp2p node.
|
|
36
|
+
* Call this on the control network node to handle incoming formation requests.
|
|
37
|
+
*/
|
|
38
|
+
registerResponder(node, protocolId) {
|
|
39
|
+
if (this.registeredNodes.has(node)) {
|
|
40
|
+
log('Node already registered');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.sessionManager.register(node, protocolId);
|
|
44
|
+
this.registeredNodes.add(node);
|
|
45
|
+
log('Registered as responder on node');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Unregister the protocol handler from a libp2p node.
|
|
49
|
+
*/
|
|
50
|
+
unregisterResponder(node, protocolId) {
|
|
51
|
+
if (!this.registeredNodes.has(node)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.sessionManager.unregister(node, protocolId);
|
|
55
|
+
this.registeredNodes.delete(node);
|
|
56
|
+
log('Unregistered from node');
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Form a strand with a responder via an open invitation.
|
|
60
|
+
*
|
|
61
|
+
* This is the initiator-side operation. It:
|
|
62
|
+
* 1. Converts the OpenInvitation to a BootstrapLink
|
|
63
|
+
* 2. Calls SessionManager.initiateBootstrap()
|
|
64
|
+
* 3. Returns the result as a FormStrandResult
|
|
65
|
+
*/
|
|
66
|
+
async formStrand(invitation, disclosure, node) {
|
|
67
|
+
log('Forming strand with invitation token: %s', invitation.token);
|
|
68
|
+
// Convert OpenInvitation to BootstrapLink
|
|
69
|
+
const link = {
|
|
70
|
+
responderPeerAddrs: invitation.bootstrap,
|
|
71
|
+
token: invitation.token,
|
|
72
|
+
tokenExpiryUtc: invitation.expiration.toISOString(),
|
|
73
|
+
mode: 'responderCreates' // Responder provisions the strand
|
|
74
|
+
};
|
|
75
|
+
// The disclosure is passed via the identityBundle in the contact message
|
|
76
|
+
// We store it temporarily so the hooks can access it
|
|
77
|
+
// Note: strand-proto's DialerSession constructs the contact message internally
|
|
78
|
+
// We need to extend this to pass the disclosure - for now we use partyId
|
|
79
|
+
const result = await this.sessionManager.initiateBootstrap(link, node);
|
|
80
|
+
log('Strand formed: %s', result.strand.strandId);
|
|
81
|
+
return {
|
|
82
|
+
memberKey: this.partyId, // The initiator's member key
|
|
83
|
+
invitePrivateKey: '', // Would come from key generation
|
|
84
|
+
strandId: result.strand.strandId
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get the number of active sessions
|
|
89
|
+
*/
|
|
90
|
+
getActiveSessionCounts() {
|
|
91
|
+
return this.sessionManager.getActiveSessionCounts();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create SessionHooks that delegate to our interfaces
|
|
95
|
+
*/
|
|
96
|
+
createSessionHooks() {
|
|
97
|
+
return {
|
|
98
|
+
validateToken: async (token, sessionId) => {
|
|
99
|
+
log('validateToken: %s (session: %s)', token, sessionId);
|
|
100
|
+
if (!this.formationUsageRecorder) {
|
|
101
|
+
// No recorder configured - accept all tokens
|
|
102
|
+
return { mode: 'responderCreates', valid: true };
|
|
103
|
+
}
|
|
104
|
+
const tokenCheck = await this.formationUsageRecorder.isTokenValid(token);
|
|
105
|
+
if (!tokenCheck.valid) {
|
|
106
|
+
log('Token invalid: %s', token);
|
|
107
|
+
return { mode: 'responderCreates', valid: false };
|
|
108
|
+
}
|
|
109
|
+
const isUsed = await this.formationUsageRecorder.isTokenUsed(token);
|
|
110
|
+
if (isUsed) {
|
|
111
|
+
log('Token already used: %s', token);
|
|
112
|
+
return { mode: 'responderCreates', valid: false };
|
|
113
|
+
}
|
|
114
|
+
return { mode: 'responderCreates', valid: true };
|
|
115
|
+
},
|
|
116
|
+
validateIdentity: async (identity, sessionId) => {
|
|
117
|
+
log('validateIdentity (session: %s)', sessionId);
|
|
118
|
+
if (!this.disclosureValidator) {
|
|
119
|
+
// No validator configured - accept all identities
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
// Extract disclosure from identity bundle
|
|
123
|
+
const disclosure = identity;
|
|
124
|
+
// We need the token here - it's passed via the contact message
|
|
125
|
+
// For now, use a placeholder - this will be refined
|
|
126
|
+
const token = identity?.token ?? '';
|
|
127
|
+
return this.disclosureValidator.validateDisclosure(token, disclosure);
|
|
128
|
+
},
|
|
129
|
+
provisionStrand: async (creator, creatorPartyId, otherPartyId, sessionId) => {
|
|
130
|
+
log('provisionStrand: creator=%s, other=%s (session: %s)', creatorPartyId, otherPartyId, sessionId);
|
|
131
|
+
if (!this.strandProvisioner) {
|
|
132
|
+
// No provisioner - return a placeholder
|
|
133
|
+
const strandId = `strand-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
134
|
+
return {
|
|
135
|
+
strand: { strandId, createdBy: creator },
|
|
136
|
+
dbConnectionInfo: { endpoint: 'local', credentialsRef: '' }
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Determine initiator/responder keys based on creator
|
|
140
|
+
const initiatorKey = creator === 'initiator' ? creatorPartyId : otherPartyId;
|
|
141
|
+
const responderKey = creator === 'responder' ? creatorPartyId : otherPartyId;
|
|
142
|
+
const result = await this.strandProvisioner.provisionStrand('', // sAppId - would come from invitation
|
|
143
|
+
initiatorKey, responderKey);
|
|
144
|
+
return {
|
|
145
|
+
strand: { strandId: result.strandId, createdBy: creator },
|
|
146
|
+
dbConnectionInfo: { endpoint: 'local', credentialsRef: '' }
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
validateResponse: async (response, sessionId) => {
|
|
150
|
+
log('validateResponse (session: %s)', sessionId);
|
|
151
|
+
// Accept all responses for now
|
|
152
|
+
return true;
|
|
153
|
+
},
|
|
154
|
+
validateDatabaseResult: async (result, sessionId) => {
|
|
155
|
+
log('validateDatabaseResult (session: %s)', sessionId);
|
|
156
|
+
// Accept all database results for now
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Create a StrandFormationManager with the given options
|
|
164
|
+
*/
|
|
165
|
+
export function createStrandFormationManager(options) {
|
|
166
|
+
return new StrandFormationManager(options);
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=strand-formation-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strand-formation-manager.js","sourceRoot":"","sources":["../src/strand-formation-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { StrandInstance, StrandRow, StorageConfig, NetworkConfig, LatencyHint, NodeProfile, SAppConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for starting a strand instance
|
|
4
|
+
*/
|
|
5
|
+
export interface StartStrandConfig {
|
|
6
|
+
strandRow: StrandRow;
|
|
7
|
+
/** sApp configuration provided by the hosting application */
|
|
8
|
+
sAppConfig: SAppConfig;
|
|
9
|
+
storage?: StorageConfig;
|
|
10
|
+
network?: NetworkConfig;
|
|
11
|
+
profile: NodeProfile;
|
|
12
|
+
defaultLatencyHint: LatencyHint;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get the isolated storage path for a specific strand.
|
|
16
|
+
*
|
|
17
|
+
* @deprecated This function uses Node.js path module which is not available in React Native.
|
|
18
|
+
* Use a storage provider factory function instead, which receives the strandId and can
|
|
19
|
+
* create strand-specific storage paths using platform-appropriate methods.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Instead of using getStrandStoragePath, use a storage provider factory:
|
|
23
|
+
* const storage = {
|
|
24
|
+
* provider: (strandId: string) => new FileRawStorage(`./data/strands/${strandId}`)
|
|
25
|
+
* };
|
|
26
|
+
*/
|
|
27
|
+
export declare function getStrandStoragePath(basePath: string, strandId: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Manages individual strand instances - creates and destroys isolated libp2p nodes
|
|
30
|
+
* for each strand the cadre participates in.
|
|
31
|
+
*/
|
|
32
|
+
export declare class StrandInstanceManager {
|
|
33
|
+
private instances;
|
|
34
|
+
private stopping;
|
|
35
|
+
constructor();
|
|
36
|
+
/**
|
|
37
|
+
* Get all current strand instances
|
|
38
|
+
*/
|
|
39
|
+
getInstances(): Map<string, StrandInstance>;
|
|
40
|
+
/**
|
|
41
|
+
* Get a specific strand instance
|
|
42
|
+
*/
|
|
43
|
+
getInstance(strandId: string): StrandInstance | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Check if a strand is currently running
|
|
46
|
+
*/
|
|
47
|
+
hasStrand(strandId: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Start a new strand instance
|
|
50
|
+
*/
|
|
51
|
+
startStrand(config: StartStrandConfig): Promise<StrandInstance>;
|
|
52
|
+
/**
|
|
53
|
+
* Stop a strand instance
|
|
54
|
+
*/
|
|
55
|
+
stopStrand(strandId: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Stop all strand instances
|
|
58
|
+
*/
|
|
59
|
+
stopAll(): Promise<void>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import { createLibp2pNode } from '@optimystic/db-p2p';
|
|
3
|
+
import { StrandDatabase } from './strand-database.js';
|
|
4
|
+
const log = debug('sereus:cadre:strand-manager');
|
|
5
|
+
/**
|
|
6
|
+
* Get the isolated storage path for a specific strand.
|
|
7
|
+
*
|
|
8
|
+
* @deprecated This function uses Node.js path module which is not available in React Native.
|
|
9
|
+
* Use a storage provider factory function instead, which receives the strandId and can
|
|
10
|
+
* create strand-specific storage paths using platform-appropriate methods.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // Instead of using getStrandStoragePath, use a storage provider factory:
|
|
14
|
+
* const storage = {
|
|
15
|
+
* provider: (strandId: string) => new FileRawStorage(`./data/strands/${strandId}`)
|
|
16
|
+
* };
|
|
17
|
+
*/
|
|
18
|
+
export function getStrandStoragePath(basePath, strandId) {
|
|
19
|
+
// Check if we're in a Node.js environment
|
|
20
|
+
if (typeof process === 'undefined' || !process.versions?.node) {
|
|
21
|
+
throw new Error('getStrandStoragePath is not available in React Native. ' +
|
|
22
|
+
'Use a storage provider factory function instead.');
|
|
23
|
+
}
|
|
24
|
+
// Dynamically require path only in Node.js
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
26
|
+
const path = require('path');
|
|
27
|
+
// Sanitize strandId for filesystem safety (UUIDs should be safe, but just in case)
|
|
28
|
+
const safeId = strandId.replace(/[^a-zA-Z0-9-]/g, '_');
|
|
29
|
+
return path.join(basePath, 'strands', safeId);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a storage provider for a specific strand.
|
|
33
|
+
* If the provider is a factory function, call it with the strandId.
|
|
34
|
+
*
|
|
35
|
+
* @param provider - Storage provider (instance or factory)
|
|
36
|
+
* @param strandId - The strand ID to create storage for
|
|
37
|
+
* @returns The resolved IRawStorage instance, or undefined if no provider
|
|
38
|
+
*/
|
|
39
|
+
function resolveStrandStorage(provider, strandId) {
|
|
40
|
+
if (!provider) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return typeof provider === 'function' ? provider(strandId) : provider;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Manages individual strand instances - creates and destroys isolated libp2p nodes
|
|
47
|
+
* for each strand the cadre participates in.
|
|
48
|
+
*/
|
|
49
|
+
export class StrandInstanceManager {
|
|
50
|
+
constructor() {
|
|
51
|
+
this.instances = new Map();
|
|
52
|
+
this.stopping = false;
|
|
53
|
+
log('StrandInstanceManager created');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get all current strand instances
|
|
57
|
+
*/
|
|
58
|
+
getInstances() {
|
|
59
|
+
return new Map(this.instances);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get a specific strand instance
|
|
63
|
+
*/
|
|
64
|
+
getInstance(strandId) {
|
|
65
|
+
return this.instances.get(strandId);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if a strand is currently running
|
|
69
|
+
*/
|
|
70
|
+
hasStrand(strandId) {
|
|
71
|
+
return this.instances.has(strandId);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Start a new strand instance
|
|
75
|
+
*/
|
|
76
|
+
async startStrand(config) {
|
|
77
|
+
const { strandRow, sAppConfig } = config;
|
|
78
|
+
const strandId = strandRow.Id;
|
|
79
|
+
if (this.stopping) {
|
|
80
|
+
throw new Error('StrandInstanceManager is stopping');
|
|
81
|
+
}
|
|
82
|
+
if (this.instances.has(strandId)) {
|
|
83
|
+
log('Strand %s already running', strandId);
|
|
84
|
+
return this.instances.get(strandId);
|
|
85
|
+
}
|
|
86
|
+
log('Starting strand instance: %s (sApp: %s v%s)', strandId, sAppConfig.id, sAppConfig.version);
|
|
87
|
+
// Convert SAppConfig to SAppInfo for the instance
|
|
88
|
+
const sAppInfo = {
|
|
89
|
+
id: sAppConfig.id,
|
|
90
|
+
version: sAppConfig.version,
|
|
91
|
+
schema: sAppConfig.schema,
|
|
92
|
+
signature: sAppConfig.signature
|
|
93
|
+
};
|
|
94
|
+
// Determine latency hint: sApp config > default
|
|
95
|
+
const latencyHint = sAppConfig.latencyHint ?? config.defaultLatencyHint;
|
|
96
|
+
const instance = {
|
|
97
|
+
strandId,
|
|
98
|
+
status: 'starting',
|
|
99
|
+
sAppInfo,
|
|
100
|
+
memberPrivateKey: strandRow.MemberPrivateKey ?? undefined,
|
|
101
|
+
connectedPeers: 0,
|
|
102
|
+
lastActivity: new Date(),
|
|
103
|
+
latencyHint
|
|
104
|
+
};
|
|
105
|
+
this.instances.set(strandId, instance);
|
|
106
|
+
try {
|
|
107
|
+
// Resolve storage for this strand
|
|
108
|
+
// If a factory function is provided, it will be called with the strandId
|
|
109
|
+
// to create strand-specific storage (e.g., strand-isolated directories)
|
|
110
|
+
const strandStorage = resolveStrandStorage(config.storage?.provider, strandId);
|
|
111
|
+
if (strandStorage) {
|
|
112
|
+
log('Strand %s using provided storage provider', strandId);
|
|
113
|
+
}
|
|
114
|
+
// Create isolated libp2p node for this strand
|
|
115
|
+
// Note: protocolPrefix would be used by libp2p services, but createLibp2pNode
|
|
116
|
+
// may not support it yet - tracked for future implementation
|
|
117
|
+
const _protocolPrefix = `/sereus/strand/${strandId}`;
|
|
118
|
+
// Determine relay mode: if explicitly set in config, use that;
|
|
119
|
+
// otherwise default to true for storage profile nodes
|
|
120
|
+
const enableRelay = config.network?.enableRelay ?? (config.profile === 'storage');
|
|
121
|
+
const node = await createLibp2pNode({
|
|
122
|
+
port: 0, // Random port
|
|
123
|
+
bootstrapNodes: [], // Will be populated from strand cohort
|
|
124
|
+
networkName: `strand-${strandId}`,
|
|
125
|
+
storage: strandStorage,
|
|
126
|
+
fretProfile: config.profile === 'storage' ? 'core' : 'edge',
|
|
127
|
+
relay: enableRelay,
|
|
128
|
+
clusterSize: 3,
|
|
129
|
+
clusterPolicy: {
|
|
130
|
+
allowDownsize: true,
|
|
131
|
+
sizeTolerance: 0.5
|
|
132
|
+
},
|
|
133
|
+
arachnode: {
|
|
134
|
+
enableRingZulu: true
|
|
135
|
+
// Storage ring participation stub - will be added when arachnode is built
|
|
136
|
+
// storageRing: config.profile === 'storage' ? { ring: 0 } : undefined
|
|
137
|
+
},
|
|
138
|
+
...(config.network?.transports && { transports: config.network.transports }),
|
|
139
|
+
...(config.network?.listenAddrs && { listenAddrs: config.network.listenAddrs })
|
|
140
|
+
});
|
|
141
|
+
instance.libp2pNode = node;
|
|
142
|
+
// Create and initialize the StrandDatabase
|
|
143
|
+
const strandDb = new StrandDatabase({
|
|
144
|
+
strandId,
|
|
145
|
+
sAppConfig,
|
|
146
|
+
libp2pNode: node,
|
|
147
|
+
coordinatedRepo: node.coordinatedRepo
|
|
148
|
+
});
|
|
149
|
+
await strandDb.initialize();
|
|
150
|
+
instance.database = strandDb;
|
|
151
|
+
instance.status = 'active';
|
|
152
|
+
instance.lastActivity = new Date();
|
|
153
|
+
log('Strand %s started successfully with sApp %s', strandId, sAppConfig.id);
|
|
154
|
+
return instance;
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
instance.status = 'error';
|
|
158
|
+
instance.error = error instanceof Error ? error.message : String(error);
|
|
159
|
+
log('Failed to start strand %s: %s', strandId, instance.error);
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Stop a strand instance
|
|
165
|
+
*/
|
|
166
|
+
async stopStrand(strandId) {
|
|
167
|
+
const instance = this.instances.get(strandId);
|
|
168
|
+
if (!instance) {
|
|
169
|
+
log('Strand %s not found', strandId);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
log('Stopping strand instance: %s', strandId);
|
|
173
|
+
instance.status = 'stopping';
|
|
174
|
+
try {
|
|
175
|
+
// Close the database before stopping libp2p
|
|
176
|
+
if (instance.database) {
|
|
177
|
+
await instance.database.close();
|
|
178
|
+
instance.database = undefined;
|
|
179
|
+
}
|
|
180
|
+
if (instance.libp2pNode) {
|
|
181
|
+
await instance.libp2pNode.stop();
|
|
182
|
+
instance.libp2pNode = undefined;
|
|
183
|
+
}
|
|
184
|
+
instance.status = 'stopped';
|
|
185
|
+
this.instances.delete(strandId);
|
|
186
|
+
log('Strand %s stopped successfully', strandId);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
instance.status = 'error';
|
|
190
|
+
instance.error = error instanceof Error ? error.message : String(error);
|
|
191
|
+
log('Error stopping strand %s: %s', strandId, instance.error);
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Stop all strand instances
|
|
197
|
+
*/
|
|
198
|
+
async stopAll() {
|
|
199
|
+
this.stopping = true;
|
|
200
|
+
log('Stopping all strand instances (%d)', this.instances.size);
|
|
201
|
+
const stopPromises = Array.from(this.instances.keys()).map(id => this.stopStrand(id).catch(err => {
|
|
202
|
+
log('Error stopping strand %s during shutdown: %s', id, err);
|
|
203
|
+
}));
|
|
204
|
+
await Promise.all(stopPromises);
|
|
205
|
+
this.stopping = false;
|
|
206
|
+
log('All strand instances stopped');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=strand-instance-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strand-instance-manager.js","sourceRoot":"","sources":["../src/strand-instance-manager.ts"],"names":[],"mappings":"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"}
|