rule.spec.js
1 import { db, staff } from './microshaft.db.js' 2 import { $, fact, Memory, Text, Data, same, Link } from './lib.js' 3 4 const mallory = { 5 'Person/name': 'Mallory', 6 } 7 8 const bob = { 9 'Person/name': 'Bob', 10 'Manages/employee': mallory, 11 } 12 13 const alice = { 14 'Person/name': 'Alice', 15 'Manages/employee': bob, 16 } 17 18 /** 19 * @type {import('entail').Suite} 20 */ 21 export const testRules = { 22 'unifying variables': async (assert) => { 23 const result = await same({ this: 1, as: $.q }).query({ from: db }) 24 assert.deepEqual([...result], [{ this: 1, as: 1 }]) 25 26 assert.deepEqual( 27 [...(await same({ this: $.q, as: 2 }).query({ from: db }))], 28 [{ this: 2, as: 2 }] 29 ) 30 31 assert.throws( 32 () => same({ this: $.q, as: $.q2 }).query({ from: db }), 33 /Rule application requires binding for \?this/ 34 ) 35 }, 36 37 'test nested same': async (assert) => { 38 const Counter = fact({ the: 'counter', count: Number }) 39 const Init = Counter.where((counter) => [ 40 Counter.not({ this: counter.this }), 41 Init.claim({ count: 0 }), 42 ]) 43 44 assert.deepEqual(await Init().query({ from: db }), [ 45 Counter.assert({ count: 0 }), 46 ]) 47 }, 48 49 'test basic': async (assert) => { 50 const Person = fact({ the: 'person', name: String }) 51 52 const Alyssa = Person.where((person) => [ 53 Person(person), 54 Data.same({ this: 'Hacker Alyssa P', as: person.name }), 55 ]) 56 57 assert.deepEqual(await Alyssa().query({ from: db }), [ 58 Person.assert({ this: Link.of(staff.alyssa), name: 'Hacker Alyssa P' }), 59 ]) 60 }, 61 62 'test wheel rule': async (assert) => { 63 const Supervisor = fact({ the: 'job', supervisor: Object }) 64 65 const Wheel = fact({ the: 'wheel' }) 66 .with({ manager: Object, employee: Object }) 67 .where(({ this: wheel, manager, employee }) => [ 68 Supervisor({ this: manager, supervisor: wheel }), 69 Supervisor({ this: employee, supervisor: manager }), 70 Wheel.claim({ this: wheel }), 71 ]) 72 73 const Person = fact({ the: 'person', name: String }) 74 75 const Query = Person.where(({ this: wheel, name }) => [ 76 Wheel({ this: wheel }), 77 Person({ this: wheel, name }), 78 ]) 79 80 assert.deepEqual(await Query().query({ from: db }), [ 81 Person.assert({ this: Link.of(staff.oliver), name: 'Warbucks Oliver' }), 82 Person.assert({ this: Link.of(staff.ben), name: 'Bitdiddle Ben' }), 83 ]) 84 }, 85 86 'leaves near': async (assert) => { 87 const Person = fact({ the: 'person', name: String, address: String }) 88 89 const LivesNear = fact({ 90 employee: String, 91 coworker: String, 92 }) 93 .with({ 94 employeeEntity: Object, 95 employeeAddress: String, 96 coworkerEntity: Object, 97 coworkerAddress: String, 98 word: String, 99 pattern: String, 100 }) 101 .where( 102 ({ 103 employee, 104 employeeEntity, 105 employeeAddress, 106 coworker, 107 coworkerEntity, 108 word, 109 coworkerAddress, 110 pattern, 111 }) => [ 112 Person({ 113 this: employeeEntity, 114 address: employeeAddress, 115 name: employee, 116 }), 117 Person({ 118 this: coworkerEntity, 119 address: coworkerAddress, 120 name: coworker, 121 }), 122 Text.Words({ of: employeeAddress, is: word }), 123 Text.Concat({ of: [word, '*'], is: pattern }), 124 Text.match({ this: coworkerAddress, pattern }), 125 Data.same.not({ this: employee, as: coworker }), 126 LivesNear.claim({ employee, coworker }), 127 ] 128 ) 129 130 assert.deepEqual(await LivesNear().query({ from: db }), [ 131 LivesNear.assert({ 132 employee: 'Bitdiddle Ben', 133 coworker: 'Reasoner Louis', 134 }), 135 LivesNear.assert({ employee: 'Bitdiddle Ben', coworker: 'Aull DeWitt' }), 136 LivesNear.assert({ employee: 'Hacker Alyssa P', coworker: 'Fect Cy D' }), 137 LivesNear.assert({ employee: 'Fect Cy D', coworker: 'Hacker Alyssa P' }), 138 LivesNear.assert({ 139 employee: 'Reasoner Louis', 140 coworker: 'Bitdiddle Ben', 141 }), 142 LivesNear.assert({ employee: 'Reasoner Louis', coworker: 'Aull DeWitt' }), 143 LivesNear.assert({ employee: 'Aull DeWitt', coworker: 'Bitdiddle Ben' }), 144 LivesNear.assert({ employee: 'Aull DeWitt', coworker: 'Reasoner Louis' }), 145 ]) 146 }, 147 148 'test rules do not share a scope': async (assert) => { 149 const Person = fact({ the: 'person', name: String }) 150 const Supervisor = fact({ the: 'job', supervisor: Object }) 151 152 const Manager = fact({ 153 name: String, 154 employee: Object, 155 }).where(({ this: manager, employee, name }) => [ 156 Supervisor({ this: employee, supervisor: manager }), 157 Person({ this: manager, name }), 158 ]) 159 160 const Report = fact({ 161 employee: String, 162 supervisor: String, 163 }) 164 .with({ manager: Object, subordinate: Object }) 165 .where(({ employee, supervisor, manager, subordinate }) => [ 166 Manager({ this: manager, employee: subordinate, name: supervisor }), 167 Person({ this: subordinate, name: employee }), 168 Report.claim({ employee, supervisor }), 169 ]) 170 171 assert.deepEqual(await Report().query({ from: db }), [ 172 Report.assert({ 173 employee: 'Scrooge Eben', 174 supervisor: 'Warbucks Oliver', 175 }), 176 Report.assert({ 177 employee: 'Cratchet Robert', 178 supervisor: 'Scrooge Eben', 179 }), 180 Report.assert({ 181 employee: 'Bitdiddle Ben', 182 supervisor: 'Warbucks Oliver', 183 }), 184 Report.assert({ 185 employee: 'Hacker Alyssa P', 186 supervisor: 'Bitdiddle Ben', 187 }), 188 Report.assert({ employee: 'Fect Cy D', supervisor: 'Bitdiddle Ben' }), 189 Report.assert({ employee: 'Tweakit Lem E', supervisor: 'Bitdiddle Ben' }), 190 Report.assert({ 191 employee: 'Reasoner Louis', 192 supervisor: 'Hacker Alyssa P', 193 }), 194 Report.assert({ employee: 'Aull DeWitt', supervisor: 'Warbucks Oliver' }), 195 ]) 196 }, 197 198 'test composite facts': async (assert) => { 199 const Person = fact({ the: 'Person', name: String }) 200 const Manages = fact({ the: 'Manages', employee: Object }) 201 202 const Position = fact({ 203 manager: String, 204 employee: String, 205 }) 206 .with({ subordinate: Object, supervisor: Object }) 207 .where(({ supervisor, manager, subordinate, employee }) => [ 208 Person({ this: subordinate, name: employee }), 209 Person({ this: supervisor, name: manager }), 210 Manages({ this: supervisor, employee: subordinate }), 211 Position.claim({ manager, employee }), 212 ]) 213 214 assert.deepEqual( 215 await Position().query({ from: Memory.create({ alice }) }), 216 [ 217 Position.assert({ manager: 'Alice', employee: 'Bob' }), 218 Position.assert({ manager: 'Bob', employee: 'Mallory' }), 219 ] 220 ) 221 222 assert.deepEqual( 223 await Position.match({ employee: 'Bob' }).query({ 224 from: Memory.create({ alice }), 225 }), 226 [Position.assert({ manager: 'Alice', employee: 'Bob' })] 227 ) 228 }, 229 }