@feltdb/core 0.8.3 → 0.8.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 +4 -1
- package/dist/cli/provisioning-neutrality.js +79 -0
- package/dist/collection.d.ts +43 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +192 -22
- package/dist/create/create.js +25 -21
- package/dist/create/managed-account.js +11 -0
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +165 -0
- package/dist/create/server-source/Cargo.toml +9 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +3 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +44 -44
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +12 -24
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +7 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +13 -10
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +12 -9
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +78 -25
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +16 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +13 -5
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +41 -22
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +33 -15
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +100 -33
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +56 -20
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +116 -41
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +186 -55
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +64 -26
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +46 -15
- package/dist/create/server-source/crates/feltdb/src/admission.rs +8 -15
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +43 -13
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +15 -42
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +65 -19
- package/dist/create/server-source/crates/feltdb/src/application.rs +113 -30
- package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +475 -140
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +17 -15
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +266 -114
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +25 -7
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +10 -15
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +3 -11
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +14 -5
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +21 -25
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +14 -11
- package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +103 -22
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +18 -15
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +12 -8
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +1 -5
- package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +87 -23
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +3 -7
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +10 -9
- package/dist/create/server-source/crates/feltdb/src/equality_index.rs +595 -0
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +1 -6
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +35 -38
- package/dist/create/server-source/crates/feltdb/src/lib.rs +1050 -117
- package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +4 -1
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +1 -2
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +107 -30
- package/dist/create/server-source/crates/feltdb/src/observability.rs +19 -6
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +12 -11
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +9 -4
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +65 -18
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +193 -57
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +37 -27
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +38 -10
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +20 -14
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +394 -229
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +8 -6
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +11 -13
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +90 -72
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +3 -3
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +33 -11
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +6 -6
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +14 -56
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +29 -27
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +35 -17
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +8 -2
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +70 -17
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +23 -6
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +12 -3
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +41 -11
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +56 -25
- package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +701 -245
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +31 -13
- package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +126 -0
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +5 -5
- package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +48 -18
- package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +30 -11
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +4 -3
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +36 -10
- package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +516 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +305 -6
- package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +222 -0
- package/dist/create/server-source/crates/feltdb/src/state_facade.rs +82 -54
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +2 -2
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +1514 -537
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +672 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +5 -11
- package/dist/create/server-source/crates/feltdb/src/sync.rs +12 -0
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +24 -35
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +24 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +248 -59
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +17 -20
- package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +749 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +512 -4
- package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +442 -0
- package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +288 -0
- package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +392 -0
- package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +436 -220
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +1916 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +1878 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +1862 -0
- package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +234 -0
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +947 -0
- package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +5 -8
- package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +52 -44
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +33 -16
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +22 -7
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +41 -22
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +25 -8
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +9 -6
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +120 -69
- package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +21 -10
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +1032 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +868 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +221 -0
- package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +338 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +1039 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +739 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +427 -0
- package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +3 -3
- package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +25 -22
- package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +1799 -0
- package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +1316 -0
- package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +53 -61
- package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +156 -61
- package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +255 -0
- package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +249 -0
- package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +994 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +2 -1
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +1137 -29
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +800 -34
- package/dist/db.d.ts +33 -34
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +74 -20
- package/dist/deployment.d.ts +30 -0
- package/dist/deployment.d.ts.map +1 -0
- package/dist/deployment.js +130 -0
- package/dist/embedded-transaction.d.ts +22 -4
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +51 -5
- package/dist/feltdb.d.ts +14 -2
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.js +1 -1
- package/dist/http-client.d.ts +14 -0
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +23 -5
- package/dist/http-db.d.ts +119 -1
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +346 -31
- package/dist/index-core.d.ts +2 -0
- package/dist/index-core.d.ts.map +1 -1
- package/dist/index-core.js +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/indexeddb-db.d.ts.map +1 -1
- package/dist/indexeddb-db.js +35 -21
- package/dist/managed-recovery.d.ts +192 -0
- package/dist/managed-recovery.d.ts.map +1 -0
- package/dist/managed-recovery.js +242 -0
- package/dist/memory-db.js +1 -1
- package/dist/studio-app/assets/{feltdb_wasm-DB8cX151.js → feltdb_wasm-DaNwCLRX.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-DnsHNv6g.wasm +0 -0
- package/dist/studio-app/assets/index-j8IlhNqJ.js +29 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/transaction.d.ts +30 -0
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +41 -0
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workload.d.ts +2 -1
- package/dist/workload.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-ClhDHp0S.wasm +0 -0
- package/dist/studio-app/assets/index-B0k4UAlI.js +0 -29
package/dist/cli/commands.js
CHANGED
|
@@ -293,8 +293,11 @@ async function handleAi(args) {
|
|
|
293
293
|
if (stored) {
|
|
294
294
|
const client = proposalService();
|
|
295
295
|
const readiness = await client.status(stored.id);
|
|
296
|
+
// Coded like every other refusal this command can produce
|
|
297
|
+
// (SOURCE_SYNC_CONFLICT, REPOSITORY_AUTHORITY_REQUIRED), so a caller can
|
|
298
|
+
// tell a stale proposal from a failed apply without reading the prose.
|
|
296
299
|
if (readiness.blockers.length)
|
|
297
|
-
throw new Error(`Cannot apply Proposal ${stored.id}. Proposal is stale: ${readiness.blockers.join(', ')}. Proposal contract: ${stored.base_contract_hash}. Current contract: ${readiness.current_contract_hash}. Regenerate the Proposal before applying.`);
|
|
300
|
+
throw new Error(`STALE_PROPOSAL: Cannot apply Proposal ${stored.id}. Proposal is stale: ${readiness.blockers.join(', ')}. Proposal contract: ${stored.base_contract_hash}. Current contract: ${readiness.current_contract_hash}. Regenerate the Proposal before applying.`);
|
|
298
301
|
if (!stored.preview_id)
|
|
299
302
|
throw new Error(`Cannot apply Proposal ${stored.id}. A current preview is required.`);
|
|
300
303
|
await client.getPreview(stored.preview_id);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
function canonicalMode(input) {
|
|
5
|
+
const value = (input.mode || input.deploymentMode || input.runtime || (input.url || input.endpoint ? 'remote' : 'browser')).toLowerCase();
|
|
6
|
+
if (value === 'browser')
|
|
7
|
+
return 'browser';
|
|
8
|
+
if (value === 'local' || value === 'node')
|
|
9
|
+
return 'local';
|
|
10
|
+
if (value === 'remote' || value === 'self-hosted' || value === 'managed')
|
|
11
|
+
return 'remote';
|
|
12
|
+
throw new Error(`Unsupported FeltDB provisioning mode "${value}"`);
|
|
13
|
+
}
|
|
14
|
+
function sortRecord(record) {
|
|
15
|
+
if (!record)
|
|
16
|
+
return undefined;
|
|
17
|
+
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
18
|
+
}
|
|
19
|
+
export function canonicalizeProvisioningConfiguration(input) {
|
|
20
|
+
const mode = canonicalMode(input);
|
|
21
|
+
const credentialReference = input.credentialReference?.trim();
|
|
22
|
+
const hasToken = !!input.token;
|
|
23
|
+
return {
|
|
24
|
+
deployment: {
|
|
25
|
+
mode,
|
|
26
|
+
endpoint: input.endpoint || input.url || undefined,
|
|
27
|
+
localDataPath: input.localDataPath || input.path || undefined,
|
|
28
|
+
},
|
|
29
|
+
application: {
|
|
30
|
+
id: input.applicationId || undefined,
|
|
31
|
+
namespace: input.namespace || undefined,
|
|
32
|
+
environment: input.environment || undefined,
|
|
33
|
+
},
|
|
34
|
+
credentials: {
|
|
35
|
+
present: hasToken || !!credentialReference,
|
|
36
|
+
type: credentialReference ? 'reference' : hasToken ? 'token' : 'none',
|
|
37
|
+
reference: credentialReference || undefined,
|
|
38
|
+
},
|
|
39
|
+
capabilities: sortRecord(input.capabilities),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function provisioningConfigurationsEquivalent(left, right) {
|
|
43
|
+
return JSON.stringify(canonicalizeProvisioningConfiguration(left)) === JSON.stringify(canonicalizeProvisioningConfiguration(right));
|
|
44
|
+
}
|
|
45
|
+
export function provisioningConfigurationHash(input) {
|
|
46
|
+
const canonical = canonicalizeProvisioningConfiguration({ ...input, token: input.token ? '<present>' : undefined });
|
|
47
|
+
return crypto.createHash('sha256').update(JSON.stringify(canonical)).digest('hex');
|
|
48
|
+
}
|
|
49
|
+
export function writeDeploymentConfiguration(projectDir, input) {
|
|
50
|
+
const canonical = canonicalizeProvisioningConfiguration(input);
|
|
51
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
52
|
+
fs.mkdirSync(path.join(projectDir, '.feltdb'), { recursive: true });
|
|
53
|
+
fs.writeFileSync(path.join(projectDir, 'feltdb.config.json'), `${JSON.stringify({
|
|
54
|
+
mode: canonical.deployment.mode,
|
|
55
|
+
namespace: canonical.application.namespace,
|
|
56
|
+
environment: canonical.application.environment,
|
|
57
|
+
applicationId: canonical.application.id,
|
|
58
|
+
url: canonical.deployment.endpoint,
|
|
59
|
+
path: canonical.deployment.localDataPath,
|
|
60
|
+
capabilities: canonical.capabilities || {},
|
|
61
|
+
}, null, 2)}\n`);
|
|
62
|
+
const envLines = [
|
|
63
|
+
'# Generated by feltdb provisioning. Do not commit secrets.',
|
|
64
|
+
`FELTDB_MODE=${canonical.deployment.mode}`,
|
|
65
|
+
canonical.deployment.endpoint ? `FELTDB_URL=${canonical.deployment.endpoint}` : undefined,
|
|
66
|
+
canonical.deployment.localDataPath ? `FELTDB_PATH=${canonical.deployment.localDataPath}` : undefined,
|
|
67
|
+
canonical.application.id ? `FELTDB_APPLICATION_ID=${canonical.application.id}` : undefined,
|
|
68
|
+
canonical.application.namespace ? `FELTDB_NAMESPACE=${canonical.application.namespace}` : undefined,
|
|
69
|
+
canonical.application.environment ? `FELTDB_ENVIRONMENT=${canonical.application.environment}` : undefined,
|
|
70
|
+
canonical.credentials.type === 'reference' && canonical.credentials.reference ? `FELTDB_CREDENTIAL_REFERENCE=${canonical.credentials.reference}` : undefined,
|
|
71
|
+
'',
|
|
72
|
+
].filter((line) => line !== undefined);
|
|
73
|
+
fs.writeFileSync(path.join(projectDir, '.env.local'), envLines.join('\n'), { mode: 0o600 });
|
|
74
|
+
fs.writeFileSync(path.join(projectDir, '.feltdb', 'provisioning.json'), `${JSON.stringify({
|
|
75
|
+
...canonical,
|
|
76
|
+
configurationHash: provisioningConfigurationHash(input),
|
|
77
|
+
}, null, 2)}\n`, { mode: 0o600 });
|
|
78
|
+
return canonical;
|
|
79
|
+
}
|
package/dist/collection.d.ts
CHANGED
|
@@ -97,6 +97,8 @@ export declare class Collection<T> {
|
|
|
97
97
|
* Returns cached results (live-updated).
|
|
98
98
|
*/
|
|
99
99
|
all(): Promise<T[]>;
|
|
100
|
+
/** List records through the canonical application collection API. */
|
|
101
|
+
list(): Promise<T[]>;
|
|
100
102
|
/**
|
|
101
103
|
* Find records whose fields match the supplied query.
|
|
102
104
|
*
|
|
@@ -112,6 +114,8 @@ export declare class Collection<T> {
|
|
|
112
114
|
* `lastQueryPlan()` reports which path ran.
|
|
113
115
|
*/
|
|
114
116
|
find(query?: Partial<T>): Promise<T[]>;
|
|
117
|
+
/** Query records by field equality through the canonical application API. */
|
|
118
|
+
query(query?: Partial<T>): Promise<T[]>;
|
|
115
119
|
/**
|
|
116
120
|
* How the most recent `find` was answered.
|
|
117
121
|
*
|
|
@@ -171,6 +175,8 @@ export declare class Collection<T> {
|
|
|
171
175
|
* Automatically initializes __version to 1 for durable atomic transitions.
|
|
172
176
|
*/
|
|
173
177
|
insert(data: Partial<T>, id?: string | number): Promise<string>;
|
|
178
|
+
/** Put a record through the canonical application collection API. */
|
|
179
|
+
put(data: Partial<T>, id?: string | number): Promise<string>;
|
|
174
180
|
/**
|
|
175
181
|
* Update a record in this collection.
|
|
176
182
|
*/
|
|
@@ -182,6 +188,10 @@ export declare class Collection<T> {
|
|
|
182
188
|
* The version check and update happen atomically at the backend boundary,
|
|
183
189
|
* ensuring exactly one writer succeeds when multiple writers race.
|
|
184
190
|
*
|
|
191
|
+
* A runtime without a dedicated compare-and-set carries the same fence
|
|
192
|
+
* through its atomic transaction, so this means the same thing on the file
|
|
193
|
+
* runtime as it does against a managed authority.
|
|
194
|
+
*
|
|
185
195
|
* @param id Record identifier
|
|
186
196
|
* @param expectedVersion The version you observed when you read this record
|
|
187
197
|
* @param updates Fields to update (does not include __version; version is auto-incremented)
|
|
@@ -217,9 +227,41 @@ export declare class Collection<T> {
|
|
|
217
227
|
* Check if a record exists.
|
|
218
228
|
*/
|
|
219
229
|
exists(id: string | number): Promise<boolean>;
|
|
230
|
+
/**
|
|
231
|
+
* Split a storage key back into the collection and record it addresses.
|
|
232
|
+
*
|
|
233
|
+
* The transaction contract is expressed in collections and ids rather than
|
|
234
|
+
* in storage keys, so a helper that holds a key has to name the record the
|
|
235
|
+
* same way the authority does.
|
|
236
|
+
*/
|
|
237
|
+
private addressOf;
|
|
238
|
+
/**
|
|
239
|
+
* The runtime's atomic transaction, used as the conditional-create surface.
|
|
240
|
+
*
|
|
241
|
+
* Returns the same shape a runtime's own `putIfAbsent` returns, so the caller
|
|
242
|
+
* above cannot tell which route it took. A refusal that is a lost race is
|
|
243
|
+
* resolved by reading the record the winner wrote; anything else -- a
|
|
244
|
+
* missing capability, authentication, schema, transport, a server error --
|
|
245
|
+
* is raised.
|
|
246
|
+
*/
|
|
247
|
+
private putIfAbsentThroughTransaction;
|
|
248
|
+
/**
|
|
249
|
+
* The runtime's atomic transaction, used as the version-fenced update surface.
|
|
250
|
+
*
|
|
251
|
+
* Returns the same shape a runtime's own `cas` returns. The authority owns
|
|
252
|
+
* the next version, so the proposed value carries none.
|
|
253
|
+
*/
|
|
254
|
+
private updateIfVersionThroughTransaction;
|
|
220
255
|
/**
|
|
221
256
|
* Atomically insert a record only if the key does not already exist.
|
|
222
|
-
*
|
|
257
|
+
*
|
|
258
|
+
* Process-safe on the file runtime and across clients against an authority:
|
|
259
|
+
* the condition is evaluated by the runtime inside its own commit boundary,
|
|
260
|
+
* never by this method. A runtime without a dedicated conditional create
|
|
261
|
+
* uses its atomic transaction, so this means the same thing everywhere.
|
|
262
|
+
*
|
|
263
|
+
* The created record starts at `__version: 1`, so it can be fenced by
|
|
264
|
+
* `updateIfVersion` immediately.
|
|
223
265
|
*
|
|
224
266
|
* @returns {inserted: true, value: newRecord} if this call created the record
|
|
225
267
|
* @returns {inserted: false, value: existingRecord} if record already existed
|
package/dist/collection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,QAAQ,EACd,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
|
package/dist/collection.js
CHANGED
|
@@ -9,8 +9,23 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { describeFreshness, isCacheCurrent, readRevision, } from './freshness.js';
|
|
11
11
|
import { getReactiveDependencyGraph } from './reactive-graph.js';
|
|
12
|
+
import { conditionalRefusal } from './transaction.js';
|
|
12
13
|
import { IndexBackend } from './index-backend.js';
|
|
13
14
|
import { IndexStore } from './index-store.js';
|
|
15
|
+
/**
|
|
16
|
+
* A durable identity for one conditional write attempt.
|
|
17
|
+
*
|
|
18
|
+
* The authority deduplicates by transaction id, so a redelivered request
|
|
19
|
+
* cannot apply the same conditional write twice. Each *attempt* gets its own
|
|
20
|
+
* id: a retry after a conflict is a new decision against fresh state and has
|
|
21
|
+
* to be evaluated again rather than answered from the first attempt.
|
|
22
|
+
*/
|
|
23
|
+
function conditionalTransactionId(purpose) {
|
|
24
|
+
const unique = typeof globalThis.crypto?.randomUUID === 'function'
|
|
25
|
+
? globalThis.crypto.randomUUID()
|
|
26
|
+
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
27
|
+
return `${purpose}-${unique}`;
|
|
28
|
+
}
|
|
14
29
|
/**
|
|
15
30
|
* A live collection that automatically updates when underlying data changes.
|
|
16
31
|
* Represents application state, not a one-time query result.
|
|
@@ -96,6 +111,10 @@ export class Collection {
|
|
|
96
111
|
await this.refresh();
|
|
97
112
|
return [...this.cache];
|
|
98
113
|
}
|
|
114
|
+
/** List records through the canonical application collection API. */
|
|
115
|
+
async list() {
|
|
116
|
+
return this.all();
|
|
117
|
+
}
|
|
99
118
|
/**
|
|
100
119
|
* Find records whose fields match the supplied query.
|
|
101
120
|
*
|
|
@@ -142,6 +161,10 @@ export class Collection {
|
|
|
142
161
|
};
|
|
143
162
|
return found;
|
|
144
163
|
}
|
|
164
|
+
/** Query records by field equality through the canonical application API. */
|
|
165
|
+
async query(query = {}) {
|
|
166
|
+
return this.find(query);
|
|
167
|
+
}
|
|
145
168
|
/**
|
|
146
169
|
* How the most recent `find` was answered.
|
|
147
170
|
*
|
|
@@ -325,6 +348,10 @@ export class Collection {
|
|
|
325
348
|
await graph.emitChange(this.name, change);
|
|
326
349
|
return String(recordId);
|
|
327
350
|
}
|
|
351
|
+
/** Put a record through the canonical application collection API. */
|
|
352
|
+
async put(data, id) {
|
|
353
|
+
return this.insert(data, id);
|
|
354
|
+
}
|
|
328
355
|
/**
|
|
329
356
|
* Update a record in this collection.
|
|
330
357
|
*/
|
|
@@ -361,6 +388,10 @@ export class Collection {
|
|
|
361
388
|
* The version check and update happen atomically at the backend boundary,
|
|
362
389
|
* ensuring exactly one writer succeeds when multiple writers race.
|
|
363
390
|
*
|
|
391
|
+
* A runtime without a dedicated compare-and-set carries the same fence
|
|
392
|
+
* through its atomic transaction, so this means the same thing on the file
|
|
393
|
+
* runtime as it does against a managed authority.
|
|
394
|
+
*
|
|
364
395
|
* @param id Record identifier
|
|
365
396
|
* @param expectedVersion The version you observed when you read this record
|
|
366
397
|
* @param updates Fields to update (does not include __version; version is auto-incremented)
|
|
@@ -394,21 +425,32 @@ export class Collection {
|
|
|
394
425
|
delete cleanUpdates.__version;
|
|
395
426
|
const updated = { ...current, ...cleanUpdates };
|
|
396
427
|
const key = `${this.name}:${id}`;
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
428
|
+
// Delegate the authoritative version and epoch check to the runtime, which
|
|
429
|
+
// evaluates both predicates inside its own commit boundary.
|
|
430
|
+
//
|
|
431
|
+
// A runtime that offers no single-record CAS but does offer an atomic
|
|
432
|
+
// transaction is not a runtime without compare-and-set: the transaction
|
|
433
|
+
// carries the same fence to the same authority. Routing through it is what
|
|
434
|
+
// keeps this helper's meaning the same everywhere, instead of making it a
|
|
435
|
+
// file-runtime privilege. Nothing here emulates CAS with separate reads
|
|
436
|
+
// and writes, because that would reintroduce the race the fence exists to
|
|
437
|
+
// remove.
|
|
438
|
+
const casResult = this.db.cas
|
|
439
|
+
? await this.db.cas({
|
|
440
|
+
key,
|
|
441
|
+
expectedVersion,
|
|
442
|
+
expectedEpoch,
|
|
443
|
+
expectedLeaseId,
|
|
444
|
+
rejectIfDiverged,
|
|
445
|
+
value: JSON.stringify(updated),
|
|
446
|
+
})
|
|
447
|
+
: await this.updateIfVersionThroughTransaction(key, {
|
|
448
|
+
expectedVersion,
|
|
449
|
+
expectedEpoch,
|
|
450
|
+
expectedLeaseId,
|
|
451
|
+
rejectIfDiverged,
|
|
452
|
+
value: updated,
|
|
453
|
+
});
|
|
412
454
|
if (!casResult.updated) {
|
|
413
455
|
// Another writer won the race
|
|
414
456
|
return {
|
|
@@ -478,24 +520,152 @@ export class Collection {
|
|
|
478
520
|
const item = await this.get(id);
|
|
479
521
|
return item !== null;
|
|
480
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Split a storage key back into the collection and record it addresses.
|
|
525
|
+
*
|
|
526
|
+
* The transaction contract is expressed in collections and ids rather than
|
|
527
|
+
* in storage keys, so a helper that holds a key has to name the record the
|
|
528
|
+
* same way the authority does.
|
|
529
|
+
*/
|
|
530
|
+
addressOf(key) {
|
|
531
|
+
const separator = key.indexOf(':');
|
|
532
|
+
return { collection: key.slice(0, separator), id: key.slice(separator + 1) };
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* The runtime's atomic transaction, used as the conditional-create surface.
|
|
536
|
+
*
|
|
537
|
+
* Returns the same shape a runtime's own `putIfAbsent` returns, so the caller
|
|
538
|
+
* above cannot tell which route it took. A refusal that is a lost race is
|
|
539
|
+
* resolved by reading the record the winner wrote; anything else -- a
|
|
540
|
+
* missing capability, authentication, schema, transport, a server error --
|
|
541
|
+
* is raised.
|
|
542
|
+
*/
|
|
543
|
+
async putIfAbsentThroughTransaction(key, value) {
|
|
544
|
+
const commit = this.db.commit_transaction;
|
|
545
|
+
if (!commit) {
|
|
546
|
+
throw new Error('putIfAbsent is not supported by this FeltDB runtime. It provides neither durable '
|
|
547
|
+
+ 'atomic conditional creation nor an atomic transaction to carry the condition, and '
|
|
548
|
+
+ 'FeltDB will not emulate one with a read followed by a write.');
|
|
549
|
+
}
|
|
550
|
+
const { collection, id } = this.addressOf(key);
|
|
551
|
+
try {
|
|
552
|
+
await commit.call(this.db, {
|
|
553
|
+
transactionId: conditionalTransactionId('put-if-absent'),
|
|
554
|
+
operations: [{ collection, id, value, requireAbsent: true }],
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
if (!conditionalRefusal(error).conflict)
|
|
559
|
+
throw error;
|
|
560
|
+
const existing = await this.db.get(key);
|
|
561
|
+
if (!existing.success) {
|
|
562
|
+
throw new Error(existing.error || `could not read the record that won ${key}`);
|
|
563
|
+
}
|
|
564
|
+
if (!existing.data) {
|
|
565
|
+
throw new Error(`${key} was created and then removed while resolving a conditional create`);
|
|
566
|
+
}
|
|
567
|
+
return { inserted: false, value: existing.data };
|
|
568
|
+
}
|
|
569
|
+
// The authority assigns version 1 to a conditional create inside the
|
|
570
|
+
// commit boundary, which is the version this call already supplied.
|
|
571
|
+
return { inserted: true, value: JSON.stringify({ ...value, __version: 1 }) };
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* The runtime's atomic transaction, used as the version-fenced update surface.
|
|
575
|
+
*
|
|
576
|
+
* Returns the same shape a runtime's own `cas` returns. The authority owns
|
|
577
|
+
* the next version, so the proposed value carries none.
|
|
578
|
+
*/
|
|
579
|
+
async updateIfVersionThroughTransaction(key, fence) {
|
|
580
|
+
const commit = this.db.commit_transaction;
|
|
581
|
+
if (!commit) {
|
|
582
|
+
throw new Error('updateIfVersion is not supported by this FeltDB runtime. It provides neither atomic '
|
|
583
|
+
+ 'version-checked update nor an atomic transaction to carry the condition, and FeltDB '
|
|
584
|
+
+ 'will not emulate one with a read followed by a write.');
|
|
585
|
+
}
|
|
586
|
+
if (fence.rejectIfDiverged) {
|
|
587
|
+
throw new Error('rejectIfDiverged is a replication predicate this runtime\'s transaction contract cannot '
|
|
588
|
+
+ 'express, so it is refused rather than silently dropped.');
|
|
589
|
+
}
|
|
590
|
+
const { collection, id } = this.addressOf(key);
|
|
591
|
+
const proposed = { ...fence.value };
|
|
592
|
+
delete proposed.__version;
|
|
593
|
+
try {
|
|
594
|
+
await commit.call(this.db, {
|
|
595
|
+
transactionId: conditionalTransactionId('update-if-version'),
|
|
596
|
+
operations: [{
|
|
597
|
+
collection, id, value: proposed,
|
|
598
|
+
expectedVersion: fence.expectedVersion,
|
|
599
|
+
expectedEpoch: fence.expectedEpoch,
|
|
600
|
+
expectedLeaseId: fence.expectedLeaseId,
|
|
601
|
+
}],
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
const refusal = conditionalRefusal(error);
|
|
606
|
+
if (!refusal.conflict)
|
|
607
|
+
throw error;
|
|
608
|
+
const failure = refusal.failure;
|
|
609
|
+
const conflictCode = failure?.predicate === 'version' ? 'VERSION_CONFLICT'
|
|
610
|
+
: failure?.predicate === 'epoch' ? 'AUTHORITY_CONFLICT'
|
|
611
|
+
: failure?.predicate === 'lease' ? 'LEASE_CONFLICT'
|
|
612
|
+
: failure?.predicate === 'missing' ? 'RECORD_MISSING'
|
|
613
|
+
: undefined;
|
|
614
|
+
if (failure?.predicate === 'version' && typeof failure.actual === 'number') {
|
|
615
|
+
return { updated: false, currentVersion: failure.actual, conflictCode };
|
|
616
|
+
}
|
|
617
|
+
// The refusal did not name a version, so the authority is asked for the
|
|
618
|
+
// one it holds -- which is what the caller would have to do next anyway.
|
|
619
|
+
const current = await this.db.get(key);
|
|
620
|
+
const record = current.success && current.data
|
|
621
|
+
? JSON.parse(current.data)
|
|
622
|
+
: undefined;
|
|
623
|
+
return {
|
|
624
|
+
updated: false,
|
|
625
|
+
currentVersion: typeof record?.__version === 'number' ? record.__version : 0,
|
|
626
|
+
currentEpoch: record?.authority?.epoch,
|
|
627
|
+
conflictCode,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
const nextVersion = fence.expectedVersion + 1;
|
|
631
|
+
return {
|
|
632
|
+
updated: true,
|
|
633
|
+
currentVersion: nextVersion,
|
|
634
|
+
currentEpoch: fence.expectedEpoch,
|
|
635
|
+
item: { ...proposed, id, __version: nextVersion },
|
|
636
|
+
};
|
|
637
|
+
}
|
|
481
638
|
/**
|
|
482
639
|
* Atomically insert a record only if the key does not already exist.
|
|
483
|
-
*
|
|
640
|
+
*
|
|
641
|
+
* Process-safe on the file runtime and across clients against an authority:
|
|
642
|
+
* the condition is evaluated by the runtime inside its own commit boundary,
|
|
643
|
+
* never by this method. A runtime without a dedicated conditional create
|
|
644
|
+
* uses its atomic transaction, so this means the same thing everywhere.
|
|
645
|
+
*
|
|
646
|
+
* The created record starts at `__version: 1`, so it can be fenced by
|
|
647
|
+
* `updateIfVersion` immediately.
|
|
484
648
|
*
|
|
485
649
|
* @returns {inserted: true, value: newRecord} if this call created the record
|
|
486
650
|
* @returns {inserted: false, value: existingRecord} if record already existed
|
|
487
651
|
*/
|
|
488
652
|
async putIfAbsent(id, data) {
|
|
489
653
|
const key = `${this.name}:${id}`;
|
|
654
|
+
// Version 1 is supplied here for the same reason `insert` supplies it: a
|
|
655
|
+
// record with no version could never be conditionally updated, which would
|
|
656
|
+
// make atomic creation useless for exactly the lifecycles that need it.
|
|
490
657
|
const stored = typeof data === 'object' && data !== null
|
|
491
|
-
? { ...data, id }
|
|
658
|
+
? { ...data, id, __version: 1 }
|
|
492
659
|
: data;
|
|
493
660
|
const stringValue = JSON.stringify(stored);
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
661
|
+
// A runtime that cannot create conditionally by itself but can commit an
|
|
662
|
+
// atomic transaction has the capability under a different name, and the
|
|
663
|
+
// transaction carries the same `requireAbsent` predicate to the same
|
|
664
|
+
// authority. See `updateIfVersion` for why this is a route and not an
|
|
665
|
+
// emulation.
|
|
666
|
+
const result = this.db.putIfAbsent
|
|
667
|
+
? await this.db.putIfAbsent(key, stringValue)
|
|
668
|
+
: await this.putIfAbsentThroughTransaction(key, stored);
|
|
499
669
|
// Emit change through reactive dependency graph if this was a new insert
|
|
500
670
|
if (result.inserted) {
|
|
501
671
|
const change = {
|
package/dist/create/create.js
CHANGED
|
@@ -79,8 +79,10 @@ export async function createProject(options) {
|
|
|
79
79
|
}
|
|
80
80
|
fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify(packageJson, null, 2));
|
|
81
81
|
// Create feltdb.config.json
|
|
82
|
+
const deploymentMode = runtime === 'browser' ? 'browser' : runtime === 'node' ? 'local' : 'remote';
|
|
82
83
|
const feltdbConfig = {
|
|
83
84
|
namespace: applicationName,
|
|
85
|
+
mode: deploymentMode,
|
|
84
86
|
runtime,
|
|
85
87
|
storage: runtime === 'browser' ? 'indexeddb' : runtime === 'managed' ? 'managed' : 'durable',
|
|
86
88
|
distributed,
|
|
@@ -195,13 +197,6 @@ ${hasAgents ? ` agent WorkspaceAssistant {
|
|
|
195
197
|
}
|
|
196
198
|
fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
|
|
197
199
|
// Create main application files
|
|
198
|
-
const runtimeOptions = runtime === 'browser'
|
|
199
|
-
? "{ namespace: import.meta.env.VITE_FELTDB_NAMESPACE || '" + applicationName + "', browser: true }"
|
|
200
|
-
: runtime === 'managed'
|
|
201
|
-
? "{ namespace: '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_MANAGED_URL || '', applicationId: import.meta.env.VITE_FELTDB_MANAGED_APPLICATION_ID, environment: 'production' } }"
|
|
202
|
-
: runtime === 'self-hosted'
|
|
203
|
-
? "{ namespace: import.meta.env.VITE_FELTDB_NAMESPACE || '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_URL || 'http://localhost:7700', token: import.meta.env.VITE_FELTDB_API_KEY || '' } }"
|
|
204
|
-
: "{ namespace: import.meta.env.VITE_FELTDB_NAMESPACE || '" + applicationName + "', memory: true }";
|
|
205
200
|
const feltdbTs = `import { configureDevelopmentRuntimeBridge, createFeltDB } from '@feltdb/core';
|
|
206
201
|
|
|
207
202
|
configureDevelopmentRuntimeBridge({
|
|
@@ -214,7 +209,7 @@ configureDevelopmentRuntimeBridge({
|
|
|
214
209
|
applicationUrl: import.meta.env.VITE_FELTDB_APPLICATION_URL,
|
|
215
210
|
});
|
|
216
211
|
|
|
217
|
-
export const db = createFeltDB(
|
|
212
|
+
export const db = createFeltDB();
|
|
218
213
|
|
|
219
214
|
// Collections
|
|
220
215
|
export const projects = db.collection('Project');
|
|
@@ -1198,7 +1193,7 @@ root.render(
|
|
|
1198
1193
|
}
|
|
1199
1194
|
fs.writeFileSync(path.join(projectDir, 'feltdb.flow'), fs.readFileSync(path.join(canonicalTemplate, 'feltdb.flow'), 'utf8').replace('app FeltDBStarter', `app ${appName}`));
|
|
1200
1195
|
const databasePath = path.join(srcDir, 'feltdb.ts');
|
|
1201
|
-
fs.writeFileSync(databasePath, fs.readFileSync(databasePath, 'utf8').replace(/export const managedRuntime[\s\S]*?\n\} : \{ namespace: import\.meta\.env\.VITE_FELTDB_NAMESPACE \|\| 'feltdb-starter', browser: true \}\);/, `export const managedRuntime = ${runtime === 'managed'};\nexport const db = createFeltDB(
|
|
1196
|
+
fs.writeFileSync(databasePath, fs.readFileSync(databasePath, 'utf8').replace(/export const managedRuntime[\s\S]*?\n\} : \{ namespace: import\.meta\.env\.VITE_FELTDB_NAMESPACE \|\| 'feltdb-starter', browser: true \}\);/, `export const managedRuntime = ${runtime === 'managed'};\nexport const db = createFeltDB();`));
|
|
1202
1197
|
}
|
|
1203
1198
|
}
|
|
1204
1199
|
else {
|
|
@@ -1234,18 +1229,27 @@ main().catch(console.error);
|
|
|
1234
1229
|
const envExample = `# FeltDB Configuration
|
|
1235
1230
|
# Copy this file to .env.local and update the values
|
|
1236
1231
|
|
|
1237
|
-
#
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
#
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
#
|
|
1247
|
-
|
|
1248
|
-
|
|
1232
|
+
# Canonical application-plane deployment configuration consumed by createFeltDB()
|
|
1233
|
+
# Supported public modes: browser, local, remote
|
|
1234
|
+
VITE_FELTDB_MODE=${deploymentMode}
|
|
1235
|
+
VITE_FELTDB_NAMESPACE=${applicationName}
|
|
1236
|
+
VITE_FELTDB_ENVIRONMENT=development
|
|
1237
|
+
|
|
1238
|
+
# FeltDB Server URL (self-hosted or managed runtime)
|
|
1239
|
+
VITE_FELTDB_URL=http://localhost:7700
|
|
1240
|
+
|
|
1241
|
+
# Optional credential for authenticated remote browser access
|
|
1242
|
+
VITE_FELTDB_TOKEN=
|
|
1243
|
+
# Legacy alias accepted by older tooling
|
|
1244
|
+
VITE_FELTDB_API_KEY=
|
|
1245
|
+
|
|
1246
|
+
# Managed example: https://runtime.your-app.feltdb.com
|
|
1247
|
+
VITE_FELTDB_MANAGED_URL=
|
|
1248
|
+
VITE_FELTDB_MANAGED_APPLICATION_ID=
|
|
1249
|
+
VITE_FELTDB_APPLICATION_ID=
|
|
1250
|
+
# Server/control-plane only. Never rename with a VITE_ prefix.
|
|
1251
|
+
FELTDB_MANAGED_CONTROL_API_KEY=
|
|
1252
|
+
FELTDB_TOKEN=
|
|
1249
1253
|
|
|
1250
1254
|
# Override the default self-hosted container image
|
|
1251
1255
|
# By default the server image is built locally from the bundled source.
|
|
@@ -100,6 +100,17 @@ export async function configureManagedAccount(options) {
|
|
|
100
100
|
}
|
|
101
101
|
const environment = [
|
|
102
102
|
'# Generated by create-feltdb managed setup. Do not commit this file.',
|
|
103
|
+
'FELTDB_MODE=remote',
|
|
104
|
+
`FELTDB_URL=${apiUrl}`,
|
|
105
|
+
`FELTDB_APPLICATION_ID=${application.id}`,
|
|
106
|
+
`FELTDB_NAMESPACE=${application.namespace || options.namespace}`,
|
|
107
|
+
'FELTDB_ENVIRONMENT=production',
|
|
108
|
+
'# Browser-safe deployment identity aliases.',
|
|
109
|
+
'VITE_FELTDB_MODE=remote',
|
|
110
|
+
`VITE_FELTDB_URL=${apiUrl}`,
|
|
111
|
+
`VITE_FELTDB_APPLICATION_ID=${application.id}`,
|
|
112
|
+
`VITE_FELTDB_NAMESPACE=${application.namespace || options.namespace}`,
|
|
113
|
+
'VITE_FELTDB_ENVIRONMENT=production',
|
|
103
114
|
`VITE_FELTDB_MANAGED_URL=${apiUrl}`,
|
|
104
115
|
`VITE_FELTDB_MANAGED_APPLICATION_ID=${application.id}`,
|
|
105
116
|
'# Control-plane credential. Never prefix this with VITE_ or expose it to browser code.',
|
|
@@ -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.8.
|
|
3
|
+
export const FELTDB_PACKAGE_VERSION = '0.8.5';
|
|
4
4
|
export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;
|