@lix-js/sdk 0.6.0-preview.2 → 0.6.0-preview.3

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 (165) hide show
  1. package/SKILL.md +4 -5
  2. package/dist/engine-wasm/wasm/lix_engine.js +1 -1
  3. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  4. package/dist/generated/builtin-schemas.d.ts +87 -162
  5. package/dist/generated/builtin-schemas.js +139 -236
  6. package/dist/open-lix.d.ts +1 -1
  7. package/dist-engine-src/src/binary_cas/types.rs +0 -6
  8. package/dist-engine-src/src/catalog/context.rs +412 -0
  9. package/dist-engine-src/src/catalog/mod.rs +10 -0
  10. package/dist-engine-src/src/catalog/schema.rs +4 -0
  11. package/dist-engine-src/src/catalog/snapshot.rs +1114 -0
  12. package/dist-engine-src/src/cel/mod.rs +1 -1
  13. package/dist-engine-src/src/cel/provider.rs +1 -1
  14. package/dist-engine-src/src/commit_graph/context.rs +328 -1015
  15. package/dist-engine-src/src/commit_graph/mod.rs +2 -3
  16. package/dist-engine-src/src/commit_graph/types.rs +7 -43
  17. package/dist-engine-src/src/commit_graph/walker.rs +57 -81
  18. package/dist-engine-src/src/commit_store/codec.rs +887 -0
  19. package/dist-engine-src/src/commit_store/context.rs +944 -0
  20. package/dist-engine-src/src/commit_store/materialization.rs +84 -0
  21. package/dist-engine-src/src/commit_store/mod.rs +16 -0
  22. package/dist-engine-src/src/commit_store/storage.rs +600 -0
  23. package/dist-engine-src/src/commit_store/types.rs +215 -0
  24. package/dist-engine-src/src/common/identity.rs +15 -5
  25. package/dist-engine-src/src/common/json_pointer.rs +67 -0
  26. package/dist-engine-src/src/common/metadata.rs +17 -12
  27. package/dist-engine-src/src/common/mod.rs +5 -5
  28. package/dist-engine-src/src/domain.rs +324 -0
  29. package/dist-engine-src/src/engine.rs +29 -43
  30. package/dist-engine-src/src/entity_identity.rs +238 -118
  31. package/dist-engine-src/src/functions/context.rs +17 -52
  32. package/dist-engine-src/src/functions/deterministic.rs +1 -1
  33. package/dist-engine-src/src/functions/mod.rs +1 -1
  34. package/dist-engine-src/src/functions/provider.rs +4 -4
  35. package/dist-engine-src/src/functions/state.rs +39 -66
  36. package/dist-engine-src/src/functions/types.rs +1 -1
  37. package/dist-engine-src/src/init.rs +204 -151
  38. package/dist-engine-src/src/json_store/context.rs +354 -60
  39. package/dist-engine-src/src/json_store/encoded.rs +6 -6
  40. package/dist-engine-src/src/json_store/mod.rs +4 -1
  41. package/dist-engine-src/src/json_store/store.rs +884 -11
  42. package/dist-engine-src/src/json_store/types.rs +166 -1
  43. package/dist-engine-src/src/lib.rs +10 -9
  44. package/dist-engine-src/src/live_state/context.rs +608 -830
  45. package/dist-engine-src/src/live_state/mod.rs +3 -3
  46. package/dist-engine-src/src/live_state/overlay.rs +7 -7
  47. package/dist-engine-src/src/live_state/reader.rs +5 -5
  48. package/dist-engine-src/src/live_state/types.rs +19 -36
  49. package/dist-engine-src/src/live_state/visibility.rs +19 -14
  50. package/dist-engine-src/src/plugin/archive.rs +3 -6
  51. package/dist-engine-src/src/plugin/install.rs +0 -18
  52. package/dist-engine-src/src/plugin/plugin_manifest.json +0 -1
  53. package/dist-engine-src/src/schema/annotations/defaults.rs +2 -7
  54. package/dist-engine-src/src/schema/builtin/lix_account.json +0 -1
  55. package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -1
  56. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -1
  57. package/dist-engine-src/src/schema/builtin/lix_change.json +11 -10
  58. package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -1
  59. package/dist-engine-src/src/schema/builtin/lix_commit.json +8 -46
  60. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +29 -22
  61. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -1
  62. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -1
  63. package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -1
  64. package/dist-engine-src/src/schema/builtin/lix_label.json +10 -3
  65. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
  66. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +2 -8
  67. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -1
  68. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -1
  69. package/dist-engine-src/src/schema/builtin/mod.rs +10 -59
  70. package/dist-engine-src/src/schema/compatibility.rs +787 -0
  71. package/dist-engine-src/src/schema/definition.json +47 -17
  72. package/dist-engine-src/src/schema/definition.rs +202 -96
  73. package/dist-engine-src/src/schema/key.rs +9 -77
  74. package/dist-engine-src/src/schema/mod.rs +4 -4
  75. package/dist-engine-src/src/schema/tests.rs +133 -92
  76. package/dist-engine-src/src/session/context.rs +40 -42
  77. package/dist-engine-src/src/session/create_version.rs +22 -14
  78. package/dist-engine-src/src/session/execute.rs +45 -14
  79. package/dist-engine-src/src/session/merge/apply.rs +4 -4
  80. package/dist-engine-src/src/session/merge/conflicts.rs +3 -2
  81. package/dist-engine-src/src/session/merge/stats.rs +1 -1
  82. package/dist-engine-src/src/session/merge/version.rs +35 -45
  83. package/dist-engine-src/src/session/mod.rs +4 -2
  84. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
  85. package/dist-engine-src/src/session/switch_version.rs +16 -28
  86. package/dist-engine-src/src/sql2/change_provider.rs +14 -20
  87. package/dist-engine-src/src/sql2/classify.rs +61 -26
  88. package/dist-engine-src/src/sql2/context.rs +22 -18
  89. package/dist-engine-src/src/sql2/directory_history_provider.rs +28 -20
  90. package/dist-engine-src/src/sql2/directory_provider.rs +131 -83
  91. package/dist-engine-src/src/sql2/entity_history_provider.rs +10 -14
  92. package/dist-engine-src/src/sql2/entity_provider.rs +680 -169
  93. package/dist-engine-src/src/sql2/error.rs +21 -1
  94. package/dist-engine-src/src/sql2/execute.rs +325 -264
  95. package/dist-engine-src/src/sql2/file_history_provider.rs +29 -21
  96. package/dist-engine-src/src/sql2/file_provider.rs +533 -108
  97. package/dist-engine-src/src/sql2/filesystem_planner.rs +58 -94
  98. package/dist-engine-src/src/sql2/filesystem_visibility.rs +37 -23
  99. package/dist-engine-src/src/sql2/history_projection.rs +3 -27
  100. package/dist-engine-src/src/sql2/history_provider.rs +11 -17
  101. package/dist-engine-src/src/sql2/history_route.rs +22 -8
  102. package/dist-engine-src/src/sql2/lix_state_provider.rs +178 -96
  103. package/dist-engine-src/src/sql2/mod.rs +6 -3
  104. package/dist-engine-src/src/sql2/predicate_typecheck.rs +246 -0
  105. package/dist-engine-src/src/sql2/public_bind/assignment.rs +46 -0
  106. package/dist-engine-src/src/sql2/public_bind/capability.rs +41 -0
  107. package/dist-engine-src/src/sql2/public_bind/dml.rs +166 -0
  108. package/dist-engine-src/src/sql2/public_bind/mod.rs +25 -0
  109. package/dist-engine-src/src/sql2/public_bind/table.rs +168 -0
  110. package/dist-engine-src/src/sql2/read_only.rs +10 -12
  111. package/dist-engine-src/src/sql2/session.rs +7 -10
  112. package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +76 -0
  113. package/dist-engine-src/src/sql2/udfs/mod.rs +8 -1
  114. package/dist-engine-src/src/sql2/udfs/public_call.rs +211 -0
  115. package/dist-engine-src/src/sql2/version_provider.rs +46 -31
  116. package/dist-engine-src/src/sql2/version_scope.rs +4 -4
  117. package/dist-engine-src/src/storage_bench.rs +1782 -325
  118. package/dist-engine-src/src/test_support.rs +183 -36
  119. package/dist-engine-src/src/tracked_state/by_file_index.rs +20 -24
  120. package/dist-engine-src/src/tracked_state/codec.rs +1519 -181
  121. package/dist-engine-src/src/tracked_state/context.rs +1155 -271
  122. package/dist-engine-src/src/tracked_state/diff.rs +249 -57
  123. package/dist-engine-src/src/tracked_state/materialization.rs +365 -103
  124. package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
  125. package/dist-engine-src/src/tracked_state/merge.rs +37 -19
  126. package/dist-engine-src/src/tracked_state/mod.rs +8 -7
  127. package/dist-engine-src/src/tracked_state/storage.rs +138 -6
  128. package/dist-engine-src/src/tracked_state/tree.rs +695 -252
  129. package/dist-engine-src/src/tracked_state/types.rs +176 -6
  130. package/dist-engine-src/src/transaction/commit.rs +695 -435
  131. package/dist-engine-src/src/transaction/context.rs +551 -310
  132. package/dist-engine-src/src/transaction/live_state_overlay.rs +9 -8
  133. package/dist-engine-src/src/transaction/mod.rs +2 -0
  134. package/dist-engine-src/src/transaction/normalization.rs +311 -447
  135. package/dist-engine-src/src/transaction/prep.rs +37 -0
  136. package/dist-engine-src/src/transaction/schema_resolver.rs +93 -71
  137. package/dist-engine-src/src/transaction/staging.rs +701 -406
  138. package/dist-engine-src/src/transaction/types.rs +231 -122
  139. package/dist-engine-src/src/transaction/validation.rs +2717 -1698
  140. package/dist-engine-src/src/untracked_state/codec.rs +40 -96
  141. package/dist-engine-src/src/untracked_state/context.rs +21 -5
  142. package/dist-engine-src/src/untracked_state/materialization.rs +10 -104
  143. package/dist-engine-src/src/untracked_state/mod.rs +3 -5
  144. package/dist-engine-src/src/untracked_state/storage.rs +105 -57
  145. package/dist-engine-src/src/untracked_state/types.rs +63 -13
  146. package/dist-engine-src/src/version/context.rs +1 -13
  147. package/dist-engine-src/src/version/lifecycle.rs +221 -0
  148. package/dist-engine-src/src/version/mod.rs +3 -2
  149. package/dist-engine-src/src/version/refs.rs +12 -103
  150. package/dist-engine-src/src/version/stage_rows.rs +15 -19
  151. package/package.json +1 -1
  152. package/dist-engine-src/src/changelog/codec.rs +0 -321
  153. package/dist-engine-src/src/changelog/context.rs +0 -92
  154. package/dist-engine-src/src/changelog/materialization.rs +0 -121
  155. package/dist-engine-src/src/changelog/mod.rs +0 -13
  156. package/dist-engine-src/src/changelog/reader.rs +0 -20
  157. package/dist-engine-src/src/changelog/storage.rs +0 -220
  158. package/dist-engine-src/src/changelog/types.rs +0 -38
  159. package/dist-engine-src/src/schema/builtin/lix_change_set.json +0 -18
  160. package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +0 -75
  161. package/dist-engine-src/src/schema/builtin/lix_entity_label.json +0 -63
  162. package/dist-engine-src/src/schema_registry.rs +0 -294
  163. package/dist-engine-src/src/sql2/commit_derived_provider.rs +0 -591
  164. package/dist-engine-src/src/tracked_state/rebuild.rs +0 -771
  165. package/dist-engine-src/src/tracked_state/tree_types.rs +0 -176
