@feltdb/core 0.4.15 → 0.4.17
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/index.js +1 -1
- package/dist/collection.d.ts +11 -0
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +34 -0
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb/src/application.rs +93 -0
- package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +787 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +139 -0
- package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +1669 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +1269 -15
- package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +406 -0
- package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +908 -0
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +1028 -0
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +332 -0
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +342 -0
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +313 -0
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +90 -0
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +571 -0
- package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +506 -0
- package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +81 -0
- package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +434 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +130 -23
- package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +2 -2
- package/dist/db.d.ts +91 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +89 -1
- package/dist/feltdb.d.ts +14 -0
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts +75 -0
- package/dist/file-db.d.ts.map +1 -0
- package/dist/file-db.js +437 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/studio-app/assets/{feltdb_wasm-CJEJryDx.js → feltdb_wasm-CBGD0zRu.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-C6ATF9mJ.wasm +0 -0
- package/dist/studio-app/assets/{index-D_p8T7nO.js → index-5siPkRSN.js} +9 -9
- package/dist/studio-app/index.html +1 -1
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/package.json +9 -4
- package/dist/studio-app/assets/feltdb_wasm_bg-BJxQXtoo.wasm +0 -0
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.
|
|
26
|
+
const VERSION = '0.4.17';
|
|
27
27
|
function prompt(question) {
|
|
28
28
|
const rl = readline.createInterface({
|
|
29
29
|
input: process.stdin,
|
package/dist/collection.d.ts
CHANGED
|
@@ -94,6 +94,17 @@ export declare class Collection<T> {
|
|
|
94
94
|
* Check if a record exists.
|
|
95
95
|
*/
|
|
96
96
|
exists(id: string | number): Promise<boolean>;
|
|
97
|
+
/**
|
|
98
|
+
* Atomically insert a record only if the key does not already exist.
|
|
99
|
+
* Process-safe across independent Node.js processes.
|
|
100
|
+
*
|
|
101
|
+
* @returns {inserted: true, value: newRecord} if this call created the record
|
|
102
|
+
* @returns {inserted: false, value: existingRecord} if record already existed
|
|
103
|
+
*/
|
|
104
|
+
putIfAbsent(id: string | number, data: Partial<T>): Promise<{
|
|
105
|
+
inserted: boolean;
|
|
106
|
+
value: T;
|
|
107
|
+
}>;
|
|
97
108
|
/**
|
|
98
109
|
* Subscribe to changes in this collection using reactive dependency graph.
|
|
99
110
|
* Returns an unsubscribe function.
|
package/dist/collection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
|
package/dist/collection.js
CHANGED
|
@@ -246,6 +246,40 @@ export class Collection {
|
|
|
246
246
|
const item = await this.get(id);
|
|
247
247
|
return item !== null;
|
|
248
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Atomically insert a record only if the key does not already exist.
|
|
251
|
+
* Process-safe across independent Node.js processes.
|
|
252
|
+
*
|
|
253
|
+
* @returns {inserted: true, value: newRecord} if this call created the record
|
|
254
|
+
* @returns {inserted: false, value: existingRecord} if record already existed
|
|
255
|
+
*/
|
|
256
|
+
async putIfAbsent(id, data) {
|
|
257
|
+
const key = `${this.name}:${id}`;
|
|
258
|
+
const stored = typeof data === 'object' && data !== null
|
|
259
|
+
? { ...data, id }
|
|
260
|
+
: data;
|
|
261
|
+
const stringValue = JSON.stringify(stored);
|
|
262
|
+
if (!this.db.putIfAbsent) {
|
|
263
|
+
throw new Error(`putIfAbsent is not supported by this FeltDB runtime. ` +
|
|
264
|
+
`Only FileJsDb (Node.js) supports durable atomic conditional creation.`);
|
|
265
|
+
}
|
|
266
|
+
const result = await this.db.putIfAbsent(key, stringValue);
|
|
267
|
+
// Emit change through reactive dependency graph if this was a new insert
|
|
268
|
+
if (result.inserted) {
|
|
269
|
+
const change = {
|
|
270
|
+
type: 'insert',
|
|
271
|
+
key,
|
|
272
|
+
value: stored,
|
|
273
|
+
timestamp: Date.now(),
|
|
274
|
+
};
|
|
275
|
+
const graph = getReactiveDependencyGraph();
|
|
276
|
+
await graph.emitChange(this.name, change);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
inserted: result.inserted,
|
|
280
|
+
value: JSON.parse(result.value)
|
|
281
|
+
};
|
|
282
|
+
}
|
|
249
283
|
/**
|
|
250
284
|
* Subscribe to changes in this collection using reactive dependency graph.
|
|
251
285
|
* Returns an unsubscribe function.
|
|
@@ -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.4.
|
|
3
|
+
export const FELTDB_PACKAGE_VERSION = '0.4.17';
|
|
4
4
|
export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;
|
|
@@ -15,7 +15,7 @@ rand = "0.8"
|
|
|
15
15
|
bincode = "1"
|
|
16
16
|
|
|
17
17
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
|
18
|
-
tokio = { version = "1", features = ["net", "io-util"] }
|
|
18
|
+
tokio = { version = "1", features = ["net", "io-util", "rt-multi-thread"] }
|
|
19
19
|
|
|
20
20
|
[dev-dependencies]
|
|
21
21
|
criterion = { version = "0.5", features = ["html_reports"] }
|
|
@@ -152,6 +152,10 @@ pub struct PolicyDefinition {
|
|
|
152
152
|
pub name: String,
|
|
153
153
|
pub resource: String,
|
|
154
154
|
#[serde(default)]
|
|
155
|
+
pub read: Option<String>,
|
|
156
|
+
#[serde(default)]
|
|
157
|
+
pub write: Option<String>,
|
|
158
|
+
#[serde(default)]
|
|
155
159
|
pub capabilities: Vec<String>,
|
|
156
160
|
}
|
|
157
161
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
@@ -363,6 +367,10 @@ pub struct ValidationReport {
|
|
|
363
367
|
pub issues: Vec<ValidationIssue>,
|
|
364
368
|
}
|
|
365
369
|
|
|
370
|
+
fn is_valid_policy_subject(subject: &str) -> bool {
|
|
371
|
+
matches!(subject, "authenticated" | "owner" | "member")
|
|
372
|
+
}
|
|
373
|
+
|
|
366
374
|
fn unique(
|
|
367
375
|
issues: &mut Vec<ValidationIssue>,
|
|
368
376
|
category: &str,
|
|
@@ -734,6 +742,24 @@ pub fn validate_manifest(
|
|
|
734
742
|
));
|
|
735
743
|
}
|
|
736
744
|
}
|
|
745
|
+
if let Some(ref read_subject) = policy.read {
|
|
746
|
+
if !is_valid_policy_subject(read_subject) {
|
|
747
|
+
issues.push(issue(
|
|
748
|
+
"semantic",
|
|
749
|
+
format!("policies.{}.read", policy.name),
|
|
750
|
+
&format!("invalid policy subject '{}'", read_subject),
|
|
751
|
+
));
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if let Some(ref write_subject) = policy.write {
|
|
755
|
+
if !is_valid_policy_subject(write_subject) {
|
|
756
|
+
issues.push(issue(
|
|
757
|
+
"semantic",
|
|
758
|
+
format!("policies.{}.write", policy.name),
|
|
759
|
+
&format!("invalid policy subject '{}'", write_subject),
|
|
760
|
+
));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
737
763
|
}
|
|
738
764
|
let state_schema = crate::state_contract::schema_from_manifest(manifest, "validation");
|
|
739
765
|
let schema_validation = crate::state_contract::validate_schema(&state_schema);
|
|
@@ -2241,4 +2267,71 @@ mod tests {
|
|
|
2241
2267
|
Some(promotion.diff_hash.as_str())
|
|
2242
2268
|
);
|
|
2243
2269
|
}
|
|
2270
|
+
|
|
2271
|
+
#[test]
|
|
2272
|
+
fn policy_with_valid_authenticated_subject() {
|
|
2273
|
+
let mut manifest = ApplicationManifest::empty("tenant", "app", "test");
|
|
2274
|
+
manifest.policies.push(PolicyDefinition {
|
|
2275
|
+
name: "TodoPolicy".into(),
|
|
2276
|
+
resource: "Todo".into(),
|
|
2277
|
+
read: Some("authenticated".into()),
|
|
2278
|
+
write: Some("authenticated".into()),
|
|
2279
|
+
capabilities: vec![],
|
|
2280
|
+
});
|
|
2281
|
+
|
|
2282
|
+
let report = validate_manifest(&manifest, "tenant", "app", None);
|
|
2283
|
+
assert!(report.valid, "Policy with authenticated subject should be valid: {:?}", report.issues);
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
#[test]
|
|
2287
|
+
fn policy_with_valid_owner_subject() {
|
|
2288
|
+
let mut manifest = ApplicationManifest::empty("tenant", "app", "test");
|
|
2289
|
+
manifest.policies.push(PolicyDefinition {
|
|
2290
|
+
name: "ProjectPolicy".into(),
|
|
2291
|
+
resource: "Project".into(),
|
|
2292
|
+
read: Some("authenticated".into()),
|
|
2293
|
+
write: Some("owner".into()),
|
|
2294
|
+
capabilities: vec![],
|
|
2295
|
+
});
|
|
2296
|
+
|
|
2297
|
+
let report = validate_manifest(&manifest, "tenant", "app", None);
|
|
2298
|
+
assert!(report.valid, "Policy with owner subject should be valid: {:?}", report.issues);
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
#[test]
|
|
2302
|
+
fn policy_with_invalid_subject_rejected() {
|
|
2303
|
+
let mut manifest = ApplicationManifest::empty("tenant", "app", "test");
|
|
2304
|
+
manifest.policies.push(PolicyDefinition {
|
|
2305
|
+
name: "BadPolicy".into(),
|
|
2306
|
+
resource: "BadCollection".into(),
|
|
2307
|
+
read: Some("unknown_subject".into()),
|
|
2308
|
+
write: None,
|
|
2309
|
+
capabilities: vec![],
|
|
2310
|
+
});
|
|
2311
|
+
|
|
2312
|
+
let report = validate_manifest(&manifest, "tenant", "app", None);
|
|
2313
|
+
assert!(!report.valid, "Policy with unknown subject should be invalid");
|
|
2314
|
+
assert!(
|
|
2315
|
+
report.issues.iter().any(|i|
|
|
2316
|
+
i.path.contains("BadPolicy") && i.path.contains("read") &&
|
|
2317
|
+
i.message.contains("invalid policy subject")
|
|
2318
|
+
),
|
|
2319
|
+
"Should have error about invalid policy subject"
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
#[test]
|
|
2324
|
+
fn policy_without_subjects_is_valid() {
|
|
2325
|
+
let mut manifest = ApplicationManifest::empty("tenant", "app", "test");
|
|
2326
|
+
manifest.policies.push(PolicyDefinition {
|
|
2327
|
+
name: "LegacyPolicy".into(),
|
|
2328
|
+
resource: "SomeCollection".into(),
|
|
2329
|
+
read: None,
|
|
2330
|
+
write: None,
|
|
2331
|
+
capabilities: vec!["some:capability".into()],
|
|
2332
|
+
});
|
|
2333
|
+
|
|
2334
|
+
let report = validate_manifest(&manifest, "tenant", "app", None);
|
|
2335
|
+
assert!(report.valid, "Policy without subjects should be valid (backward compatible)");
|
|
2336
|
+
}
|
|
2244
2337
|
}
|