@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.
Files changed (42) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/collection.d.ts +11 -0
  3. package/dist/collection.d.ts.map +1 -1
  4. package/dist/collection.js +34 -0
  5. package/dist/create/package-versions.js +1 -1
  6. package/dist/create/server-source/crates/feltdb/Cargo.toml +1 -1
  7. package/dist/create/server-source/crates/feltdb/src/application.rs +93 -0
  8. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +787 -0
  9. package/dist/create/server-source/crates/feltdb/src/lib.rs +139 -0
  10. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +1669 -0
  11. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +1269 -15
  12. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +406 -0
  13. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +908 -0
  14. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +1028 -0
  15. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +332 -0
  16. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +342 -0
  17. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +313 -0
  18. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +90 -0
  19. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +571 -0
  20. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +506 -0
  21. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +81 -0
  22. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +434 -0
  23. package/dist/create/server-source/crates/feltdb-server/src/main.rs +130 -23
  24. package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +2 -2
  25. package/dist/db.d.ts +91 -0
  26. package/dist/db.d.ts.map +1 -1
  27. package/dist/db.js +89 -1
  28. package/dist/feltdb.d.ts +14 -0
  29. package/dist/feltdb.d.ts.map +1 -1
  30. package/dist/file-db.d.ts +75 -0
  31. package/dist/file-db.d.ts.map +1 -0
  32. package/dist/file-db.js +437 -0
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/studio-app/assets/{feltdb_wasm-CJEJryDx.js → feltdb_wasm-CBGD0zRu.js} +1 -1
  37. package/dist/studio-app/assets/feltdb_wasm_bg-C6ATF9mJ.wasm +0 -0
  38. package/dist/studio-app/assets/{index-D_p8T7nO.js → index-5siPkRSN.js} +9 -9
  39. package/dist/studio-app/index.html +1 -1
  40. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  41. package/package.json +9 -4
  42. 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.15';
26
+ const VERSION = '0.4.17';
27
27
  function prompt(question) {
28
28
  const rl = readline.createInterface({
29
29
  input: process.stdin,
@@ -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.
@@ -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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"}
@@ -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.15';
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
  }