@@ -6,20 +6,14 @@ use crate::LixError;
6
6
  #[derive(Debug, Clone, PartialEq, Eq, Hash)]
7
7
  pub struct SchemaKey {
8
8
  pub schema_key: String,
9
- pub schema_version: String,
10
9
  }
11
10
 
12
11
  impl SchemaKey {
13
- pub fn new(schema_key: impl Into<String>, schema_version: impl Into<String>) -> Self {
12
+ pub fn new(schema_key: impl Into<String>) -> Self {
14
13
  Self {
15
14
  schema_key: schema_key.into(),
16
- schema_version: schema_version.into(),
17
15
  }
18
16
  }
19
-
20
- pub fn version_number(&self) -> Option<u64> {
21
- self.schema_version.parse::<u64>().ok()
22
- }
23
17
  }
24
18
 
25
19
  pub fn schema_key_from_definition(schema: &JsonValue) -> Result<SchemaKey, LixError> {
@@ -38,20 +32,8 @@ pub fn schema_key_from_definition(schema: &JsonValue) -> Result<SchemaKey, LixEr
38
32
  hint: None,
39
33
  details: None,
40
34
  })?;
41
- let schema_version = object
42
- .get("x-lix-version")
43
- .and_then(JsonValue::as_str)
44
- .ok_or_else(|| LixError {
45
- code: "LIX_ERROR_UNKNOWN".to_string(),
46
- message: "schema definition must include string x-lix-version".to_string(),
47
- hint: None,
48
- details: None,
49
- })?;
50
35
 
