refactor: replace NegationHelper.createNegation with DeltaBuilder.negate
- Remove NegationHelper.createNegation in favor of using DeltaBuilder's fluent API - Update all test cases to use createDelta().negate().buildV1() pattern - Update documentation to reflect the preferred way to create negation deltas - Remove unused isNegationDeltaById helper method
This commit is contained in:
parent
9f27912c4a
commit
f4ea2eca39
@ -21,11 +21,9 @@ describe('Negation System', () => {
|
||||
.setProperty('entity1', 'name', 'Alice')
|
||||
.buildV1();
|
||||
|
||||
const negationDelta = NegationHelper.createNegation(
|
||||
originalDelta.id,
|
||||
'moderator',
|
||||
'host1'
|
||||
);
|
||||
const negationDelta = createDelta('moderator', 'host1')
|
||||
.negate(originalDelta.id)
|
||||
.buildV1();
|
||||
|
||||
expect(negationDelta.creator).toBe('moderator');
|
||||
expect(negationDelta.pointers).toHaveLength(1);
|
||||
@ -42,11 +40,9 @@ describe('Negation System', () => {
|
||||
.setProperty('entity1', 'name', 'Entity 1')
|
||||
.buildV1();
|
||||
|
||||
const negationDelta = NegationHelper.createNegation(
|
||||
'delta-to-negate',
|
||||
'moderator',
|
||||
'host1'
|
||||
);
|
||||
const negationDelta = createDelta('moderator', 'host1')
|
||||
.negate('delta-to-negate')
|
||||
.buildV1();
|
||||
|
||||
expect(NegationHelper.isNegationDelta(regularDelta)).toBe(false);
|
||||
expect(NegationHelper.isNegationDelta(negationDelta)).toBe(true);
|
||||
@ -54,11 +50,9 @@ describe('Negation System', () => {
|
||||
|
||||
it('should extract negated delta ID', () => {
|
||||
const targetDeltaId = 'target-delta-123';
|
||||
const negationDelta = NegationHelper.createNegation(
|
||||
targetDeltaId,
|
||||
'moderator',
|
||||
'host1'
|
||||
);
|
||||
const negationDelta = createDelta('moderator', 'host1')
|
||||
.negate(targetDeltaId)
|
||||
.buildV1();
|
||||
|
||||
const extractedId = NegationHelper.getNegatedDeltaId(negationDelta);
|
||||
expect(extractedId).toBe(targetDeltaId);
|
||||
@ -79,9 +73,9 @@ describe('Negation System', () => {
|
||||
.setProperty('entity1', 'age', 25)
|
||||
.buildV1();
|
||||
|
||||
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
||||
const negation2 = NegationHelper.createNegation(delta1.id, 'mod2', 'host1');
|
||||
const negation3 = NegationHelper.createNegation(delta2.id, 'mod1', 'host1');
|
||||
const negation1 = createDelta('mod1', 'host1').negate(delta1.id).buildV1();
|
||||
const negation2 = createDelta('mod2', 'host1').negate(delta1.id).buildV1();
|
||||
const negation3 = createDelta('mod1', 'host1').negate(delta2.id).buildV1();
|
||||
|
||||
const allDeltas = [delta1, delta2, negation1, negation2, negation3];
|
||||
|
||||
@ -104,7 +98,7 @@ describe('Negation System', () => {
|
||||
.setProperty('entity1', 'age', 25)
|
||||
.buildV1();
|
||||
|
||||
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
||||
const negation1 = createDelta('mod1', 'host1').negate(delta1.id).buildV1();
|
||||
const allDeltas = [delta1, delta2, negation1];
|
||||
|
||||
expect(NegationHelper.isDeltaNegated(delta1.id, allDeltas)).toBe(true);
|
||||
@ -124,8 +118,8 @@ describe('Negation System', () => {
|
||||
.setProperty('entity1', 'email', 'entity1@example.com')
|
||||
.buildV1();
|
||||
|
||||
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
||||
const negation2 = NegationHelper.createNegation(delta2.id, 'mod2', 'host1');
|
||||
const negation1 = createDelta('mod1', 'host1').negate(delta1.id).buildV1();
|
||||
const negation2 = createDelta('mod2', 'host1').negate(delta2.id).buildV1();
|
||||
|
||||
const allDeltas = [delta1, delta2, delta3, negation1, negation2];
|
||||
const filtered = NegationHelper.filterNegatedDeltas(allDeltas);
|
||||
@ -144,7 +138,7 @@ describe('Negation System', () => {
|
||||
.setProperty('entity1', 'age', 25)
|
||||
.buildV1();
|
||||
|
||||
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
||||
const negation1 = createDelta('mod1', 'host1').negate(delta1.id).buildV1();
|
||||
const allDeltas = [delta1, delta2, negation1];
|
||||
|
||||
const stats = NegationHelper.getNegationStats(allDeltas);
|
||||
@ -166,7 +160,7 @@ describe('Negation System', () => {
|
||||
.setProperty('entity1', 'status', 'active')
|
||||
.buildV1();
|
||||
|
||||
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
||||
const negation1 = createDelta('mod1', 'host1').negate(delta1.id).buildV1();
|
||||
negation1.timeCreated = baseTime + 1000; // 1 second later
|
||||
|
||||
const delta2 = createDelta('user1', 'host1')
|
||||
@ -174,7 +168,7 @@ describe('Negation System', () => {
|
||||
.setProperty('entity1', 'status', 'inactive')
|
||||
.buildV1();
|
||||
|
||||
const negation2 = NegationHelper.createNegation(delta2.id, 'mod1', 'host1');
|
||||
const negation2 = createDelta('mod1', 'host1').negate(delta2.id).buildV1();
|
||||
negation2.timeCreated = baseTime + 3000; // 3 seconds later
|
||||
|
||||
const allDeltas = [delta1, negation1, delta2, negation2];
|
||||
@ -193,11 +187,9 @@ describe('Negation System', () => {
|
||||
.buildV1();
|
||||
|
||||
// Create negation delta
|
||||
const negationDelta = NegationHelper.createNegation(
|
||||
originalDelta.id,
|
||||
'moderator',
|
||||
'host1'
|
||||
);
|
||||
const negationDelta = createDelta('moderator', 'host1')
|
||||
.negate(originalDelta.id)
|
||||
.buildV1();
|
||||
|
||||
|
||||
// Create another non-negated delta
|
||||
@ -225,8 +217,8 @@ describe('Negation System', () => {
|
||||
.setProperty('post1', 'content', 'Original content')
|
||||
.buildV1();
|
||||
|
||||
const negation1 = NegationHelper.createNegation(originalDelta.id, 'mod1', 'host1');
|
||||
const negation2 = NegationHelper.createNegation(originalDelta.id, 'mod2', 'host1');
|
||||
const negation1 = createDelta('mod1', 'host1').negate(originalDelta.id).buildV1();
|
||||
const negation2 = createDelta('mod2', 'host1').negate(originalDelta.id).buildV1();
|
||||
|
||||
lossless.ingestDelta(originalDelta);
|
||||
lossless.ingestDelta(negation1);
|
||||
@ -247,7 +239,7 @@ describe('Negation System', () => {
|
||||
.setProperty('article1', 'content', 'Article content')
|
||||
.buildV1();
|
||||
|
||||
const negation1 = NegationHelper.createNegation(delta1.id, 'mod1', 'host1');
|
||||
const negation1 = createDelta('mod1', 'host1').negate(delta1.id).buildV1();
|
||||
|
||||
lossless.ingestDelta(delta1);
|
||||
lossless.ingestDelta(delta2);
|
||||
@ -268,11 +260,7 @@ describe('Negation System', () => {
|
||||
.setProperty('task1', 'status', 'pending')
|
||||
.buildV1();
|
||||
|
||||
const negationDelta = NegationHelper.createNegation(
|
||||
originalDelta.id,
|
||||
'admin',
|
||||
'host1'
|
||||
);
|
||||
const negationDelta = createDelta('admin', 'host1').negate(originalDelta.id).buildV1();
|
||||
|
||||
lossless.ingestDelta(originalDelta);
|
||||
lossless.ingestDelta(negationDelta);
|
||||
@ -299,7 +287,7 @@ describe('Negation System', () => {
|
||||
.buildV1();
|
||||
|
||||
// Create negation delta in same transaction
|
||||
const negationDelta = NegationHelper.createNegation(originalDelta.id, 'moderator', 'host1');
|
||||
const negationDelta = createDelta('moderator', 'host1').negate(originalDelta.id).buildV1();
|
||||
negationDelta.pointers.unshift({
|
||||
localContext: '_transaction',
|
||||
target: transactionId,
|
||||
@ -324,7 +312,7 @@ describe('Negation System', () => {
|
||||
.buildV1();
|
||||
|
||||
// Moderator negates it
|
||||
const negationDelta = NegationHelper.createNegation(postDelta.id, 'moderator', 'host1');
|
||||
const negationDelta = createDelta('moderator', 'host1').negate(postDelta.id).buildV1();
|
||||
negationDelta.timeCreated = baseTime + 1000;
|
||||
|
||||
// User edits content (after negation)
|
||||
@ -351,11 +339,7 @@ describe('Negation System', () => {
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle negation of non-existent deltas', () => {
|
||||
const negationDelta = NegationHelper.createNegation(
|
||||
'non-existent-delta-id',
|
||||
'moderator',
|
||||
'host1'
|
||||
);
|
||||
const negationDelta = createDelta('moderator', 'host1').negate('non-existent-delta-id').buildV1();
|
||||
|
||||
lossless.ingestDelta(negationDelta);
|
||||
|
||||
@ -371,7 +355,7 @@ describe('Negation System', () => {
|
||||
.setProperty('node1', 'child', 'node1') // Self-reference
|
||||
.buildV1();
|
||||
|
||||
const negationDelta = NegationHelper.createNegation(selfRefDelta.id, 'admin', 'host1');
|
||||
const negationDelta = createDelta('admin', 'host1').negate(selfRefDelta.id).buildV1();
|
||||
|
||||
lossless.ingestDelta(selfRefDelta);
|
||||
lossless.ingestDelta(negationDelta);
|
||||
@ -390,8 +374,8 @@ describe('Negation System', () => {
|
||||
.buildV1();
|
||||
|
||||
// Create two negations of the same delta
|
||||
const negation1 = NegationHelper.createNegation(originalDelta.id, 'user2', 'host1');
|
||||
const negation2 = NegationHelper.createNegation(originalDelta.id, 'user3', 'host1');
|
||||
const negation1 = createDelta('user2', 'host1').negate(originalDelta.id).buildV1();
|
||||
const negation2 = createDelta('user3', 'host1').negate(originalDelta.id).buildV1();
|
||||
|
||||
// Process all deltas
|
||||
testLossless.ingestDelta(originalDelta);
|
||||
@ -421,9 +405,9 @@ describe('Negation System', () => {
|
||||
.buildV1();
|
||||
|
||||
// Create a chain of negations: B negates A, C negates B, D negates C
|
||||
const deltaB = NegationHelper.createNegation(deltaA.id, 'user2', 'host1');
|
||||
const deltaC = NegationHelper.createNegation(deltaB.id, 'user3', 'host1');
|
||||
const deltaD = NegationHelper.createNegation(deltaC.id, 'user4', 'host1');
|
||||
const deltaB = createDelta('user2', 'host1').negate(deltaA.id).buildV1();
|
||||
const deltaC = createDelta('user3', 'host1').negate(deltaB.id).buildV1();
|
||||
const deltaD = createDelta('user4', 'host1').negate(deltaC.id).buildV1();
|
||||
|
||||
debug('Delta A (original): %s', deltaA.id);
|
||||
debug('Delta B (negates A): %s', deltaB.id);
|
||||
@ -498,8 +482,8 @@ describe('Negation System', () => {
|
||||
.buildV1();
|
||||
|
||||
// Create negations for both deltas
|
||||
const negation1 = NegationHelper.createNegation(delta1.id, 'user3', 'host1');
|
||||
const negation2 = NegationHelper.createNegation(delta2.id, 'user4', 'host1');
|
||||
const negation1 = createDelta('user3', 'host1').negate(delta1.id).buildV1();
|
||||
const negation2 = createDelta('user4', 'host1').negate(delta2.id).buildV1();
|
||||
|
||||
// Process all deltas
|
||||
testLossless.ingestDelta(delta1);
|
||||
|
83
delta-patterns/summary.md
Normal file
83
delta-patterns/summary.md
Normal file
@ -0,0 +1,83 @@
|
||||
# Delta Patterns in Rhizome-Node
|
||||
|
||||
This document outlines the distinct delta patterns identified in the Rhizome-Node test suite.
|
||||
|
||||
## 1. Basic Entity Creation
|
||||
```typescript
|
||||
createDelta('creator', 'host')
|
||||
.setProperty('entity1', 'name', 'Alice', 'user')
|
||||
.buildV1();
|
||||
```
|
||||
|
||||
## 2. Relationship Creation
|
||||
```typescript
|
||||
createDelta('creator', 'host')
|
||||
.addPointer('users', 'alice', 'friends')
|
||||
.addPointer('friend', 'bob')
|
||||
.addPointer('type', 'friendship')
|
||||
.buildV1();
|
||||
```
|
||||
|
||||
## 3. Transaction-Enabled Deltas
|
||||
```typescript
|
||||
createDelta('user1', 'host1')
|
||||
.inTransaction('tx123')
|
||||
.setProperty('doc1', 'status', 'draft')
|
||||
.buildV1();
|
||||
```
|
||||
|
||||
## 4. Negation Deltas
|
||||
```typescript
|
||||
// Creating a negation delta
|
||||
const delta = createDelta('user1', 'host1').buildV1();
|
||||
const negation = createDelta('moderator', 'host1').negate(delta.id).buildV1();
|
||||
```
|
||||
|
||||
## 5. Temporal Deltas
|
||||
```typescript
|
||||
createDelta('user1', 'host1')
|
||||
.withTimestamp(1624233600000)
|
||||
.setProperty('entity1', 'score', 100, 'game')
|
||||
.buildV1();
|
||||
```
|
||||
|
||||
## 6. Multi-Property Deltas
|
||||
```typescript
|
||||
createDelta('user1', 'host1')
|
||||
.setProperty('entity1', 'title', 'Hello World', 'post')
|
||||
.setProperty('entity1', 'content', 'This is a test', 'post')
|
||||
.setProperty('entity1', 'published', true, 'post')
|
||||
.buildV1();
|
||||
```
|
||||
|
||||
## 7. Reference-Only Deltas
|
||||
```typescript
|
||||
createDelta('system', 'host1')
|
||||
.addPointer('posts', 'post1', 'recent')
|
||||
.buildV1();
|
||||
```
|
||||
|
||||
## 8. Bulk Operation Deltas
|
||||
```typescript
|
||||
// Multiple entities in a single delta
|
||||
createDelta('batch', 'host1')
|
||||
.setProperty('user1', 'status', 'active', 'user')
|
||||
.setProperty('user2', 'status', 'inactive', 'user')
|
||||
.buildV1();
|
||||
```
|
||||
|
||||
## 9. Versioned Deltas
|
||||
```typescript
|
||||
// V1 format
|
||||
createDelta('a', 'h').buildV1();
|
||||
// V2 format
|
||||
createDelta('a', 'h').buildV2();
|
||||
```
|
||||
|
||||
## Key Observations
|
||||
- Most deltas follow a fluent builder pattern
|
||||
- Deltas can be composed of multiple operations (setProperty, addPointer, etc.)
|
||||
- Support for both V1 and V2 delta formats
|
||||
- Strong typing and schema validation is commonly used
|
||||
- Transaction support is built into the delta creation process
|
||||
- Temporal aspects can be explicitly controlled
|
90
docs/schema-validation.md
Normal file
90
docs/schema-validation.md
Normal file
@ -0,0 +1,90 @@
|
||||
# Schema Validation in Rhizome-Node
|
||||
|
||||
This document explains how schema validation works with deltas in Rhizome-Node.
|
||||
|
||||
## Overview
|
||||
|
||||
Schema validation in Rhizome-Node is enforced at the `TypedCollection` level when using the `put` method, which validates data before creating deltas. This means:
|
||||
|
||||
1. **Local Changes**: When you use `collection.put()`, the data is validated against the schema before any deltas are created and ingested.
|
||||
2. **Peer Changes**: Deltas received from other peers are ingested without validation by default, which means invalid data can enter the system.
|
||||
3. **Validation Tracking**: The system tracks which entities are valid/invalid after ingestion.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```typescript
|
||||
// 1. Define a schema for users
|
||||
const userSchema = SchemaBuilder
|
||||
.create('user')
|
||||
.name('User')
|
||||
.property('name', PrimitiveSchemas.requiredString())
|
||||
.property('email', PrimitiveSchemas.email())
|
||||
.property('age', PrimitiveSchemas.integer({ minimum: 0 }))
|
||||
.required('name')
|
||||
.build();
|
||||
|
||||
// 2. Create a typed collection with strict validation
|
||||
const collection = new TypedCollectionImpl<{
|
||||
name: string;
|
||||
email?: string;
|
||||
age?: number;
|
||||
}>('users', userSchema, schemaRegistry, {
|
||||
strictValidation: true // Enable strict validation
|
||||
});
|
||||
|
||||
// Connect to the node
|
||||
collection.rhizomeConnect(node);
|
||||
|
||||
// 3. Local changes - validated on put()
|
||||
// Valid usage - will pass schema validation
|
||||
await collection.put('user1', {
|
||||
name: 'Alice',
|
||||
email: 'alice@example.com',
|
||||
age: 30
|
||||
});
|
||||
|
||||
// Invalid usage - will throw SchemaValidationError
|
||||
await expect(collection.put('user2', {
|
||||
email: 'invalid-email', // Invalid email format
|
||||
age: -5 // Negative age
|
||||
})).rejects.toThrow(SchemaValidationError);
|
||||
|
||||
// 4. Peer data - ingested without validation by default
|
||||
const unsafeDelta = createDelta('peer1', 'peer1')
|
||||
.setProperty('user3', 'name', 'Bob', 'users')
|
||||
.setProperty('user3', 'age', 'not-a-number', 'users')
|
||||
.buildV1();
|
||||
|
||||
// This will be ingested without validation
|
||||
node.lossless.ingestDelta(unsafeDelta);
|
||||
|
||||
// 5. Check validation status after the fact
|
||||
const stats = collection.getValidationStats();
|
||||
console.log(`Valid: ${stats.validEntities}, Invalid: ${stats.invalidEntities}`);
|
||||
|
||||
// Get details about invalid entities
|
||||
const invalidUsers = collection.getInvalidEntities();
|
||||
invalidUsers.forEach(user => {
|
||||
console.log(`User ${user.entityId} is invalid:`, user.errors);
|
||||
});
|
||||
```
|
||||
|
||||
## Key Points
|
||||
|
||||
### Validation Timing
|
||||
- Schema validation happens in `TypedCollection.put()` before deltas are created
|
||||
- Deltas from peers are ingested without validation by default
|
||||
|
||||
### Validation Modes
|
||||
- `strictValidation: true`: Throws errors on invalid data (recommended for local changes)
|
||||
- `strictValidation: false`: Allows invalid data but tracks it (default)
|
||||
|
||||
### Monitoring
|
||||
- Use `getValidationStats()` to get counts of valid/invalid entities
|
||||
- Use `getInvalidEntities()` to get detailed error information
|
||||
|
||||
### Best Practices
|
||||
- Always validate data before creating deltas when accepting external input
|
||||
- Use `strictValidation: true` for collections where data integrity is critical
|
||||
- Monitor validation statistics in production to detect data quality issues
|
||||
- Consider implementing a validation layer for peer data if needed
|
@ -14,23 +14,6 @@ export interface NegationPointer {
|
||||
|
||||
// Helper functions for creating and identifying negation deltas
|
||||
export class NegationHelper {
|
||||
|
||||
/**
|
||||
* Create a negation delta that negates another delta
|
||||
*/
|
||||
static createNegation(
|
||||
deltaToNegate: DeltaID,
|
||||
creator: CreatorID,
|
||||
host: HostID
|
||||
): Delta {
|
||||
const negationDelta = createDelta(creator, host)
|
||||
.negate(deltaToNegate)
|
||||
.buildV1();
|
||||
|
||||
debug(`Created negation delta ${negationDelta.id} negating ${deltaToNegate}`);
|
||||
return negationDelta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a delta is a negation delta
|
||||
*/
|
||||
@ -361,14 +344,6 @@ export class NegationHelper {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to check if a delta with the given ID is a negation delta
|
||||
*/
|
||||
private static isNegationDeltaById(deltaId: DeltaID, deltas: Delta[]): boolean {
|
||||
const delta = deltas.find(d => d.id === deltaId);
|
||||
return delta ? this.isNegationDelta(delta) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply negations to a delta stream in chronological order
|
||||
* Later negations can override earlier ones
|
||||
|
Loading…
x
Reference in New Issue
Block a user