@feltdb/core 0.4.8 → 0.4.9

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.
@@ -7,7 +7,23 @@ import http from 'http';
7
7
  import { createRequire } from 'module';
8
8
  import { spawn, spawnSync } from 'child_process';
9
9
  import { createFeltDB, diffFlowSpec, formatFlowSpec, parseFlowSpec, planFlowSpecMigration, validateFlowSpec } from '@feltdb/core';
10
- const RELEASE_VERSION = '0.4.2';
10
+ const RELEASE_VERSION = '0.4.9';
11
+ function loadProjectEnvironment(file = path.resolve('.env.local')) {
12
+ if (!fs.existsSync(file))
13
+ return;
14
+ for (const rawLine of fs.readFileSync(file, 'utf8').split(/\r?\n/)) {
15
+ const line = rawLine.trim();
16
+ if (!line || line.startsWith('#'))
17
+ continue;
18
+ const separator = line.indexOf('=');
19
+ if (separator < 1)
20
+ continue;
21
+ const key = line.slice(0, separator).trim();
22
+ const value = line.slice(separator + 1).trim().replace(/^(['"])(.*)\1$/, '$2');
23
+ if (!(key in process.env))
24
+ process.env[key] = value;
25
+ }
26
+ }
11
27
  export async function handleCommand(command, args) {
12
28
  switch (command) {
13
29
  case 'studio':
@@ -351,7 +367,8 @@ function runLocalVite(args, waitForExit) {
351
367
  });
352
368
  }
353
369
  async function handleDev(args) {
354
- var _a;
370
+ var _a, _b, _c;
371
+ loadProjectEnvironment();
355
372
  console.log('🚀 Starting FeltDB development server...\n');
356
373
  // Check for feltdb.config.json
357
374
  const configPath = path.join(process.cwd(), 'feltdb.config.json');
@@ -360,6 +377,18 @@ async function handleDev(args) {
360
377
  process.exit(1);
361
378
  }
362
379
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
380
+ if (config.runtime === 'managed') {
381
+ (_a = process.env).VITE_FELTDB_URL || (_a.VITE_FELTDB_URL = process.env.VITE_FELTDB_MANAGED_URL);
382
+ (_b = process.env).VITE_FELTDB_API_KEY || (_b.VITE_FELTDB_API_KEY = process.env.VITE_FELTDB_MANAGED_API_KEY);
383
+ }
384
+ if (config.runtime === 'managed' && (!process.env.VITE_FELTDB_URL || !process.env.VITE_FELTDB_API_KEY)) {
385
+ throw new Error('Managed runtime requires VITE_FELTDB_MANAGED_URL and VITE_FELTDB_MANAGED_API_KEY in .env.local. Re-run managed setup or add your managed connection.');
386
+ }
387
+ const runtimeNamespace = config.runtime === 'managed'
388
+ ? process.env.VITE_FELTDB_MANAGED_NAMESPACE || config.namespace
389
+ : config.namespace;
390
+ const appPort = args.includes('--port') ? args[args.indexOf('--port') + 1] || '5173' : '5173';
391
+ const studioPort = args.includes('--studio-port') ? args[args.indexOf('--studio-port') + 1] || '3000' : '3000';
363
392
  let selfHosted;
364
393
  const stopSelfHosted = () => {
365
394
  if (selfHosted && !selfHosted.killed)
@@ -380,20 +409,20 @@ async function handleDev(args) {
380
409
  '-p', '7700:8080',
381
410
  '-v', `${containerName}-data:/data`,
382
411
  image,
412
+ '--allow-origin', `http://127.0.0.1:${appPort}`,
413
+ '--allow-origin', `http://127.0.0.1:${studioPort}`,
383
414
  ], { stdio: 'inherit' });
384
415
  selfHosted.once('exit', code => {
385
416
  if (code && code !== 0)
386
417
  console.error(`Self-hosted FeltDB server exited with status ${code}`);
387
418
  });
388
- (_a = process.env).VITE_FELTDB_URL || (_a.VITE_FELTDB_URL = 'http://127.0.0.1:7700');
419
+ (_c = process.env).VITE_FELTDB_URL || (_c.VITE_FELTDB_URL = 'http://127.0.0.1:7700');
389
420
  }
390
421
  console.log('FeltDB Dev Server');
391
422
  console.log(` Namespace: ${config.namespace}`);
392
423
  console.log(` Runtime: ${config.runtime}`);
393
424
  console.log(` Storage: ${config.storage}`);
394
425
  console.log(` Distributed: ${config.distributed}\n`);
395
- const appPort = args.includes('--port') ? args[args.indexOf('--port') + 1] || '5173' : '5173';
396
- const studioPort = args.includes('--studio-port') ? args[args.indexOf('--studio-port') + 1] || '3000' : '3000';
397
426
  const open = !args.includes('--no-open');
398
427
  console.log(`Application: http://127.0.0.1:${appPort}`);
399
428
  console.log(`Studio: http://127.0.0.1:${studioPort}\n`);
@@ -407,8 +436,9 @@ async function handleDev(args) {
407
436
  process.once('SIGTERM', () => { stopAll(); process.exit(143); });
408
437
  await handleStudio([
409
438
  '--port', studioPort,
410
- '--namespace', config.namespace || 'default',
411
- ...(config.runtime === 'self-hosted' ? ['--connect', process.env.VITE_FELTDB_URL] : []),
439
+ '--namespace', runtimeNamespace || 'default',
440
+ '--runtime', config.runtime || 'browser',
441
+ ...((config.runtime === 'self-hosted' || config.runtime === 'managed') && process.env.VITE_FELTDB_URL ? ['--connect', process.env.VITE_FELTDB_URL] : []),
412
442
  ...(open ? [] : ['--no-open']),
413
443
  ]);
414
444
  }
@@ -663,6 +693,9 @@ async function handleStudio(args) {
663
693
  const namespace = args.includes('--namespace')
664
694
  ? args[args.indexOf('--namespace') + 1] || 'default'
665
695
  : 'default';
696
+ const runtime = args.includes('--runtime')
697
+ ? args[args.indexOf('--runtime') + 1] || 'browser'
698
+ : connectUrl ? 'remote' : 'browser';
666
699
  if (connectUrl) {
667
700
  console.log(`Connecting to: ${connectUrl}`);
668
701
  console.log(`Remote Studio: http://localhost:${port}\n`);
@@ -700,7 +733,7 @@ async function handleStudio(args) {
700
733
  fs.createReadStream(file).pipe(response);
701
734
  });
702
735
  await new Promise((resolve, reject) => { server.once('error', reject); server.listen(Number(port), '127.0.0.1', resolve); });
703
- const parameters = new URLSearchParams({ namespace });
736
+ const parameters = new URLSearchParams({ namespace, runtime });
704
737
  if (connectUrl)
705
738
  parameters.set('connect', connectUrl);
706
739
  const query = `?${parameters.toString()}`;
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.4.2';
26
+ const VERSION = '0.4.9';
27
27
  function prompt(question) {
28
28
  const rl = readline.createInterface({
29
29
  input: process.stdin,
@@ -10,6 +10,7 @@ import readline from 'readline';
10
10
  import { spawn } from 'child_process';
11
11
  import { createProject } from './create.js';
12
12
  import { FELTDB_PACKAGE_VERSION } from './package-versions.js';
13
+ import { configureManagedAccount } from './managed-account.js';
13
14
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
15
  function parseArgs(args) {
15
16
  const options = {
@@ -21,6 +22,18 @@ function parseArgs(args) {
21
22
  };
22
23
  for (let i = 0; i < args.length; i++) {
23
24
  switch (args[i]) {
25
+ case '--browser':
26
+ options.runtime = 'browser';
27
+ break;
28
+ case '--server':
29
+ options.runtime = 'node';
30
+ break;
31
+ case '--self-host':
32
+ options.runtime = 'self-hosted';
33
+ break;
34
+ case '--managed':
35
+ options.runtime = 'managed';
36
+ break;
24
37
  case '--runtime':
25
38
  options.runtime = args[++i];
26
39
  break;
@@ -49,6 +62,38 @@ function run(command, args, cwd) {
49
62
  : reject(new Error(`${command} exited with status ${code ?? 'unknown'}`)));
50
63
  });
51
64
  }
65
+ async function prompt(message) {
66
+ const interface_ = readline.createInterface({ input: process.stdin, output: process.stdout });
67
+ return new Promise(resolve => interface_.question(message, value => { interface_.close(); resolve(value.trim()); }));
68
+ }
69
+ async function promptSecret(message) {
70
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
71
+ return '';
72
+ const input = process.stdin;
73
+ const output = process.stdout;
74
+ const wasRaw = input.isRaw;
75
+ readline.emitKeypressEvents(input);
76
+ input.setRawMode(true);
77
+ input.resume();
78
+ output.write(message);
79
+ return new Promise(resolve => {
80
+ let value = '';
81
+ const finish = () => { input.off('keypress', onKeypress); input.setRawMode(Boolean(wasRaw)); input.pause(); output.write('\n'); resolve(value); };
82
+ const onKeypress = (character, key) => {
83
+ if (key.ctrl && key.name === 'c')
84
+ process.exit(130);
85
+ if (key.name === 'return' || key.name === 'enter')
86
+ return finish();
87
+ if (key.name === 'backspace') {
88
+ value = value.slice(0, -1);
89
+ return;
90
+ }
91
+ if (!key.ctrl && !key.meta && character)
92
+ value += character;
93
+ };
94
+ input.on('keypress', onKeypress);
95
+ });
96
+ }
52
97
  async function select(message, choices, initialValue) {
53
98
  let selected = Math.max(0, choices.findIndex(choice => choice.value === initialValue));
54
99
  const input = process.stdin;
@@ -108,6 +153,7 @@ async function promptForOptions(defaults) {
108
153
  { label: 'Browser', value: 'browser', description: 'local-first with durable browser storage' },
109
154
  { label: 'Node.js', value: 'node', description: 'application server or worker' },
110
155
  { label: 'Self-hosted', value: 'self-hosted', description: 'dedicated FeltDB server' },
156
+ { label: 'Managed', value: 'managed', description: 'FeltDB-hosted persistence, sync, and workloads' },
111
157
  ], defaults.runtime),
112
158
  framework: await select('Choose an application framework:', [
113
159
  { label: 'React', value: 'react' },
@@ -134,22 +180,35 @@ Usage:
134
180
 
135
181
  Examples:
136
182
  create-feltdb my-app
137
- create-feltdb my-app --runtime browser --framework react
138
- create-feltdb my-app --yes --no-start
183
+ create-feltdb my-app --browser
184
+ create-feltdb my-app --server
185
+ create-feltdb my-app --self-host
186
+ create-feltdb my-app --managed
187
+ create-feltdb my-app --framework react --yes
139
188
 
140
- Runtime Options:
141
- --runtime browser Local-first with durable browser storage (OPFS)
189
+ Deployment Target (Explicit):
190
+ --browser Local-first with durable browser storage (IndexedDB)
142
191
  Best for: Client-side apps, offline-first experiences
192
+ No server required, full offline capability
143
193
 
144
- --runtime node Node.js application server or worker
194
+ --server Node.js server runtime with server-side FeltDB authority
145
195
  Best for: Server-side applications, API servers
196
+ Requires production FeltDB instance or self-host
146
197
 
147
- --runtime self-hosted Dedicated FeltDB server instance
198
+ --self-host Dedicated FeltDB server with Docker Compose
148
199
  Best for: Production deployments, multi-user systems
149
- Note: Requires Docker (image: ghcr.io/rkendel1/feltdb)
200
+ Generates Docker Compose with persistent /data volume
201
+
202
+ --managed Managed FeltDB runtime
203
+ Best for: Production without operating servers
204
+ Requires VITE_FELTDB_URL and VITE_FELTDB_API_KEY
150
205
 
151
206
  Options:
152
- --runtime <runtime> Set application runtime (default: browser)
207
+ --browser Explicit: Browser runtime with IndexedDB
208
+ --server Explicit: Node.js server runtime
209
+ --self-host Explicit: Self-hosted with Docker Compose
210
+ --managed Explicit: Managed FeltDB service
211
+ --runtime <runtime> Alternative: Set runtime (browser, node, self-hosted, managed)
153
212
  --framework <framework> Choose framework: react or vanilla (default: react)
154
213
  --no-distributed Disable distributed operation
155
214
  --no-agents Exclude agent examples
@@ -160,11 +219,6 @@ Options:
160
219
  -h, --help Show this help message
161
220
  --version Show version number
162
221
 
163
- Vector Search:
164
- Vector search requires additional dependencies and setup. If you choose
165
- "Search + vector search" during setup, make sure to review the documentation
166
- for configuring your vector database connection.
167
-
168
222
  Environment Variables:
169
223
  FELTDB_IMAGE Override the default self-hosted container image
170
224
  NODE_ENV Set to 'development' or 'production'
@@ -193,21 +247,31 @@ Learn more: https://github.com/rkendel1/feltdb`);
193
247
  const shouldInstall = !args.includes('--no-install');
194
248
  const shouldStart = !args.includes('--no-start');
195
249
  let options = parseArgs(args);
250
+ const runtimeDescriptions = {
251
+ 'browser': 'Browser with IndexedDB (local-first, no server)',
252
+ 'node': 'Node.js server (server-side authority)',
253
+ 'self-hosted': 'Self-hosted with Docker Compose (production)',
254
+ 'managed': 'Managed FeltDB service (hosted persistence, sync, and workloads)',
255
+ };
256
+ if (!runtimeDescriptions[options.runtime]) {
257
+ throw new Error(`Unsupported runtime "${options.runtime}". Choose browser, node, self-hosted, or managed.`);
258
+ }
196
259
  console.log('\n✨ Creating FeltDB Application\n');
197
260
  if (!shouldAutoYes) {
198
261
  options = await promptForOptions(options);
199
- const runtimeDescriptions = {
200
- 'browser': 'Local-first with browser storage',
201
- 'node': 'Node.js server runtime',
202
- 'self-hosted': 'Dedicated FeltDB server',
203
- };
204
- console.log('\nConfiguration:');
205
- console.log(` Runtime: ${options.runtime} (${runtimeDescriptions[options.runtime]})`);
206
- console.log(` Framework: ${options.framework}`);
207
- console.log(` Distributed: ${options.distributed ? 'yes' : 'no'}`);
208
- console.log(` Agents: ${options.agents ? 'yes' : 'no'}`);
209
- console.log(` Capabilities: ${options.capabilities}\n`);
210
262
  }
263
+ // Display the deployment target explicitly
264
+ console.log('\n═══════════════════════════════════════════════');
265
+ console.log('📦 DEPLOYMENT TARGET');
266
+ console.log('═══════════════════════════════════════════════');
267
+ console.log(`\nRuntime: ${options.runtime.toUpperCase()}`);
268
+ console.log(`Description: ${runtimeDescriptions[options.runtime]}`);
269
+ console.log(`Framework: ${options.framework}`);
270
+ console.log(`Distributed: ${options.distributed ? 'yes' : 'no'}`);
271
+ console.log(`Agents: ${options.agents ? 'yes' : 'no'}`);
272
+ console.log(`Capabilities: ${options.capabilities}`);
273
+ console.log('\n═══════════════════════════════════════════════\n');
274
+ const projectDir = path.resolve(process.cwd(), projectName);
211
275
  try {
212
276
  await createProject({
213
277
  projectName,
@@ -219,8 +283,18 @@ Learn more: https://github.com/rkendel1/feltdb`);
219
283
  agents: options.agents,
220
284
  capabilities: options.capabilities,
221
285
  });
286
+ if (options.runtime === 'managed') {
287
+ console.log('\n☁️ Setting up your managed FeltDB account...\n');
288
+ const email = process.env.FELTDB_MANAGED_EMAIL || (shouldAutoYes ? '' : await prompt('Email: '));
289
+ const password = process.env.FELTDB_MANAGED_PASSWORD || (shouldAutoYes ? '' : await promptSecret('Password (8+ characters): '));
290
+ if (!email || password.length < 8) {
291
+ throw new Error('Managed setup needs an email and an 8+ character password. In non-interactive mode set FELTDB_MANAGED_EMAIL and FELTDB_MANAGED_PASSWORD.');
292
+ }
293
+ const managed = await configureManagedAccount({ projectDir, applicationName: projectName, namespace: projectName, email, password });
294
+ console.log(`✓ Managed application: ${managed.application.name}`);
295
+ console.log(`✓ Managed environment written to ${projectName}/.env.local`);
296
+ }
222
297
  console.log('\n✅ FeltDB application created successfully!\n');
223
- const projectDir = path.resolve(process.cwd(), projectName);
224
298
  const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
225
299
  if (shouldInstall) {
226
300
  console.log('📦 Installing application, Studio, and local AI dependencies...\n');