51
- Ok(SchemaKey::new(
52
- schema_key.to_string(),
53
- schema_version.to_string(),
54
- ))
36
+ Ok(SchemaKey::new(schema_key.to_string()))
55
37
  }
56
38
 
57
39
  pub fn schema_from_registered_snapshot(
@@ -79,59 +61,26 @@ pub fn schema_from_registered_snapshot(
79
61
  hint: None,
80
62
  details: None,
81
63
  })?;
82
- let schema_version = value
83
- .get("x-lix-version")
84
- .and_then(|value| value.as_str())
85
- .ok_or_else(|| LixError {
86
- code: "LIX_ERROR_UNKNOWN".to_string(),
87
- message: "registered schema value.x-lix-version must be string".to_string(),
88
- hint: None,
89
- details: None,
90
- })?;
91
64
 
92
65
  Ok((
93
- SchemaKey::new(schema_key.to_string(), schema_version.to_string()),
66
+ SchemaKey::new(schema_key.to_string()),
94
67
  JsonValue::Object(value.clone()),
95
68
  ))
96
69
  }
97
70
 
98
- pub(crate) fn reject_unsupported_registered_schema_version(
99
- key: &SchemaKey,
100
- ) -> Result<(), LixError> {
101
- if key.schema_version != "1" {
102
- return Err(LixError::new(
103
- LixError::CODE_SCHEMA_DEFINITION,
104
- format!(
105
- "schema '{}' uses x-lix-version '{}', but schema evolution is not supported yet; register schemas with x-lix-version '1'",
106
- key.schema_key, key.schema_version
107
- ),
108
- ));
109
- }
110
- Ok(())
111
- }
112
-
113
- pub(crate) fn registered_schema_entity_id(
114
- schema_key: &str,
115
- schema_version: &str,
116
- ) -> Result<EntityIdentity, LixError> {
71
+ pub(crate) fn registered_schema_entity_id(schema_key: &str) -> Result<EntityIdentity, LixError> {
117
72
  EntityIdentity::from_primary_key_paths(
118
73
  &serde_json::json!({
119
74
  "value": {
120
75
  "x-lix-key": schema_key,
121
- "x-lix-version": schema_version,
122
76
  }
123
77
  }),
124
- &[
125
- vec!["value".to_string(), "x-lix-key".to_string()],
126
- vec!["value".to_string(), "x-lix-version".to_string()],
127
- ],
78
+ &[vec!["value".to_string(), "x-lix-key".to_string()]],
128
79
  )
129
80
  .map_err(|error| {
130
81
  LixError::new(
131
82
  LixError::CODE_SCHEMA_DEFINITION,
132
- format!(
133
- "registered schema identity could not be derived for schema '{schema_key}' version '{schema_version}': {error}"
134
- ),
83
+ format!("registered schema identity could not be derived for schema '{schema_key}': {error}"),
135
84
  )
136
85
  })
137
86
  }
@@ -142,32 +91,17 @@ mod tests {
142
91
 
143
92
  use super::{schema_from_registered_snapshot, schema_key_from_definition, SchemaKey};
144
93
 
145
- #[test]
146
- fn schema_key_entity_id_and_numeric_version() {
147
- let key = SchemaKey::new("users", "42");
148
-
149
- assert_eq!(key.version_number(), Some(42));
150
- }
151
-
152
- #[test]
153
- fn schema_key_non_numeric_version_returns_none() {
154
- let key = SchemaKey::new("users", "v2");
155
-
156
- assert_eq!(key.version_number(), None);
157
- }
158
-
159
94
  #[test]
160
95
  fn schema_from_registered_snapshot_extracts_key_and_schema() {
161
96
  let snapshot = json!({
162
97
  "value": {
163
98
  "x-lix-key": "profile",
164
- "x-lix-version": "1",
165
99
  "type": "object"
166
100
  }
167
101
  });
168
102
 
169
103
  let (key, schema) = schema_from_registered_snapshot(&snapshot).expect("schema is valid");
170
- assert_eq!(key, SchemaKey::new("profile", "1"));
104
+ assert_eq!(key, SchemaKey::new("profile"));
171
105
  assert_eq!(schema["type"], json!("object"));
172
106
  }
173
107
 
@@ -184,7 +118,6 @@ mod tests {
184
118
  let snapshot = json!({
185
119
  "value": {
186
120
  "x-lix-key": 1,
187
- "x-lix-version": "1"
188
121
  }
189
122
  });
190
123
 
@@ -193,14 +126,13 @@ mod tests {
193
126
  }
194
127
 
195
128
  #[test]
196
- fn schema_key_from_definition_extracts_key_and_version() {
129
+ fn schema_key_from_definition_extracts_key() {
197
130
  let schema = json!({
198
131
  "x-lix-key": "users",
199
- "x-lix-version": "2",
200
132
  "type": "object"
201
133
  });
202
134
 
203
135
  let key = schema_key_from_definition(&schema).expect("schema key");
204
- assert_eq!(key, SchemaKey::new("users", "2"));
136
+ assert_eq!(key, SchemaKey::new("users"));
205
137
  }
206
138
  }
@@ -1,19 +1,19 @@
1
- mod annotations;
2
1
  mod builtin;
2
+ #[allow(dead_code)]
3
+ pub(crate) mod compatibility;
3
4
  mod definition;
4
5
  mod key;
5
6
  pub(crate) mod seed;
6
7
  #[cfg(test)]
7
8
  mod tests;
8
9
 
9
- pub(crate) use annotations::defaults::apply_schema_defaults_with_shared_runtime;
10
- pub(crate) use builtin::lix_state_surface_schema_definition;
10
+ pub(crate) use compatibility::validate_schema_amendment;
11
11
  pub(crate) use definition::{compile_lix_schema, format_lix_schema_validation_errors};
12
12
  pub use definition::{
13
13
  lix_schema_definition, lix_schema_definition_json, validate_lix_schema,
14
14
  validate_lix_schema_definition,
15
15
  };
16
- pub(crate) use key::{registered_schema_entity_id, reject_unsupported_registered_schema_version};
16
+ pub(crate) use key::registered_schema_entity_id;
17
17
  pub use key::{schema_from_registered_snapshot, schema_key_from_definition, SchemaKey};
18
18
  #[cfg(test)]
19
19
  pub(crate) use seed::seed_schema_definition;
@@ -5,7 +5,6 @@ use serde_json::json;
5
5
  fn validate_lix_schema_definition_passes_for_valid_schema() {
6
6
  let valid_schema = json!({
7
7
  "x-lix-key": "test_entity",
8
- "x-lix-version": "1",
9
8
  "type": "object",
10
9
  "properties": {
11
10
  "id": { "type": "string" }
@@ -20,7 +19,6 @@ fn validate_lix_schema_definition_passes_for_valid_schema() {
20
19
  fn validate_lix_schema_definition_rejects_unprojectable_entity_properties() {
21
20
  let schema = json!({
22
21
  "x-lix-key": "test_entity",
23
- "x-lix-version": "1",
24
22
  "type": "object",
25
23
  "properties": {
26
24
  "id": { "type": "string" },
@@ -41,10 +39,34 @@ fn validate_lix_schema_definition_rejects_unprojectable_entity_properties() {
41
39
  );
42
40
  }
43
41
 
42
+ #[test]
43
+ fn validate_lix_schema_definition_rejects_reserved_lix_property_prefixes() {
44
+ for property_name in ["lixcol_entity_id", "lix_internal", "lixfoo"] {
45
+ let schema = json!({
46
+ "x-lix-key": "test_entity",
47
+ "type": "object",
48
+ "properties": {
49
+ "id": { "type": "string" },
50
+ property_name: { "type": "string" }
51
+ },
52
+ "required": ["id", property_name],
53
+ "additionalProperties": false
54
+ });
55
+
56
+ let err = validate_lix_schema_definition(&schema)
57
+ .expect_err("reserved property names should be rejected");
58
+ assert!(
59
+ err.to_string().contains(&format!(
60
+ "property '/{property_name}' uses reserved prefix 'lix'"
61
+ )),
62
+ "error should identify the reserved property name: {err:?}"
63
+ );
64
+ }
65
+ }
66
+
44
67
  #[test]
45
68
  fn validate_lix_schema_definition_throws_for_invalid_schema() {
46
69
  let invalid_schema = json!({
47
- "x-lix-version": "1",
48
70
  "type": "object",
49
71
  "properties": {
50
72
  "id": { "type": "string" }
@@ -60,7 +82,6 @@ fn validate_lix_schema_definition_throws_for_invalid_schema() {
60
82
  fn validate_lix_schema_validates_both_schema_and_data_successfully() {
61
83
  let schema = json!({
62
84
  "x-lix-key": "user",
63
- "x-lix-version": "1",
64
85
  "type": "object",
65
86
  "properties": {
66
87
  "id": { "type": "string" },
@@ -81,7 +102,6 @@ fn validate_lix_schema_validates_both_schema_and_data_successfully() {
81
102
  #[test]
82
103
  fn validate_lix_schema_throws_when_schema_is_invalid() {
83
104
  let invalid_schema = json!({
84
- "x-lix-version": "1",
85
105
  "type": "object",
86
106
  "properties": {
87
107
  "id": { "type": "string" }
@@ -99,7 +119,6 @@ fn validate_lix_schema_throws_when_schema_is_invalid() {
99
119
  fn validate_lix_schema_throws_when_data_does_not_match_schema() {
100
120
  let schema = json!({
101
121
  "x-lix-key": "user",
102
- "x-lix-version": "1",
103
122
  "type": "object",
104
123
  "properties": {
105
124
  "id": { "type": "string" },
@@ -119,7 +138,6 @@ fn validate_lix_schema_throws_when_data_does_not_match_schema() {
119
138
  fn validate_lix_schema_definition_rejects_when_additional_properties_missing() {
120
139
  let schema = json!({
121
140
  "x-lix-key": "user",
122
- "x-lix-version": "1",
123
141
  "type": "object",
124
142
  "properties": {
125
143
  "id": { "type": "string" }
@@ -135,7 +153,6 @@ fn validate_lix_schema_definition_rejects_when_additional_properties_missing() {
135
153
  fn additional_properties_must_be_false() {
136
154
  let schema_with_additional_props = json!({
137
155
  "x-lix-key": "user",
138
- "x-lix-version": "1",
139
156
  "type": "object",
140
157
  "properties": {
141
158
  "id": { "type": "string" },
@@ -149,7 +166,6 @@ fn additional_properties_must_be_false() {
149
166
 
150
167
  let valid_schema = json!({
151
168
  "x-lix-key": "user",
152
- "x-lix-version": "1",
153
169
  "type": "object",
154
170
  "properties": {
155
171
  "id": { "type": "string" },
@@ -175,7 +191,6 @@ fn additional_properties_must_be_false() {
175
191
  fn validate_lix_schema_definition_rejects_missing_primary_key_properties() {
176
192
  let schema = json!({
177
193
  "x-lix-key": "missing_pk",
178
- "x-lix-version": "1",
179
194
  "type": "object",
180
195
  "properties": {
181
196
  "value": { "type": "string" }
@@ -191,11 +206,51 @@ fn validate_lix_schema_definition_rejects_missing_primary_key_properties() {
191
206
  .contains("x-lix-primary-key references missing property"));
192
207
  }
193
208
 
209
+ #[test]
210
+ fn validate_lix_schema_definition_rejects_non_string_primary_key_properties() {
211
+ let schema = json!({
212
+ "x-lix-key": "numeric_pk",
213
+ "type": "object",
214
+ "properties": {
215
+ "id": { "type": "number" },
216
+ "value": { "type": "string" }
217
+ },
218
+ "required": ["id", "value"],
219
+ "x-lix-primary-key": ["/id"],
220
+ "additionalProperties": false
221
+ });
222
+
223
+ let err = validate_lix_schema_definition(&schema).unwrap_err();
224
+ assert!(err
225
+ .to_string()
226
+ .contains("x-lix-primary-key property \"/id\" must have type \"string\""));
227
+ }
228
+
229
+ #[test]
230
+ fn validate_lix_schema_definition_rejects_optional_primary_key_properties() {
231
+ let schema = json!({
232
+ "x-lix-key": "optional_pk",
233
+ "type": "object",
234
+ "properties": {
235
+ "id": { "type": "string" },
236
+ "value": { "type": "string" }
237
+ },
238
+ "required": ["value"],
239
+ "x-lix-primary-key": ["/id"],
240
+ "additionalProperties": false
241
+ });
242
+
243
+ let err = validate_lix_schema_definition(&schema)
244
+ .expect_err("primary-key property should be required");
245
+ assert!(err
246
+ .to_string()
247
+ .contains("x-lix-primary-key property \"/id\" must be required"));
248
+ }
249
+
194
250
  #[test]
195
251
  fn validate_lix_schema_definition_rejects_missing_unique_constraint_properties() {
196
252
  let schema = json!({
197
253
  "x-lix-key": "missing_unique",
198
- "x-lix-version": "1",
199
254
  "type": "object",
200
255
  "properties": {
201
256
  "value": { "type": "string" }
@@ -215,7 +270,6 @@ fn x_key_is_required() {
215
270
  let schema = json!({
216
271
  "type": "object",
217
272
  "x-lix-key": null,
218
- "x-lix-version": "1",
219
273
  "properties": {
220
274
  "name": { "type": "string" }
221
275
  },
@@ -230,7 +284,6 @@ fn x_key_is_required() {
230
284
  fn x_lix_key_must_be_snake_case() {
231
285
  let base_schema = json!({
232
286
  "type": "object",
233
- "x-lix-version": "1",
234
287
  "properties": {
235
288
  "name": { "type": "string" }
236
289
  },
@@ -266,7 +319,6 @@ fn x_lix_unique_is_optional() {
266
319
  let schema = json!({
267
320
  "type": "object",
268
321
  "x-lix-key": "mock",
269
- "x-lix-version": "1",
270
322
  "properties": {
271
323
  "name": { "type": "string" }
272
324
  },
@@ -282,7 +334,6 @@ fn x_lix_unique_must_be_array_of_arrays_when_present() {
282
334
  let schema = json!({
283
335
  "type": "object",
284
336
  "x-lix-key": "mock",
285
- "x-lix-version": "1",
286
337
  "x-lix-unique": [["/id"], ["/name", "/age"]],
287
338
  "properties": {
288
339
  "id": { "type": "string" },
@@ -301,7 +352,6 @@ fn x_lix_unique_fails_with_invalid_structure() {
301
352
  let schema = json!({
302
353
  "type": "object",
303
354
  "x-lix-key": "mock",
304
- "x-lix-version": "1",
305
355
  "x-lix-unique": ["/id", "/name"],
306
356
  "properties": {
307
357
  "id": { "type": "string" },
@@ -319,7 +369,6 @@ fn x_lix_primary_key_must_include_at_least_one_unique_pointer() {
319
369
  let base_schema = json!({
320
370
  "type": "object",
321
371
  "x-lix-key": "mock",
322
- "x-lix-version": "1",
323
372
  "properties": {
324
373
  "id": { "type": "string" }
325
374
  },
@@ -345,7 +394,6 @@ fn x_lix_unique_groups_must_include_unique_pointers() {
345
394
  let base_schema = json!({
346
395
  "type": "object",
347
396
  "x-lix-key": "mock",
348
- "x-lix-version": "1",
349
397
  "properties": {
350
398
  "id": { "type": "string" },
351
399
  "email": { "type": "string" }
@@ -372,7 +420,6 @@ fn x_lix_entity_views_is_rejected() {
372
420
  let schema = json!({
373
421
  "type": "object",
374
422
  "x-lix-key": "mock",
375
- "x-lix-version": "1",
376
423
  "x-lix-entity-views": ["lix_state", "lix_state_by_version"],
377
424
  "properties": {
378
425
  "name": { "type": "string" }
@@ -391,7 +438,6 @@ fn x_lix_primary_key_is_optional() {
391
438
  let schema = json!({
392
439
  "type": "object",
393
440
  "x-lix-key": "mock",
394
- "x-lix-version": "1",
395
441
  "properties": {
396
442
  "name": { "type": "string" }
397
443
  },
@@ -407,7 +453,6 @@ fn x_lix_primary_key_must_be_array_of_strings_when_present() {
407
453
  let schema = json!({
408
454
  "type": "object",
409
455
  "x-lix-key": "mock",
410
- "x-lix-version": "1",
411
456
  "x-lix-primary-key": ["/id", "/version"],
412
457
  "properties": {
413
458
  "id": { "type": "string" },
@@ -426,7 +471,6 @@ fn x_lix_foreign_keys_is_optional() {
426
471
  let schema = json!({
427
472
  "type": "object",
428
473
  "x-lix-key": "blog_post",
429
- "x-lix-version": "1",
430
474
  "properties": {
431
475
  "id": { "type": "string" },
432
476
  "author_id": { "type": "string" }
@@ -443,7 +487,6 @@ fn x_lix_foreign_keys_with_valid_structure() {
443
487
  let schema = json!({
444
488
  "type": "object",
445
489
  "x-lix-key": "blog_post",
446
- "x-lix-version": "1",
447
490
  "x-lix-foreign-keys": [
448
491
  {
449
492
  "properties": ["/author_id"],
@@ -477,7 +520,6 @@ fn x_lix_foreign_keys_reject_duplicate_pointers() {
477
520
  let schema = json!({
478
521
  "type": "object",
479
522
  "x-lix-key": "invalid_fk_duplicates",
480
- "x-lix-version": "1",
481
523
  "x-lix-foreign-keys": [
482
524
  {
483
525
  "properties": ["/local", "/local"],
@@ -502,7 +544,6 @@ fn x_lix_foreign_keys_fails_without_required_fields() {
502
544
  let schema = json!({
503
545
  "type": "object",
504
546
  "x-lix-key": "blog_post",
505
- "x-lix-version": "1",
506
547
  "x-lix-foreign-keys": [
507
548
  {
508
549
  "properties": ["/author_id"]
@@ -520,18 +561,16 @@ fn x_lix_foreign_keys_fails_without_required_fields() {
520
561
  }
521
562
 
522
563
  #[test]
523
- fn x_lix_foreign_keys_rejects_schema_version() {
564
+ fn x_lix_foreign_keys_use_schema_key_identity_only() {
524
565
  let schema = json!({
525
566
  "type": "object",
526
567
  "x-lix-key": "comment",
527
- "x-lix-version": "1",
528
568
  "x-lix-foreign-keys": [
529
569
  {
530
570
  "properties": ["/post_id"],
531
571
  "references": {
532
572
  "schemaKey": "blog_post",
533
- "properties": ["/id"],
534
- "schemaVersion": "1"
573
+ "properties": ["/id"]
535
574
  }
536
575
  }
537
576
  ],
@@ -543,7 +582,7 @@ fn x_lix_foreign_keys_rejects_schema_version() {
543
582
  "additionalProperties": false
544
583
  });
545
584
 
546
- assert!(validate_lix_schema_definition(&schema).is_err());
585
+ assert!(validate_lix_schema_definition(&schema).is_ok());
547
586
  }
548
587
 
549
588
  #[test]
@@ -551,7 +590,6 @@ fn x_lix_foreign_keys_rejects_mode_field() {
551
590
  let schema = json!({
552
591
  "type": "object",
553
592
  "x-lix-key": "child_entity",
554
- "x-lix-version": "1",
555
593
  "x-lix-primary-key": ["/id"],
556
594
  "x-lix-foreign-keys": [
557
595
  {
@@ -577,7 +615,6 @@ fn x_lix_foreign_keys_rejects_scope_field() {
577
615
  let schema = json!({
578
616
  "type": "object",
579
617
  "x-lix-key": "child_entity",
580
- "x-lix-version": "1",
581
618
  "x-lix-primary-key": ["/id"],
582
619
  "x-lix-foreign-keys": [
583
620
  {
@@ -599,19 +636,14 @@ fn x_lix_foreign_keys_rejects_scope_field() {
599
636
  }
600
637
 
601
638
  #[test]
602
- fn x_lix_foreign_keys_with_composite_key() {
639
+ fn x_lix_state_foreign_keys_with_ordered_state_address_tuple() {
603
640
  let schema = json!({
604
641
  "type": "object",
605
- "x-lix-key": "entity_label",
606
- "x-lix-version": "1",
642
+ "x-lix-key": "label_assignment",
643
+ "x-lix-state-foreign-keys": [
644
+ ["/target_entity_id", "/target_schema_key", "/target_file_id"]
645
+ ],
607
646
  "x-lix-foreign-keys": [
608
- {
609
- "properties": ["/entity_id", "/schema_key", "/file_id"],
610
- "references": {
611
- "schemaKey": "lix_state",
612
- "properties": ["/entity_id", "/schema_key", "/file_id"]
613
- }
614
- },
615
647
  {
616
648
  "properties": ["/label_id"],
617
649
  "references": {
@@ -621,12 +653,16 @@ fn x_lix_foreign_keys_with_composite_key() {
621
653
  }
622
654
  ],
623
655
  "properties": {
624
- "entity_id": { "type": "string" },
625
- "schema_key": { "type": "string" },
626
- "file_id": { "type": "string" },
656
+ "target_entity_id": {
657
+ "type": "array",
658
+ "items": { "type": "string" },
659
+ "minItems": 1
660
+ },
661
+ "target_schema_key": { "type": "string" },
662
+ "target_file_id": { "type": ["string", "null"] },
627
663
  "label_id": { "type": "string" }
628
664
  },
629
- "required": ["entity_id", "schema_key", "file_id", "label_id"],
665
+ "required": ["target_entity_id", "target_schema_key", "target_file_id", "label_id"],
630
666
  "additionalProperties": false
631
667
  });
632
668
 
@@ -634,78 +670,85 @@ fn x_lix_foreign_keys_with_composite_key() {
634
670
  }
635
671
 
636
672
  #[test]
637
- fn x_lix_foreign_keys_rejects_unaliased_lix_table_reference() {
673
+ fn x_lix_state_foreign_keys_rejects_wrong_tuple_order_by_type() {
638
674
  let schema = json!({
639
675
  "type": "object",
640
- "x-lix-key": "entity_label",
641
- "x-lix-version": "1",
642
- "x-lix-foreign-keys": [
643
- {
644
- "properties": ["/entity_id", "/schema_key", "/file_id"],
645
- "references": {
646
- "schemaKey": "state",
647
- "properties": ["/entity_id", "/schema_key", "/file_id"]
648
- }
649
- }
676
+ "x-lix-key": "bad_label_assignment",
677
+ "x-lix-state-foreign-keys": [
678
+ ["/target_schema_key", "/target_entity_id", "/target_file_id"]
650
679
  ],
651
680
  "properties": {
652
- "entity_id": { "type": "string" },
653
- "schema_key": { "type": "string" },
654
- "file_id": { "type": "string" }
655
- },
656
- "required": ["entity_id", "schema_key", "file_id"],
657
- "additionalProperties": false
658
- });
659
-
660
- assert!(validate_lix_schema_definition(&schema).is_err());
661
- }
662
-
663
- #[test]
664
- fn x_version_is_required() {
665
- let schema = json!({
666
- "type": "object",
667
- "x-lix-version": null,
668
- "x-lix-key": "mock",
669
- "properties": {
670
- "name": { "type": "string" }
681
+ "target_entity_id": {
682
+ "type": "array",
683
+ "items": { "type": "string" },
684
+ "minItems": 1
685
+ },
686
+ "target_schema_key": { "type": "string" },
687
+ "target_file_id": { "type": ["string", "null"] }
671
688
  },
672
- "required": ["name"],
689
+ "required": ["target_entity_id", "target_schema_key", "target_file_id"],
673
690
  "additionalProperties": false
674
691
  });
675
692
 
676
- assert!(validate_lix_schema_definition(&schema).is_err());
693
+ let err =
694
+ validate_lix_schema_definition(&schema).expect_err("wrong tuple order should be rejected");
695
+ assert!(
696
+ err.message.contains("[entity_id, schema_key, file_id]"),
697
+ "unexpected error: {err:?}"
698
+ );
677
699
  }
678
700
 
679
701
  #[test]
680
- fn x_version_must_be_monotonic_integer() {
702
+ fn x_lix_state_foreign_keys_requires_address_tuple_properties() {
681
703
  let schema = json!({
682
704
  "type": "object",
683
- "x-lix-version": "v1",
684
- "x-lix-key": "mock",
705
+ "x-lix-key": "optional_label_assignment",
706
+ "x-lix-state-foreign-keys": [
707
+ ["/target_entity_id", "/target_schema_key", "/target_file_id"]
708
+ ],
685
709
  "properties": {
686
- "name": { "type": "string" }
710
+ "target_entity_id": {
711
+ "type": "array",
712
+ "items": { "type": "string" },
713
+ "minItems": 1
714
+ },
715
+ "target_schema_key": { "type": "string" },
716
+ "target_file_id": { "type": ["string", "null"] }
687
717
  },
688
- "required": ["name"],
718
+ "required": ["target_entity_id", "target_schema_key"],
689
719
  "additionalProperties": false
690
720
  });
691
721
 
692
- assert!(validate_lix_schema_definition(&schema).is_err());
722
+ let err = validate_lix_schema_definition(&schema)
723
+ .expect_err("state foreign key tuple fields should be required");
724
+ assert!(
725
+ err.message.contains("file_id") && err.message.contains("must be required"),
726
+ "unexpected error: {err:?}"
727
+ );
693
728
  }
694
729
 
695
730
  #[test]
696
- fn x_version_rejects_leading_zeros() {
731
+ fn x_lix_foreign_keys_treat_schema_keys_literally() {
697
732
  let schema = json!({
698
733
  "type": "object",
699
- "x-lix-version": "01",
700
- "x-lix-key": "mock",
734
+ "x-lix-key": "custom_label_assignment",
735
+ "x-lix-foreign-keys": [
736
+ {
737
+ "properties": ["/label_id"],
738
+ "references": {
739
+ "schemaKey": "label",
740
+ "properties": ["/id"]
741
+ }
742
+ }
743
+ ],
701
744
  "properties": {
702
- "name": { "type": "string" }
745
+ "label_id": { "type": "string" }
703
746
  },
704
- "required": ["name"],
747
+ "required": ["label_id"],
705
748
  "additionalProperties": false
706
749
  });
707
750
 
708
- assert!(validate_lix_schema_definition(&schema).is_err());
751
+ assert!(validate_lix_schema_definition(&schema).is_ok());
709
752
  }
710
753
 
711
754
  #[test]
@@ -713,7 +756,6 @@ fn x_lix_default_accepts_valid_cel_expression() {
713
756
  let schema = json!({
714
757
  "type": "object",
715
758
  "x-lix-key": "mock",
716
- "x-lix-version": "1",
717
759
  "properties": {
718
760
  "id": { "type": "string", "x-lix-default": "lix_uuid_v7()" }
719
761
  },
@@ -728,7 +770,6 @@ fn x_lix_default_rejects_invalid_cel_expression() {
728
770
  let schema = json!({
729
771
  "type": "object",
730
772
  "x-lix-key": "mock",
731
- "x-lix-version": "1",
732
773
  "properties": {
733
774
  "id": { "type": "string", "x-lix-default": "lix_uuid_v7(" }
734
775
  },