@ibgib/ts-gib 0.4.9

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 (87) hide show
  1. package/.vscode/launch.json +24 -0
  2. package/.vscode/settings.json +34 -0
  3. package/.vscode/tasks.json +37 -0
  4. package/CHANGELOG.md +159 -0
  5. package/README.md +502 -0
  6. package/dist/V1/constants.d.mts +22 -0
  7. package/dist/V1/constants.d.mts.map +1 -0
  8. package/dist/V1/constants.mjs +21 -0
  9. package/dist/V1/constants.mjs.map +1 -0
  10. package/dist/V1/factory.d.mts +23 -0
  11. package/dist/V1/factory.d.mts.map +1 -0
  12. package/dist/V1/factory.mjs +78 -0
  13. package/dist/V1/factory.mjs.map +1 -0
  14. package/dist/V1/index.d.mts +6 -0
  15. package/dist/V1/index.d.mts.map +1 -0
  16. package/dist/V1/index.mjs +6 -0
  17. package/dist/V1/index.mjs.map +1 -0
  18. package/dist/V1/sha256v1.d.mts +19 -0
  19. package/dist/V1/sha256v1.d.mts.map +1 -0
  20. package/dist/V1/sha256v1.mjs +86 -0
  21. package/dist/V1/sha256v1.mjs.map +1 -0
  22. package/dist/V1/transforms/fork.d.mts +16 -0
  23. package/dist/V1/transforms/fork.d.mts.map +1 -0
  24. package/dist/V1/transforms/fork.mjs +111 -0
  25. package/dist/V1/transforms/fork.mjs.map +1 -0
  26. package/dist/V1/transforms/index.d.mts +5 -0
  27. package/dist/V1/transforms/index.d.mts.map +1 -0
  28. package/dist/V1/transforms/index.mjs +5 -0
  29. package/dist/V1/transforms/index.mjs.map +1 -0
  30. package/dist/V1/transforms/mut8.d.mts +50 -0
  31. package/dist/V1/transforms/mut8.d.mts.map +1 -0
  32. package/dist/V1/transforms/mut8.mjs +246 -0
  33. package/dist/V1/transforms/mut8.mjs.map +1 -0
  34. package/dist/V1/transforms/rel8.d.mts +14 -0
  35. package/dist/V1/transforms/rel8.d.mts.map +1 -0
  36. package/dist/V1/transforms/rel8.mjs +176 -0
  37. package/dist/V1/transforms/rel8.mjs.map +1 -0
  38. package/dist/V1/transforms/transform-helper.d.mts +92 -0
  39. package/dist/V1/transforms/transform-helper.d.mts.map +1 -0
  40. package/dist/V1/transforms/transform-helper.mjs +189 -0
  41. package/dist/V1/transforms/transform-helper.mjs.map +1 -0
  42. package/dist/V1/types.d.mts +79 -0
  43. package/dist/V1/types.d.mts.map +1 -0
  44. package/dist/V1/types.mjs +12 -0
  45. package/dist/V1/types.mjs.map +1 -0
  46. package/dist/helper.d.mts +77 -0
  47. package/dist/helper.d.mts.map +1 -0
  48. package/dist/helper.mjs +179 -0
  49. package/dist/helper.mjs.map +1 -0
  50. package/dist/index.cjs +4 -0
  51. package/dist/index.cjs.map +1 -0
  52. package/dist/index.d.cts +4 -0
  53. package/dist/index.d.cts.map +1 -0
  54. package/dist/index.d.mts +4 -0
  55. package/dist/index.d.mts.map +1 -0
  56. package/dist/index.mjs +4 -0
  57. package/dist/index.mjs.map +1 -0
  58. package/dist/types.d.mts +242 -0
  59. package/dist/types.d.mts.map +1 -0
  60. package/dist/types.mjs +2 -0
  61. package/dist/types.mjs.map +1 -0
  62. package/jasmine-browser.json +18 -0
  63. package/jasmine.json +6 -0
  64. package/package.json +61 -0
  65. package/src/V1/constants.mts +23 -0
  66. package/src/V1/factory.mts +110 -0
  67. package/src/V1/factory.spec.mts +162 -0
  68. package/src/V1/index.mts +5 -0
  69. package/src/V1/sha256v1.mts +85 -0
  70. package/src/V1/sha256v1.spec.mts +221 -0
  71. package/src/V1/transforms/fork.mts +100 -0
  72. package/src/V1/transforms/fork.spec.mts +410 -0
  73. package/src/V1/transforms/index.mts +4 -0
  74. package/src/V1/transforms/mut8.mts +239 -0
  75. package/src/V1/transforms/mut8.spec.mts +656 -0
  76. package/src/V1/transforms/rel8.mts +173 -0
  77. package/src/V1/transforms/rel8.spec.mts +556 -0
  78. package/src/V1/transforms/transform-helper.mts +263 -0
  79. package/src/V1/transforms/transform-helper.spec.mts +45 -0
  80. package/src/V1/types.mts +84 -0
  81. package/src/helper.mts +192 -0
  82. package/src/helper.spec.mts +127 -0
  83. package/src/index.cts +3 -0
  84. package/src/index.mts +3 -0
  85. package/src/types.mts +242 -0
  86. package/tsconfig.json +15 -0
  87. package/tsconfig.test.json +10 -0
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Test basic fork transforms.
3
+ *
4
+ * A 'fork' transform creates a "new" ibgib datum by taking an existing
5
+ * source ibgib record (src), clones it, appends the source to the list
6
+ * of ancestors, and clears the ibgibs past (if any).
7
+ *
8
+ * Sometimes you want a thing to be unique when it is created (forked).
9
+ * When this is done, you give that first address a special name, kinda
10
+ * like the OPPOSITE of a HEAD revision. This is called the temporal
11
+ * junction point (from Back to the Future II), or just tjp. It's like
12
+ * the "name" of the ibgib's timeline.
13
+ *
14
+ * NOTE:
15
+ * This only tests the node implementation, and manual testing
16
+ * is required for browser until I get some kind of browser testing
17
+ * going.
18
+ */
19
+ import { IbGib_V1, IbGibRel8ns_V1, Rel8n } from '../types.mjs';
20
+ import { fork } from './fork.mjs';
21
+ import { TransformOpts_Fork, IbGibAddr, Ib, TransformResult } from '../../types.mjs';
22
+ import { pretty, clone, delay, getIbGibAddr } from '../../helper.mjs';
23
+ import { ROOT, ROOT_ADDR } from '../constants.mjs';
24
+ import { mut8 } from './mut8.mjs';
25
+ import { getGib } from './transform-helper.mjs';
26
+
27
+ describe(`when forking the root`, () => {
28
+
29
+ const src = ROOT;
30
+
31
+ it(`should create a new ibgib`, async () => {
32
+ const { newIbGib } = await fork({ src });
33
+ expect(newIbGib).toBeTruthy();
34
+ });
35
+
36
+ it(`should have no rel8ns (because forked from the root)`, async () => {
37
+ const { newIbGib } = await fork({ src });
38
+ expect(newIbGib.rel8ns).toBeUndefined();
39
+ });
40
+
41
+ describe(`should respect param`, () => {
42
+ it(`noTimestamp`, async () => {
43
+ const { newIbGib } = await fork({ src, noTimestamp: true });
44
+ if (newIbGib?.data) {
45
+ expect(newIbGib.data!.timestamp).toBeUndefined();
46
+ } else {
47
+ // data being falsy is passing so no expect statement needed
48
+ }
49
+ });
50
+
51
+ it(`destIb`, async () => {
52
+ // just an example, ib can be any value/metadata per use case
53
+ // in this example, we have a canonical form tag [tagName]
54
+ // this way, we can just pass around the address (tag like ^ABCD123)
55
+ // and operate on it (the metadata) without having to send the
56
+ // entire data record.
57
+ const destIb = 'tag like';
58
+ const { newIbGib } = await fork({ src, destIb });
59
+ expect(newIbGib.ib).toEqual(destIb);
60
+ });
61
+
62
+ it(`dna`, async () => {
63
+ // NOTE: more extensive dna testing is below in other tests
64
+ const destIb = "This will be the new ib";
65
+ const { newIbGib, dnas } = await fork({ src, dna: true, destIb });
66
+ expect(dnas).toBeTruthy();
67
+ });
68
+
69
+ it(`uuid`, async () => {
70
+ const { newIbGib } = await fork({ src, uuid: true });
71
+ expect(newIbGib?.data).toBeTruthy();
72
+ expect(newIbGib!.data?.uuid).toBeTruthy();
73
+ });
74
+
75
+ describe(`tjp...`, () => {
76
+ it(`timestamp`, async () => {
77
+ const { newIbGib } = await fork({ src, tjp: { timestamp: true } });
78
+ expect(newIbGib?.data).toBeTruthy();
79
+ expect(newIbGib!.data?.timestamp).toBeTruthy();
80
+ const testDate = new Date(newIbGib!.data!.timestamp!);
81
+ expect(testDate).toBeTruthy();
82
+ expect(testDate).toBeTruthy();
83
+ expect(testDate.toString()).not.toEqual("Invalid Date");
84
+ // counting on environment (node) to be consistent with invalid dates in the future
85
+ const invalidDate = new Date("asdf");
86
+ expect(invalidDate.toString()).toEqual("Invalid Date");
87
+ expect(newIbGib!.data?.isTjp).withContext("isTjp").toBeTrue();
88
+ });
89
+ it(`uuid`, async () => {
90
+ const { newIbGib } = await fork({ src, tjp: { uuid: true } });
91
+ expect(newIbGib?.data).toBeTruthy();
92
+ expect(newIbGib!.data?.uuid).toBeTruthy();
93
+ expect(newIbGib!.data?.isTjp).withContext("isTjp").toBeTrue()
94
+ });
95
+ it(`timestamp && uuid`, async () => {
96
+ const { newIbGib } = await fork({ src, tjp: { timestamp: true, uuid: true } });
97
+ expect(newIbGib?.data).toBeTruthy();
98
+ expect(newIbGib!.data?.timestamp).toBeTruthy();
99
+ const testDate = new Date(newIbGib!.data!.timestamp!);
100
+ expect(testDate).toBeTruthy();
101
+ expect(testDate).toBeTruthy();
102
+ expect(testDate.toString()).not.toEqual("Invalid Date");
103
+ expect(newIbGib!.data?.uuid).toBeTruthy();
104
+ expect(newIbGib!.data?.isTjp).withContext("isTjp").toBeTrue()
105
+ });
106
+ });
107
+
108
+ it(`nCounter`, async () => {
109
+ const { newIbGib } = await fork({ src, nCounter: true });
110
+ expect(newIbGib?.data).toBeTruthy();
111
+ expect(newIbGib!.data?.n).toEqual(0);
112
+ });
113
+
114
+
115
+ it(`cloneRel8ns (setting should be ignored since forking root)`, async () => {
116
+ const { newIbGib } = await fork({ src, cloneRel8ns: true, noTimestamp: true });
117
+ expect(newIbGib?.rel8ns).toBeUndefined();
118
+ });
119
+
120
+ it(`cloneData (setting should be ignored since forking root)`, async () => {
121
+ const { newIbGib } = await fork({ src, cloneData: true, noTimestamp: true });
122
+ expect(newIbGib?.data).toBeUndefined();
123
+ });
124
+ });
125
+
126
+ describe(`...and creating dna`, () => {
127
+ it(`should have well-formed dna, like...`, async () => {
128
+ const destIb = "This will be the new ib";
129
+ const { newIbGib, intermediateIbGibs, dnas } = await fork({ src, dna: true, destIb });
130
+ const forkDna = dnas![0];
131
+
132
+ expect(forkDna.ib).withContext(`ib should be fork`).toEqual('fork');
133
+
134
+ let ibGibs = [newIbGib, ...(intermediateIbGibs ?? []), ...(dnas ?? [])];
135
+ for (let i = 0; i < ibGibs.length; i++) {
136
+ const ibGib = ibGibs[i];
137
+ let gottenGib = await getGib({ ibGib });
138
+ expect(ibGib.gib).withContext(`should have a gib that is corroborated with getGib`).toEqual(gottenGib);
139
+ }
140
+
141
+ expect(forkDna?.rel8ns).withContext(`should descend from fork^gib primitive`).toBeTruthy();
142
+ const forkDnaRel8ns: IbGibRel8ns_V1 = forkDna?.rel8ns!;
143
+ expect(forkDnaRel8ns.ancestor).withContext(`should descend from fork^gib primitive`).toBeTruthy();
144
+ expect(forkDnaRel8ns.ancestor!.length).withContext(`should descend from fork^gib primitive`).toEqual(1);
145
+ expect(forkDnaRel8ns.ancestor![0]).withContext(`should descend from fork^gib primitive`).toEqual('fork^gib');
146
+
147
+ const forkDnaData = (forkDna.data as TransformOpts_Fork<IbGib_V1>)!;
148
+
149
+ expect(forkDna.data).withContext(`should have well-formed common transform opts`).toBeTruthy();
150
+ expect(forkDnaData.dna).withContext(`should have well-formed common transform opts`).toBeTrue()
151
+ expect(forkDnaData.srcAddr).withContext(`should have well-formed common transform opts`).toBeUndefined();
152
+ expect(forkDnaData.src).withContext(`should have well-formed common transform opts`).toBeUndefined();
153
+
154
+ expect(forkDnaData.type).withContext(`should have well-formed data specific to fork transform`).toEqual("fork");
155
+ expect(forkDnaData.destIb).withContext(`should have well-formed data specific to fork transform`).toEqual(destIb);
156
+
157
+ // we're going to do another fork with the same options gotten from the dna
158
+ // (kinda like translating a foreign language translation back into the
159
+ // original language and making sure it says the same thing)
160
+ // we should produce an entirely new ibGib, because the first newIbGib was timestamped.
161
+ // but the dna "produced" (recreated) should be exactly the same as our initial dna.
162
+
163
+ // timestamp should be different (at least 1 second) making newIbGib2 unique
164
+ await delay(1100);
165
+
166
+ // NOTE: we're getting the new fork args from the **generated dna**, NOT from
167
+ // our initial fork args. This ensures that we're saving all of the required
168
+ // data in the dna record.
169
+ const forkOpts2: TransformOpts_Fork<IbGib_V1> = clone(forkDnaData);
170
+ forkOpts2.src = ROOT;
171
+ const { newIbGib: newIbGib2, dnas: dnas2 } = await fork(forkOpts2);
172
+ expect(newIbGib2).withContext(`should produce pure dna function, and non-unique dna (without timestamps, tjp, or uuid) (1s delay)`).toBeTruthy();
173
+ expect(dnas2).withContext(`should produce pure dna function, and non-unique dna (without timestamps, tjp, or uuid) (1s delay)`).toBeTruthy();
174
+ const forkDna2 = dnas2![0];
175
+
176
+ // dna itself should be exactly the same
177
+ expect(forkDna2).withContext(`should produce pure dna function, and non-unique dna (without timestamps, tjp, or uuid) (1s delay)`).toEqual(forkDna);
178
+
179
+ // the ibGibs **created** should NOT be the same because of timestamping
180
+ // (and later on, other factors would change like identity and other rel8ns)
181
+ expect(newIbGib2.gib).withContext(`should produce pure dna function, and non-unique dna (without timestamps, tjp, or uuid) (1s delay)`).not.toEqual(newIbGib.gib);
182
+
183
+ // when forking using the same options, the ib should be the same
184
+ expect(newIbGib2.ib).withContext(`should produce pure dna function, and non-unique dna (without timestamps, tjp, or uuid) (1s delay)`).toEqual(newIbGib.ib);
185
+ });
186
+ }); // dna
187
+
188
+ });
189
+
190
+
191
+ describe(`when forking a regular ibgib`, () => {
192
+
193
+ let src: IbGib_V1;
194
+ let srcAddr: IbGibAddr;
195
+
196
+ beforeEach(async () => {
197
+ // This will be the src we're working off of.
198
+ const beforeResult = await fork({ src: ROOT, uuid: true });
199
+ src = beforeResult.newIbGib;
200
+
201
+ expect(src).toBeTruthy();
202
+ });
203
+
204
+ it(`should create a new ibgib`, async () => {
205
+ const { newIbGib } = await fork({ src });
206
+ expect(newIbGib).toBeTruthy();
207
+ });
208
+
209
+ it(`should have src ib^gib address in ancestor rel8n`, async () => {
210
+ const { newIbGib } = await fork({ src });
211
+ expect(newIbGib?.rel8ns).toBeTruthy();
212
+ expect(newIbGib?.rel8ns).toBeTruthy();
213
+ expect(newIbGib?.rel8ns!.ancestor).toBeTruthy();
214
+ expect(newIbGib?.rel8ns!.ancestor).toBeTruthy();
215
+ expect(newIbGib?.rel8ns!.ancestor!.length).toEqual(1);
216
+ srcAddr = getIbGibAddr({ ibGib: src });
217
+ expect(newIbGib?.rel8ns!.ancestor![0]).toEqual(srcAddr);
218
+ });
219
+
220
+ // TODO: TEST FORK cloneRel8ns option ONCE REL8 IS IMPLEMENTED
221
+ // it(`cloneRel8ns`, async () => {
222
+ // const testData = {x: 1, y: 2, deeper: { zzz: 333 }};
223
+ // const { newIbGib: grandparent } = await fork({ src, cloneData: true});
224
+ // const { newIbGib: parent } = await mut8({ src: grandparent, dataToAddOrPatch: testData });
225
+ // const { newIbGib } = await fork({ src: parent, cloneData: true});
226
+
227
+ // expect(newIbGib).toBeTruthy();
228
+ // expect(newIbGib!.rel8ns).toBeTruthy();
229
+ // expect(newIbGib!.rel8ns).toBeTruthy();
230
+ // expect(newIbGib!.rel8ns!.ancestor).toBeTruthy();
231
+ // expect(newIbGib!.rel8ns!.ancestor).toBeTruthy();
232
+ // const parentAddr = getIbGibAddr({ibGib: parent});
233
+ // expect(newIbGib!.rel8ns!.ancestor![newIbGib!.rel8ns!.ancestor!.length-1]).toEqual(parentAddr);
234
+ // expect(newIbGib!.data).toBeTruthy();
235
+ // expect(newIbGib!.data).toEqual(testData);
236
+ // });
237
+
238
+ it(`cloneData`, async () => {
239
+ // adding uuid/timestamp for testing hack
240
+ // need to reconsider inability to rename/remove timestamp data
241
+ // seems silly since bad actors can always do this and security is
242
+ // really through other means.
243
+ const testData = { x: 1, y: 2, deeper: { zzz: 333 }, uuid: 'tbd', timestamp: 'tbd' };
244
+ const { newIbGib: grandparent } = await fork({ src, cloneData: true, noTimestamp: true });
245
+ testData.uuid = grandparent.data!.uuid!;
246
+ testData.timestamp = grandparent.data!.timestamp!;
247
+ const { newIbGib: parent } =
248
+ await mut8({
249
+ src: grandparent,
250
+ dataToAddOrPatch: testData,
251
+ noTimestamp: true
252
+ });
253
+ const { newIbGib } = await fork({ src: parent, cloneData: true, noTimestamp: true });
254
+
255
+ expect(newIbGib).toBeTruthy();
256
+ expect(newIbGib!.rel8ns).toBeTruthy();
257
+ expect(newIbGib!.rel8ns).toBeTruthy();
258
+ expect(newIbGib!.rel8ns!.ancestor).toBeTruthy();
259
+ expect(newIbGib!.rel8ns!.ancestor).toBeTruthy();
260
+ const parentAddr = getIbGibAddr({ ibGib: parent });
261
+ expect(newIbGib!.rel8ns!.ancestor![newIbGib!.rel8ns!.ancestor!.length - 1]).toEqual(parentAddr);
262
+ expect(newIbGib!.data).toBeTruthy();
263
+ expect(newIbGib!.data).toEqual(testData);
264
+ });
265
+
266
+ // it(`should clone data`, async () => {
267
+ // });
268
+ });
269
+
270
+ describe(`when forking multiple regular ibgibs, NON LINKED REL8NS`, () => {
271
+
272
+ const ibs: Ib[] = ["a", "b", "c", "d"];
273
+ let srcForks: { [ib: string]: TransformResult<IbGib_V1> } = {};
274
+ let srcIbGibs: { [ib: string]: IbGib_V1 } = {};
275
+
276
+ beforeEach(async () => {
277
+ // These will be the srcs we're working off of.
278
+ // a forked from root, b forked from a, etc.
279
+ let prevIbGib: IbGib_V1 | undefined;
280
+ for (let ib of ibs) {
281
+ const forkResult = await fork({ src: prevIbGib || ROOT, destIb: ib });
282
+ srcForks[ib] = forkResult;
283
+ srcIbGibs[ib] = forkResult.newIbGib;
284
+ prevIbGib = forkResult.newIbGib;
285
+ }
286
+ });
287
+ afterEach(() => {
288
+ srcForks = {};
289
+ srcIbGibs = {};
290
+ })
291
+
292
+ it(`should create a new ibgib`, async () => {
293
+ ibs.forEach(ib => {
294
+ const ibGib = srcIbGibs[ib];
295
+ expect(ibGib).toBeTruthy();
296
+ expect(ibGib).toBeTruthy();
297
+ expect(ibGib.ib).toEqual(ib);
298
+ })
299
+ });
300
+
301
+ it(`should have src ib^gib (address) in ancestor rel8n`, async () => {
302
+ const getPrev: (ib: Ib) => IbGib_V1 | null = (ib) => {
303
+ switch (ib) {
304
+ case "a": return null;
305
+ case "b": return srcIbGibs.a;
306
+ case "c": return srcIbGibs.b;
307
+ case "d": return srcIbGibs.c;
308
+ default: throw new Error("unknown ib")
309
+ }
310
+ };
311
+ // the first one forks from the root, so the rel8ns should be undefined which is tested elsewhere
312
+ ibs.filter(ib => ib !== "a").forEach(ib => {
313
+ const newIbGib = srcIbGibs[ib]!;
314
+ const prevIbGib = getPrev(ib)!;
315
+ expect(newIbGib?.rel8ns).toBeTruthy();
316
+ expect(newIbGib?.rel8ns).toBeTruthy();
317
+ expect(newIbGib?.rel8ns!.ancestor).toBeTruthy();
318
+ expect(newIbGib?.rel8ns!.ancestor).toBeTruthy();
319
+ expect(newIbGib?.rel8ns!.ancestor!.length).toBeGreaterThan(0);
320
+ const prevIbGibAddr = getIbGibAddr({ ibGib: prevIbGib });
321
+ expect(newIbGib?.rel8ns!.ancestor![newIbGib!.rel8ns!.ancestor!.length - 1]).toEqual(prevIbGibAddr);
322
+ })
323
+ });
324
+
325
+ // TODO: need to implement mut8 before the clone data can be tested
326
+ // it(`should clone data`, async () => {
327
+ // throw new Error('not implemented yet');
328
+ // const testData = { yo: "there" }
329
+ // const { newIbGib } = await fork({ src });
330
+ // expect(newIbGib?.rel8ns).toBeTruthy();
331
+ // expect(newIbGib?.rel8ns).toBeTruthy();
332
+ // expect(newIbGib?.rel8ns!.ancestor).toBeTruthy();
333
+ // expect(newIbGib?.rel8ns!.ancestor).toBeTruthy();
334
+ // expect(newIbGib?.rel8ns!.ancestor!.length).toEqual(1);
335
+ // srcAddr = getIbGibAddr({ibGib: src});
336
+ // expect(newIbGib?.rel8ns!.ancestor![0]).toEqual(srcAddr);
337
+ // });
338
+ });
339
+
340
+ describe(`when forking with YES LINKED 'ancestor' rel8n...`, () => {
341
+
342
+ const ibs: Ib[] = ["a", "b", "c", "d"];
343
+ let srcForks: { [ib: string]: TransformResult<IbGib_V1> } = {};
344
+ let srcIbGibs: { [ib: string]: IbGib_V1 } = {};
345
+
346
+ beforeEach(async () => {
347
+ // These will be the srcs we're working off of.
348
+ // a forked from root, b forked from a, etc.
349
+ let prevIbGib: IbGib_V1 | undefined;
350
+ for (let i = 0; i < ibs.length; i++) {
351
+ const ib = ibs[i];
352
+ const forkResult = await fork({
353
+ src: prevIbGib || ROOT,
354
+ destIb: ib,
355
+ linkedRel8ns: [Rel8n.ancestor],
356
+ });
357
+ srcForks[ib] = forkResult;
358
+ srcIbGibs[ib] = forkResult.newIbGib;
359
+ prevIbGib = forkResult.newIbGib;
360
+
361
+ }
362
+ });
363
+ afterEach(() => {
364
+ srcForks = {};
365
+ srcIbGibs = {};
366
+ })
367
+
368
+ it(`should create a new ibgib`, async () => {
369
+ ibs.forEach(ib => {
370
+ const ibGib = srcIbGibs[ib];
371
+ expect(ibGib).toBeTruthy();
372
+ expect(ibGib).toBeTruthy();
373
+ expect(ibGib.ib).toEqual(ib);
374
+ })
375
+ });
376
+
377
+ it(`should have src ib^gib (address) in ancestor rel8n`, async () => {
378
+ const getPrevIbGib: (ib: Ib) => IbGib_V1 | null = (ib) => {
379
+ switch (ib) {
380
+ case "a": return null;
381
+ case "b": return srcIbGibs.a;
382
+ case "c": return srcIbGibs.b;
383
+ case "d": return srcIbGibs.c;
384
+ default: throw new Error("unknown ib")
385
+ }
386
+ };
387
+ // the first one forks from the root, so the rel8ns should be undefined which is tested elsewhere
388
+ ibs.filter(ib => ib === "a").forEach(ib => {
389
+ const newIbGib = srcIbGibs[ib]!;
390
+ expect(newIbGib?.rel8ns).toBeUndefined();
391
+ // expect(newIbGib?.rel8ns!.ancestor).toBeTruthy();
392
+ // expect(newIbGib?.rel8ns!.ancestor).toBeTruthy();
393
+ // expect(newIbGib?.rel8ns!.ancestor!.length).toEqual(1);
394
+ // const prevIbGibAddr = getIbGibAddr({ibGib: prevIbGib});
395
+ // expect(newIbGib?.rel8ns!.ancestor![0]).toEqual(prevIbGibAddr);
396
+ });
397
+ ibs.filter(ib => ib !== "a").forEach(ib => {
398
+ const newIbGib = srcIbGibs[ib]!;
399
+ const prevIbGib = getPrevIbGib(ib)!;
400
+ expect(newIbGib?.rel8ns).toBeTruthy();
401
+ expect(newIbGib?.rel8ns).toBeTruthy();
402
+ expect(newIbGib?.rel8ns!.ancestor).toBeTruthy();
403
+ expect(newIbGib?.rel8ns!.ancestor).toBeTruthy();
404
+ expect(newIbGib?.rel8ns!.ancestor!.length).toEqual(1);
405
+ const prevIbGibAddr = getIbGibAddr({ ibGib: prevIbGib });
406
+ expect(newIbGib?.rel8ns!.ancestor![0]).toEqual(prevIbGibAddr);
407
+ });
408
+ });
409
+
410
+ });
@@ -0,0 +1,4 @@
1
+ export * from './fork.mjs';
2
+ export * from './mut8.mjs';
3
+ export * from './rel8.mjs';
4
+ export { isPrimitive, isDna } from './transform-helper.mjs';
@@ -0,0 +1,239 @@
1
+ import { getIbGibAddr, clone, getTimestamp } from '../../helper.mjs';
2
+ import { IbGib_V1, IbGibData_V1, IbGibRel8ns_V1, Rel8n } from '../types.mjs';
3
+ import { TransformOpts_Mut8, TransformResult } from '../../types.mjs';
4
+ import { IBGIB_DELIMITER, } from '../constants.mjs';
5
+ import { buildDna, isPrimitive, getGib } from './transform-helper.mjs';
6
+
7
+ /**
8
+ * Original-ish V1 transform behavior.
9
+ * Going to create a new ibGib which mut8s internal data (or the ib) from the src ibgib.
10
+ *
11
+ * Takes the immutable src ibGib record, applies a mutation to its internal (intrinsic)
12
+ * `data` field, creates a new ibGib with the new intrinsic data.
13
+ *
14
+ * ### remember, ibgib basic structure
15
+ *
16
+ * Remember, the ibGib has the following basic structure (here represented in JSON):
17
+ * {
18
+ * ib: 'some ib, can be a simple name, or other metadata, up to very complex metadata.',
19
+ * gib: 'ABC123', // hash of the other three fields of our JSON data structure
20
+ * rel8ns: {
21
+ * 'link name': [ 'other ibgib^ABC345', 'yo^456789' ],
22
+ * 'called an edge in other jargon': [ 'ib^gib' ],
23
+ * 'past': ['ib^gib']
24
+ * ...
25
+ * },
26
+ * data: {
27
+ * 'intrinsic data': 'intrinsic value',
28
+ * 'copied each datum': 'so should be relatively small in practice',
29
+ * 'can...': {
30
+ * '...also be objects if you really': ['want']
31
+ * }
32
+ * }
33
+ * }
34
+ *
35
+ * So this transform is primarily concerned with that `data` property (though we can mut8 the ib also).
36
+ * If it's really anything complex, most likely it would be better to do
37
+ * another ibGib and link to it via the `rel8ns` (in the same way that in OOP it
38
+ * is good to have single responsibility for class architecture).
39
+ *
40
+ * ### what we can do with mutations
41
+ *
42
+ * We can mutate the `ib` value. So if we need to change metadata (e.g. a 'rename'), we can.
43
+ * This has implications for updating pointer refs and the tjp, but that's more advanced.
44
+ *
45
+ * We can also mutate the intrinsic `data` property, which is basically a JS object style of
46
+ * key/value store.
47
+ *
48
+ * ### notes
49
+ * * This is NOT going to do the plan^gib stuff ATM.
50
+ * * This does NOT add any identity information ATM.
51
+ *
52
+ */
53
+ export async function mut8<TNewData = any>(
54
+ opts: TransformOpts_Mut8<IbGib_V1, TNewData>
55
+ ): Promise<TransformResult<IbGib_V1>> {
56
+ const {
57
+ noTimestamp, dna, linkedRel8ns,
58
+ dataToRename, dataToRemove, dataToAddOrPatch,
59
+ mut8Ib,
60
+ type = 'mut8'
61
+ } = opts;
62
+ let src = opts.src;
63
+ const lc = '[mut8_v1]';
64
+ // #region validation
65
+
66
+ if (type !== 'mut8') { throw new Error(`${lc} not a mut8 transform.`); }
67
+ if (!opts.type) { opts.type = 'mut8' }
68
+
69
+ if (!src) { throw new Error(`${lc} src required to mut8.`); }
70
+ if (!src!.ib) { throw new Error(`${lc} src.ib required.`); }
71
+ if (src!.ib!.includes(IBGIB_DELIMITER)) {
72
+ throw new Error(`${lc} ib can't contain hardcoded delimiter (${IBGIB_DELIMITER}) right now.`);
73
+ }
74
+ if (!src!.gib) { throw new Error(`${lc} src.gib required.`); }
75
+
76
+ if (!mut8Ib && !dataToRename && !dataToRemove && !dataToAddOrPatch) {
77
+ throw new Error(`${lc} gotta provide either a mut8Ib or some data to change.`);
78
+ }
79
+
80
+ const srcAddr = getIbGibAddr({ ib: src!.ib, gib: src.gib });
81
+ if (opts.srcAddr && srcAddr !== opts.srcAddr) { throw new Error(`${lc} srcAddr from src does not equal opts.srcAddr`); }
82
+ opts.srcAddr = srcAddr;
83
+
84
+ // const srcIsPrimitive = src.gib === GIB || !src.gib;
85
+ // if (srcIsPrimitive) { throw new Error(`${lc} cannot mutate primitive ibgib`); }
86
+ if (isPrimitive({ ibGib: src })) { throw new Error(`${lc} cannot mutate primitive ibgib`); }
87
+
88
+ // #endregion
89
+
90
+ // we want to forget `src` proper very quickly because it may have other
91
+ // non-IbGib properties that are not relevant to our transform.
92
+ let dto: IbGib_V1 = { ib: src.ib, gib: src.gib, };
93
+ if (src.data && Object.keys(src.data).length > 0) { dto.data = src.data; }
94
+ if (src.rel8ns && Object.keys(src.rel8ns).length > 0) { dto.rel8ns = src.rel8ns; }
95
+ src = dto;
96
+
97
+ const newIbGib = clone(src) as IbGib_V1<IbGibData_V1>;
98
+
99
+ const srcRel8ns = src.rel8ns ? src.rel8ns : {};
100
+ const rel8ns: IbGibRel8ns_V1 = clone(srcRel8ns);
101
+ rel8ns.past = linkedRel8ns?.includes(Rel8n.past) ?
102
+ [srcAddr] :
103
+ (rel8ns.past || []).concat([srcAddr]);
104
+
105
+ let data: any = src?.data ? clone(src!.data) : {};
106
+ if (dataToRename) { data = renameOrRemove(data, dataToRename, 'rename'); }
107
+ if (dataToRemove) { data = renameOrRemove(data, dataToRemove, 'remove'); }
108
+ if (dataToAddOrPatch) { data = patch(data, dataToAddOrPatch); }
109
+ if (!noTimestamp) { data.timestamp = getTimestamp(); }
110
+
111
+ // n-related
112
+ if (opts.nCounter || Object.keys(data).includes('n')) {
113
+ if (Object.keys(data).includes('n')) {
114
+ if (Number.isInteger(data.n)) {
115
+ if (data.n >= 0) {
116
+ data.n = data.n + 1;
117
+ } else {
118
+ console.warn(`${lc} data.n is less than 0, which is unexpected. Resetting data.n to 0.`);
119
+ data.n = 0;
120
+ }
121
+ } else {
122
+ throw new Error('cannot increment nCounter because data.n is not a number.');
123
+ }
124
+ } else {
125
+ data.n = 0;
126
+ }
127
+ }
128
+
129
+ // tjp-related
130
+ // let tjpAddr: IbGibAddr | undefined = undefined;
131
+
132
+ // const srcIsTjp = data.isTjp && (rel8ns.tjp ?? []).length === 0; // if we only want one tjp
133
+ const srcIsTjp = data.isTjp; // if we want to allow more than one tjp
134
+ if (srcIsTjp) {
135
+ // if the src is tjp, e.g. is the first ibgib after a fork,
136
+ // then srcAddr is the tjpAddr
137
+ const tjpRel8n: string[] = rel8ns.tjp ?? [];
138
+ tjpRel8n.push(srcAddr);
139
+ rel8ns.tjp = tjpRel8n;
140
+ delete data.isTjp;
141
+ }
142
+
143
+ // dna-related
144
+ let transformDna: IbGib_V1 | null = null;
145
+ if (dna) {
146
+ transformDna = await buildDna(opts);
147
+ const dnaAddr = getIbGibAddr({ ibGib: transformDna });
148
+ rel8ns.dna = linkedRel8ns?.includes(Rel8n.dna) ?
149
+ rel8ns.dna = [dnaAddr] :
150
+ rel8ns.dna = (rel8ns.dna || []).concat(dnaAddr);
151
+ }
152
+
153
+ // set final ib, data, rel8ns, gib values
154
+ newIbGib.ib = mut8Ib ? mut8Ib : newIbGib.ib;
155
+ newIbGib.rel8ns = rel8ns;
156
+ if (Object.keys(data).length > 0) {
157
+ newIbGib.data = data;
158
+ } else {
159
+ // we've removed the last key in the data object so delete it
160
+ delete newIbGib.data;
161
+ }
162
+ const hasTjp = (rel8ns.tjp?.length ?? 0) > 0;
163
+ newIbGib.gib = await getGib({ ibGib: newIbGib, hasTjp });
164
+
165
+ // wrap in the result and return
166
+ const result: TransformResult<IbGib_V1> = { newIbGib };
167
+ if (transformDna) { result.dnas = [transformDna]; }
168
+ return result;
169
+ }
170
+
171
+
172
+ function renameOrRemove(obj: any, info: any, which: 'rename' | 'remove'): any {
173
+ const lc = `[renameOrRemove]`;
174
+ const FORBIDDEN_RENAME_REMOVE_KEYS = ['timestamp'];
175
+ Object.keys(info).forEach(key => {
176
+ if (FORBIDDEN_RENAME_REMOVE_KEYS.includes(key)) {
177
+ throw new Error(`${lc} Cannot rename to ${key}.`);
178
+ }
179
+ if (Object.keys(obj).includes(key)) {
180
+ let infoVal = info[key];
181
+ if (typeof (infoVal) === 'string') {
182
+ // rename
183
+ if (FORBIDDEN_RENAME_REMOVE_KEYS.includes(infoVal)) {
184
+ throw new Error(`${lc} Cannot rename to ${infoVal}.`);
185
+ }
186
+ if (which === 'rename') {
187
+ obj[infoVal] = obj[key];
188
+ }
189
+ delete obj[key];
190
+ }
191
+ else {
192
+ // recurse
193
+ obj[key] = renameOrRemove(obj[key], infoVal, which);
194
+ }
195
+ }
196
+ else {
197
+ console.log(`${lc} key to ${which} does not exist`);
198
+ }
199
+ });
200
+ return obj;
201
+ };
202
+
203
+ /**
204
+ * Patches obj (active mutate, does not copy) with patchObj data. Special keys
205
+ * can rename/remove keys, otherwise is an additive patch.
206
+ *
207
+ * @param obj source obj to patch, possibly is a subobject in a recursive call
208
+ * @param patchInfo data to patch into obj, possibly containing nested objects
209
+ */
210
+ function patch(obj: any, patchInfo: any): any {
211
+ Object.keys(patchInfo).forEach(patchKey => {
212
+ // grab the patchVal before updating patchKey
213
+ let patchVal = patchInfo[patchKey];
214
+ let objVal = obj[patchKey];
215
+ if (objVal) {
216
+ if (Array.isArray(patchVal) || Array.isArray(objVal)) {
217
+ // both are arrays, so full replace
218
+ obj[patchKey] = patchVal;
219
+ }
220
+ else if (typeof (patchVal) === 'object' && typeof (objVal) === 'object') {
221
+ // {a: {b: 2}}, {a: {b: 3, c: 4}}
222
+ // patchKey = 'a';
223
+ // patchVal = {b: 3, c: 4}
224
+ // objVal = {b: 2}
225
+ // recurse
226
+ obj[patchKey] = patch(objVal, patchVal);
227
+ }
228
+ else {
229
+ // not both objects, so full replace
230
+ obj[patchKey] = patchVal;
231
+ }
232
+ }
233
+ else {
234
+ // does not exist yet, so set new
235
+ obj[patchKey] = patchVal;
236
+ }
237
+ });
238
+ return obj;
239
+ };