@feltdb/core 0.6.2 → 0.6.4
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/cli/commands.js +16 -6
- package/dist/cli/index.js +1 -1
- package/dist/cli/workspace-integration.js +37 -0
- package/dist/create/create.js +1 -1
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +0 -4
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/workspace-connection.d.ts +22 -21
- package/dist/workspace/workspace-connection.d.ts.map +1 -1
- package/dist/workspace/workspace-connection.js +213 -53
- package/package.json +2 -1
package/dist/cli/commands.js
CHANGED
|
@@ -8,7 +8,7 @@ import net from 'net';
|
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
9
|
import { spawn, spawnSync } from 'child_process';
|
|
10
10
|
import { createFeltDB, diffFlowSpec, formatFlowSpec, parseFlowSpec, planFlowSpecMigration, validateFlowSpec } from '@feltdb/core';
|
|
11
|
-
import { discoverWorkspace, generatePairingToken, persistPairingToken, displayWorkspaceStatus, initializeWorkspace } from './workspace-integration.js';
|
|
11
|
+
import { discoverWorkspace, generatePairingToken, persistPairingToken, displayWorkspaceStatus, initializeWorkspace, startPairingDiscoveryServer } from './workspace-integration.js';
|
|
12
12
|
function loadProjectEnvironment(file = path.resolve('.env.local')) {
|
|
13
13
|
if (!fs.existsSync(file))
|
|
14
14
|
return;
|
|
@@ -488,7 +488,7 @@ async function handleDev(args) {
|
|
|
488
488
|
? process.env.VITE_FELTDB_MANAGED_NAMESPACE || config.namespace
|
|
489
489
|
: config.namespace;
|
|
490
490
|
const requestedAppPort = Number(args.includes('--port') ? args[args.indexOf('--port') + 1] || '5173' : '5173');
|
|
491
|
-
const requestedStudioPort = Number(args.includes('--studio-port') ? args[args.indexOf('--studio-port') + 1] || '
|
|
491
|
+
const requestedStudioPort = Number(args.includes('--studio-port') ? args[args.indexOf('--studio-port') + 1] || '7701' : '7701');
|
|
492
492
|
const appPort = String(await availablePort(requestedAppPort));
|
|
493
493
|
const studioPort = String(await availablePort(requestedStudioPort));
|
|
494
494
|
if (appPort !== String(requestedAppPort))
|
|
@@ -522,10 +522,19 @@ async function handleDev(args) {
|
|
|
522
522
|
// Generate pairing token for the workspace
|
|
523
523
|
// This enables browsers and IDEs to discover and connect to the workspace
|
|
524
524
|
let pairingToken = null;
|
|
525
|
+
let pairingDiscoveryServer = null;
|
|
525
526
|
if (workspace) {
|
|
526
527
|
const token = generatePairingToken();
|
|
527
528
|
token.workspaceId = workspace.workspaceId;
|
|
529
|
+
token.endpoint = process.env.VITE_FELTDB_URL;
|
|
530
|
+
token.authorityEndpoint = token.endpoint;
|
|
531
|
+
token.namespace = runtimeNamespace || config.namespace;
|
|
532
|
+
if (!token.endpoint) {
|
|
533
|
+
throw new Error('Development Workspace pairing requires a FeltDB authority endpoint');
|
|
534
|
+
}
|
|
528
535
|
persistPairingToken(process.cwd(), token);
|
|
536
|
+
const discoveryPort = Number(args.includes('--discovery-port') ? args[args.indexOf('--discovery-port') + 1] || '7799' : '7799');
|
|
537
|
+
pairingDiscoveryServer = await startPairingDiscoveryServer(token, discoveryPort);
|
|
529
538
|
pairingToken = token.token;
|
|
530
539
|
}
|
|
531
540
|
console.log('FeltDB Dev Server');
|
|
@@ -538,6 +547,7 @@ async function handleDev(args) {
|
|
|
538
547
|
console.log(`🔗 Development Workspace`);
|
|
539
548
|
if (pairingToken) {
|
|
540
549
|
console.log(` Pairing Code: ${pairingToken}`);
|
|
550
|
+
console.log(` Discovery: http://127.0.0.1:${(pairingDiscoveryServer?.address()).port}`);
|
|
541
551
|
console.log(` Clients can use this code to auto-discover the workspace`);
|
|
542
552
|
}
|
|
543
553
|
}
|
|
@@ -550,7 +560,7 @@ async function handleDev(args) {
|
|
|
550
560
|
let shuttingDown = false;
|
|
551
561
|
const stopVite = () => { if (!vite.killed)
|
|
552
562
|
vite.kill('SIGTERM'); };
|
|
553
|
-
const stopAll = () => { shuttingDown = true; stopVite(); stopSelfHosted(); };
|
|
563
|
+
const stopAll = () => { shuttingDown = true; stopVite(); pairingDiscoveryServer?.close(); stopSelfHosted(); };
|
|
554
564
|
// In an inherited terminal Ctrl-C can reach Vite before this parent process.
|
|
555
565
|
// Never leave Studio (or its port) running after the application exits.
|
|
556
566
|
vite.once('exit', code => {
|
|
@@ -816,8 +826,8 @@ async function handleStudio(args) {
|
|
|
816
826
|
? args[args.indexOf('--connect') + 1]
|
|
817
827
|
: undefined;
|
|
818
828
|
const port = args.includes('--port')
|
|
819
|
-
? args[args.indexOf('--port') + 1] || '
|
|
820
|
-
: '
|
|
829
|
+
? args[args.indexOf('--port') + 1] || '7701'
|
|
830
|
+
: '7701';
|
|
821
831
|
const host = args.includes('--host')
|
|
822
832
|
? args[args.indexOf('--host') + 1] || '127.0.0.1'
|
|
823
833
|
: '127.0.0.1';
|
|
@@ -918,7 +928,7 @@ Commands:
|
|
|
918
928
|
Studio:
|
|
919
929
|
feltdb studio Launch local Studio
|
|
920
930
|
feltdb studio --connect <url> Connect to remote instance
|
|
921
|
-
feltdb studio --port
|
|
931
|
+
feltdb studio --port 7701 --no-open Start on port 7701 without opening browser
|
|
922
932
|
|
|
923
933
|
Server Options:
|
|
924
934
|
feltdb server [--port 7700] [--data ./data] [--auth]
|
package/dist/cli/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import * as path from 'path';
|
|
|
23
23
|
import * as readline from 'readline';
|
|
24
24
|
import { getClient } from './api-client.js';
|
|
25
25
|
import { loadFeltDBConfig, createDefaultConfig, validateModel, } from './config.js';
|
|
26
|
-
const VERSION = '0.6.
|
|
26
|
+
const VERSION = '0.6.4';
|
|
27
27
|
function prompt(question) {
|
|
28
28
|
const rl = readline.createInterface({
|
|
29
29
|
input: process.stdin,
|
|
@@ -10,6 +10,43 @@
|
|
|
10
10
|
import fs from 'fs';
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import { randomBytes } from 'crypto';
|
|
13
|
+
import http from 'http';
|
|
14
|
+
export function startPairingDiscoveryServer(token, port = 7799, host = '127.0.0.1') {
|
|
15
|
+
const authorityEndpoint = token.authorityEndpoint || token.endpoint;
|
|
16
|
+
if (!token.workspaceId || !authorityEndpoint || !token.namespace) {
|
|
17
|
+
throw new Error('Pairing discovery requires workspaceId, endpoint, and namespace');
|
|
18
|
+
}
|
|
19
|
+
const route = `/api/v1/development/pairing/${encodeURIComponent(token.token)}`;
|
|
20
|
+
const server = http.createServer((request, response) => {
|
|
21
|
+
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
22
|
+
response.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
23
|
+
response.setHeader('Access-Control-Allow-Headers', 'Accept, Content-Type');
|
|
24
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
25
|
+
if (request.method === 'OPTIONS') {
|
|
26
|
+
response.statusCode = 204;
|
|
27
|
+
response.end();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const pathname = new URL(request.url || '/', `http://${host}`).pathname;
|
|
31
|
+
if (request.method !== 'GET' || pathname !== route || Date.now() >= token.expiresAt) {
|
|
32
|
+
response.statusCode = 404;
|
|
33
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
34
|
+
response.end(JSON.stringify({ error: 'PAIRING_CODE_NOT_FOUND' }));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
38
|
+
response.end(JSON.stringify({
|
|
39
|
+
workspaceId: token.workspaceId,
|
|
40
|
+
endpoint: authorityEndpoint,
|
|
41
|
+
expiresAt: token.expiresAt,
|
|
42
|
+
namespace: token.namespace,
|
|
43
|
+
}));
|
|
44
|
+
});
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
server.once('error', reject);
|
|
47
|
+
server.listen(port, host, () => resolve(server));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
13
50
|
/**
|
|
14
51
|
* Discover workspace identity from .feltdb/workspace.json
|
|
15
52
|
* Returns null if workspace is not initialized
|
package/dist/create/create.js
CHANGED
|
@@ -1234,7 +1234,7 @@ NODE_ENV=development
|
|
|
1234
1234
|
framework,
|
|
1235
1235
|
port: 7700,
|
|
1236
1236
|
replicationPort: 7701,
|
|
1237
|
-
studioPort:
|
|
1237
|
+
studioPort: 7701,
|
|
1238
1238
|
};
|
|
1239
1239
|
fs.writeFileSync(path.join(projectDir, 'docker-compose.yml'), generateDockerCompose(dockerConfig));
|
|
1240
1240
|
fs.writeFileSync(path.join(projectDir, 'Dockerfile'), generateDockerfile(dockerConfig, framework));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// One release train keeps generated applications installable. The repository
|
|
2
2
|
// validation script checks these values against every workspace manifest.
|
|
3
|
-
export const FELTDB_PACKAGE_VERSION = '0.6.
|
|
3
|
+
export const FELTDB_PACKAGE_VERSION = '0.6.4';
|
|
4
4
|
export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;
|
|
@@ -5856,10 +5856,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
5856
5856
|
axum::routing::patch(platform_update_application_member),
|
|
5857
5857
|
)
|
|
5858
5858
|
.route("/api/tenants/{tenant_id}", get(get_tenant))
|
|
5859
|
-
.route(
|
|
5860
|
-
"/api/certification/fixtures/{application_id}",
|
|
5861
|
-
axum::routing::delete(delete_certification_fixture),
|
|
5862
|
-
)
|
|
5863
5859
|
.route("/api/keys", get(list_keys).post(create_key))
|
|
5864
5860
|
.route("/api/keys/{id}", axum::routing::delete(revoke_key))
|
|
5865
5861
|
.route(
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* to the same shared development workspace.
|
|
13
13
|
*/
|
|
14
14
|
export type { DevelopmentWorkspace, ClientType, WorkspaceEventPayload, DevelopmentTask, CodeChange, VerificationResult, SourceLocation, WorkspaceClient, WorkspaceDiscovery, } from './workspace-types.js';
|
|
15
|
-
export type { WorkspaceConnectionOptions, WorkspaceMetadata, PairingTokenData, ClientInfo, } from './workspace-connection.js';
|
|
15
|
+
export type { WorkspaceConnectionOptions, WorkspaceMetadata, PairingTokenData, BrowserPairingResolution, PairingDiscoveryOptions, ClientInfo, } from './workspace-connection.js';
|
|
16
16
|
export { connectDevelopmentWorkspace, discoverDevelopmentWorkspace, resolvePairingCode, DevelopmentWorkspaceConnection, } from './workspace-connection.js';
|
|
17
17
|
export { createWorkspaceId, isValidWorkspaceId, createDevelopmentWorkspace, updateWorkspaceTimestamp, discoverWorkspace, persistWorkspaceDiscovery, } from './workspace-identity.js';
|
|
18
18
|
export { DevelopmentNode, getDevelopmentNode, shutdownDevelopmentNode, } from './development-node.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/workspace/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,YAAY,EACV,oBAAoB,EACpB,UAAU,EACV,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,0BAA0B,EAC1B,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,GACX,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,EAC1B,wBAAwB,EACxB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/workspace/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,YAAY,EACV,oBAAoB,EACpB,UAAU,EACV,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,0BAA0B,EAC1B,iBAAiB,EACjB,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,UAAU,GACX,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,kBAAkB,EAClB,8BAA8B,GAC/B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,EAC1B,wBAAwB,EACxB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* to a FeltDB Development Workspace.
|
|
6
6
|
*/
|
|
7
7
|
import type { ClientType } from './workspace-types.js';
|
|
8
|
+
import type { WorkspaceEventPayload } from './workspace-types.js';
|
|
8
9
|
export interface WorkspaceConnectionOptions {
|
|
9
10
|
pairingCode?: string;
|
|
10
11
|
workspaceId?: string;
|
|
@@ -12,6 +13,7 @@ export interface WorkspaceConnectionOptions {
|
|
|
12
13
|
clientId?: string;
|
|
13
14
|
clientType?: ClientType;
|
|
14
15
|
endpoint?: string;
|
|
16
|
+
discoveryEndpoint?: string;
|
|
15
17
|
auth?: {
|
|
16
18
|
token: string;
|
|
17
19
|
apiKey?: string;
|
|
@@ -26,6 +28,18 @@ export interface PairingTokenData {
|
|
|
26
28
|
token: string;
|
|
27
29
|
workspaceId: string;
|
|
28
30
|
expiresAt: number;
|
|
31
|
+
endpoint?: string;
|
|
32
|
+
authorityEndpoint?: string;
|
|
33
|
+
namespace?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface BrowserPairingResolution {
|
|
36
|
+
workspaceId: string;
|
|
37
|
+
endpoint: string;
|
|
38
|
+
expiresAt: number;
|
|
39
|
+
namespace: string;
|
|
40
|
+
}
|
|
41
|
+
export interface PairingDiscoveryOptions {
|
|
42
|
+
endpoint?: string;
|
|
29
43
|
}
|
|
30
44
|
export interface ClientInfo {
|
|
31
45
|
clientId: string;
|
|
@@ -54,7 +68,8 @@ export declare function discoverDevelopmentWorkspace(options: {
|
|
|
54
68
|
*
|
|
55
69
|
* Used by clients to auto-discover workspace without manual entry.
|
|
56
70
|
*/
|
|
57
|
-
export declare function resolvePairingCode(pairingCode: string, projectDir
|
|
71
|
+
export declare function resolvePairingCode(pairingCode: string, projectDir: string): Promise<string>;
|
|
72
|
+
export declare function resolvePairingCode(pairingCode: string, options?: PairingDiscoveryOptions): Promise<BrowserPairingResolution>;
|
|
58
73
|
/**
|
|
59
74
|
* Connect to a Development Workspace
|
|
60
75
|
*
|
|
@@ -101,8 +116,9 @@ export declare class DevelopmentWorkspaceConnection {
|
|
|
101
116
|
clientType: ClientType;
|
|
102
117
|
private options;
|
|
103
118
|
private subscriptions;
|
|
104
|
-
|
|
105
|
-
private
|
|
119
|
+
connectedAt: number;
|
|
120
|
+
private transport;
|
|
121
|
+
private unsubscribeEvents;
|
|
106
122
|
constructor(workspaceId: string, clientId: string, clientType: ClientType, options: WorkspaceConnectionOptions);
|
|
107
123
|
/**
|
|
108
124
|
* Establish connection to workspace
|
|
@@ -112,10 +128,6 @@ export declare class DevelopmentWorkspaceConnection {
|
|
|
112
128
|
* Close connection to workspace
|
|
113
129
|
*/
|
|
114
130
|
disconnect(): Promise<void>;
|
|
115
|
-
/**
|
|
116
|
-
* Register this client as connected
|
|
117
|
-
*/
|
|
118
|
-
private registerClient;
|
|
119
131
|
/**
|
|
120
132
|
* Get capabilities for this client type
|
|
121
133
|
*/
|
|
@@ -123,7 +135,7 @@ export declare class DevelopmentWorkspaceConnection {
|
|
|
123
135
|
/**
|
|
124
136
|
* List connected clients in workspace
|
|
125
137
|
*/
|
|
126
|
-
clients(): ClientInfo[]
|
|
138
|
+
clients(): Promise<ClientInfo[]>;
|
|
127
139
|
/**
|
|
128
140
|
* Subscribe to workspace entity changes
|
|
129
141
|
*
|
|
@@ -157,18 +169,7 @@ export declare class DevelopmentWorkspaceConnection {
|
|
|
157
169
|
* Notify subscribers of entity changes
|
|
158
170
|
*/
|
|
159
171
|
private notifySubscribers;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
* Event payload for workspace changes
|
|
163
|
-
*/
|
|
164
|
-
export interface WorkspaceEventPayload<T = unknown> {
|
|
165
|
-
id: string;
|
|
166
|
-
workspaceId: string;
|
|
167
|
-
collection: string;
|
|
168
|
-
entityId: string;
|
|
169
|
-
type: 'created' | 'updated' | 'deleted';
|
|
170
|
-
value?: T;
|
|
171
|
-
timestamp: number;
|
|
172
|
-
originClientId: string;
|
|
172
|
+
private requireTransport;
|
|
173
|
+
private resolveEndpoint;
|
|
173
174
|
}
|
|
174
175
|
//# sourceMappingURL=workspace-connection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-connection.d.ts","sourceRoot":"","sources":["../../src/workspace/workspace-connection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"workspace-connection.d.ts","sourceRoot":"","sources":["../../src/workspace/workspace-connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Public API for clients (browser, IDE, agent) to discover and connect
|
|
5
5
|
* to a FeltDB Development Workspace.
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
8
|
-
|
|
7
|
+
import { HttpJsDb } from '../http-db.js';
|
|
8
|
+
const DEFAULT_PAIRING_DISCOVERY_ENDPOINT = 'http://127.0.0.1:7799';
|
|
9
9
|
/**
|
|
10
10
|
* Discover workspace from .feltdb/workspace.json
|
|
11
11
|
*
|
|
@@ -17,6 +17,10 @@ import path from 'path';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export async function discoverDevelopmentWorkspace(options) {
|
|
20
|
+
const [{ default: fs }, { default: path }] = await Promise.all([
|
|
21
|
+
import('node:fs'),
|
|
22
|
+
import('node:path'),
|
|
23
|
+
]);
|
|
20
24
|
const workspacePath = path.join(options.projectDir, '.feltdb', 'workspace.json');
|
|
21
25
|
if (!fs.existsSync(workspacePath)) {
|
|
22
26
|
throw new Error(`Development Workspace not found at ${workspacePath}. Run "feltdb dev" or create-feltdb to initialize.`);
|
|
@@ -30,20 +34,17 @@ export async function discoverDevelopmentWorkspace(options) {
|
|
|
30
34
|
throw new Error(`Failed to read workspace metadata: ${error instanceof Error ? error.message : String(error)}`);
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
|
-
|
|
34
|
-
* Resolve pairing code to workspace ID
|
|
35
|
-
*
|
|
36
|
-
* Pairing codes are short-lived tokens (FELT-XXXX) that resolve to
|
|
37
|
-
* workspace IDs. They expire after 15 minutes.
|
|
38
|
-
*
|
|
39
|
-
* Used by clients to auto-discover workspace without manual entry.
|
|
40
|
-
*/
|
|
41
|
-
export async function resolvePairingCode(pairingCode, projectDir) {
|
|
37
|
+
export async function resolvePairingCode(pairingCode, source = {}) {
|
|
42
38
|
if (!pairingCode.startsWith('FELT-')) {
|
|
43
39
|
throw new Error('Invalid pairing code format');
|
|
44
40
|
}
|
|
45
|
-
//
|
|
46
|
-
if (
|
|
41
|
+
// Keep filesystem resolution for CLI and IDE callers that explicitly pass a project directory.
|
|
42
|
+
if (typeof source === 'string') {
|
|
43
|
+
const [{ default: fs }, { default: path }] = await Promise.all([
|
|
44
|
+
import('node:fs'),
|
|
45
|
+
import('node:path'),
|
|
46
|
+
]);
|
|
47
|
+
const projectDir = source;
|
|
47
48
|
const pairingPath = path.join(projectDir, '.feltdb', 'pairing.json');
|
|
48
49
|
if (fs.existsSync(pairingPath)) {
|
|
49
50
|
try {
|
|
@@ -58,6 +59,26 @@ export async function resolvePairingCode(pairingCode, projectDir) {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
}
|
|
62
|
+
else {
|
|
63
|
+
const discoveryEndpoint = (source.endpoint || DEFAULT_PAIRING_DISCOVERY_ENDPOINT).replace(/\/$/, '');
|
|
64
|
+
let response;
|
|
65
|
+
try {
|
|
66
|
+
response = await fetch(`${discoveryEndpoint}/api/v1/development/pairing/${encodeURIComponent(pairingCode)}`, {
|
|
67
|
+
headers: { Accept: 'application/json' },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
throw new Error(`Pairing discovery unavailable at ${discoveryEndpoint}: ${error instanceof Error ? error.message : String(error)}`);
|
|
72
|
+
}
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`Pairing code ${pairingCode} not found or expired (discovery returned ${response.status})`);
|
|
75
|
+
}
|
|
76
|
+
const resolution = await response.json();
|
|
77
|
+
if (!resolution.workspaceId || !resolution.endpoint || !resolution.expiresAt || !resolution.namespace) {
|
|
78
|
+
throw new Error('Pairing discovery returned an invalid response');
|
|
79
|
+
}
|
|
80
|
+
return resolution;
|
|
81
|
+
}
|
|
61
82
|
throw new Error(`Pairing code ${pairingCode} not found or expired. Ensure "feltdb dev" is running and the code is correct.`);
|
|
62
83
|
}
|
|
63
84
|
/**
|
|
@@ -95,9 +116,19 @@ export async function resolvePairingCode(pairingCode, projectDir) {
|
|
|
95
116
|
*/
|
|
96
117
|
export async function connectDevelopmentWorkspace(options) {
|
|
97
118
|
let workspaceId = options.workspaceId;
|
|
119
|
+
let authorityEndpoint = options.endpoint;
|
|
98
120
|
// Resolve pairing code to workspace ID if provided
|
|
99
121
|
if (options.pairingCode && !workspaceId) {
|
|
100
|
-
|
|
122
|
+
if (options.projectDir) {
|
|
123
|
+
workspaceId = await resolvePairingCode(options.pairingCode, options.projectDir);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const resolution = await resolvePairingCode(options.pairingCode, {
|
|
127
|
+
endpoint: options.discoveryEndpoint || options.endpoint,
|
|
128
|
+
});
|
|
129
|
+
workspaceId = resolution.workspaceId;
|
|
130
|
+
authorityEndpoint = resolution.endpoint;
|
|
131
|
+
}
|
|
101
132
|
}
|
|
102
133
|
// Discover workspace if no ID provided
|
|
103
134
|
if (!workspaceId && options.projectDir) {
|
|
@@ -109,7 +140,9 @@ export async function connectDevelopmentWorkspace(options) {
|
|
|
109
140
|
}
|
|
110
141
|
const clientId = options.clientId || generateClientId(options.clientType);
|
|
111
142
|
const clientType = options.clientType || 'other';
|
|
112
|
-
|
|
143
|
+
const connection = new DevelopmentWorkspaceConnection(workspaceId, clientId, clientType, { ...options, endpoint: authorityEndpoint });
|
|
144
|
+
await connection.connect();
|
|
145
|
+
return connection;
|
|
113
146
|
}
|
|
114
147
|
/**
|
|
115
148
|
* Generate a unique client ID based on type
|
|
@@ -130,7 +163,8 @@ export class DevelopmentWorkspaceConnection {
|
|
|
130
163
|
constructor(workspaceId, clientId, clientType, options) {
|
|
131
164
|
this.subscriptions = new Map();
|
|
132
165
|
this.connectedAt = 0;
|
|
133
|
-
this.
|
|
166
|
+
this.transport = null;
|
|
167
|
+
this.unsubscribeEvents = null;
|
|
134
168
|
this.workspaceId = workspaceId;
|
|
135
169
|
this.clientId = clientId;
|
|
136
170
|
this.clientType = clientType;
|
|
@@ -140,26 +174,34 @@ export class DevelopmentWorkspaceConnection {
|
|
|
140
174
|
* Establish connection to workspace
|
|
141
175
|
*/
|
|
142
176
|
async connect() {
|
|
143
|
-
this.
|
|
144
|
-
|
|
177
|
+
if (this.transport)
|
|
178
|
+
return;
|
|
179
|
+
const endpoint = await this.resolveEndpoint();
|
|
180
|
+
const transport = new HttpWorkspaceTransport(endpoint, this.options.auth?.token ?? '');
|
|
181
|
+
await transport.assertReachable();
|
|
182
|
+
const connectedAt = Date.now();
|
|
183
|
+
await transport.registerClient(this.workspaceId, {
|
|
184
|
+
clientId: this.clientId,
|
|
185
|
+
clientType: this.clientType,
|
|
186
|
+
connectedAt,
|
|
187
|
+
capabilities: this.getCapabilities(),
|
|
188
|
+
});
|
|
189
|
+
this.transport = transport;
|
|
190
|
+
this.unsubscribeEvents = transport.subscribe(this.workspaceId, event => this.notifySubscribers(event));
|
|
191
|
+
this.connectedAt = connectedAt;
|
|
145
192
|
}
|
|
146
193
|
/**
|
|
147
194
|
* Close connection to workspace
|
|
148
195
|
*/
|
|
149
196
|
async disconnect() {
|
|
197
|
+
this.unsubscribeEvents?.();
|
|
198
|
+
this.unsubscribeEvents = null;
|
|
199
|
+
if (this.transport) {
|
|
200
|
+
await this.transport.unregisterClient(this.workspaceId, this.clientId);
|
|
201
|
+
}
|
|
150
202
|
this.subscriptions.clear();
|
|
151
|
-
this.
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Register this client as connected
|
|
155
|
-
*/
|
|
156
|
-
registerClient() {
|
|
157
|
-
this.connectedClients.set(this.clientId, {
|
|
158
|
-
clientId: this.clientId,
|
|
159
|
-
clientType: this.clientType,
|
|
160
|
-
connectedAt: this.connectedAt,
|
|
161
|
-
capabilities: this.getCapabilities(),
|
|
162
|
-
});
|
|
203
|
+
this.transport = null;
|
|
204
|
+
this.connectedAt = 0;
|
|
163
205
|
}
|
|
164
206
|
/**
|
|
165
207
|
* Get capabilities for this client type
|
|
@@ -181,8 +223,8 @@ export class DevelopmentWorkspaceConnection {
|
|
|
181
223
|
/**
|
|
182
224
|
* List connected clients in workspace
|
|
183
225
|
*/
|
|
184
|
-
clients() {
|
|
185
|
-
return
|
|
226
|
+
async clients() {
|
|
227
|
+
return this.requireTransport().clients(this.workspaceId);
|
|
186
228
|
}
|
|
187
229
|
/**
|
|
188
230
|
* Subscribe to workspace entity changes
|
|
@@ -214,56 +256,44 @@ export class DevelopmentWorkspaceConnection {
|
|
|
214
256
|
*/
|
|
215
257
|
async publish(collection, entity) {
|
|
216
258
|
const id = generateEntityId();
|
|
217
|
-
|
|
218
|
-
id: generateEventId(),
|
|
259
|
+
await this.requireTransport().publish({
|
|
219
260
|
workspaceId: this.workspaceId,
|
|
220
261
|
collection,
|
|
221
262
|
entityId: id,
|
|
222
|
-
type: 'created',
|
|
223
263
|
value: entity,
|
|
224
|
-
timestamp: Date.now(),
|
|
225
264
|
originClientId: this.clientId,
|
|
226
|
-
};
|
|
227
|
-
this.notifySubscribers(collection, event);
|
|
265
|
+
});
|
|
228
266
|
return id;
|
|
229
267
|
}
|
|
230
268
|
/**
|
|
231
269
|
* Get entity from workspace
|
|
232
270
|
*/
|
|
233
271
|
async get(collection, entityId) {
|
|
234
|
-
|
|
235
|
-
// For now, returns null (placeholder)
|
|
236
|
-
return null;
|
|
272
|
+
return this.requireTransport().get({ workspaceId: this.workspaceId, collection, entityId });
|
|
237
273
|
}
|
|
238
274
|
/**
|
|
239
275
|
* Query entities in workspace collection
|
|
240
276
|
*/
|
|
241
277
|
async query(collection) {
|
|
242
|
-
|
|
243
|
-
// For now, returns empty array (placeholder)
|
|
244
|
-
return [];
|
|
278
|
+
return this.requireTransport().query({ workspaceId: this.workspaceId, collection });
|
|
245
279
|
}
|
|
246
280
|
/**
|
|
247
281
|
* Update entity in workspace
|
|
248
282
|
*/
|
|
249
283
|
async update(collection, entityId, updates) {
|
|
250
|
-
|
|
251
|
-
id: generateEventId(),
|
|
284
|
+
await this.requireTransport().update({
|
|
252
285
|
workspaceId: this.workspaceId,
|
|
253
286
|
collection,
|
|
254
287
|
entityId,
|
|
255
|
-
|
|
256
|
-
value: updates,
|
|
257
|
-
timestamp: Date.now(),
|
|
288
|
+
updates,
|
|
258
289
|
originClientId: this.clientId,
|
|
259
|
-
};
|
|
260
|
-
this.notifySubscribers(collection, event);
|
|
290
|
+
});
|
|
261
291
|
}
|
|
262
292
|
/**
|
|
263
293
|
* Notify subscribers of entity changes
|
|
264
294
|
*/
|
|
265
|
-
notifySubscribers(
|
|
266
|
-
const handlers = this.subscriptions.get(collection);
|
|
295
|
+
notifySubscribers(event) {
|
|
296
|
+
const handlers = this.subscriptions.get(event.collection);
|
|
267
297
|
if (handlers) {
|
|
268
298
|
for (const handler of handlers) {
|
|
269
299
|
try {
|
|
@@ -275,6 +305,31 @@ export class DevelopmentWorkspaceConnection {
|
|
|
275
305
|
}
|
|
276
306
|
}
|
|
277
307
|
}
|
|
308
|
+
requireTransport() {
|
|
309
|
+
if (!this.transport)
|
|
310
|
+
throw new Error('Development Workspace is not connected');
|
|
311
|
+
return this.transport;
|
|
312
|
+
}
|
|
313
|
+
async resolveEndpoint() {
|
|
314
|
+
if (this.options.endpoint)
|
|
315
|
+
return this.options.endpoint;
|
|
316
|
+
const environmentEndpoint = typeof process !== 'undefined'
|
|
317
|
+
? process.env.FELTDB_WORKSPACE_ENDPOINT || process.env.VITE_FELTDB_URL
|
|
318
|
+
: undefined;
|
|
319
|
+
if (environmentEndpoint)
|
|
320
|
+
return environmentEndpoint;
|
|
321
|
+
if (this.options.pairingCode && this.options.projectDir) {
|
|
322
|
+
const [{ default: fs }, { default: path }] = await Promise.all([
|
|
323
|
+
import('node:fs'),
|
|
324
|
+
import('node:path'),
|
|
325
|
+
]);
|
|
326
|
+
const pairingPath = path.join(this.options.projectDir, '.feltdb', 'pairing.json');
|
|
327
|
+
const pairing = JSON.parse(fs.readFileSync(pairingPath, 'utf8'));
|
|
328
|
+
if (pairing.authorityEndpoint || pairing.endpoint)
|
|
329
|
+
return pairing.authorityEndpoint || pairing.endpoint;
|
|
330
|
+
}
|
|
331
|
+
throw new Error('Cannot connect: workspace authority endpoint is required');
|
|
332
|
+
}
|
|
278
333
|
}
|
|
279
334
|
/**
|
|
280
335
|
* Generate unique entity ID
|
|
@@ -288,3 +343,108 @@ function generateEntityId() {
|
|
|
288
343
|
function generateEventId() {
|
|
289
344
|
return `event_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
290
345
|
}
|
|
346
|
+
const ENTITY_COLLECTION = '_development_workspace_entities';
|
|
347
|
+
const EVENT_COLLECTION = '_development_workspace_events';
|
|
348
|
+
const CLIENT_COLLECTION = '_development_workspace_clients';
|
|
349
|
+
class HttpWorkspaceTransport {
|
|
350
|
+
constructor(endpoint, token) {
|
|
351
|
+
this.seenEvents = new Set();
|
|
352
|
+
this.db = new HttpJsDb({ url: endpoint, token });
|
|
353
|
+
}
|
|
354
|
+
async assertReachable() {
|
|
355
|
+
const result = await this.db.query(ENTITY_COLLECTION);
|
|
356
|
+
if (!result.success)
|
|
357
|
+
throw new Error(`Workspace authority unavailable: ${result.error}`);
|
|
358
|
+
}
|
|
359
|
+
async registerClient(workspaceId, client) {
|
|
360
|
+
const result = await this.db.insert(`${CLIENT_COLLECTION}:${recordId(workspaceId, client.clientId)}`, JSON.stringify({ workspaceId, ...client }));
|
|
361
|
+
if (!result.success) {
|
|
362
|
+
const update = await this.db.update(`${CLIENT_COLLECTION}:${recordId(workspaceId, client.clientId)}`, JSON.stringify({ workspaceId, ...client }));
|
|
363
|
+
if (!update.success)
|
|
364
|
+
throw new Error(`Client registration failed: ${update.error}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async unregisterClient(workspaceId, clientId) {
|
|
368
|
+
const result = await this.db.delete(`${CLIENT_COLLECTION}:${recordId(workspaceId, clientId)}`);
|
|
369
|
+
if (!result.success && !String(result.error).includes('404'))
|
|
370
|
+
throw new Error(`Client cleanup failed: ${result.error}`);
|
|
371
|
+
}
|
|
372
|
+
async clients(workspaceId) {
|
|
373
|
+
return (await this.readCollection(CLIENT_COLLECTION))
|
|
374
|
+
.filter(client => client.workspaceId === workspaceId);
|
|
375
|
+
}
|
|
376
|
+
async publish(input) {
|
|
377
|
+
const envelope = { ...input, updatedAt: Date.now() };
|
|
378
|
+
const stored = await this.db.insert(`${ENTITY_COLLECTION}:${recordId(input.workspaceId, input.collection, input.entityId)}`, JSON.stringify(envelope));
|
|
379
|
+
if (!stored.success)
|
|
380
|
+
throw new Error(`Workspace publish failed: ${stored.error}`);
|
|
381
|
+
await this.appendEvent(input, 'created', input.value);
|
|
382
|
+
}
|
|
383
|
+
async get(input) {
|
|
384
|
+
const result = await this.db.get(`${ENTITY_COLLECTION}:${recordId(input.workspaceId, input.collection, input.entityId)}`);
|
|
385
|
+
if (!result.success)
|
|
386
|
+
throw new Error(`Workspace get failed: ${result.error}`);
|
|
387
|
+
if (!result.data)
|
|
388
|
+
return null;
|
|
389
|
+
const envelope = JSON.parse(result.data);
|
|
390
|
+
return envelope.workspaceId === input.workspaceId && envelope.collection === input.collection ? envelope.value : null;
|
|
391
|
+
}
|
|
392
|
+
async query(input) {
|
|
393
|
+
return (await this.readCollection(ENTITY_COLLECTION))
|
|
394
|
+
.filter(entity => entity.workspaceId === input.workspaceId && entity.collection === input.collection)
|
|
395
|
+
.map(entity => entity.value);
|
|
396
|
+
}
|
|
397
|
+
async update(input) {
|
|
398
|
+
const current = await this.get(input);
|
|
399
|
+
if (current === null)
|
|
400
|
+
throw new Error(`Workspace entity not found: ${input.entityId}`);
|
|
401
|
+
const value = { ...current, ...input.updates };
|
|
402
|
+
const envelope = { workspaceId: input.workspaceId, collection: input.collection, entityId: input.entityId, value, updatedAt: Date.now() };
|
|
403
|
+
const result = await this.db.update(`${ENTITY_COLLECTION}:${recordId(input.workspaceId, input.collection, input.entityId)}`, JSON.stringify(envelope));
|
|
404
|
+
if (!result.success)
|
|
405
|
+
throw new Error(`Workspace update failed: ${result.error}`);
|
|
406
|
+
await this.appendEvent(input, 'updated', value);
|
|
407
|
+
}
|
|
408
|
+
subscribe(workspaceId, handler) {
|
|
409
|
+
let stopped = false;
|
|
410
|
+
const subscribedAt = Date.now();
|
|
411
|
+
const unsubscribe = this.db.subscribe_changes(collection => {
|
|
412
|
+
if (collection !== EVENT_COLLECTION || stopped)
|
|
413
|
+
return;
|
|
414
|
+
void this.deliverEvents(workspaceId, subscribedAt, handler);
|
|
415
|
+
});
|
|
416
|
+
return () => { stopped = true; unsubscribe(); };
|
|
417
|
+
}
|
|
418
|
+
async appendEvent(input, type, value) {
|
|
419
|
+
const event = { id: generateEventId(), ...input, type, value, timestamp: Date.now() };
|
|
420
|
+
const result = await this.db.insert(`${EVENT_COLLECTION}:${event.id}`, JSON.stringify(event));
|
|
421
|
+
if (!result.success)
|
|
422
|
+
throw new Error(`Workspace event persistence failed: ${result.error}`);
|
|
423
|
+
}
|
|
424
|
+
async deliverEvents(workspaceId, subscribedAt, handler) {
|
|
425
|
+
for (const event of await this.readCollection(EVENT_COLLECTION)) {
|
|
426
|
+
if (event.workspaceId !== workspaceId || event.timestamp < subscribedAt || this.seenEvents.has(event.id))
|
|
427
|
+
continue;
|
|
428
|
+
this.seenEvents.add(event.id);
|
|
429
|
+
handler(event);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async readCollection(collection) {
|
|
433
|
+
const result = await this.db.query(collection);
|
|
434
|
+
if (!result.success)
|
|
435
|
+
throw new Error(`Workspace query failed: ${result.error}`);
|
|
436
|
+
return result.data ? JSON.parse(result.data) : [];
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function recordId(...parts) {
|
|
440
|
+
const value = parts.join('\u0000');
|
|
441
|
+
const hash = (seed) => {
|
|
442
|
+
let current = seed;
|
|
443
|
+
for (const byte of new TextEncoder().encode(value)) {
|
|
444
|
+
current ^= BigInt(byte);
|
|
445
|
+
current = BigInt.asUintN(64, current * 0x100000001b3n);
|
|
446
|
+
}
|
|
447
|
+
return current.toString(16).padStart(16, '0');
|
|
448
|
+
};
|
|
449
|
+
return `workspace_${hash(0xcbf29ce484222325n)}${hash(0x84222325cbf29ce4n)}`;
|
|
450
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feltdb/core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "FeltDB Core - Application-facing database with Browser, Local, and Server runtimes. Durable state, reactive APIs.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "rm -rf dist && tsc && node ../../tools/stage-core-wasm.mjs && npm run build --workspace @feltdb/react && npm run build --workspace @feltdb/migration-sherpa && npm run build --workspace @feltdb/studio && npm run build --workspace @feltdb/cli && npm run build --workspace create-feltdb && node ../../tools/assemble-core-sdk.mjs",
|
|
58
58
|
"test": "node ../../test-runner.ts",
|
|
59
|
+
"test:workspace-e2e": "node --test test/workspace-connection.e2e.test.mjs",
|
|
59
60
|
"type-check": "tsc --noEmit"
|
|
60
61
|
},
|
|
61
62
|
"keywords": [
|