@feltdb/core 0.6.3 → 0.6.5
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 +30 -7
- 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/workspace/index.d.ts +3 -1
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +1 -0
- package/dist/workspace/local-development-authority.d.ts +12 -0
- package/dist/workspace/local-development-authority.d.ts.map +1 -0
- package/dist/workspace/local-development-authority.js +137 -0
- package/dist/workspace/workspace-connection.d.ts +14 -1
- package/dist/workspace/workspace-connection.d.ts.map +1 -1
- package/dist/workspace/workspace-connection.js +51 -17
- package/package.json +1 -1
package/dist/cli/commands.js
CHANGED
|
@@ -7,8 +7,8 @@ import http from 'http';
|
|
|
7
7
|
import net from 'net';
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
9
|
import { spawn, spawnSync } from 'child_process';
|
|
10
|
-
import { createFeltDB, diffFlowSpec, formatFlowSpec, parseFlowSpec, planFlowSpecMigration, validateFlowSpec } from '@feltdb/core';
|
|
11
|
-
import { discoverWorkspace, generatePairingToken, persistPairingToken, displayWorkspaceStatus, initializeWorkspace } from './workspace-integration.js';
|
|
10
|
+
import { createFeltDB, diffFlowSpec, formatFlowSpec, parseFlowSpec, planFlowSpecMigration, startLocalDevelopmentAuthority, validateFlowSpec } from '@feltdb/core';
|
|
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;
|
|
@@ -429,6 +429,10 @@ function runLocalVite(args, waitForExit) {
|
|
|
429
429
|
}
|
|
430
430
|
async function handleDev(args) {
|
|
431
431
|
var _a, _b, _c;
|
|
432
|
+
if (args.includes('--help')) {
|
|
433
|
+
console.log('Usage: feltdb dev [--port 5173] [--studio-port 7701] [--authority-port 7700] [--discovery-port 7799] [--no-open]');
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
432
436
|
loadProjectEnvironment();
|
|
433
437
|
console.log('🚀 Starting FeltDB development server...\n');
|
|
434
438
|
const projectDir = process.cwd();
|
|
@@ -488,7 +492,7 @@ async function handleDev(args) {
|
|
|
488
492
|
? process.env.VITE_FELTDB_MANAGED_NAMESPACE || config.namespace
|
|
489
493
|
: config.namespace;
|
|
490
494
|
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] || '
|
|
495
|
+
const requestedStudioPort = Number(args.includes('--studio-port') ? args[args.indexOf('--studio-port') + 1] || '7701' : '7701');
|
|
492
496
|
const appPort = String(await availablePort(requestedAppPort));
|
|
493
497
|
const studioPort = String(await availablePort(requestedStudioPort));
|
|
494
498
|
if (appPort !== String(requestedAppPort))
|
|
@@ -498,6 +502,7 @@ async function handleDev(args) {
|
|
|
498
502
|
process.env.APP_PORT = appPort;
|
|
499
503
|
process.env.STUDIO_PORT = studioPort;
|
|
500
504
|
let selfHostedStarted = false;
|
|
505
|
+
let localAuthority = null;
|
|
501
506
|
const stopSelfHosted = () => {
|
|
502
507
|
if (selfHostedStarted) {
|
|
503
508
|
spawnSync('docker', ['compose', 'stop', 'feltdb'], { cwd: process.cwd(), stdio: 'ignore' });
|
|
@@ -519,17 +524,34 @@ async function handleDev(args) {
|
|
|
519
524
|
selfHostedStarted = true;
|
|
520
525
|
(_c = process.env).VITE_FELTDB_URL || (_c.VITE_FELTDB_URL = 'http://127.0.0.1:7700');
|
|
521
526
|
}
|
|
527
|
+
if (!process.env.VITE_FELTDB_URL) {
|
|
528
|
+
const authorityPort = Number(args.includes('--authority-port') ? args[args.indexOf('--authority-port') + 1] || '7700' : '7700');
|
|
529
|
+
localAuthority = await startLocalDevelopmentAuthority({
|
|
530
|
+
dataDir: path.join(projectDir, '.feltdb', 'authority'),
|
|
531
|
+
port: authorityPort,
|
|
532
|
+
});
|
|
533
|
+
process.env.VITE_FELTDB_URL = localAuthority.endpoint;
|
|
534
|
+
}
|
|
522
535
|
// Generate pairing token for the workspace
|
|
523
536
|
// This enables browsers and IDEs to discover and connect to the workspace
|
|
524
537
|
let pairingToken = null;
|
|
538
|
+
let pairingDiscoveryServer = null;
|
|
525
539
|
if (workspace) {
|
|
526
540
|
const token = generatePairingToken();
|
|
527
541
|
token.workspaceId = workspace.workspaceId;
|
|
528
542
|
token.endpoint = process.env.VITE_FELTDB_URL;
|
|
543
|
+
token.authorityEndpoint = token.endpoint;
|
|
544
|
+
token.namespace = runtimeNamespace || config.namespace;
|
|
545
|
+
if (!token.endpoint) {
|
|
546
|
+
throw new Error('Development Workspace pairing requires a FeltDB authority endpoint');
|
|
547
|
+
}
|
|
529
548
|
persistPairingToken(process.cwd(), token);
|
|
549
|
+
const discoveryPort = Number(args.includes('--discovery-port') ? args[args.indexOf('--discovery-port') + 1] || '7799' : '7799');
|
|
550
|
+
pairingDiscoveryServer = await startPairingDiscoveryServer(token, discoveryPort);
|
|
530
551
|
pairingToken = token.token;
|
|
531
552
|
}
|
|
532
553
|
console.log('FeltDB Dev Server');
|
|
554
|
+
console.log(` Authority: ${process.env.VITE_FELTDB_URL}`);
|
|
533
555
|
console.log(` Namespace: ${config.namespace}`);
|
|
534
556
|
console.log(` Runtime: ${config.runtime}`);
|
|
535
557
|
console.log(` Storage: ${config.storage}`);
|
|
@@ -539,6 +561,7 @@ async function handleDev(args) {
|
|
|
539
561
|
console.log(`🔗 Development Workspace`);
|
|
540
562
|
if (pairingToken) {
|
|
541
563
|
console.log(` Pairing Code: ${pairingToken}`);
|
|
564
|
+
console.log(` Pairing discovery: http://127.0.0.1:${(pairingDiscoveryServer?.address()).port}`);
|
|
542
565
|
console.log(` Clients can use this code to auto-discover the workspace`);
|
|
543
566
|
}
|
|
544
567
|
}
|
|
@@ -551,7 +574,7 @@ async function handleDev(args) {
|
|
|
551
574
|
let shuttingDown = false;
|
|
552
575
|
const stopVite = () => { if (!vite.killed)
|
|
553
576
|
vite.kill('SIGTERM'); };
|
|
554
|
-
const stopAll = () => { shuttingDown = true; stopVite(); stopSelfHosted(); };
|
|
577
|
+
const stopAll = () => { shuttingDown = true; stopVite(); pairingDiscoveryServer?.close(); void localAuthority?.close(); stopSelfHosted(); };
|
|
555
578
|
// In an inherited terminal Ctrl-C can reach Vite before this parent process.
|
|
556
579
|
// Never leave Studio (or its port) running after the application exits.
|
|
557
580
|
vite.once('exit', code => {
|
|
@@ -817,8 +840,8 @@ async function handleStudio(args) {
|
|
|
817
840
|
? args[args.indexOf('--connect') + 1]
|
|
818
841
|
: undefined;
|
|
819
842
|
const port = args.includes('--port')
|
|
820
|
-
? args[args.indexOf('--port') + 1] || '
|
|
821
|
-
: '
|
|
843
|
+
? args[args.indexOf('--port') + 1] || '7701'
|
|
844
|
+
: '7701';
|
|
822
845
|
const host = args.includes('--host')
|
|
823
846
|
? args[args.indexOf('--host') + 1] || '127.0.0.1'
|
|
824
847
|
: '127.0.0.1';
|
|
@@ -919,7 +942,7 @@ Commands:
|
|
|
919
942
|
Studio:
|
|
920
943
|
feltdb studio Launch local Studio
|
|
921
944
|
feltdb studio --connect <url> Connect to remote instance
|
|
922
|
-
feltdb studio --port
|
|
945
|
+
feltdb studio --port 7701 --no-open Start on port 7701 without opening browser
|
|
923
946
|
|
|
924
947
|
Server Options:
|
|
925
948
|
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.5';
|
|
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.5';
|
|
4
4
|
export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;
|
|
@@ -12,9 +12,11 @@
|
|
|
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';
|
|
19
19
|
export type { DevelopmentNodeConfig } from './development-node.js';
|
|
20
|
+
export { startLocalDevelopmentAuthority } from './local-development-authority.js';
|
|
21
|
+
export type { LocalDevelopmentAuthority, LocalDevelopmentAuthorityOptions } from './local-development-authority.js';
|
|
20
22
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -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;AAEnE,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAClF,YAAY,EAAE,yBAAyB,EAAE,gCAAgC,EAAE,MAAM,kCAAkC,CAAC"}
|
package/dist/workspace/index.js
CHANGED
|
@@ -14,3 +14,4 @@
|
|
|
14
14
|
export { connectDevelopmentWorkspace, discoverDevelopmentWorkspace, resolvePairingCode, DevelopmentWorkspaceConnection, } from './workspace-connection.js';
|
|
15
15
|
export { createWorkspaceId, isValidWorkspaceId, createDevelopmentWorkspace, updateWorkspaceTimestamp, discoverWorkspace, persistWorkspaceDiscovery, } from './workspace-identity.js';
|
|
16
16
|
export { DevelopmentNode, getDevelopmentNode, shutdownDevelopmentNode, } from './development-node.js';
|
|
17
|
+
export { startLocalDevelopmentAuthority } from './local-development-authority.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface LocalDevelopmentAuthority {
|
|
2
|
+
endpoint: string;
|
|
3
|
+
close(): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export interface LocalDevelopmentAuthorityOptions {
|
|
6
|
+
dataDir: string;
|
|
7
|
+
host?: string;
|
|
8
|
+
port?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Start the durable, unauthenticated loopback authority used by `feltdb dev`. */
|
|
11
|
+
export declare function startLocalDevelopmentAuthority(options: LocalDevelopmentAuthorityOptions): Promise<LocalDevelopmentAuthority>;
|
|
12
|
+
//# sourceMappingURL=local-development-authority.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-development-authority.d.ts","sourceRoot":"","sources":["../../src/workspace/local-development-authority.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,kFAAkF;AAClF,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAAC,yBAAyB,CAAC,CAyFpC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { FileJsDb } from '../file-db.js';
|
|
4
|
+
/** Start the durable, unauthenticated loopback authority used by `feltdb dev`. */
|
|
5
|
+
export async function startLocalDevelopmentAuthority(options) {
|
|
6
|
+
const host = options.host || '127.0.0.1';
|
|
7
|
+
const port = options.port ?? 7700;
|
|
8
|
+
if (host !== '127.0.0.1' && host !== 'localhost' && host !== '::1') {
|
|
9
|
+
throw new Error('The development authority may only bind to a loopback address');
|
|
10
|
+
}
|
|
11
|
+
const db = new FileJsDb(path.resolve(options.dataDir));
|
|
12
|
+
const streams = new Set();
|
|
13
|
+
const server = http.createServer(async (request, response) => {
|
|
14
|
+
try {
|
|
15
|
+
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
16
|
+
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE, OPTIONS');
|
|
17
|
+
response.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, FeltDB-Protocol');
|
|
18
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
19
|
+
if (request.method === 'OPTIONS') {
|
|
20
|
+
response.statusCode = 204;
|
|
21
|
+
response.end();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const pathname = new URL(request.url || '/', `http://${host}`).pathname;
|
|
25
|
+
if (request.method === 'GET' && (pathname === '/health' || pathname === '/ready')) {
|
|
26
|
+
json(response, 200, { status: 'ready', storage: 'writable', authority: 'local-development' });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (request.method === 'GET' && pathname === '/events') {
|
|
30
|
+
response.writeHead(200, {
|
|
31
|
+
'Content-Type': 'text/event-stream',
|
|
32
|
+
Connection: 'keep-alive',
|
|
33
|
+
'Cache-Control': 'no-cache',
|
|
34
|
+
'Access-Control-Allow-Origin': '*',
|
|
35
|
+
});
|
|
36
|
+
response.write(': connected\n\n');
|
|
37
|
+
streams.add(response);
|
|
38
|
+
request.once('close', () => streams.delete(response));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const match = pathname.match(/^\/collections\/([^/]+)(?:\/([^/]+))?$/);
|
|
42
|
+
if (!match) {
|
|
43
|
+
json(response, 404, { error: 'NOT_FOUND' });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const collection = decodeURIComponent(match[1]);
|
|
47
|
+
const id = match[2] ? decodeURIComponent(match[2]) : undefined;
|
|
48
|
+
const body = await readBody(request);
|
|
49
|
+
let result;
|
|
50
|
+
if (request.method === 'GET' && !id) {
|
|
51
|
+
result = db.query(collection);
|
|
52
|
+
json(response, result.success ? 200 : 500, result.success
|
|
53
|
+
? JSON.parse(result.data || '[]').map(value => ({ value }))
|
|
54
|
+
: { error: result.error });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (request.method === 'GET' && id) {
|
|
58
|
+
result = db.get(`${collection}:${id}`);
|
|
59
|
+
if (!result.success) {
|
|
60
|
+
json(response, 500, { error: result.error });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!result.data) {
|
|
64
|
+
json(response, 404, { error: 'NOT_FOUND' });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
json(response, 200, { value: JSON.parse(result.data) });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (request.method === 'POST' && !id) {
|
|
71
|
+
const value = parseObject(body);
|
|
72
|
+
const recordId = typeof value.id === 'string' ? value.id : '';
|
|
73
|
+
if (!recordId) {
|
|
74
|
+
json(response, 400, { error: 'Record must have an id' });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
result = db.insert(`${collection}:${recordId}`, JSON.stringify(value));
|
|
78
|
+
}
|
|
79
|
+
else if (request.method === 'PATCH' && id) {
|
|
80
|
+
result = db.update(`${collection}:${id}`, JSON.stringify(parseObject(body)));
|
|
81
|
+
}
|
|
82
|
+
else if (request.method === 'DELETE' && id) {
|
|
83
|
+
result = db.delete(`${collection}:${id}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
json(response, 404, { error: 'NOT_FOUND' });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!result.success) {
|
|
90
|
+
json(response, 400, { error: result.error });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
json(response, request.method === 'POST' ? 201 : 200, { ok: true });
|
|
94
|
+
const frame = `data: ${JSON.stringify({ change: { capability: collection } })}\n\n`;
|
|
95
|
+
for (const stream of streams)
|
|
96
|
+
stream.write(frame);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (!response.headersSent)
|
|
100
|
+
json(response, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
101
|
+
else
|
|
102
|
+
response.end();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
await new Promise((resolve, reject) => {
|
|
106
|
+
server.once('error', reject);
|
|
107
|
+
server.listen(port, host, resolve);
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
endpoint: `http://${host}:${port}`,
|
|
111
|
+
close: () => new Promise((resolve, reject) => {
|
|
112
|
+
for (const stream of streams)
|
|
113
|
+
stream.end();
|
|
114
|
+
server.close(error => error ? reject(error) : resolve());
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function json(response, status, value) {
|
|
119
|
+
response.statusCode = status;
|
|
120
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
121
|
+
response.end(JSON.stringify(value));
|
|
122
|
+
}
|
|
123
|
+
function readBody(request) {
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
let body = '';
|
|
126
|
+
request.setEncoding('utf8');
|
|
127
|
+
request.on('data', chunk => { body += chunk; });
|
|
128
|
+
request.once('end', () => resolve(body));
|
|
129
|
+
request.once('error', reject);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function parseObject(body) {
|
|
133
|
+
const value = JSON.parse(body || '{}');
|
|
134
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
135
|
+
throw new Error('Request body must be an object');
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
@@ -13,6 +13,7 @@ export interface WorkspaceConnectionOptions {
|
|
|
13
13
|
clientId?: string;
|
|
14
14
|
clientType?: ClientType;
|
|
15
15
|
endpoint?: string;
|
|
16
|
+
discoveryEndpoint?: string;
|
|
16
17
|
auth?: {
|
|
17
18
|
token: string;
|
|
18
19
|
apiKey?: string;
|
|
@@ -28,6 +29,17 @@ export interface PairingTokenData {
|
|
|
28
29
|
workspaceId: string;
|
|
29
30
|
expiresAt: number;
|
|
30
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;
|
|
31
43
|
}
|
|
32
44
|
export interface ClientInfo {
|
|
33
45
|
clientId: string;
|
|
@@ -56,7 +68,8 @@ export declare function discoverDevelopmentWorkspace(options: {
|
|
|
56
68
|
*
|
|
57
69
|
* Used by clients to auto-discover workspace without manual entry.
|
|
58
70
|
*/
|
|
59
|
-
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>;
|
|
60
73
|
/**
|
|
61
74
|
* Connect to a Development Workspace
|
|
62
75
|
*
|
|
@@ -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,9 +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 fs from 'fs';
|
|
8
|
-
import path from 'path';
|
|
9
7
|
import { HttpJsDb } from '../http-db.js';
|
|
8
|
+
const DEFAULT_PAIRING_DISCOVERY_ENDPOINT = 'http://127.0.0.1:7799';
|
|
10
9
|
/**
|
|
11
10
|
* Discover workspace from .feltdb/workspace.json
|
|
12
11
|
*
|
|
@@ -18,6 +17,10 @@ import { HttpJsDb } from '../http-db.js';
|
|
|
18
17
|
* ```
|
|
19
18
|
*/
|
|
20
19
|
export async function discoverDevelopmentWorkspace(options) {
|
|
20
|
+
const [{ default: fs }, { default: path }] = await Promise.all([
|
|
21
|
+
import('node:fs'),
|
|
22
|
+
import('node:path'),
|
|
23
|
+
]);
|
|
21
24
|
const workspacePath = path.join(options.projectDir, '.feltdb', 'workspace.json');
|
|
22
25
|
if (!fs.existsSync(workspacePath)) {
|
|
23
26
|
throw new Error(`Development Workspace not found at ${workspacePath}. Run "feltdb dev" or create-feltdb to initialize.`);
|
|
@@ -31,20 +34,17 @@ export async function discoverDevelopmentWorkspace(options) {
|
|
|
31
34
|
throw new Error(`Failed to read workspace metadata: ${error instanceof Error ? error.message : String(error)}`);
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
|
-
|
|
35
|
-
* Resolve pairing code to workspace ID
|
|
36
|
-
*
|
|
37
|
-
* Pairing codes are short-lived tokens (FELT-XXXX) that resolve to
|
|
38
|
-
* workspace IDs. They expire after 15 minutes.
|
|
39
|
-
*
|
|
40
|
-
* Used by clients to auto-discover workspace without manual entry.
|
|
41
|
-
*/
|
|
42
|
-
export async function resolvePairingCode(pairingCode, projectDir) {
|
|
37
|
+
export async function resolvePairingCode(pairingCode, source = {}) {
|
|
43
38
|
if (!pairingCode.startsWith('FELT-')) {
|
|
44
39
|
throw new Error('Invalid pairing code format');
|
|
45
40
|
}
|
|
46
|
-
//
|
|
47
|
-
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;
|
|
48
48
|
const pairingPath = path.join(projectDir, '.feltdb', 'pairing.json');
|
|
49
49
|
if (fs.existsSync(pairingPath)) {
|
|
50
50
|
try {
|
|
@@ -59,6 +59,26 @@ export async function resolvePairingCode(pairingCode, projectDir) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
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
|
+
}
|
|
62
82
|
throw new Error(`Pairing code ${pairingCode} not found or expired. Ensure "feltdb dev" is running and the code is correct.`);
|
|
63
83
|
}
|
|
64
84
|
/**
|
|
@@ -96,9 +116,19 @@ export async function resolvePairingCode(pairingCode, projectDir) {
|
|
|
96
116
|
*/
|
|
97
117
|
export async function connectDevelopmentWorkspace(options) {
|
|
98
118
|
let workspaceId = options.workspaceId;
|
|
119
|
+
let authorityEndpoint = options.endpoint;
|
|
99
120
|
// Resolve pairing code to workspace ID if provided
|
|
100
121
|
if (options.pairingCode && !workspaceId) {
|
|
101
|
-
|
|
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
|
+
}
|
|
102
132
|
}
|
|
103
133
|
// Discover workspace if no ID provided
|
|
104
134
|
if (!workspaceId && options.projectDir) {
|
|
@@ -110,7 +140,7 @@ export async function connectDevelopmentWorkspace(options) {
|
|
|
110
140
|
}
|
|
111
141
|
const clientId = options.clientId || generateClientId(options.clientType);
|
|
112
142
|
const clientType = options.clientType || 'other';
|
|
113
|
-
const connection = new DevelopmentWorkspaceConnection(workspaceId, clientId, clientType, options);
|
|
143
|
+
const connection = new DevelopmentWorkspaceConnection(workspaceId, clientId, clientType, { ...options, endpoint: authorityEndpoint });
|
|
114
144
|
await connection.connect();
|
|
115
145
|
return connection;
|
|
116
146
|
}
|
|
@@ -289,10 +319,14 @@ export class DevelopmentWorkspaceConnection {
|
|
|
289
319
|
if (environmentEndpoint)
|
|
290
320
|
return environmentEndpoint;
|
|
291
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
|
+
]);
|
|
292
326
|
const pairingPath = path.join(this.options.projectDir, '.feltdb', 'pairing.json');
|
|
293
327
|
const pairing = JSON.parse(fs.readFileSync(pairingPath, 'utf8'));
|
|
294
|
-
if (pairing.endpoint)
|
|
295
|
-
return pairing.endpoint;
|
|
328
|
+
if (pairing.authorityEndpoint || pairing.endpoint)
|
|
329
|
+
return pairing.authorityEndpoint || pairing.endpoint;
|
|
296
330
|
}
|
|
297
331
|
throw new Error('Cannot connect: workspace authority endpoint is required');
|
|
298
332
|
}
|
package/package.json
CHANGED