@feltdb/core 0.12.1 → 0.13.0
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/build-identity.json +13 -4
- package/dist/cli/commands.js +96 -45
- package/dist/cli/workspace-integration.js +8 -0
- package/dist/collection.d.ts +41 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +116 -47
- package/dist/composition.d.ts.map +1 -1
- package/dist/composition.js +11 -1
- package/dist/create/build-identity.json +13 -4
- package/dist/create/cli.js +49 -40
- package/dist/create/create.js +32 -19
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +18 -3
- package/dist/create/server-source/Cargo.toml +4 -2
- package/dist/create/server-source/crates/feltdb/Cargo.toml +20 -0
- package/dist/create/server-source/crates/feltdb/LICENSE +21 -0
- package/dist/create/server-source/crates/feltdb/README.md +75 -0
- package/dist/create/server-source/crates/feltdb/src/agent_state.rs +320 -0
- package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +1545 -0
- package/dist/create/server-source/crates/feltdb/src/authority.rs +145 -15
- package/dist/create/server-source/crates/feltdb/src/lib.rs +227 -23
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +1 -5
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +204 -1
- package/dist/create/server-source/crates/feltdb/src/workload.rs +685 -53
- package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +1188 -0
- package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +2884 -0
- package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +534 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +23340 -0
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +18 -12
- package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +17 -5
- package/dist/create/server-source/crates/feltdb-client/Cargo.toml +29 -0
- package/dist/create/server-source/crates/feltdb-client/LICENSE +21 -0
- package/dist/create/server-source/crates/feltdb-client/README.md +70 -0
- package/dist/create/server-source/crates/feltdb-client/src/application.rs +527 -0
- package/dist/create/server-source/crates/feltdb-client/src/authority.rs +679 -0
- package/dist/create/server-source/crates/feltdb-client/src/control.rs +292 -0
- package/dist/create/server-source/crates/feltdb-client/src/error.rs +227 -0
- package/dist/create/server-source/crates/feltdb-client/src/events.rs +146 -0
- package/dist/create/server-source/crates/feltdb-client/src/lib.rs +466 -0
- package/dist/create/server-source/crates/feltdb-client/tests/fixtures/README.md +4 -0
- package/dist/create/server-source/crates/feltdb-client/tests/fixtures/private-ca.pem +20 -0
- package/dist/create/server-source/crates/feltdb-client/tests/fixtures/server-key.pem +28 -0
- package/dist/create/server-source/crates/feltdb-client/tests/fixtures/server.pem +20 -0
- package/dist/create/server-source/crates/feltdb-client/tests/private_ca.rs +95 -0
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +20 -1
- package/dist/create/server-source/crates/feltdb-server/LICENSE +21 -0
- package/dist/create/server-source/crates/feltdb-server/README.md +12 -1
- package/dist/create/server-source/crates/feltdb-server/src/agent_execution.rs +511 -0
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +28 -0
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +6 -2
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +21 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +845 -138
- package/dist/create/server-source/crates/feltdb-server/tests/rust_client.rs +683 -0
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +3 -1
- package/dist/create/setup-summary.js +40 -0
- package/dist/create/template/default-project/README.md +12 -7
- package/dist/create/template/default-project/src/pages/Dashboard.tsx +1 -1
- package/dist/create/template/dot-feltdb-README.md +3 -3
- package/dist/db.d.ts +27 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +60 -21
- package/dist/development-runtime-bridge.d.ts +20 -0
- package/dist/development-runtime-bridge.d.ts.map +1 -1
- package/dist/development-runtime-bridge.js +74 -4
- package/dist/embedded-transaction.d.ts +75 -8
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +124 -18
- package/dist/error-codes.d.ts +2 -0
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +2 -0
- package/dist/feltdb.d.ts +1 -11
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts +6 -0
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +43 -11
- package/dist/http-db.d.ts +13 -0
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +33 -4
- package/dist/indexeddb-db.d.ts.map +1 -1
- package/dist/indexeddb-db.js +65 -41
- package/dist/memory-db.d.ts.map +1 -1
- package/dist/memory-db.js +29 -8
- package/dist/studio/{KeyManagementPanel-DZuSeWBK.js → KeyManagementPanel-skUNnKRw.js} +36 -28
- package/dist/studio/app.d.ts +8 -1
- package/dist/studio/app.d.ts.map +1 -1
- package/dist/studio/components/KeyManagementPanel.d.ts.map +1 -1
- package/dist/studio/components/KeyManagementPanel.js +1 -1
- package/dist/studio/components/RouteErrorBoundary.d.ts +5 -0
- package/dist/studio/components/RouteErrorBoundary.d.ts.map +1 -0
- package/dist/studio/components/index.js +2 -2
- package/dist/studio/{components-Bivm2lX6.js → components-BSGmHfif.js} +6 -6
- package/dist/studio/control-plane/Agents.d.ts.map +1 -1
- package/dist/studio/control-plane/Data.d.ts.map +1 -1
- package/dist/studio/control-plane/Instance.d.ts +4 -1
- package/dist/studio/control-plane/Instance.d.ts.map +1 -1
- package/dist/studio/control-plane/Operations.d.ts +4 -1
- package/dist/studio/control-plane/Operations.d.ts.map +1 -1
- package/dist/studio/control-plane/Overview.d.ts.map +1 -1
- package/dist/studio/control-plane/Schema.d.ts.map +1 -1
- package/dist/studio/control-plane/context.d.ts +4 -1
- package/dist/studio/control-plane/context.d.ts.map +1 -1
- package/dist/studio/control-plane/index.js +2 -2
- package/dist/studio/control-plane/primitives.d.ts +24 -0
- package/dist/studio/control-plane/primitives.d.ts.map +1 -1
- package/dist/studio/{control-plane-CLm69XwX.js → control-plane-CaBA7Rgv.js} +1374 -1298
- package/dist/studio/index.d.ts +1 -0
- package/dist/studio/index.d.ts.map +1 -1
- package/dist/studio/index.js +415 -237
- package/dist/studio/navigation.d.ts +57 -0
- package/dist/studio/navigation.d.ts.map +1 -0
- package/dist/studio/studio.css +1 -1
- package/dist/studio/utils/control-plane-api.d.ts +2 -0
- package/dist/studio/utils/control-plane-api.d.ts.map +1 -1
- package/dist/studio/utils/control-plane-api.js +9 -4
- package/dist/studio/utils/service-api.d.ts.map +1 -1
- package/dist/studio/utils/service-api.js +5 -4
- package/dist/studio-app/assets/{feltdb_wasm-DACcD-zs.js → feltdb_wasm-3hDngl1h.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-CFQT4gCy.wasm +0 -0
- package/dist/studio-app/assets/index-Dql1hS5m.js +30 -0
- package/dist/studio-app/assets/{index-CiC8VY-a.css → index-vi21oEQa.css} +1 -1
- package/dist/studio-app/index.html +2 -2
- package/dist/telemetry.d.ts +1 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +4 -3
- package/dist/transaction-identity.d.ts +62 -0
- package/dist/transaction-identity.d.ts.map +1 -0
- package/dist/transaction-identity.js +138 -0
- package/dist/transaction.d.ts +50 -5
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +28 -0
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/wasm/package.json +1 -1
- package/dist/workspace/local-development-authority.d.ts +19 -0
- package/dist/workspace/local-development-authority.d.ts.map +1 -1
- package/dist/workspace/local-development-authority.js +335 -15
- package/package.json +1 -1
- package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
- package/dist/studio-app/assets/feltdb_wasm_bg-BejThWID.wasm +0 -0
- package/dist/studio-app/assets/index-CoZaxnlm.js +0 -30
package/dist/transaction.d.ts
CHANGED
|
@@ -166,14 +166,37 @@ export interface AtomicTransactionResult {
|
|
|
166
166
|
transactionId: string;
|
|
167
167
|
/** True when this id was already committed; nothing was applied again. */
|
|
168
168
|
duplicate: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* How a duplicate was recognized (present only when `duplicate` is true on an
|
|
171
|
+
* embedded runtime):
|
|
172
|
+
* - `verified`: the id and the digest of this request both match what was
|
|
173
|
+
* committed, so this request *is* the original one;
|
|
174
|
+
* - `legacy-unverified`: the id was committed by a build that did not record
|
|
175
|
+
* a digest, so only the id could be checked. Treat the outcome as
|
|
176
|
+
* uncertain if the request might differ from the original.
|
|
177
|
+
*/
|
|
178
|
+
binding?: 'verified' | 'legacy-unverified';
|
|
179
|
+
/**
|
|
180
|
+
* Whether the fields below describe the original commit. `unavailable` means
|
|
181
|
+
* the durable record no longer holds the original positions (an id committed
|
|
182
|
+
* by a build that did not record them, since folded into a snapshot); they
|
|
183
|
+
* are then absent rather than guessed. Absent on a first commit, where they
|
|
184
|
+
* are always known.
|
|
185
|
+
*/
|
|
186
|
+
outcome?: 'original' | 'unavailable';
|
|
169
187
|
operations: number;
|
|
170
|
-
|
|
171
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Position of the *original* commit: the store's sequence before and after
|
|
190
|
+
* it, not the store's current sequence. Absent when `outcome` is
|
|
191
|
+
* `unavailable`.
|
|
192
|
+
*/
|
|
193
|
+
stateBefore?: number;
|
|
194
|
+
stateAfter?: number;
|
|
172
195
|
/** Authority revision before and after this atomic commit. */
|
|
173
|
-
baseRevision
|
|
174
|
-
commitRevision
|
|
196
|
+
baseRevision?: number;
|
|
197
|
+
commitRevision?: number;
|
|
175
198
|
/** Stable revision and operation identities; never aliases of transactionId. */
|
|
176
|
-
revisionId
|
|
199
|
+
revisionId?: string;
|
|
177
200
|
operationIds: string[];
|
|
178
201
|
}
|
|
179
202
|
/** Staging surface for one collection inside a transaction. */
|
|
@@ -234,6 +257,28 @@ export declare class AtomicTransactionError extends Error {
|
|
|
234
257
|
readonly status?: number;
|
|
235
258
|
constructor(message: string, transactionId: string, code?: string, status?: number, cause?: unknown);
|
|
236
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* The transaction id was already committed with a different payload.
|
|
262
|
+
*
|
|
263
|
+
* Nothing was applied and the recorded transaction is unchanged. Retrying with
|
|
264
|
+
* the same id cannot succeed: choose a new id for a new transaction, or resend
|
|
265
|
+
* the original payload to confirm the original.
|
|
266
|
+
*/
|
|
267
|
+
export declare class TransactionIdConflictError extends AtomicTransactionError {
|
|
268
|
+
readonly code: "IDEMPOTENCY_CONFLICT";
|
|
269
|
+
constructor(transactionId: string, message?: string);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* The transaction id exists, but its recorded identity cannot be checked by
|
|
273
|
+
* this build (a digest of a newer construction, or a malformed one).
|
|
274
|
+
*
|
|
275
|
+
* Not a conflict and not a duplicate: neither can be established. Nothing was
|
|
276
|
+
* applied. Do not assume either outcome.
|
|
277
|
+
*/
|
|
278
|
+
export declare class TransactionIdUnverifiableError extends AtomicTransactionError {
|
|
279
|
+
readonly code: "IDEMPOTENCY_UNVERIFIABLE";
|
|
280
|
+
constructor(transactionId: string, message?: string);
|
|
281
|
+
}
|
|
237
282
|
/** Which predicate failed, and what the authority actually holds. */
|
|
238
283
|
export interface ConditionalConflictDetail {
|
|
239
284
|
predicate: 'version' | 'epoch' | 'lease' | 'present' | 'missing' | 'fence';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,6BAA6B;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,yDAAyD;AACzD,MAAM,WAAW,sBAAsB;IACrC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,0DAA0D;AAC1D,MAAM,WAAW,iCAAiC;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,iFAAiF;IACjF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,yBAAyB;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,6BAA6B,EAAE,CAAC;IAChD,MAAM,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAChC,UAAU,EAAE,iCAAiC,EAAE,CAAC;CACjD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAC1F;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,yEAAyE;AACzE,eAAO,MAAM,0BAA0B,OAAQ,CAAC;AAIhD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,kBAAkB,GAAG,QAAQ,CAAC,kBAAkB,CAAC,CAgCxF;AAED,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,iCAAiC,EAAE,CAAC;IAChD;;;;;OAKG;IACH,aAAa,CAAC,EAAE,6BAA6B,EAAE,CAAC;IAChD;;;;OAIG;IACH,MAAM,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"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"}
|
package/dist/transaction.js
CHANGED
|
@@ -79,6 +79,34 @@ export class AtomicTransactionError extends Error {
|
|
|
79
79
|
this.status = status;
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* The transaction id was already committed with a different payload.
|
|
84
|
+
*
|
|
85
|
+
* Nothing was applied and the recorded transaction is unchanged. Retrying with
|
|
86
|
+
* the same id cannot succeed: choose a new id for a new transaction, or resend
|
|
87
|
+
* the original payload to confirm the original.
|
|
88
|
+
*/
|
|
89
|
+
export class TransactionIdConflictError extends AtomicTransactionError {
|
|
90
|
+
code = 'IDEMPOTENCY_CONFLICT';
|
|
91
|
+
constructor(transactionId, message) {
|
|
92
|
+
super(message ?? `Transaction ${transactionId} was already committed with a different payload`, transactionId);
|
|
93
|
+
this.name = 'TransactionIdConflictError';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The transaction id exists, but its recorded identity cannot be checked by
|
|
98
|
+
* this build (a digest of a newer construction, or a malformed one).
|
|
99
|
+
*
|
|
100
|
+
* Not a conflict and not a duplicate: neither can be established. Nothing was
|
|
101
|
+
* applied. Do not assume either outcome.
|
|
102
|
+
*/
|
|
103
|
+
export class TransactionIdUnverifiableError extends AtomicTransactionError {
|
|
104
|
+
code = 'IDEMPOTENCY_UNVERIFIABLE';
|
|
105
|
+
constructor(transactionId, message) {
|
|
106
|
+
super(message ?? `Transaction ${transactionId} exists, but its recorded identity cannot be verified by this build`, transactionId);
|
|
107
|
+
this.name = 'TransactionIdUnverifiableError';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
82
110
|
/**
|
|
83
111
|
* A transaction was refused because a precondition did not hold.
|
|
84
112
|
*
|
|
Binary file
|
package/dist/wasm/package.json
CHANGED
|
@@ -20,6 +20,25 @@ export interface LocalDevelopmentAuthorityOptions {
|
|
|
20
20
|
applicationUrl?: string;
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
+
export declare const DEVELOPMENT_CONTROL_PLANE_CAPABILITIES: {
|
|
24
|
+
contract: string;
|
|
25
|
+
version: number;
|
|
26
|
+
capabilities: ({
|
|
27
|
+
status: "available";
|
|
28
|
+
note: string;
|
|
29
|
+
area: string;
|
|
30
|
+
title: string;
|
|
31
|
+
surface: string[];
|
|
32
|
+
id: string;
|
|
33
|
+
} | {
|
|
34
|
+
id: string;
|
|
35
|
+
area: string;
|
|
36
|
+
title: string;
|
|
37
|
+
status: "unsupported";
|
|
38
|
+
surface: string[];
|
|
39
|
+
note: string;
|
|
40
|
+
})[];
|
|
41
|
+
};
|
|
23
42
|
/** Start the durable, unauthenticated loopback authority used by `feltdb dev`. */
|
|
24
43
|
export declare function startLocalDevelopmentAuthority(options: LocalDevelopmentAuthorityOptions): Promise<LocalDevelopmentAuthority>;
|
|
25
44
|
//# sourceMappingURL=local-development-authority.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-development-authority.d.ts","sourceRoot":"","sources":["../../src/workspace/local-development-authority.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"local-development-authority.d.ts","sourceRoot":"","sources":["../../src/workspace/local-development-authority.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwD,KAAK,eAAe,EAAsB,MAAM,gBAAgB,CAAC;AAEhI,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAE7E,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,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;IACd,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;IACjD,kBAAkB,CAAC,EAAE,MAAM,qBAAqB,EAAE,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC;IACtF,cAAc,CAAC,EAAE;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,OAAO,CAAC;QACrB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAiFD,eAAO,MAAM,sCAAsC;;;;;;cA1Dd,MAAM;eAAS,MAAM;iBAAW,MAAM,EAAE;;;;;;;iBA+DL,MAAM,EAAE;;;CAC/E,CAAC;AAEF,kFAAkF;AAClF,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAAC,yBAAyB,CAAC,CAsWpC"}
|
|
@@ -1,19 +1,91 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
|
+
import { createHash, randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { FileJsDb } from '../file-db.js';
|
|
4
5
|
import { FELTDB_CONVERGENCE_COLLECTION, FeltDBProposalService } from '../proposal.js';
|
|
5
6
|
import { IntentService } from '../intent.js';
|
|
6
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Why the development authority does not serve a part of the control plane.
|
|
9
|
+
*
|
|
10
|
+
* `feltdb dev` runs a lightweight authority that holds application state and
|
|
11
|
+
* the application contract. Everything else the control plane names belongs to
|
|
12
|
+
* a deployed feltdb-server. Each capability is declared — available or not —
|
|
13
|
+
* so Studio can tell "not part of this instance" from "unknown to Studio", and
|
|
14
|
+
* the reason says where the feature does live.
|
|
15
|
+
*/
|
|
16
|
+
const SERVER_ONLY = {
|
|
17
|
+
data: 'Record inspection, writes, sorting and index management through the control plane are served by feltdb-server. In development, the application writes its own state, and Data shows it.',
|
|
18
|
+
state: 'Semantic history, diff, conflict classification and reconciliation are served by feltdb-server; the development authority keeps current state only.',
|
|
19
|
+
operations: 'Operation history, rejections, transaction status and audit are recorded by feltdb-server; the development authority does not record them.',
|
|
20
|
+
agents: 'Agent principals, sessions, authorization decisions and Durable Work run on feltdb-server. The development authority does not run agents; the agents your application declares are listed under Build › Agents.',
|
|
21
|
+
replication: 'Replication health is reported by a feltdb-server cluster; the development authority is a single local node.',
|
|
22
|
+
applications: 'Managing applications is part of a deployed feltdb-server; a development session serves exactly one application.',
|
|
23
|
+
monitoring: 'Instance metrics come from feltdb-server; the development authority does not export them.',
|
|
24
|
+
settings: 'Instance settings belong to a deployed feltdb-server; a development session is configured by feltdb.config.json.',
|
|
25
|
+
developer: 'Developer access is issued by feltdb-server; the development authority is unauthenticated and loopback-only.',
|
|
26
|
+
};
|
|
27
|
+
const SERVED = {
|
|
28
|
+
'overview.instance': { area: 'overview', title: 'Development authority overview', surface: ['GET /v1/control-plane/overview'] },
|
|
29
|
+
'data.collections.list': { area: 'data', title: 'List declared collections', surface: ['GET /v1/application/schema'] },
|
|
30
|
+
'data.schema.inspect': { area: 'data', title: 'Inspect the application schema', surface: ['GET /v1/application/schema'] },
|
|
31
|
+
'data.records.browse': { area: 'data', title: 'Browse records', surface: ['GET /v1/data/{collection}'] },
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Every capability the feltdb-server control plane declares, in its order,
|
|
35
|
+
* with the area it belongs to. Held equal to `control_plane.rs` by
|
|
36
|
+
* `development-authority-capabilities.test.mjs`.
|
|
37
|
+
*/
|
|
38
|
+
const CONTROL_PLANE_IDS = [
|
|
39
|
+
['overview.instance', 'overview', 'Instance overview'],
|
|
40
|
+
['data.collections.list', 'data', 'List collections'],
|
|
41
|
+
['data.records.browse', 'data', 'Browse records'],
|
|
42
|
+
['data.records.inspect', 'data', 'Inspect a record'],
|
|
43
|
+
['data.records.create', 'data', 'Create records'],
|
|
44
|
+
['data.records.update', 'data', 'Update records'],
|
|
45
|
+
['data.records.delete', 'data', 'Delete records'],
|
|
46
|
+
['data.records.sort', 'data', 'Sort records'],
|
|
47
|
+
['data.schema.inspect', 'data', 'Inspect the schema'],
|
|
48
|
+
['data.indexes.inspect', 'data', 'Inspect indexes'],
|
|
49
|
+
['data.indexes.rebuild', 'data', 'Rebuild indexes'],
|
|
50
|
+
['data.records.history', 'state', 'Record history'],
|
|
51
|
+
['state.resources.list', 'state', 'List state resources'],
|
|
52
|
+
['state.history', 'state', 'State history'],
|
|
53
|
+
['state.diff.semantic', 'state', 'Semantic diff'],
|
|
54
|
+
['state.conflict.classify', 'state', 'Classify conflicts'],
|
|
55
|
+
['state.reconciliation.plan', 'state', 'Plan reconciliation'],
|
|
56
|
+
['state.reconciliation.execute', 'state', 'Execute reconciliation'],
|
|
57
|
+
['state.branch.heads', 'state', 'Branch heads'],
|
|
58
|
+
['provenance.record', 'state', 'Record provenance'],
|
|
59
|
+
['provenance.transactions', 'operations', 'Recent attributed transactions'],
|
|
60
|
+
['agents.list', 'agents', 'List agents'],
|
|
61
|
+
['agents.identity', 'agents', 'Agent identity'],
|
|
62
|
+
['agents.register', 'agents', 'Register agents'],
|
|
63
|
+
['agents.work', 'agents', 'Agent work (Durable Work)'],
|
|
64
|
+
['agents.work.review_gate', 'agents', 'Agent work review gate'],
|
|
65
|
+
['agents.sessions', 'agents', 'Agent sessions'],
|
|
66
|
+
['agents.authorization_decisions', 'agents', 'Agent authorization decisions'],
|
|
67
|
+
['agents.task_context', 'agents', 'Agent task context'],
|
|
68
|
+
['operations.recent', 'operations', 'Recent operations'],
|
|
69
|
+
['operations.rejections', 'operations', 'Rejected operations'],
|
|
70
|
+
['operations.events', 'operations', 'Operation events'],
|
|
71
|
+
['operations.transaction_status', 'operations', 'Transaction status'],
|
|
72
|
+
['operations.correlation_id', 'operations', 'Correlation ids'],
|
|
73
|
+
['operations.audit_log', 'operations', 'Audit log'],
|
|
74
|
+
['replication.health', 'replication', 'Replication health'],
|
|
75
|
+
['applications.list', 'applications', 'List applications'],
|
|
76
|
+
['monitoring.metrics', 'monitoring', 'Instance metrics'],
|
|
77
|
+
['monitoring.latency', 'monitoring', 'Latency'],
|
|
78
|
+
['settings.instance', 'settings', 'Instance settings'],
|
|
79
|
+
['settings.mutate', 'settings', 'Change settings'],
|
|
80
|
+
['developer.tool_projection', 'developer', 'Tool projection'],
|
|
81
|
+
['developer.access', 'developer', 'Developer access'],
|
|
82
|
+
];
|
|
83
|
+
export const DEVELOPMENT_CONTROL_PLANE_CAPABILITIES = {
|
|
7
84
|
contract: 'feltdb.control-plane.capabilities',
|
|
8
85
|
version: 1,
|
|
9
|
-
capabilities: [
|
|
10
|
-
{ id
|
|
11
|
-
{ id
|
|
12
|
-
{ id: 'data.schema.inspect', area: 'data', title: 'Inspect the application schema', status: 'available', surface: ['GET /v1/application/schema'], note: '' },
|
|
13
|
-
{ id: 'data.records.browse', area: 'data', title: 'Browse records', status: 'available', surface: ['GET /v1/data/{collection}'], note: '' },
|
|
14
|
-
{ id: 'provenance.transactions', area: 'operations', title: 'Recent attributed transactions', status: 'unsupported', surface: [], note: 'Transaction observability is not available from the lightweight development authority.' },
|
|
15
|
-
{ id: 'replication.health', area: 'replication', title: 'Replication health', status: 'unsupported', surface: [], note: 'Replication health is not available from the lightweight development authority.' },
|
|
16
|
-
],
|
|
86
|
+
capabilities: CONTROL_PLANE_IDS.map(([id, area, title]) => SERVED[id]
|
|
87
|
+
? { id, ...SERVED[id], status: 'available', note: '' }
|
|
88
|
+
: { id, area, title, status: 'unsupported', surface: [], note: SERVER_ONLY[area] }),
|
|
17
89
|
};
|
|
18
90
|
/** Start the durable, unauthenticated loopback authority used by `feltdb dev`. */
|
|
19
91
|
export async function startLocalDevelopmentAuthority(options) {
|
|
@@ -37,9 +109,26 @@ export async function startLocalDevelopmentAuthority(options) {
|
|
|
37
109
|
const proposalService = options.proposalContext ? new FeltDBProposalService(store, options.proposalContext, proposalAuthorityToken) : undefined;
|
|
38
110
|
const intentService = proposalService && options.proposalContext ? new IntentService(store, options.proposalContext, proposalService, options.intentObservations) : undefined;
|
|
39
111
|
const streams = new Set();
|
|
112
|
+
const notify = (collection) => {
|
|
113
|
+
// `resource` is the field every client reads (the event contract
|
|
114
|
+
// feltdb-server serves); `change` is kept for readers of the older frame.
|
|
115
|
+
const frame = `data: ${JSON.stringify({ resource: collection, change: { capability: collection } })}\n\n`;
|
|
116
|
+
for (const stream of streams)
|
|
117
|
+
stream.write(frame);
|
|
118
|
+
};
|
|
40
119
|
let endpoint = '';
|
|
41
120
|
const server = http.createServer(async (request, response) => {
|
|
42
121
|
try {
|
|
122
|
+
// The authority is unauthenticated, so which pages may reach it is the
|
|
123
|
+
// whole boundary: only a loopback origin (the developer's own application
|
|
124
|
+
// and Studio) and a loopback Host (not a rebound DNS name). A request
|
|
125
|
+
// without an Origin is not a browser page and is not restricted here.
|
|
126
|
+
if (!loopbackHost(request.headers.host) || (request.headers.origin !== undefined && !loopbackOrigin(request.headers.origin))) {
|
|
127
|
+
response.statusCode = 403;
|
|
128
|
+
response.setHeader('Content-Type', 'application/json');
|
|
129
|
+
response.end(JSON.stringify({ error: 'NON_LOOPBACK_ORIGIN', message: 'The development authority serves loopback origins only.' }));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
43
132
|
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
44
133
|
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE, OPTIONS');
|
|
45
134
|
response.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, FeltDB-Protocol, FeltDB-Proposal-Authority');
|
|
@@ -349,14 +438,106 @@ export async function startLocalDevelopmentAuthority(options) {
|
|
|
349
438
|
json(response, 405, { error: 'METHOD_NOT_ALLOWED' });
|
|
350
439
|
return;
|
|
351
440
|
}
|
|
441
|
+
const authMatch = pathname.match(/^\/v1\/auth\/(signup|signin|signout|session)$/);
|
|
442
|
+
if (authMatch) {
|
|
443
|
+
const operation = authMatch[1];
|
|
444
|
+
const expectedMethod = operation === 'session' ? 'GET' : 'POST';
|
|
445
|
+
if (request.method !== expectedMethod) {
|
|
446
|
+
json(response, 405, { error: 'METHOD_NOT_ALLOWED' });
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const body = operation === 'signup' || operation === 'signin' ? parseObject(await readBody(request)) : {};
|
|
450
|
+
const bearer = String(request.headers.authorization || '').replace(/^Bearer\s+/i, '');
|
|
451
|
+
const outcome = developmentAuth(db, operation, body, bearer);
|
|
452
|
+
json(response, outcome.status, outcome.body);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
// The direct data plane `HttpJsDb` speaks to any authority, so an
|
|
456
|
+
// application connected to this one behaves as it would against
|
|
457
|
+
// feltdb-server: same routes, same shapes, same refusals.
|
|
458
|
+
if (request.method === 'GET' && pathname === '/revision') {
|
|
459
|
+
json(response, 200, { revision: db.get_sequence(), scope: db.instance_id() });
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (request.method === 'POST' && pathname === '/query') {
|
|
463
|
+
const query = parseObject(await readBody(request));
|
|
464
|
+
if (namesPrivateCollection([query.collection])) {
|
|
465
|
+
refusePrivate(response);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
try {
|
|
469
|
+
json(response, 200, await db.authority_query(query));
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
const code = error.code;
|
|
473
|
+
json(response, 400, { code, error: errorMessage(error), message: errorMessage(error) });
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (request.method === 'POST' && pathname === '/transactions/read-basis') {
|
|
478
|
+
const reads = parseObject(await readBody(request));
|
|
479
|
+
if (namesPrivateCollection(readSetCollections(reads))) {
|
|
480
|
+
refusePrivate(response);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
json(response, 200, await db.read_basis(reads));
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (request.method === 'POST' && pathname === '/transactions') {
|
|
487
|
+
const transaction = parseObject(await readBody(request));
|
|
488
|
+
if (namesPrivateCollection([
|
|
489
|
+
...(transaction.operations ?? []).map((operation) => operation?.collection),
|
|
490
|
+
...(transaction.preconditions ?? []).map((condition) => condition?.collection),
|
|
491
|
+
...(transaction.fences ?? []).flatMap((fence) => readSetCollections(fence?.reads)),
|
|
492
|
+
])) {
|
|
493
|
+
refusePrivate(response);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
let result;
|
|
497
|
+
try {
|
|
498
|
+
result = await db.commit_transaction(transaction);
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
json(response, transactionRefusalStatus(error), transactionRefusal(error));
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
json(response, 200, { ...result, status: 'committed' });
|
|
505
|
+
if (!result.duplicate) {
|
|
506
|
+
const collections = new Set((transaction.operations ?? []).map((operation) => operation.collection));
|
|
507
|
+
for (const collection of collections)
|
|
508
|
+
notify(collection);
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const casMatch = pathname.match(/^\/collections\/([^/]+)\/([^/]+)\/cas$/);
|
|
513
|
+
if (request.method === 'POST' && casMatch) {
|
|
514
|
+
const collection = decodeURIComponent(casMatch[1]);
|
|
515
|
+
if (namesPrivateCollection([collection])) {
|
|
516
|
+
refusePrivate(response);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const input = parseObject(await readBody(request));
|
|
520
|
+
const result = await db.cas({
|
|
521
|
+
key: `${collection}:${decodeURIComponent(casMatch[2])}`,
|
|
522
|
+
expectedVersion: Number(input.expectedVersion),
|
|
523
|
+
expectedEpoch: input.expectedEpoch,
|
|
524
|
+
expectedLeaseId: input.expectedLeaseId,
|
|
525
|
+
rejectIfDiverged: input.rejectIfDiverged,
|
|
526
|
+
value: JSON.stringify(input.value ?? {}),
|
|
527
|
+
});
|
|
528
|
+
json(response, 200, result);
|
|
529
|
+
if (result.updated)
|
|
530
|
+
notify(collection);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
352
533
|
const match = pathname.match(/^\/collections\/([^/]+)(?:\/([^/]+))?$/);
|
|
353
534
|
if (!match) {
|
|
354
535
|
json(response, 404, { error: 'NOT_FOUND' });
|
|
355
536
|
return;
|
|
356
537
|
}
|
|
357
538
|
const collection = decodeURIComponent(match[1]);
|
|
358
|
-
if (collection
|
|
359
|
-
|
|
539
|
+
if (namesPrivateCollection([collection])) {
|
|
540
|
+
refusePrivate(response);
|
|
360
541
|
return;
|
|
361
542
|
}
|
|
362
543
|
const id = match[2] ? decodeURIComponent(match[2]) : undefined;
|
|
@@ -406,9 +587,7 @@ export async function startLocalDevelopmentAuthority(options) {
|
|
|
406
587
|
return;
|
|
407
588
|
}
|
|
408
589
|
json(response, request.method === 'POST' ? 201 : 200, { ok: true });
|
|
409
|
-
|
|
410
|
-
for (const stream of streams)
|
|
411
|
-
stream.write(frame);
|
|
590
|
+
notify(collection);
|
|
412
591
|
}
|
|
413
592
|
catch (error) {
|
|
414
593
|
if (!response.headersSent)
|
|
@@ -440,11 +619,152 @@ function json(response, status, value) {
|
|
|
440
619
|
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
441
620
|
response.end(JSON.stringify(value));
|
|
442
621
|
}
|
|
622
|
+
const IDENTITIES = '_feltdb.development_identities';
|
|
623
|
+
const SESSIONS = '_feltdb.development_sessions';
|
|
624
|
+
const SESSION_TTL_MS = 15 * 60_000;
|
|
625
|
+
function developmentAuth(db, operation, body, bearer) {
|
|
626
|
+
const read = (key) => {
|
|
627
|
+
const result = db.get(key);
|
|
628
|
+
if (!result.success)
|
|
629
|
+
throw new Error(result.error);
|
|
630
|
+
return result.data ? JSON.parse(result.data) : undefined;
|
|
631
|
+
};
|
|
632
|
+
const tokenHash = (token) => createHash('sha256').update(token).digest('hex');
|
|
633
|
+
const passwordHash = (password, salt) => scryptSync(password, salt, 32).toString('hex');
|
|
634
|
+
const refuse = (status, code, message) => ({ status, body: { code, error: code, message } });
|
|
635
|
+
const issue = (identity) => {
|
|
636
|
+
const token = randomBytes(32).toString('hex');
|
|
637
|
+
const session = { session_id: `session_${randomBytes(12).toString('hex')}`, expires_at: Date.now() + SESSION_TTL_MS };
|
|
638
|
+
const stored = db.insert(`${SESSIONS}:${tokenHash(token)}`, JSON.stringify({ ...session, user_id: identity.id }));
|
|
639
|
+
if (!stored.success)
|
|
640
|
+
throw new Error(stored.error);
|
|
641
|
+
return { status: 200, body: { access_token: token, actor: { user_id: identity.id, email: identity.email, display_name: identity.display_name }, session } };
|
|
642
|
+
};
|
|
643
|
+
if (operation === 'signout') {
|
|
644
|
+
if (bearer)
|
|
645
|
+
db.delete(`${SESSIONS}:${tokenHash(bearer)}`);
|
|
646
|
+
return { status: 200, body: {} };
|
|
647
|
+
}
|
|
648
|
+
if (operation === 'session') {
|
|
649
|
+
const sessionKey = `${SESSIONS}:${tokenHash(bearer)}`;
|
|
650
|
+
const session = bearer ? read(sessionKey) : undefined;
|
|
651
|
+
// An expired session is dead; remove it rather than keep it forever.
|
|
652
|
+
if (session && session.expires_at <= Date.now())
|
|
653
|
+
db.delete(sessionKey);
|
|
654
|
+
const identity = session && session.expires_at > Date.now()
|
|
655
|
+
? read(`${IDENTITIES}:${session.user_id}`)
|
|
656
|
+
: undefined;
|
|
657
|
+
if (!session || !identity)
|
|
658
|
+
return refuse(401, 'AUTHENTICATION_REQUIRED', 'Authentication required');
|
|
659
|
+
return { status: 200, body: { access_token: bearer, actor: { user_id: identity.id, email: identity.email, display_name: identity.display_name }, session: { session_id: session.session_id, expires_at: session.expires_at } } };
|
|
660
|
+
}
|
|
661
|
+
const email = String(body.email ?? '').trim().toLowerCase();
|
|
662
|
+
const password = String(body.password ?? '');
|
|
663
|
+
if (!email || password.length < 8)
|
|
664
|
+
return refuse(422, 'INVALID_CREDENTIALS_FORMAT', 'email and a password of at least 8 characters are required');
|
|
665
|
+
const byEmail = `${IDENTITIES}:email:${createHash('sha256').update(email).digest('hex')}`;
|
|
666
|
+
const existingId = read(byEmail)?.id;
|
|
667
|
+
if (operation === 'signup') {
|
|
668
|
+
if (existingId)
|
|
669
|
+
return refuse(409, 'CONFLICT', 'identity already exists');
|
|
670
|
+
const salt = randomBytes(16).toString('hex');
|
|
671
|
+
const identity = { id: `user_${randomBytes(12).toString('hex')}`, email, display_name: typeof body.display_name === 'string' ? body.display_name : undefined, salt, password_hash: passwordHash(password, salt), created_at: new Date().toISOString() };
|
|
672
|
+
const claimed = db.insert(byEmail, JSON.stringify({ id: identity.id }));
|
|
673
|
+
if (!claimed.success)
|
|
674
|
+
return refuse(409, 'CONFLICT', 'identity already exists');
|
|
675
|
+
const stored = db.insert(`${IDENTITIES}:${identity.id}`, JSON.stringify(identity));
|
|
676
|
+
if (!stored.success) {
|
|
677
|
+
// Do not leave the email claimed by an identity that was never stored.
|
|
678
|
+
db.delete(byEmail);
|
|
679
|
+
throw new Error(stored.error);
|
|
680
|
+
}
|
|
681
|
+
return issue(identity);
|
|
682
|
+
}
|
|
683
|
+
const identity = existingId ? read(`${IDENTITIES}:${existingId}`) : undefined;
|
|
684
|
+
const presented = Buffer.from(passwordHash(password, identity?.salt ?? 'no-identity'), 'hex');
|
|
685
|
+
const expected = Buffer.from(identity?.password_hash ?? '0'.repeat(64), 'hex');
|
|
686
|
+
if (!identity || presented.length !== expected.length || !timingSafeEqual(presented, expected)) {
|
|
687
|
+
return refuse(401, 'INVALID_CREDENTIALS', 'invalid credentials');
|
|
688
|
+
}
|
|
689
|
+
return issue(identity);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* `_feltdb.*` collections are the authority's own records — development
|
|
693
|
+
* identities and sessions among them — and no client route reads or writes
|
|
694
|
+
* them: not `/collections`, not a query, not a transaction or its fences.
|
|
695
|
+
*/
|
|
696
|
+
function namesPrivateCollection(collections) {
|
|
697
|
+
// Coerced the way the transaction and query engines coerce a collection
|
|
698
|
+
// name, so a value that is not a string (`["_feltdb.x"]` stringifies to the
|
|
699
|
+
// private name) is judged by what it will be read as, not by its type.
|
|
700
|
+
return collections.some(collection => collection !== undefined && collection !== null
|
|
701
|
+
&& String(collection).trim().toLowerCase().startsWith('_feltdb.'));
|
|
702
|
+
}
|
|
703
|
+
function loopbackHost(host) {
|
|
704
|
+
if (!host)
|
|
705
|
+
return true;
|
|
706
|
+
try {
|
|
707
|
+
return LOOPBACK_HOSTNAMES.has(new URL(`http://${host}`).hostname);
|
|
708
|
+
}
|
|
709
|
+
catch {
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function loopbackOrigin(origin) {
|
|
714
|
+
if (!origin)
|
|
715
|
+
return false;
|
|
716
|
+
try {
|
|
717
|
+
return LOOPBACK_HOSTNAMES.has(new URL(origin).hostname);
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
const LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
724
|
+
function readSetCollections(reads) {
|
|
725
|
+
const set = (reads ?? {});
|
|
726
|
+
return [...(set.records ?? []), ...(set.predicates ?? [])].map(entry => entry?.collection);
|
|
727
|
+
}
|
|
728
|
+
function refusePrivate(response) {
|
|
729
|
+
json(response, 403, { error: 'SYSTEM_COLLECTION_REQUIRES_SERVICE_API' });
|
|
730
|
+
}
|
|
731
|
+
function errorMessage(error) {
|
|
732
|
+
return error instanceof Error ? error.message : String(error);
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* A refused transaction, in the document feltdb-server answers with: a lost
|
|
736
|
+
* conditional race is `409 PRECONDITION_FAILED` with the failed predicate, a
|
|
737
|
+
* reused transaction id is `409` with its idempotency code, and a malformed
|
|
738
|
+
* request is `400`. Callers type the refusal from `code`, never from prose.
|
|
739
|
+
*/
|
|
740
|
+
function transactionRefusal(error) {
|
|
741
|
+
const refusal = error;
|
|
742
|
+
return {
|
|
743
|
+
error: errorMessage(error),
|
|
744
|
+
code: refusal.feltdbCode ?? refusal.code,
|
|
745
|
+
...(refusal.feltdbConflict ? { failure: refusal.feltdbConflict } : {}),
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function transactionRefusalStatus(error) {
|
|
749
|
+
const refusal = error;
|
|
750
|
+
if (refusal.feltdbCode === 'PRECONDITION_FAILED')
|
|
751
|
+
return 409;
|
|
752
|
+
if (refusal.code === 'IDEMPOTENCY_CONFLICT' || refusal.code === 'IDEMPOTENCY_UNVERIFIABLE')
|
|
753
|
+
return 409;
|
|
754
|
+
return 400;
|
|
755
|
+
}
|
|
756
|
+
const MAX_BODY_BYTES = 1024 * 1024;
|
|
443
757
|
function readBody(request) {
|
|
444
758
|
return new Promise((resolve, reject) => {
|
|
445
759
|
let body = '';
|
|
446
760
|
request.setEncoding('utf8');
|
|
447
|
-
request.on('data', chunk => {
|
|
761
|
+
request.on('data', chunk => {
|
|
762
|
+
body += chunk;
|
|
763
|
+
if (body.length > MAX_BODY_BYTES) {
|
|
764
|
+
request.destroy();
|
|
765
|
+
reject(new Error('Request body is too large'));
|
|
766
|
+
}
|
|
767
|
+
});
|
|
448
768
|
request.once('end', () => resolve(body));
|
|
449
769
|
request.once('error', reject);
|
|
450
770
|
});
|
package/package.json
CHANGED
|
File without changes
|
|
Binary file
|