@wovin/core 0.2.2 → 0.3.2
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.
- package/dist/applog/applog-utils.d.ts +25 -1
- package/dist/applog/applog-utils.d.ts.map +1 -1
- package/dist/applog/datom-types.d.ts.map +1 -1
- package/dist/applog.js +5 -3
- package/dist/blockstore.js +2 -0
- package/dist/blockstore.js.map +1 -1
- package/dist/{chunk-SHUHRHOT.js → chunk-4MKPGQIM.js} +17 -18
- package/dist/chunk-4MKPGQIM.js.map +1 -0
- package/dist/chunk-64EJIJAJ.js +17 -0
- package/dist/chunk-64EJIJAJ.js.map +1 -0
- package/dist/{chunk-HUIQ54TT.js → chunk-6CSJTSQP.js} +3 -3
- package/dist/chunk-7QEGHKR4.js +17 -0
- package/dist/chunk-7QEGHKR4.js.map +1 -0
- package/dist/{chunk-BLF5MAWU.js → chunk-BIYQEX3N.js} +2 -2
- package/dist/{chunk-6ALNRM3J.js → chunk-H3JNNTVP.js} +21 -8
- package/dist/chunk-H3JNNTVP.js.map +1 -0
- package/dist/{chunk-OC6Z6CQW.js → chunk-H4YVJKB7.js} +2 -2
- package/dist/chunk-ICBK7NC4.js +27 -0
- package/dist/chunk-ICBK7NC4.js.map +1 -0
- package/dist/chunk-N5QPZNKD.js +288 -0
- package/dist/chunk-N5QPZNKD.js.map +1 -0
- package/dist/{chunk-3SUFNJEZ.js → chunk-N7SEGHU4.js} +7 -2
- package/dist/{chunk-3SUFNJEZ.js.map → chunk-N7SEGHU4.js.map} +1 -1
- package/dist/chunk-XF4DWOAE.js +25 -0
- package/dist/chunk-XF4DWOAE.js.map +1 -0
- package/dist/index.js +20 -10
- package/dist/ipfs/car.d.ts.map +1 -1
- package/dist/ipfs.js +4 -4
- package/dist/ipns/gateway-resolver.d.ts +21 -0
- package/dist/ipns/gateway-resolver.d.ts.map +1 -0
- package/dist/ipns/ipns-record.d.ts +46 -7
- package/dist/ipns/ipns-record.d.ts.map +1 -1
- package/dist/ipns/ipns-w3name.d.ts +45 -0
- package/dist/ipns/ipns-w3name.d.ts.map +1 -0
- package/dist/ipns/ipns-watcher.d.ts +190 -0
- package/dist/ipns/ipns-watcher.d.ts.map +1 -0
- package/dist/ipns.d.ts +3 -0
- package/dist/ipns.d.ts.map +1 -1
- package/dist/ipns.js +591 -14
- package/dist/ipns.js.map +1 -1
- package/dist/pubsub/snap-push.d.ts +2 -2
- package/dist/pubsub/snap-push.d.ts.map +1 -1
- package/dist/pubsub.js +4 -4
- package/dist/query/epoch-snapshot.d.ts +205 -0
- package/dist/query/epoch-snapshot.d.ts.map +1 -0
- package/dist/query.d.ts +1 -0
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +12 -4
- package/dist/retrieve.js +4 -4
- package/dist/thread.js +1 -1
- package/dist/viewmodel/adapters/arktype.d.ts +33 -0
- package/dist/viewmodel/adapters/arktype.d.ts.map +1 -0
- package/dist/viewmodel/adapters/arktype.js +7 -0
- package/dist/viewmodel/adapters/arktype.js.map +1 -0
- package/dist/viewmodel/adapters/typebox.d.ts +35 -0
- package/dist/viewmodel/adapters/typebox.d.ts.map +1 -0
- package/dist/viewmodel/adapters/typebox.js +7 -0
- package/dist/viewmodel/adapters/typebox.js.map +1 -0
- package/dist/viewmodel/adapters/typia.d.ts +40 -0
- package/dist/viewmodel/adapters/typia.d.ts.map +1 -0
- package/dist/viewmodel/adapters/typia.js +7 -0
- package/dist/viewmodel/adapters/typia.js.map +1 -0
- package/dist/viewmodel/adapters/zod.d.ts +30 -0
- package/dist/viewmodel/adapters/zod.d.ts.map +1 -0
- package/dist/viewmodel/adapters/zod.js +7 -0
- package/dist/viewmodel/adapters/zod.js.map +1 -0
- package/dist/viewmodel/builder.d.ts +40 -0
- package/dist/viewmodel/builder.d.ts.map +1 -0
- package/dist/viewmodel/examples/all-adapters.d.ts +26 -0
- package/dist/viewmodel/examples/all-adapters.d.ts.map +1 -0
- package/dist/viewmodel/factory.d.ts +38 -0
- package/dist/viewmodel/factory.d.ts.map +1 -0
- package/dist/viewmodel/index.d.ts +10 -0
- package/dist/viewmodel/index.d.ts.map +1 -0
- package/dist/viewmodel/index.js +313 -0
- package/dist/viewmodel/index.js.map +1 -0
- package/dist/viewmodel/schema-adapter.d.ts +16 -0
- package/dist/viewmodel/schema-adapter.d.ts.map +1 -0
- package/dist/viewmodel/types.d.ts +97 -0
- package/dist/viewmodel/types.d.ts.map +1 -0
- package/package.json +29 -3
- package/src/applog/applog-utils.test.ts +43 -1
- package/src/applog/applog-utils.ts +38 -21
- package/src/applog/datom-types.ts +2 -2
- package/src/applog/object-values.test.ts +9 -9
- package/src/ipfs/car.ts +14 -2
- package/src/ipns/gateway-resolver.ts +63 -0
- package/src/ipns/ipns-record.ts +158 -29
- package/src/ipns/ipns-w3name.ts +207 -0
- package/src/ipns/ipns-watcher.ts +608 -0
- package/src/ipns.ts +3 -0
- package/src/pubsub/snap-push.ts +6 -5
- package/src/query/epoch-snapshot.test.ts +594 -0
- package/src/query/epoch-snapshot.ts +392 -0
- package/src/query.ts +1 -0
- package/src/viewmodel/adapters/arktype.ts +44 -0
- package/src/viewmodel/adapters/typebox.ts +59 -0
- package/src/viewmodel/adapters/typia.ts +50 -0
- package/src/viewmodel/adapters/zod.ts +55 -0
- package/src/viewmodel/builder.ts +71 -0
- package/src/viewmodel/examples/all-adapters.ts +206 -0
- package/src/viewmodel/factory.ts +330 -0
- package/src/viewmodel/index.ts +22 -0
- package/src/viewmodel/schema-adapter.ts +27 -0
- package/src/viewmodel/types.ts +152 -0
- package/dist/chunk-22WDFLXO.js +0 -138
- package/dist/chunk-22WDFLXO.js.map +0 -1
- package/dist/chunk-6ALNRM3J.js.map +0 -1
- package/dist/chunk-SHUHRHOT.js.map +0 -1
- /package/dist/{chunk-HUIQ54TT.js.map → chunk-6CSJTSQP.js.map} +0 -0
- /package/dist/{chunk-BLF5MAWU.js.map → chunk-BIYQEX3N.js.map} +0 -0
- /package/dist/{chunk-OC6Z6CQW.js.map → chunk-H4YVJKB7.js.map} +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/viewmodel/adapters/arktype.ts
|
|
2
|
+
function createArkTypeAdapter(_arkTypeSchema, entityPrefix, options) {
|
|
3
|
+
return {
|
|
4
|
+
getAttributeDefs: () => options?.attributes ?? [],
|
|
5
|
+
getDefaults: () => options?.defaults ?? {},
|
|
6
|
+
getEntityPrefix: () => entityPrefix,
|
|
7
|
+
createValidator: () => (value) => {
|
|
8
|
+
const result = _arkTypeSchema(value);
|
|
9
|
+
return typeof result !== "string";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
createArkTypeAdapter
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=chunk-64EJIJAJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/viewmodel/adapters/arktype.ts"],"sourcesContent":["import type { ApplogValue } from '../../applog/datom-types.ts'\nimport type { ISchemaAdapter, VMAttributeDef } from '../types.ts'\n\n/**\n * Pattern for creating a schema adapter from an ArkType schema.\n *\n * ArkType is NOT a runtime dependency of @wovin/core, so this adapter\n * works by accepting the ArkType type object directly.\n *\n * Usage:\n * ```ts\n * import { type } from 'arktype'\n * import { createArkTypeAdapter } from '@wovin/core/viewmodel/adapters/arktype'\n *\n * const MySchema = type({\n * name: 'string',\n * age: 'number | undefined',\n * })\n *\n * const adapter = createArkTypeAdapter(MySchema, 'myEntity')\n * ```\n */\nexport function createArkTypeAdapter<T extends Record<string, ApplogValue>>(\n\t_arkTypeSchema: { infer: T; (input: unknown): T | string },\n\tentityPrefix: string,\n\toptions?: {\n\t\t/** Override at-paths for specific attributes */\n\t\tatOverrides?: Record<string, string>\n\t\t/** Attribute definitions (must be provided for ArkType since we can't introspect the type) */\n\t\tattributes: VMAttributeDef[]\n\t\t/** Default values */\n\t\tdefaults?: Partial<T>\n\t},\n): ISchemaAdapter<T> {\n\treturn {\n\t\tgetAttributeDefs: () => options?.attributes ?? [],\n\t\tgetDefaults: () => (options?.defaults ?? {}) as Partial<T>,\n\t\tgetEntityPrefix: () => entityPrefix,\n\t\tcreateValidator: () => (value: unknown): value is T => {\n\t\t\tconst result = _arkTypeSchema(value as any)\n\t\t\treturn typeof result !== 'string'\n\t\t},\n\t}\n}\n"],"mappings":";AAsBO,SAAS,qBACf,gBACA,cACA,SAQoB;AACpB,SAAO;AAAA,IACN,kBAAkB,MAAM,SAAS,cAAc,CAAC;AAAA,IAChD,aAAa,MAAO,SAAS,YAAY,CAAC;AAAA,IAC1C,iBAAiB,MAAM;AAAA,IACvB,iBAAiB,MAAM,CAAC,UAA+B;AACtD,YAAM,SAAS,eAAe,KAAY;AAC1C,aAAO,OAAO,WAAW;AAAA,IAC1B;AAAA,EACD;AACD;","names":[]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
unchunkApplogsBlock
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-H3JNNTVP.js";
|
|
4
4
|
import {
|
|
5
5
|
areCidsEqual,
|
|
6
6
|
removeDuplicateAppLogs
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4MKPGQIM.js";
|
|
8
8
|
|
|
9
9
|
// src/retrieve/update-thread.ts
|
|
10
10
|
import * as dagJson from "@ipld/dag-json";
|
|
@@ -141,4 +141,4 @@ export {
|
|
|
141
141
|
withBlockCache,
|
|
142
142
|
updateThreadFromSnapshot
|
|
143
143
|
};
|
|
144
|
-
//# sourceMappingURL=chunk-
|
|
144
|
+
//# sourceMappingURL=chunk-6CSJTSQP.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/viewmodel/adapters/typia.ts
|
|
2
|
+
function createTypiaAdapter(_typiaValidator, entityPrefix, options) {
|
|
3
|
+
return {
|
|
4
|
+
getAttributeDefs: () => options.attributes,
|
|
5
|
+
getDefaults: () => options?.defaults ?? {},
|
|
6
|
+
getEntityPrefix: () => entityPrefix,
|
|
7
|
+
createValidator: () => (value) => {
|
|
8
|
+
const result = _typiaValidator(value);
|
|
9
|
+
return result.success;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
createTypiaAdapter
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=chunk-7QEGHKR4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/viewmodel/adapters/typia.ts"],"sourcesContent":["import type { ApplogValue } from '../../applog/datom-types.ts'\nimport type { ISchemaAdapter, VMAttributeDef } from '../types.ts'\n\n/**\n * Pattern for creating a schema adapter from a Typia-generated schema.\n *\n * Typia is NOT a runtime dependency of @wovin/core, so this adapter\n * works by accepting the Typia type object directly along with the\n * attribute definitions (which must be provided separately since\n * Typia doesn't expose runtime property introspection by default).\n *\n * Usage:\n * ```ts\n * import typia from 'typia'\n * import { createTypiaAdapter } from '@wovin/core/viewmodel/adapters/typia'\n *\n * interface MyEntity { name: string; age?: number }\n *\n * const adapter = createTypiaAdapter<MyEntity>(\n * typia.createValidate<MyEntity>(),\n * 'myEntity',\n * {\n * attributes: [\n * { name: 'name', atPath: 'myEntity/name', optional: false },\n * { name: 'age', atPath: 'myEntity/age', optional: true },\n * ],\n * },\n * )\n * ```\n */\nexport function createTypiaAdapter<T extends Record<string, ApplogValue>>(\n\t_typiaValidator: (input: unknown) => { success: boolean; data: T | null; errors?: any[] },\n\tentityPrefix: string,\n\toptions: {\n\t\t/** Attribute definitions (required for Typia since property introspection is compile-time) */\n\t\tattributes: VMAttributeDef[]\n\t\t/** Default values */\n\t\tdefaults?: Partial<T>\n\t},\n): ISchemaAdapter<T> {\n\treturn {\n\t\tgetAttributeDefs: () => options.attributes,\n\t\tgetDefaults: () => (options?.defaults ?? {}) as Partial<T>,\n\t\tgetEntityPrefix: () => entityPrefix,\n\t\tcreateValidator: () => (value: unknown): value is T => {\n\t\t\tconst result = _typiaValidator(value)\n\t\t\treturn result.success\n\t\t},\n\t}\n}\n"],"mappings":";AA8BO,SAAS,mBACf,iBACA,cACA,SAMoB;AACpB,SAAO;AAAA,IACN,kBAAkB,MAAM,QAAQ;AAAA,IAChC,aAAa,MAAO,SAAS,YAAY,CAAC;AAAA,IAC1C,iBAAiB,MAAM;AAAA,IACvB,iBAAiB,MAAM,CAAC,UAA+B;AACtD,YAAM,SAAS,gBAAgB,KAAK;AACpC,aAAO,OAAO;AAAA,IACf;AAAA,EACD;AACD;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
areCidsEqual
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4MKPGQIM.js";
|
|
4
4
|
|
|
5
5
|
// src/ipfs/fetch-snapshot-chain.ts
|
|
6
6
|
import * as dagJson from "@ipld/dag-json";
|
|
@@ -83,4 +83,4 @@ function createMemoryBlockStore() {
|
|
|
83
83
|
export {
|
|
84
84
|
fetchSnapshotChainUntil
|
|
85
85
|
};
|
|
86
|
-
//# sourceMappingURL=chunk-
|
|
86
|
+
//# sourceMappingURL=chunk-BIYQEX3N.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-YDAKBU6Q.js";
|
|
4
4
|
import {
|
|
5
5
|
lastWriteWins
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-N7SEGHU4.js";
|
|
7
7
|
import {
|
|
8
8
|
anyOf,
|
|
9
9
|
areCidsEqual,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
getLogsFromThread,
|
|
13
13
|
prepareForPub,
|
|
14
14
|
rollingFilter
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-4MKPGQIM.js";
|
|
16
16
|
|
|
17
17
|
// src/pubsub/snap-push.ts
|
|
18
18
|
import * as dagJson2 from "@ipld/dag-json";
|
|
@@ -56,10 +56,22 @@ async function decodePubFromBlocks({ rootCID, blockStore }, _recursionTrace = []
|
|
|
56
56
|
if (root?.info) {
|
|
57
57
|
if (!applogsCID) applogsCID = root.applogs;
|
|
58
58
|
const applogsBlock = await getDecodedBlock(blockStore, root.applogs);
|
|
59
|
+
if (!applogsBlock) {
|
|
60
|
+
throw ERROR("[decodePubFromBlocks] applogs block missing from blockStore", {
|
|
61
|
+
cid: root.applogs.toString(),
|
|
62
|
+
rootCID: cidStr
|
|
63
|
+
});
|
|
64
|
+
}
|
|
59
65
|
pubLogsArray = await unchunkApplogsBlock(applogsBlock, blockStore);
|
|
60
66
|
if (!firstInfo) {
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
const decoded = await getDecodedBlock(blockStore, root.info);
|
|
68
|
+
if (decoded) {
|
|
69
|
+
firstInfo = decoded;
|
|
70
|
+
DEBUG(`new format - infoLogs`, firstInfo.logs.map((l) => ({ [l.toString()]: l })));
|
|
71
|
+
} else {
|
|
72
|
+
WARN(`[decodePubFromBlocks] info block not found for ${root.info}, using empty info`);
|
|
73
|
+
firstInfo = { logs: [] };
|
|
74
|
+
}
|
|
63
75
|
}
|
|
64
76
|
} else {
|
|
65
77
|
pubLogsArray = root.applogs;
|
|
@@ -376,20 +388,21 @@ async function chunkApplogs(applogCids, size = 1e4) {
|
|
|
376
388
|
return { rootCID: root.cid, blocks, chunks };
|
|
377
389
|
}
|
|
378
390
|
async function unchunkApplogsBlock(block, blockStore) {
|
|
391
|
+
if (!block) return [];
|
|
379
392
|
if (isSnapBlockChunks(block)) {
|
|
380
393
|
return (await Promise.all(
|
|
381
394
|
block.chunks.map(async (chunkCid) => {
|
|
382
395
|
const block2 = await getDecodedBlock(blockStore, chunkCid);
|
|
383
|
-
if (!block2
|
|
396
|
+
if (!block2?.logs) throw ERROR2(`Weird chunk`, block2);
|
|
384
397
|
return block2.logs;
|
|
385
398
|
})
|
|
386
399
|
)).flat();
|
|
387
400
|
} else {
|
|
388
|
-
return block.logs;
|
|
401
|
+
return block.logs ?? [];
|
|
389
402
|
}
|
|
390
403
|
}
|
|
391
404
|
function isSnapBlockChunks(block) {
|
|
392
|
-
return block
|
|
405
|
+
return !!block && "chunks" in block;
|
|
393
406
|
}
|
|
394
407
|
async function encodeSnapshotApplogsAsCar(applogs) {
|
|
395
408
|
const encoded = await encodeApplogsAsIPLD(applogs);
|
|
@@ -432,4 +445,4 @@ export {
|
|
|
432
445
|
collectDagBlocks,
|
|
433
446
|
streamReaderToIterable
|
|
434
447
|
};
|
|
435
|
-
//# sourceMappingURL=chunk-
|
|
448
|
+
//# sourceMappingURL=chunk-H3JNNTVP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pubsub/snap-push.ts","../src/ipfs/car.ts"],"sourcesContent":["import * as dagJson from '@ipld/dag-json'\nimport { Logger } from 'besonders-logger'\nimport { CID } from 'multiformats/cid'\nimport stringify from 'safe-stable-stringify'\nimport { ensureTsPvAndFinalizeApplog } from '../applog/applog-helpers.ts'\nimport type {\n\tApplog,\n\tApplogArrayMaybeEncrypted,\n\tApplogArrayMaybeEncryptedRO,\n\tApplogArrayNoCIDMaybeEncryptedRO,\n\tApplogEnc,\n\tApplogEncNoCid,\n\tCidString,\n} from '../applog/datom-types.ts'\nimport { BlockStoreish, DecodedCar, getDecodedBlock, makeCarBlob } from '../ipfs/car.ts'\nimport { encodeBlockOriginal, prepareForPub } from '../ipfs/ipfs-utils.ts'\nimport { lastWriteWins } from './../query/basic.ts'\nimport { anyOf } from '../query/matchers.ts'\nimport { ApplogsOrThread, getLogsFromThread, Thread } from '../thread.ts'\nimport { rollingFilter } from '../thread/filters.ts'\nimport { keepTruthy } from '../utils.ts'\nimport type { AppAgent, IShare, SnapBlockChunks, SnapBlockLogs, SnapBlockLogsOrChunks } from './pubsub-types.ts'\n\nconst { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars\n\n// export const neverEncryptAttrs = [\n// \t'agent/jwkd',\n// \t'agent/appAgent',\n// \t'pub/encryptedFor',\n// \t'pub/sharedKey',\n// ]\n\n// export interface WovinPublicationInfo {\n// \tid: string\n// }\n\nexport async function prepareSnapshotForPush(\n\tagent: AppAgent,\n\tappThread: Thread,\n\tthreadToPublish: ApplogsOrThread,\n\tshare: IShare,\n\tprevSnapCID: CID | null,\n\tprevCounter: number | null,\n) {\n\tif (prevCounter !== null && !prevSnapCID) {\n\t\tthrow ERROR('[prepareSnapshotForPush] prevCounter provided without prevSnapCID')\n\t}\n\t// TODO prevent publish if there is no new info\n\tlet logsToPublish = getLogsFromThread(threadToPublish)\n\n\t// const logsFromLastPush = await getLogsFromPub(publication)\n\t// logsToPublish = logsToPublish.filter(eachLog => !logsFromLastPush.includes(eachLog.cid)) // TODO deep compare includes\n\t// const prevPushCIDs = [\n\t// \tpublication.lastCID,\n\t// \t//TODO add this one and update the publication data after push\n\t// ]\n\t// const includedLogCIDs = [\n\t// \t'full array of CIDS from all previous pushes'\n\t// ]\n\n\tDEBUG(`[preparePubForPush] Collected ${logsToPublish.length} logs :`, {\n\t\tlogsToPublish,\n\t\tthreadOrLogsCount: (threadToPublish as any).nameAndSizeUntracked || (`[${(threadToPublish as any).length}]`),\n\t})\n\n\tconst { sharedAgents, sharedKeyMap, sharedKey, pubCounter } = share ?? {}\n\n\tconst getExistingOrNewLog = (thread: Thread, share: IShare, ag: string, at: string, vl) => {\n\t\tlet logInQuestion = rollingFilter(lastWriteWins(thread), { en: share.id, at }).latestLog\n\t\tif (!logInQuestion && vl !== undefined) {\n\t\t\tlogInQuestion = ensureTsPvAndFinalizeApplog({ ag, en: share.id, at, vl }, thread)\n\t\t}\n\t\treturn logInQuestion // can be undefined if the passed vl is undefined and the log is not found\n\t}\n\tconst shareNameLog = getExistingOrNewLog(appThread, share, agent.ag, 'share/name', share.name)\n\n\t// ? using did as it is derived from the same ecdh in note3 and part of the minimal AppAgent type required here in wovin core\n\tconst shareCounterLog = getExistingOrNewLog(appThread, share, agent.ag, 'share/counter', `${agent.did}<::>${pubCounter}`)\n\t// ? discuss if this works to bind the counter to a specific derivation key - did is not necessarily derived from the same key by all lib users\n\n\tconst encryptApplog = async (applog: Applog, keyToUse: CryptoKey): Promise<Uint8Array> => {\n\t\tconst { log: eachLog, cid } = prepareForPub(applog) // without cid\n\t\tconst enc = new TextEncoder()\n\t\tconst stringified = stringify(eachLog)\n\t\tconst stringifiedEncodedAppLogPayload = enc.encode(stringified) // TODO: consider encodeToDagJson instead\n\t\tVERBOSE('[odd]', { eachLog, stringified, stringifiedEncodedAppLogPayload })\n\n\t\ttry {\n\t\t\t// @ts-expect-error\n\t\t\tconst encPayload = await agent.crypto?.aes.encrypt(stringifiedEncodedAppLogPayload, keyToUse, 'AES-GCM')\n\t\t\t// TODO get rid of odd down here\n\t\t\tVERBOSE('[odd] encrypted length:', stringifiedEncodedAppLogPayload.length, { encPayload })\n\t\t\treturn encPayload\n\t\t} catch (err) {\n\t\t\tthrow ERROR('FAILED TO ENC payload length:', stringifiedEncodedAppLogPayload.length, { err })\n\t\t}\n\n\t\t// const decrypted = await decryptWithAesSharedKey(encPayload, keyToUse, 'string')\n\t}\n\n\tlet maybeEncryptedApplogs: ApplogEncNoCid[] | readonly Applog[]\n\tconst encryptedApplogs = [] as { enc: Uint8Array }[]\n\tconst agentSharedKeyLogs = []\n\tif (sharedAgents) { // encrypt all Applogs\n\t\tif (!sharedKey || !sharedKeyMap) {\n\t\t\tthrow ERROR('sharedAgents but no Keys/Map', { sharedAgents, sharedKeyMap, sharedKey })\n\t\t}\n\t\tVERBOSE('encrypting', { sharedAgents, sharedKeyMap })\n\n\t\tfor (const [eachAgent, eachEncKey] of Array.from(sharedKeyMap.entries())) {\n\t\t\tVERBOSE('adding key', { eachAgent, eachEncKey })\n\t\t\tagentSharedKeyLogs.push({\n\t\t\t\tag: agent.ag,\n\t\t\t\ten: eachAgent,\n\t\t\t\tat: 'share/sharedKey',\n\t\t\t\tvl: eachEncKey, // these are encrypted with the derived key from the local agent private and remote agent public keys\n\t\t\t})\n\t\t}\n\t\t// const encryptedForLogs = await insertApplogsInAppDB(agentSharedKeyLogs)\n\t\t// DEBUG(`[publish] adding agentSharedKeyLogs:`, encryptedForLogs)\n\t\tconst CIDlist: { cid: CidString; encCID?: CidString }[] = []\n\t\tconst pubCIDmap: Record<CidString, typeof CIDlist> = {}\n\t\t// TODO ensure that all needed keys are in\n\t\tfor (const eachLog of logsToPublish) {\n\t\t\tVERBOSE('[crypto] encrypting ', { eachLog, sharedKey })\n\t\t\t// try {\n\t\t\tconst encPayload = await encryptApplog(eachLog, sharedKey)\n\t\t\tDEBUG('[crypto] encrypted ', { eachLog, encPayload, sharedKey })\n\t\t\t// } catch (err) {\n\t\t\t// \t// its already traced in encryptAndTestDecrypt\n\t\t\t// \t// continue\n\t\t\t// }\n\t\t\tencryptedApplogs.push({ enc: encPayload })\n\t\t}\n\t\tmaybeEncryptedApplogs = encryptedApplogs\n\t} else {\n\t\tmaybeEncryptedApplogs = logsToPublish // publish nonEncrypted\n\t}\n\n\tDEBUG('adding all agent info and shareAtoms', {\n\t\tshare,\n\t\tagent,\n\t\tlogsToPublish,\n\t\t// threadToPublish, - very verbose\n\t\tagentSharedKeyLogs,\n\t})\n\tconst infoLogs = [\n\t\t...rollingFilter(lastWriteWins(appThread), { // TODO: use static filter for performance\n\t\t\ten: agent.ag,\n\t\t\tat: anyOf('agent/ecdh', 'agent/jwkd', 'agent/appAgent'),\n\t\t}).applogs,\n\t\t...(shareNameLog ? [shareNameLog] : []),\n\t\t...(shareCounterLog ? [shareCounterLog] : []),\n\t\t...agentSharedKeyLogs,\n\t]\n\tDEBUG(`[prepareSnapshotForPush] info logs:`, infoLogs)\n\tif (!infoLogs.find(({ at }) => at === 'agent/appAgent')) throw ERROR(`[prepareSnapshotForPush] appThread missing agent/appAgent log`)\n\n\tconst applogsToEncode = keepTruthy(maybeEncryptedApplogs)\n\tconst infologsToEncode = keepTruthy(infoLogs)\n\tif (!applogsToEncode.length) {\n\t\tthrow ERROR('no valid applogs', { agent, maybeEncryptedApplogs, infoLogs, applogsToEncode, infologsToEncode, prevSnapCID })\n\t}\n\tif (!infologsToEncode.length) {\n\t\tthrow ERROR('no valid infologs', { agent, maybeEncryptedApplogs, infoLogs, applogsToEncode, infologsToEncode, prevSnapCID })\n\t}\n\tconst encodedSnapshot = await encodeSnapshotAsCar(agent, applogsToEncode, infologsToEncode, prevSnapCID, prevCounter)\n\tDEBUG('inPrepareSnapshotForPush', { encodedSnapshot })\n\treturn encodedSnapshot\n}\n\n/**\n * @param applogs Encrypted or plain applogs\n * @returns Car file\n */\nexport async function encodeSnapshotAsCar(\n\tagent: AppAgent,\n\tapplogs: ApplogArrayNoCIDMaybeEncryptedRO,\n\tinfoLogs: readonly Applog[],\n\tprevSnapCID: CID | null,\n\tprevCounter: number | null,\n) {\n\tDEBUG(`[encodeSnapshotAsCar] encoding`, { agent, applogs, infoLogs })\n\tconst { cids: infoLogCids, encodedApplogs: encodedInfoLogs } = await encodeApplogsAsIPLD(infoLogs)\n\tconst { cids: applogCids, encodedApplogs } = await encodeApplogsAsIPLD(applogs)\n\tlet blocks = encodedApplogs.concat(encodedInfoLogs)\n\t// We need to wrap the array to get a CID\n\tconst infoLogsWrap = await encodeBlockOriginal({ logs: infoLogCids })\n\tblocks.push(infoLogsWrap)\n\tconst { rootCID: chunkRootCID, blocks: chunkBlocks } = await chunkApplogs(applogCids)\n\tblocks = blocks.concat(chunkBlocks) // (i) concat bc. https://stackoverflow.com/a/51860949\n\tconst infoSignature = await agent.sign(infoLogsWrap.cid.bytes)\n\tconst applogsSignature = await agent.sign(chunkRootCID.bytes)\n\tconst root = {\n\t\tinfo: infoLogsWrap.cid,\n\t\tapplogs: chunkRootCID,\n\t\tinfoSignature,\n\t\tapplogsSignature,\n\t\tprev: prevSnapCID,\n\t\tprevCounter: !prevSnapCID ? 0 : prevCounter !== null ? prevCounter + 1 : null,\n\t}\n\tDEBUG('[encodeSnapshotAsCar] encoding root', { root, logCids: applogCids, infoLogCids })\n\tconst encodedRoot = await encodeBlockOriginal(root)\n\tblocks.push(encodedRoot)\n\tDEBUG('[encodeSnapshotAsCar] => root', { encodedRoot })\n\n\treturn {\n\t\tcid: encodedRoot.cid,\n\t\tblob: await makeCarBlob(encodedRoot.cid, blocks), // TODO: create CarBuilder (incl .encodeAndAdd({...}))\n\t\tblocks,\n\t\tinfoLogCids,\n\t\tapplogCids,\n\t}\n}\n\n/** (i) IPFS has a block size limit of 1MB - which is about 15K CIDs */\nexport async function chunkApplogs(applogCids: CID<unknown, 297, 18, 1>[], size = 10000) {\n\tif (!applogCids.length) throw ERROR(`[chunkApplogs] called with empty array`)\n\tconst chunks = []\n\t// TODO: chunk by stable btree based on size or something like that\n\tfor (let i = 0; i < applogCids.length; i += size) {\n\t\tconst chunk = await encodeBlockOriginal({ logs: applogCids.slice(i, Math.min(i + applogCids.length, i + size)) })\n\t\tchunks.push(chunk)\n\t}\n\tif (chunks.length === 1) return { rootCID: chunks[0].cid, blocks: chunks }\n\tconst root = await encodeBlockOriginal({ chunks: chunks.map(chunk => chunk.cid) })\n\tconst blocks = [root, ...chunks]\n\tDEBUG(`[chunkApplogs] ${applogCids.length} logs chunked into ${chunks.length}`, { applogCids, root, blocks, chunks, dagJson })\n\treturn { rootCID: root.cid, blocks, chunks }\n}\nexport async function unchunkApplogsBlock(block: SnapBlockLogsOrChunks | null | undefined, blockStore: BlockStoreish): Promise<CID[]> {\n\tif (!block) return []\n\tif (isSnapBlockChunks(block)) {\n\t\treturn (await Promise.all(\n\t\t\tblock.chunks.map(async (chunkCid) => {\n\t\t\t\tconst block = (await getDecodedBlock(blockStore, chunkCid)) as SnapBlockLogs\n\t\t\t\tif (!block?.logs) throw ERROR(`Weird chunk`, block)\n\t\t\t\treturn block.logs\n\t\t\t}),\n\t\t)).flat()\n\t} else {\n\t\treturn block.logs ?? []\n\t}\n}\nexport function isSnapBlockChunks(block: SnapBlockLogsOrChunks | null | undefined): block is SnapBlockChunks {\n\treturn !!block && 'chunks' in block\n}\n/**\n * @param applogs Encrypted or plain applogs\n * @returns Car file\n */\nexport async function encodeSnapshotApplogsAsCar(\n\tapplogs: ApplogArrayMaybeEncryptedRO,\n) {\n\tconst encoded = await encodeApplogsAsIPLD(applogs)\n\tif (!encoded) throw ERROR('invalid applogs cannot continue', { applogs, encoded })\n\tconst { cids, encodedApplogs } = encoded\n\tconst root = { applogs: cids }\n\tconst encodedRoot = await encodeBlockOriginal(root)\n\tDEBUG('[encodeSnapshotApplogsAsCar] encoded root', { cids, encodedRoot })\n\n\treturn await makeCarBlob(encodedRoot.cid, [encodedRoot, ...encodedApplogs])\n}\n\nasync function encodeApplogsAsIPLD(applogs: ApplogArrayNoCIDMaybeEncryptedRO) {\n\tDEBUG({ applogs })\n\tconst validApplogs = applogs.filter(eachLog => !!eachLog)\n\tDEBUG({ validApplogs })\n\tif (!validApplogs.length) throw ERROR('no valid applogs')\n\tconst preppedLogs = validApplogs.map(log => prepareForPub(log as Applog).log)\n\tconst encodedApplogs = await Promise.all(preppedLogs.map(encodeBlockOriginal))\n\tDEBUG('[encodeApplogsAsIpld] encoded applogs', { preppedLogs, encodedApplogs })\n\n\tconst cids = encodedApplogs.map(b => {\n\t\tif (!b.cid) throw ERROR(`[publish] no cid for encoded log:`, b)\n\t\treturn b.cid\n\t})\n\treturn { cids, encodedApplogs }\n}\n","import { CarReader, CarWriter } from '@ipld/car'\nimport * as dagJson from '@ipld/dag-json'\nimport { Logger } from 'besonders-logger'\nimport { BlockView, CID } from 'multiformats'\nimport { sortApplogsByTs } from '../applog/applog-utils.ts'\nimport { Applog, ApplogArrayMaybeEncrypted, CidString } from '../applog/datom-types.ts'\nimport { unchunkApplogsBlock } from '../pubsub/snap-push.ts'\nimport { SnapBlockLogs, SnapBlockLogsOrChunks, SnapRootBlock } from '../pubsub/pubsub-types.ts'\nimport { areCidsEqual, containsCid } from './ipfs-utils.ts'\n\nconst { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars\n\nexport type CIDForCar = CID // Exclude<Parameters<(typeof CarWriter)['create']>[0], void>\nexport type BlockForCar = Parameters<CarWriter['put']>[0]\n\nexport interface BlockStoreish {\n\tget(cid: CID): PromiseLike<Uint8Array> // (i) not using decoded version to be similar to blockstore-idb\n}\n\nexport interface DecodedCar {\n\trootCID: CID\n\t// blocks: Map<CidString, any>\n\tblockStore: BlockStoreish\n}\n\n/** Warning: unsorted & maybe encrypted */\nexport async function decodePubFromCar(car: CarReader) {\n\tconst decoded = await getBlocksOfCar(car)\n\treturn await decodePubFromBlocks(decoded)\n}\n\nexport async function decodePubFromBlocks(\n\t{ rootCID, blockStore }: DecodedCar,\n\t_recursionTrace: CID[] = [], // DEPRECATED: kept for API compat, unused in iterative version\n\tstopAtCID?: CID // NEW: stop iteration when we hit this CID\n) {\n\tif (!rootCID || !blockStore) {\n\t\tthrow ERROR('Empty roots/blocks', { rootCID, blockStore })\n\t}\n\n\tlet allApplogs: ApplogArrayMaybeEncrypted = []\n\tlet firstInfo: { logs: CID[] } | null = null\n\tlet currentCID: CID | undefined = rootCID\n\tconst visited = new Set<string>() // Loop detection (replaces recursionTrace)\n\tlet applogsCID: CID | null = null // From first snapshot only\n\n\twhile (currentCID) {\n\t\tconst cidStr = currentCID.toString()\n\n\t\t// Loop detection\n\t\tif (visited.has(cidStr)) {\n\t\t\tthrow ERROR('[decodePubFromBlocks] pub chain has a loop', {\n\t\t\t\tcurrentCID: cidStr,\n\t\t\t\tvisited: [...visited]\n\t\t\t})\n\t\t}\n\t\tvisited.add(cidStr)\n\n\t\t// Decode current snapshot\n\t\tconst root = (await getDecodedBlock(blockStore, currentCID)) as SnapRootBlock\n\t\tVERBOSE(`[decodePubFromBlocks] root:`, cidStr, root, { blockStore })\n\t\tif (!root) {\n\t\t\tthrow ERROR('[decodePubFromBlocks] root not found in blockStore', { blockStore, currentCID: cidStr })\n\t\t}\n\n\t\t// Decode applogs for this snapshot\n\t\tlet pubLogsArray: CID[]\n\t\tif (root?.info) {\n\t\t\t// New(er) format\n\t\t\tif (!applogsCID) applogsCID = root.applogs // Save from first snapshot\n\t\t\tconst applogsBlock = (await getDecodedBlock(blockStore, root.applogs)) as SnapBlockLogsOrChunks\n\t\t\tif (!applogsBlock) {\n\t\t\t\tthrow ERROR('[decodePubFromBlocks] applogs block missing from blockStore', {\n\t\t\t\t\tcid: root.applogs.toString(),\n\t\t\t\t\trootCID: cidStr,\n\t\t\t\t})\n\t\t\t}\n\t\t\tpubLogsArray = await unchunkApplogsBlock(applogsBlock, blockStore)\n\t\t\t// Info only from first (most recent) snapshot\n\t\t\tif (!firstInfo) {\n\t\t\t\tconst decoded = (await getDecodedBlock(blockStore, root.info)) as SnapBlockLogs\n\t\t\t\tif (decoded) {\n\t\t\t\t\tfirstInfo = decoded\n\t\t\t\t\tDEBUG(`new format - infoLogs`, firstInfo.logs.map(l => ({ [l.toString()]: l })))\n\t\t\t\t} else {\n\t\t\t\t\tWARN(`[decodePubFromBlocks] info block not found for ${root.info}, using empty info`)\n\t\t\t\t\tfirstInfo = { logs: [] }\n\t\t\t\t}\n\t\t\t}\n\t\t\t// TODO: verify signatures\n\t\t} else {\n\t\t\t// Old format\n\t\t\tpubLogsArray = root.applogs as any as CID[]\n\t\t}\n\n\t\tconst resolveLogFromCidLink = async (cidOrLink: CID) => {\n\t\t\tconst cid = cidOrLink\n\t\t\tconst applog = (await getDecodedBlock(blockStore, cid)) as Applog\n\t\t\tif (!applog) {\n\t\t\t\tERROR(`Could not find applog CID in pub blocks:`, cid.toString(), { cid, root, blockStore })\n\t\t\t\tthrow new Error(`Could not find applog CID in pub blocks: ${cid.toString()}`)\n\t\t\t}\n\t\t\tif ((applog.pv as any) instanceof CID) applog.pv = (applog.pv as any as CID).toV1().toString()\n\t\t\treturn {\n\t\t\t\t...applog,\n\t\t\t\tcid: cid.toV1().toString(),\n\t\t\t}\n\t\t}\n\n\t\tconst snapshotApplogs = await Promise.all(pubLogsArray.map(resolveLogFromCidLink))\n\t\tallApplogs = allApplogs.concat(snapshotApplogs)\n\n\t\t// Check if we should stop\n\t\tif (!root.prev) {\n\t\t\tbreak // End of chain\n\t\t}\n\t\tif (stopAtCID && areCidsEqual(root.prev, stopAtCID)) {\n\t\t\tDEBUG('[decodePubFromBlocks] stopping at stopAtCID:', stopAtCID.toString())\n\t\t\tbreak // Reached already-pulled snapshot\n\t\t}\n\n\t\t// Verify prev exists before continuing\n\t\tconst prevBytes = await blockStore.get(root.prev)\n\t\tif (!prevBytes) {\n\t\t\tthrow ERROR('[decodePubFromBlocks] prev snapshot missing from blockStore', {\n\t\t\t\tcurrentCID: cidStr,\n\t\t\t\tprev: root.prev.toString(),\n\t\t\t\tstopAtCID: stopAtCID?.toString(),\n\t\t\t\tvisited: [...visited]\n\t\t\t})\n\t\t}\n\n\t\tcurrentCID = root.prev // Move to previous snapshot\n\t}\n\n\tconst result = {\n\t\tcid: rootCID,\n\t\tinfo: firstInfo ? {\n\t\t\t...firstInfo,\n\t\t\tlogs: await Promise.all(firstInfo.logs.map(async (cidOrLink: CID) => {\n\t\t\t\tconst cid = cidOrLink\n\t\t\t\tconst applog = (await getDecodedBlock(blockStore, cid)) as Applog\n\t\t\t\tif (!applog) {\n\t\t\t\t\tERROR(`Could not find info log CID in pub blocks:`, cid.toString(), { cid, blockStore })\n\t\t\t\t\tthrow new Error(`Could not find info log CID in pub blocks: ${cid.toString()}`)\n\t\t\t\t}\n\t\t\t\tif ((applog.pv as any) instanceof CID) applog.pv = (applog.pv as any as CID).toV1().toString()\n\t\t\t\treturn {\n\t\t\t\t\t...applog,\n\t\t\t\t\tcid: cid.toV1().toString(),\n\t\t\t\t}\n\t\t\t})),\n\t\t} : null,\n\t\tapplogsCID,\n\t\tapplogs: allApplogs,\n\t}\n\tDEBUG('[decodePubFromBlocks] result:', result, { rootCID: rootCID.toString(), blockStore, applogs: allApplogs })\n\treturn result\n}\n\nexport async function getBlocksOfCar(car: CarReader) {\n\tconst rootsFromCar = await car.getRoots()\n\tconst roots = rootsFromCar.map(c => ((typeof c.toV1 === 'function') ? c : CID.decode(c.bytes)).toV1().toString() as CidString) // HACK\n\tconst blocks = new Map<CidString, any>()\n\tfor await (const { cid: cidFromCarblocks, bytes } of car.blocks()) {\n\t\tconst cid = (typeof cidFromCarblocks.toV1 === 'function') ? cidFromCarblocks : CID.decode(cidFromCarblocks.bytes)\n\t\tVERBOSE({ cidFromCarblocks, cid })\n\t\t// blocks.set(cid.toV1().toString(), dagJson.decode(bytes)) // HACK: tried using CID as map key, but because it's based on referential equality it's not working\n\t\tblocks.set(cid.toV1().toString(), bytes) // HACK: tried using CID as map key, but because it's based on referential equality it's not working\n\t}\n\tif (roots.length !== 1) {\n\t\tWARN('Unexpected roots count:', roots)\n\t}\n\treturn {\n\t\trootCID: CID.parse(roots[0]),\n\t\tblockStore: {\n\t\t\tget: (cid) => blocks.get(cid.toV1().toString()),\n\t\t},\n\t} satisfies DecodedCar\n}\nexport async function getDecodedBlock(blockStore: BlockStoreish, cid: CID) {\n\ttry {\n\t\tvar blob = await blockStore.get(cid)\n\t\tif (!blob) {\n\t\t\tWARN('returning null')\n\t\t\treturn null // I don't think this ever happens actually\n\t\t}\n\t} catch (err) {\n\t\tif ((err as any).message === 'Not Found') return null\n\t\tthrow err\n\t}\n\treturn dagJson.decode(blob)\n}\n\n// make out in the car... been a while but also sounds nice\nexport async function makeCarOut(roots: CIDForCar, blocks: BlockForCar[]) {\n\tconst { writer, out } = CarWriter.create(Array.isArray(roots) ? roots : [roots])\n\n\t// add the blocks to the CAR and close it\n\tVERBOSE(`Writing ${blocks.length} blocks to CAR`, { roots, blocks })\n\tblocks.forEach(b => writer.put(b))\n\twriter.close()\n\t// VERBOSE(`Wrote ${blocks.length} blocks to CAR`, writer)\n\treturn out\n} /** create a new CarWriter, with the encoded block as the root */\n\n// export async function makeCarReader(roots: CIDForCar, blocks: BlockForCar[]) {\n// \tconst out = await makeCarOut(roots, blocks)\n\n// \t// create a new CarReader we can hand to web3.storage.putCar\n// \tconst reader = await CarReader.fromIterable(out)\n// \tVERBOSE(`CAR reader`, reader)\n// \treturn reader\n// } /** create a new CarWriter, with the encoded block as the root */\n\nexport async function makeCarBlob(roots: CIDForCar, blocks: BlockForCar[]) {\n\tconst carOut = await makeCarOut(roots, blocks)\n\tconst chunks = []\n\tfor await (const chunk of carOut) {\n\t\tchunks.push(chunk)\n\t}\n\tconst blob = new Blob(chunks)\n\treturn blob\n}\nexport async function carFromBlob(blob: Blob | File): Promise<CarReader> {\n\treturn CarReader.fromBytes(new Uint8Array(await blob.arrayBuffer()))\n}\n\nfunction extractCids(value: unknown): CID[] {\n\tif (value instanceof CID) return [value]\n\tif (Array.isArray(value)) return value.flatMap(extractCids)\n\tif (value && typeof value === 'object') return Object.values(value).flatMap(extractCids)\n\treturn []\n}\n\nconst MAX_COLLECT_BLOCKS = 1_000_000\n\nexport async function collectDagBlocks(\n\tstartCID: CID,\n\tblockStore: BlockStoreish,\n): Promise<BlockForCar[]> {\n\tconst visited = new Set<string>()\n\tconst blocks: BlockForCar[] = []\n\tconst queue: CID[] = [startCID]\n\n\twhile (queue.length > 0) {\n\t\tif (blocks.length >= MAX_COLLECT_BLOCKS) {\n\t\t\tWARN(`[collectDagBlocks] hit ${MAX_COLLECT_BLOCKS} block limit, returning partial result`)\n\t\t\tbreak\n\t\t}\n\n\t\tconst cid = queue.shift()!\n\t\tconst cidStr = cid.toString()\n\t\tif (visited.has(cidStr)) continue\n\t\tvisited.add(cidStr)\n\n\t\tlet bytes: Uint8Array\n\t\ttry {\n\t\t\tbytes = await blockStore.get(cid)\n\t\t} catch {\n\t\t\tWARN(`[collectDagBlocks] block not found: ${cidStr}, stopping this branch`)\n\t\t\tcontinue\n\t\t}\n\t\tif (!bytes) {\n\t\t\tWARN(`[collectDagBlocks] block not found: ${cidStr}, stopping this branch`)\n\t\t\tcontinue\n\t\t}\n\n\t\tblocks.push({ cid, bytes })\n\n\t\tif (blocks.length % 1000 === 0) {\n\t\t\tLOG(`[collectDagBlocks] collected ${blocks.length} blocks...`)\n\t\t}\n\n\t\ttry {\n\t\t\tconst decoded = dagJson.decode(bytes)\n\t\t\tconst childCids = extractCids(decoded)\n\t\t\tfor (const child of childCids) {\n\t\t\t\tif (!visited.has(child.toString())) {\n\t\t\t\t\tqueue.push(child)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Not dag-json — leaf block, no children to walk\n\t\t}\n\t}\n\n\tDEBUG(`[collectDagBlocks] collected ${blocks.length} blocks from ${startCID.toString()}`)\n\treturn blocks\n}\n\nexport function streamReaderToIterable(bodyReader: ReadableStreamDefaultReader<Uint8Array>): AsyncIterable<Uint8Array> {\n\treturn (async function*() {\n\t\twhile (true) {\n\t\t\tconst { done, value } = await bodyReader.read()\n\t\t\tVERBOSE(`[car] chunk`, { done, value })\n\t\t\tif (done) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tyield value\n\t\t}\n\t})()\n}\n"],"mappings":"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","names":["dagJson","Logger","WARN","LOG","DEBUG","VERBOSE","ERROR","Logger","share","dagJson","block"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cyrb53hash,
|
|
3
3
|
ensureTsPvAndFinalizeApplog
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-4MKPGQIM.js";
|
|
5
5
|
|
|
6
6
|
// src/pubsub/pub-pull.ts
|
|
7
7
|
import { Logger } from "besonders-logger";
|
|
@@ -37,4 +37,4 @@ export {
|
|
|
37
37
|
isSubscription,
|
|
38
38
|
agentToShortHash
|
|
39
39
|
};
|
|
40
|
-
//# sourceMappingURL=chunk-
|
|
40
|
+
//# sourceMappingURL=chunk-H4YVJKB7.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/viewmodel/adapters/typebox.ts
|
|
2
|
+
function createTypeBoxAdapter(typeBoxSchema, entityPrefix, options) {
|
|
3
|
+
const properties = typeBoxSchema.properties;
|
|
4
|
+
const toAtPath = options?.toAtPath ?? ((prefix, name) => `${prefix}/${name}`);
|
|
5
|
+
const attributeDefs = Object.entries(properties).map(([name, propSchema]) => {
|
|
6
|
+
const isOptional = propSchema.optional ?? false;
|
|
7
|
+
const wovinAtName = propSchema.wovinAtName;
|
|
8
|
+
const atPath = options?.atOverrides?.[name] ?? wovinAtName ?? toAtPath(entityPrefix, name);
|
|
9
|
+
return {
|
|
10
|
+
name,
|
|
11
|
+
atPath,
|
|
12
|
+
optional: isOptional,
|
|
13
|
+
defaultValue: propSchema.default
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
getAttributeDefs: () => attributeDefs,
|
|
18
|
+
getDefaults: () => options?.defaults ?? {},
|
|
19
|
+
getEntityPrefix: () => entityPrefix,
|
|
20
|
+
createValidator: options?.validator ? () => options.validator : void 0
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
createTypeBoxAdapter
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=chunk-ICBK7NC4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/viewmodel/adapters/typebox.ts"],"sourcesContent":["import type { ApplogValue } from '../../applog/datom-types.ts'\nimport type { ISchemaAdapter, VMAttributeDef } from '../types.ts'\n\n/**\n * Create a schema adapter from a TypeBox object schema.\n *\n * Usage:\n * ```ts\n * import { Type } from '@sinclair/typebox'\n * import { createTypeBoxAdapter } from '@wovin/core/viewmodel/adapters/typebox'\n *\n * const MySchema = Type.Object({\n * name: Type.String(),\n * age: Type.Optional(Type.Number()),\n * })\n *\n * const adapter = createTypeBoxAdapter(MySchema, 'myEntity')\n * ```\n *\n * The type parameter `T` is the inferred static type of the schema.\n * Can pass a compiled validator separately if runtime validation is needed.\n */\nexport function createTypeBoxAdapter<T extends Record<string, ApplogValue>>(\n\ttypeBoxSchema: { properties: Record<string, any>; type?: string },\n\tentityPrefix: string,\n\toptions?: {\n\t\t/** Override at-paths for specific attributes */\n\t\tatOverrides?: Record<string, string>\n\t\t/** Default values */\n\t\tdefaults?: Partial<T>\n\t\t/** Property name to at-path mapping function */\n\t\ttoAtPath?: (entityPrefix: string, attrName: string) => string\n\t\t/** A pre-compiled validator function (e.g. from TypeCompiler.Compile) */\n\t\tvalidator?: (value: unknown) => value is T\n\t},\n): ISchemaAdapter<T> {\n\tconst properties = typeBoxSchema.properties as Record<string, any>\n\tconst toAtPath = options?.toAtPath ?? ((prefix, name) => `${prefix}/${name}`)\n\n\tconst attributeDefs: VMAttributeDef[] = Object.entries(properties).map(([name, propSchema]: [string, any]) => {\n\t\tconst isOptional = propSchema.optional ?? false\n\t\tconst wovinAtName = propSchema.wovinAtName\n\t\tconst atPath = options?.atOverrides?.[name] ?? wovinAtName ?? toAtPath(entityPrefix, name)\n\n\t\treturn {\n\t\t\tname,\n\t\t\tatPath,\n\t\t\toptional: isOptional,\n\t\t\tdefaultValue: propSchema.default,\n\t\t}\n\t})\n\n\treturn {\n\t\tgetAttributeDefs: () => attributeDefs,\n\t\tgetDefaults: () => (options?.defaults ?? {}) as Partial<T>,\n\t\tgetEntityPrefix: () => entityPrefix,\n\t\tcreateValidator: options?.validator ? () => options.validator! : undefined,\n\t}\n}\n"],"mappings":";AAsBO,SAAS,qBACf,eACA,cACA,SAUoB;AACpB,QAAM,aAAa,cAAc;AACjC,QAAM,WAAW,SAAS,aAAa,CAAC,QAAQ,SAAS,GAAG,MAAM,IAAI,IAAI;AAE1E,QAAM,gBAAkC,OAAO,QAAQ,UAAU,EAAE,IAAI,CAAC,CAAC,MAAM,UAAU,MAAqB;AAC7G,UAAM,aAAa,WAAW,YAAY;AAC1C,UAAM,cAAc,WAAW;AAC/B,UAAM,SAAS,SAAS,cAAc,IAAI,KAAK,eAAe,SAAS,cAAc,IAAI;AAEzF,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,cAAc,WAAW;AAAA,IAC1B;AAAA,EACD,CAAC;AAED,SAAO;AAAA,IACN,kBAAkB,MAAM;AAAA,IACxB,aAAa,MAAO,SAAS,YAAY,CAAC;AAAA,IAC1C,iBAAiB,MAAM;AAAA,IACvB,iBAAiB,SAAS,YAAY,MAAM,QAAQ,YAAa;AAAA,EAClE;AACD;","names":[]}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveKeyMapper
|
|
3
|
+
} from "./chunk-N7SEGHU4.js";
|
|
4
|
+
import {
|
|
5
|
+
SubscribableImpl,
|
|
6
|
+
ThreadInMemory,
|
|
7
|
+
anyOf,
|
|
8
|
+
createDebugName,
|
|
9
|
+
isInitEvent,
|
|
10
|
+
isoDateStrCompare,
|
|
11
|
+
makeFilter,
|
|
12
|
+
memoizedFn,
|
|
13
|
+
rollingFilter
|
|
14
|
+
} from "./chunk-4MKPGQIM.js";
|
|
15
|
+
|
|
16
|
+
// src/query/divergences.ts
|
|
17
|
+
import { Logger } from "besonders-logger";
|
|
18
|
+
var { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO);
|
|
19
|
+
var queryDivergencesByPrev = memoizedFn("queryDivergencesByPrev", function queryConflictingByPrev(sourceThread) {
|
|
20
|
+
DEBUG(`queryDivergencesByPrev<${sourceThread.nameAndSizeUntracked}>`);
|
|
21
|
+
if (sourceThread.filters.includes("lastWriteWins")) WARN(`queryDivergencesByPrev on thread lastWriteWins`, sourceThread);
|
|
22
|
+
const logsForNode = /* @__PURE__ */ new Map();
|
|
23
|
+
const leafs = /* @__PURE__ */ new Set();
|
|
24
|
+
VERBOSE("all applogs:", sourceThread.applogs);
|
|
25
|
+
for (const log of sourceThread.applogs) {
|
|
26
|
+
let prevLogs;
|
|
27
|
+
if (log.pv) {
|
|
28
|
+
prevLogs = log.pv && logsForNode.get(log.pv.toString());
|
|
29
|
+
leafs.delete(log.pv.toString());
|
|
30
|
+
}
|
|
31
|
+
VERBOSE("traversing log", { log, prevLogs, leafs: Array.from(leafs) });
|
|
32
|
+
logsForNode.set(log.cid, prevLogs ? [...prevLogs, log] : [log]);
|
|
33
|
+
leafs.add(log.cid);
|
|
34
|
+
}
|
|
35
|
+
const divergences = Array.from(leafs).map((leafID) => {
|
|
36
|
+
const thread = new ThreadInMemory(
|
|
37
|
+
createDebugName({
|
|
38
|
+
caller: "DivergenceLeaf",
|
|
39
|
+
thread: sourceThread,
|
|
40
|
+
pattern: `leaf: ${leafID}`
|
|
41
|
+
}),
|
|
42
|
+
logsForNode.get(leafID),
|
|
43
|
+
sourceThread.filters,
|
|
44
|
+
true
|
|
45
|
+
);
|
|
46
|
+
return { log: thread.latestLog, thread };
|
|
47
|
+
});
|
|
48
|
+
return divergences;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// src/query/entity-collection.ts
|
|
52
|
+
import { Logger as Logger2 } from "besonders-logger";
|
|
53
|
+
var { DEBUG: DEBUG2 } = Logger2.setup(Logger2.INFO);
|
|
54
|
+
function liveEntityCollection(thread, discoveryPattern, liveAttributes, opts) {
|
|
55
|
+
return _liveEntityCollection(
|
|
56
|
+
thread,
|
|
57
|
+
discoveryPattern,
|
|
58
|
+
liveAttributes,
|
|
59
|
+
opts
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
var _liveEntityCollection = memoizedFn(
|
|
63
|
+
"liveEntityCollection",
|
|
64
|
+
function liveEntityCollection2(thread, discoveryPattern, liveAttributes, opts) {
|
|
65
|
+
DEBUG2("liveEntityCollection", discoveryPattern, liveAttributes);
|
|
66
|
+
const discoveryAttr = discoveryPattern.at;
|
|
67
|
+
const allAttrs = /* @__PURE__ */ new Set([discoveryAttr, ...liveAttributes]);
|
|
68
|
+
const filtered = rollingFilter(thread, { at: anyOf(...allAttrs) });
|
|
69
|
+
const isDiscoveryMatch = makeFilter(discoveryPattern);
|
|
70
|
+
const attrSet = new Set(liveAttributes);
|
|
71
|
+
const key = resolveKeyMapper(opts);
|
|
72
|
+
const map = /* @__PURE__ */ new Map();
|
|
73
|
+
function makeRecord(entityId) {
|
|
74
|
+
const record = {};
|
|
75
|
+
for (const attr of liveAttributes) record[key(attr)] = null;
|
|
76
|
+
for (const log of filtered.applogs) {
|
|
77
|
+
if (log.en === entityId && attrSet.has(log.at)) {
|
|
78
|
+
record[key(log.at)] = log.vl;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return record;
|
|
82
|
+
}
|
|
83
|
+
function buildFull(applogs) {
|
|
84
|
+
map.clear();
|
|
85
|
+
for (const log of isDiscoveryMatch(applogs)) {
|
|
86
|
+
if (!map.has(log.en)) map.set(log.en, makeRecord(log.en));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function addLog(log) {
|
|
90
|
+
if (isDiscoveryMatch([log]).length > 0 && !map.has(log.en)) {
|
|
91
|
+
map.set(log.en, makeRecord(log.en));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (attrSet.has(log.at)) {
|
|
95
|
+
const record = map.get(log.en);
|
|
96
|
+
if (record) record[key(log.at)] = log.vl;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function removeLog(log) {
|
|
100
|
+
if (isDiscoveryMatch([log]).length > 0) {
|
|
101
|
+
const stillDiscovered = filtered.applogs.some(
|
|
102
|
+
(l) => l.en === log.en && isDiscoveryMatch([l]).length > 0
|
|
103
|
+
);
|
|
104
|
+
if (!stillDiscovered) {
|
|
105
|
+
map.delete(log.en);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (attrSet.has(log.at)) {
|
|
110
|
+
const record = map.get(log.en);
|
|
111
|
+
if (record) {
|
|
112
|
+
const current = filtered.applogs.find((l) => l.en === log.en && l.at === log.at);
|
|
113
|
+
record[key(log.at)] = current?.vl ?? null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
buildFull(filtered.applogs);
|
|
118
|
+
const result = new SubscribableImpl(
|
|
119
|
+
map,
|
|
120
|
+
() => filtered.subscribe((event) => {
|
|
121
|
+
if (isInitEvent(event)) {
|
|
122
|
+
buildFull(event.init);
|
|
123
|
+
} else {
|
|
124
|
+
if (event.removed) for (const log of event.removed) removeLog(log);
|
|
125
|
+
for (const log of event.added) addLog(log);
|
|
126
|
+
}
|
|
127
|
+
result._set(map);
|
|
128
|
+
}, "derived"),
|
|
129
|
+
{ equals: false }
|
|
130
|
+
);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// src/query/epoch-snapshot.ts
|
|
136
|
+
import { addDays, addHours, addMinutes, addSeconds, subDays, subHours, subMinutes, subSeconds } from "date-fns";
|
|
137
|
+
function createEpochSnapshot(thread, options) {
|
|
138
|
+
const { start, end } = options;
|
|
139
|
+
if (isoDateStrCompare(start, end, "asc") >= 0) {
|
|
140
|
+
throw new RangeError(
|
|
141
|
+
`[createEpochSnapshot] Empty time range: start (${start}) >= end (${end})`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const groups = /* @__PURE__ */ Object.create(null);
|
|
145
|
+
for (const log of thread.applogs) {
|
|
146
|
+
if (log.ts < start || log.ts >= end) continue;
|
|
147
|
+
let bucket = groups[log.at];
|
|
148
|
+
if (!bucket) {
|
|
149
|
+
bucket = [];
|
|
150
|
+
groups[log.at] = bucket;
|
|
151
|
+
}
|
|
152
|
+
bucket.push(log);
|
|
153
|
+
}
|
|
154
|
+
const keys = Object.keys(groups);
|
|
155
|
+
for (let i = 0; i < keys.length; i++) {
|
|
156
|
+
const attr = keys[i];
|
|
157
|
+
groups[attr].sort((a, b) => isoDateStrCompare(a.ts, b.ts, "asc"));
|
|
158
|
+
}
|
|
159
|
+
const map = /* @__PURE__ */ new Map();
|
|
160
|
+
for (let i = 0; i < keys.length; i++) {
|
|
161
|
+
const attr = keys[i];
|
|
162
|
+
map.set(attr, groups[attr]);
|
|
163
|
+
}
|
|
164
|
+
return map;
|
|
165
|
+
}
|
|
166
|
+
function resolveTimeSpec(spec, thread, now) {
|
|
167
|
+
if (typeof spec === "number") {
|
|
168
|
+
if (spec === -1) {
|
|
169
|
+
if (thread.applogs.length === 0) return now.toISOString();
|
|
170
|
+
return thread.applogs[0].ts;
|
|
171
|
+
}
|
|
172
|
+
if (spec === 0) return now.toISOString();
|
|
173
|
+
return new Date(spec).toISOString();
|
|
174
|
+
}
|
|
175
|
+
if (spec === "now") return now.toISOString();
|
|
176
|
+
const relMatch = spec.match(/^([+-]?\d+)([dhms])$/);
|
|
177
|
+
if (relMatch) {
|
|
178
|
+
const amount = parseInt(relMatch[1], 10);
|
|
179
|
+
const unit = relMatch[2];
|
|
180
|
+
let result;
|
|
181
|
+
if (amount < 0) {
|
|
182
|
+
const abs = Math.abs(amount);
|
|
183
|
+
switch (unit) {
|
|
184
|
+
case "d":
|
|
185
|
+
result = subDays(now, abs);
|
|
186
|
+
break;
|
|
187
|
+
case "h":
|
|
188
|
+
result = subHours(now, abs);
|
|
189
|
+
break;
|
|
190
|
+
case "m":
|
|
191
|
+
result = subMinutes(now, abs);
|
|
192
|
+
break;
|
|
193
|
+
case "s":
|
|
194
|
+
result = subSeconds(now, abs);
|
|
195
|
+
break;
|
|
196
|
+
default:
|
|
197
|
+
result = now;
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
switch (unit) {
|
|
201
|
+
case "d":
|
|
202
|
+
result = addDays(now, amount);
|
|
203
|
+
break;
|
|
204
|
+
case "h":
|
|
205
|
+
result = addHours(now, amount);
|
|
206
|
+
break;
|
|
207
|
+
case "m":
|
|
208
|
+
result = addMinutes(now, amount);
|
|
209
|
+
break;
|
|
210
|
+
case "s":
|
|
211
|
+
result = addSeconds(now, amount);
|
|
212
|
+
break;
|
|
213
|
+
default:
|
|
214
|
+
result = now;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return result.toISOString();
|
|
218
|
+
}
|
|
219
|
+
return spec;
|
|
220
|
+
}
|
|
221
|
+
function serializeEpochSnapshot(snapshot, metadata) {
|
|
222
|
+
const groups = {};
|
|
223
|
+
let logCount = 0;
|
|
224
|
+
for (const [attr, logs] of snapshot) {
|
|
225
|
+
groups[attr] = logs;
|
|
226
|
+
logCount += logs.length;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
format: "wovin-epoch-snapshot-v1",
|
|
230
|
+
attribute: metadata.attribute,
|
|
231
|
+
timeRange: metadata.timeRange,
|
|
232
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
233
|
+
logCount,
|
|
234
|
+
groups
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async function purgeHistory(thread, attribute, start, end, options) {
|
|
238
|
+
const now = options?.now ?? /* @__PURE__ */ new Date();
|
|
239
|
+
const resolvedStart = resolveTimeSpec(start, thread, now);
|
|
240
|
+
const resolvedEnd = resolveTimeSpec(end, thread, now);
|
|
241
|
+
const timeRange = { start: resolvedStart, end: resolvedEnd };
|
|
242
|
+
const snapshot = createEpochSnapshot(thread, timeRange);
|
|
243
|
+
if (options?.persistSnapshot) {
|
|
244
|
+
const serialized = serializeEpochSnapshot(snapshot, {
|
|
245
|
+
attribute,
|
|
246
|
+
timeRange
|
|
247
|
+
});
|
|
248
|
+
await options.persistSnapshot(serialized);
|
|
249
|
+
}
|
|
250
|
+
const targetLogs = snapshot.get(attribute);
|
|
251
|
+
if (!targetLogs || targetLogs.length === 0) {
|
|
252
|
+
return { snapshot, purgedCount: 0, keptCount: 0, timeRange };
|
|
253
|
+
}
|
|
254
|
+
const byEntity = /* @__PURE__ */ new Map();
|
|
255
|
+
for (const log of targetLogs) {
|
|
256
|
+
let bucket = byEntity.get(log.en);
|
|
257
|
+
if (!bucket) {
|
|
258
|
+
bucket = [];
|
|
259
|
+
byEntity.set(log.en, bucket);
|
|
260
|
+
}
|
|
261
|
+
bucket.push(log);
|
|
262
|
+
}
|
|
263
|
+
const cidsToPurge = [];
|
|
264
|
+
let keptCount = 0;
|
|
265
|
+
for (const [, logs] of byEntity) {
|
|
266
|
+
if (logs.length <= 1) {
|
|
267
|
+
keptCount += logs.length;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const winner = logs[logs.length - 1];
|
|
271
|
+
keptCount += 1;
|
|
272
|
+
for (let i = 0; i < logs.length - 1; i++) {
|
|
273
|
+
cidsToPurge.push(logs[i].cid);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const purgedCount = thread.purge(cidsToPurge);
|
|
277
|
+
return { snapshot, purgedCount, keptCount, timeRange };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export {
|
|
281
|
+
queryDivergencesByPrev,
|
|
282
|
+
liveEntityCollection,
|
|
283
|
+
createEpochSnapshot,
|
|
284
|
+
resolveTimeSpec,
|
|
285
|
+
serializeEpochSnapshot,
|
|
286
|
+
purgeHistory
|
|
287
|
+
};
|
|
288
|
+
//# sourceMappingURL=chunk-N5QPZNKD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/query/divergences.ts","../src/query/entity-collection.ts","../src/query/epoch-snapshot.ts"],"sourcesContent":["import { Logger } from 'besonders-logger'\nimport stringify from 'safe-stable-stringify'\nimport { Applog, CidString } from '../applog/datom-types.ts'\nimport { createDebugName } from '../utils/debug-name.ts'\nimport { Thread } from '../thread/basic.ts'\nimport { ThreadInMemory } from '../thread/writeable.ts'\nimport { memoizedFn } from './memoized.ts'\n\nconst { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars\n\nexport interface DivergenceLeaf {\n\tlog: Applog\n\tthread: Thread\n}\n\nexport const queryDivergencesByPrev = memoizedFn('queryDivergencesByPrev', function queryConflictingByPrev(\n\tsourceThread: Thread,\n) {\n\tDEBUG(`queryDivergencesByPrev<${sourceThread.nameAndSizeUntracked}>`)\n\tif (sourceThread.filters.includes('lastWriteWins')) WARN(`queryDivergencesByPrev on thread lastWriteWins`, sourceThread)\n\n\tconst logsForNode = new Map<CidString, Applog[]>()\n\tconst leafs = new Set<CidString>()\n\tVERBOSE('all applogs:', sourceThread.applogs)\n\tfor (const log of sourceThread.applogs) {\n\t\tlet prevLogs\n\t\tif (log.pv) {\n\t\t\tprevLogs = log.pv && logsForNode.get(log.pv.toString())\n\t\t\tleafs.delete(log.pv.toString())\n\t\t}\n\t\tVERBOSE('traversing log', { log, prevLogs, leafs: Array.from(leafs) })\n\t\tlogsForNode.set(log.cid, prevLogs ? [...prevLogs, log] : [log])\n\t\tleafs.add(log.cid)\n\t}\n\tconst divergences = Array.from(leafs).map(leafID => {\n\t\tconst thread = new ThreadInMemory(\n\t\t\tcreateDebugName({\n\t\t\t\tcaller: 'DivergenceLeaf',\n\t\t\t\tthread: sourceThread,\n\t\t\t\tpattern: `leaf: ${leafID}`,\n\t\t\t}),\n\t\t\tlogsForNode.get(leafID),\n\t\t\tsourceThread.filters,\n\t\t\ttrue,\n\t\t)\n\t\treturn ({ log: thread.latestLog, thread })\n\t})\n\t// TODO: migrate to SubscribableArray for reactive updates\n\treturn divergences\n})\n","import { Logger } from 'besonders-logger'\nimport { Applog, ApplogValue, DatalogQueryPattern, EntityID } from '../applog/datom-types.ts'\nimport { isInitEvent, Thread } from '../thread/basic.ts'\nimport { makeFilter, rollingFilter } from '../thread/filters.ts'\nimport { resolveKeyMapper } from './basic.ts'\nimport { anyOf } from './matchers.ts'\nimport { memoizedFn } from './memoized.ts'\nimport { SubscribableImpl } from './subscribable.ts'\nimport type { Subscribable } from './subscribable.ts'\nimport type { StripExplicitPrefix, StripFirstPrefix } from './attr-helpers.ts'\n\nconst { DEBUG } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars\n\nexport function liveEntityCollection<A extends string>(\n\tthread: Thread, discoveryPattern: DatalogQueryPattern, liveAttributes: readonly A[],\n): Subscribable<ReadonlyMap<EntityID, Record<A, ApplogValue | null>>>\nexport function liveEntityCollection<A extends string>(\n\tthread: Thread, discoveryPattern: DatalogQueryPattern, liveAttributes: readonly A[],\n\topts: { stripAtPrefix: true },\n): Subscribable<ReadonlyMap<EntityID, Record<StripFirstPrefix<A>, ApplogValue | null>>>\nexport function liveEntityCollection<A extends string, P extends string>(\n\tthread: Thread, discoveryPattern: DatalogQueryPattern, liveAttributes: readonly A[],\n\topts: { stripAtPrefix: P },\n): Subscribable<ReadonlyMap<EntityID, Record<StripExplicitPrefix<A, P>, ApplogValue | null>>>\nexport function liveEntityCollection<A extends string>(\n\tthread: Thread, discoveryPattern: DatalogQueryPattern, liveAttributes: readonly A[],\n\topts: { mapKeys: (attr: A) => string },\n): Subscribable<ReadonlyMap<EntityID, Record<string, ApplogValue | null>>>\nexport function liveEntityCollection<A extends string>(\n\tthread: Thread, discoveryPattern: DatalogQueryPattern, liveAttributes: readonly A[],\n\topts: { stripAtPrefix?: true | string; mapKeys?: (attr: A) => string },\n): Subscribable<ReadonlyMap<EntityID, Record<string, ApplogValue | null>>>\nexport function liveEntityCollection<A extends string>(\n\tthread: Thread,\n\tdiscoveryPattern: DatalogQueryPattern,\n\tliveAttributes: readonly A[],\n\topts?: { stripAtPrefix?: true | string; mapKeys?: (attr: A) => string },\n): Subscribable<ReadonlyMap<EntityID, Record<string, ApplogValue | null>>> {\n\treturn _liveEntityCollection(thread, discoveryPattern, liveAttributes,\n\t\topts as { stripAtPrefix?: true | string; mapKeys?: (attr: string) => string })\n}\n\nconst _liveEntityCollection = memoizedFn('liveEntityCollection',\n\tfunction liveEntityCollection<A extends string>(\n\t\tthread: Thread,\n\t\tdiscoveryPattern: DatalogQueryPattern,\n\t\tliveAttributes: readonly A[],\n\t\topts?: { stripAtPrefix?: true | string; mapKeys?: (attr: string) => string },\n\t): Subscribable<ReadonlyMap<EntityID, Record<string, ApplogValue | null>>> {\n\t\tDEBUG('liveEntityCollection', discoveryPattern, liveAttributes)\n\t\tconst discoveryAttr = discoveryPattern.at as string\n\t\tconst allAttrs = new Set([discoveryAttr, ...liveAttributes])\n\t\tconst filtered = rollingFilter(thread, { at: anyOf(...allAttrs) })\n\t\tconst isDiscoveryMatch = makeFilter(discoveryPattern)\n\t\tconst attrSet = new Set<string>(liveAttributes)\n\t\tconst key = resolveKeyMapper(opts)\n\n\t\tconst map = new Map<EntityID, Record<string, ApplogValue | null>>()\n\n\t\tfunction makeRecord(entityId: EntityID): Record<string, ApplogValue | null> {\n\t\t\tconst record = {} as Record<string, ApplogValue | null>\n\t\t\tfor (const attr of liveAttributes) record[key(attr)] = null\n\t\t\t// Backfill from current filtered state\n\t\t\tfor (const log of filtered.applogs) {\n\t\t\t\tif (log.en === entityId && attrSet.has(log.at)) {\n\t\t\t\t\trecord[key(log.at)] = log.vl\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn record\n\t\t}\n\n\t\tfunction buildFull(applogs: readonly Applog[]) {\n\t\t\tmap.clear()\n\t\t\tfor (const log of isDiscoveryMatch(applogs)) {\n\t\t\t\tif (!map.has(log.en)) map.set(log.en, makeRecord(log.en))\n\t\t\t}\n\t\t}\n\n\t\tfunction addLog(log: Applog) {\n\t\t\t// Discovery match → ensure entity exists\n\t\t\tif (isDiscoveryMatch([log]).length > 0 && !map.has(log.en)) {\n\t\t\t\tmap.set(log.en, makeRecord(log.en))\n\t\t\t\treturn // makeRecord already backfilled attrs\n\t\t\t}\n\t\t\t// Attribute match → update value\n\t\t\tif (attrSet.has(log.at)) {\n\t\t\t\tconst record = map.get(log.en)\n\t\t\t\tif (record) record[key(log.at)] = log.vl\n\t\t\t}\n\t\t}\n\n\t\tfunction removeLog(log: Applog) {\n\t\t\tif (isDiscoveryMatch([log]).length > 0) {\n\t\t\t\t// Check if entity still has another discovery match\n\t\t\t\tconst stillDiscovered = filtered.applogs.some(\n\t\t\t\t\tl => l.en === log.en && isDiscoveryMatch([l]).length > 0,\n\t\t\t\t)\n\t\t\t\tif (!stillDiscovered) {\n\t\t\t\t\tmap.delete(log.en)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (attrSet.has(log.at)) {\n\t\t\t\tconst record = map.get(log.en)\n\t\t\t\tif (record) {\n\t\t\t\t\t// Find current value from remaining applogs\n\t\t\t\t\tconst current = filtered.applogs.find(l => l.en === log.en && l.at === log.at)\n\t\t\t\t\trecord[key(log.at)] = current?.vl ?? null\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Initial build\n\t\tbuildFull(filtered.applogs)\n\n\t\tconst result = new SubscribableImpl<ReadonlyMap<EntityID, Record<string, ApplogValue | null>>>(\n\t\t\tmap,\n\t\t\t() => filtered.subscribe((event) => {\n\t\t\t\tif (isInitEvent(event)) {\n\t\t\t\t\tbuildFull(event.init)\n\t\t\t\t} else {\n\t\t\t\t\t// Process removes before adds — LWW updates appear as remove+add in same delta\n\t\t\t\t\tif (event.removed) for (const log of event.removed) removeLog(log)\n\t\t\t\t\tfor (const log of event.added) addLog(log)\n\t\t\t\t}\n\t\t\t\tresult._set(map)\n\t\t\t}, 'derived'),\n\t\t\t{ equals: false },\n\t\t)\n\t\treturn result\n\t},\n)\n","import { addDays, addHours, addMinutes, addSeconds, subDays, subHours, subMinutes, subSeconds } from 'date-fns'\nimport type { Applog, Attribute, CidString, EntityID, Timestamp } from '../applog/datom-types.ts'\nimport { isoDateStrCompare } from '../applog/applog-utils.ts'\nimport type { Thread } from '../thread/basic.ts'\nimport type { WriteableThread } from '../thread/writeable.ts'\n\n/**\n * An EpochSnapshot maps every {@link Attribute} present in a time window to the\n * array of {@link Applog}s (from any entity) that carry that attribute.\n *\n * This is **not** a flat array — logs are grouped by attribute first so that\n * consumers can answer questions like \"which entities had their `movie/title`\n * updated in this window?\" without re-filtering.\n *\n * @example\n * ```typescript\n * const snap = createEpochSnapshot(thread, {\n * start: '2024-01-01T00:00:00.000Z',\n * end: '2024-06-01T00:00:00.000Z',\n * })\n * for (const [attr, logs] of snap) {\n * console.log(`${attr}: ${logs.length} logs`)\n * }\n * ```\n */\nexport type EpochSnapshot = ReadonlyMap<Attribute, readonly Applog[]>\n\n/**\n * Options for {@link createEpochSnapshot}.\n */\nexport interface EpochSnapshotOptions {\n\t/**\n\t * Start of the time window (inclusive). ISO 8601 string matching the\n\t * {@link Timestamp} format used on every {@link Applog}.\n\t */\n\treadonly start: Timestamp\n\t/**\n\t * End of the time window (exclusive). ISO 8601 string.\n\t * Logs whose `ts` is **equal to** `end` are excluded.\n\t */\n\treadonly end: Timestamp\n}\n\n/**\n * Creates an {@link EpochSnapshot} — a point-in-time grouping of all app logs\n * within `[start, end)` grouped by their attribute name.\n *\n * The function is a **one-off (snapshot) computation**: it reads the current\n * state of the thread and returns a frozen map. If you need reactivity, wrap\n * the result in a reactive derivation or subscribe to thread changes and\n * re-compute.\n *\n * @param thread - The thread to snapshot.\n * @param options - Time range bounds.\n * @returns A `ReadonlyMap` keyed by attribute name. Each value is the\n * (chronologically sorted) array of applogs whose `at` matches that\n * key and whose `ts` falls inside `[start, end)`.\n *\n * @throws {Error} If `start` is lexically `>=` `end` (empty range).\n *\n * @example\n * ```typescript\n * const snap = createEpochSnapshot(thread, {\n * start: '2024-01-01T00:00:00.000Z',\n * end: '2025-01-01T00:00:00.000Z',\n * })\n * // Iterate attribute groups:\n * for (const [attr, logs] of snap) {\n * console.log(attr, logs.length)\n * }\n * ```\n */\nexport function createEpochSnapshot(\n\tthread: Thread,\n\toptions: EpochSnapshotOptions,\n): EpochSnapshot {\n\tconst { start, end } = options\n\n\t// Validate range — reject empty windows early.\n\tif (isoDateStrCompare(start, end, 'asc') >= 0) {\n\t\tthrow new RangeError(\n\t\t\t`[createEpochSnapshot] Empty time range: start (${start}) >= end (${end})`,\n\t\t)\n\t}\n\n\t// Phase 1: filter by time range, group by attribute.\n\t// Using a plain object for accumulation because:\n\t// - we know all keys are strings (Attribute) so no mixed-type Map key issues\n\t// - bulk construction into a ReadonlyMap at the end gives the desired API shape\n\tconst groups: Record<Attribute, Applog[]> = Object.create(null)\n\n\tfor (const log of thread.applogs) {\n\t\t// Half-open range: [start, end)\n\t\tif (log.ts < start || log.ts >= end) continue\n\n\t\tlet bucket = groups[log.at as Attribute]\n\t\tif (!bucket) {\n\t\t\tbucket = []\n\t\t\tgroups[log.at as Attribute] = bucket\n\t\t}\n\t\tbucket.push(log)\n\t}\n\n\t// Phase 2: sort each group by timestamp (ascending) for deterministic output.\n\tconst keys = Object.keys(groups)\n\tfor (let i = 0; i < keys.length; i++) {\n\t\tconst attr = keys[i] as Attribute\n\t\tgroups[attr].sort((a, b) => isoDateStrCompare(a.ts, b.ts, 'asc'))\n\t}\n\n\t// Phase 3: freeze into a ReadonlyMap.\n\tconst map = new Map<Attribute, readonly Applog[]>()\n\tfor (let i = 0; i < keys.length; i++) {\n\t\tconst attr = keys[i] as Attribute\n\t\tmap.set(attr, groups[attr])\n\t}\n\n\treturn map\n}\n\n// ─── Time Specification ───────────────────────────────────────────\n\n/**\n * A flexible time specification used by {@link purgeHistory}.\n *\n * - `-1` (number) — the earliest log's timestamp in the thread (\"beginning\")\n * - `0` (number) — current time (\"now\")\n * - Positive number — treated as a Unix millisecond timestamp\n * - `'-1d'`, `'-2h'`, `'-30m'`, `'-5s'` — relative offset **before** now\n * - `'+1d'`, `'2h'` — relative offset **after** now (no sign = forward)\n * - ISO 8601 string — absolute timestamp, used as-is\n * - `'now'` — current time\n */\nexport type TimeSpec = number | string\n\n/**\n * Resolve a {@link TimeSpec} to an absolute ISO {@link Timestamp}.\n *\n * @param spec - The time specification to resolve.\n * @param thread - The thread (used to find earliest log when spec is `-1`).\n * @param now - The reference \"now\" for relative specs.\n * @returns An ISO 8601 timestamp string.\n */\nexport function resolveTimeSpec(\n\tspec: TimeSpec,\n\tthread: Thread,\n\tnow: Date,\n): Timestamp {\n\tif (typeof spec === 'number') {\n\t\tif (spec === -1) {\n\t\t\t// Beginning of thread — use earliest log's timestamp, or now if empty\n\t\t\tif (thread.applogs.length === 0) return now.toISOString()\n\t\t\treturn thread.applogs[0].ts\n\t\t}\n\t\tif (spec === 0) return now.toISOString()\n\t\t// Treat as Unix milliseconds\n\t\treturn new Date(spec).toISOString()\n\t}\n\n\t// String spec\n\tif (spec === 'now') return now.toISOString()\n\n\t// Relative time: -1d, -2h, -30m, +1d, 2h, 0d, etc.\n\tconst relMatch = spec.match(/^([+-]?\\d+)([dhms])$/)\n\tif (relMatch) {\n\t\tconst amount = parseInt(relMatch[1], 10)\n\t\tconst unit = relMatch[2]\n\t\tlet result: Date\n\t\tif (amount < 0) {\n\t\t\tconst abs = Math.abs(amount)\n\t\t\tswitch (unit) {\n\t\t\t\tcase 'd': result = subDays(now, abs); break\n\t\t\t\tcase 'h': result = subHours(now, abs); break\n\t\t\t\tcase 'm': result = subMinutes(now, abs); break\n\t\t\t\tcase 's': result = subSeconds(now, abs); break\n\t\t\t\tdefault: result = now\n\t\t\t}\n\t\t} else {\n\t\t\tswitch (unit) {\n\t\t\t\tcase 'd': result = addDays(now, amount); break\n\t\t\t\tcase 'h': result = addHours(now, amount); break\n\t\t\t\tcase 'm': result = addMinutes(now, amount); break\n\t\t\t\tcase 's': result = addSeconds(now, amount); break\n\t\t\t\tdefault: result = now\n\t\t\t}\n\t\t}\n\t\treturn result.toISOString()\n\t}\n\n\t// Fall through — assume it's an absolute ISO string\n\treturn spec\n}\n\n// ─── Serialization ────────────────────────────────────────────────\n\n/**\n * A JSON-serializable representation of an {@link EpochSnapshot}, safe for\n * writing to disk, sending over the wire, or storing in IPFS.\n */\nexport interface SerializedEpochSnapshot {\n\t/** Format marker for versioning */\n\treadonly format: 'wovin-epoch-snapshot-v1'\n\t/** The attribute whose history was captured / purged */\n\treadonly attribute: string\n\t/** The time range this snapshot covers */\n\treadonly timeRange: { readonly start: Timestamp; readonly end: Timestamp }\n\t/** When this snapshot was created */\n\treadonly createdAt: Timestamp\n\t/** Total number of applogs across all groups */\n\treadonly logCount: number\n\t/**\n\t * The grouped applogs. Each key is an attribute name; the value is the\n\t * array of applogs that carried that attribute within the time window.\n\t */\n\treadonly groups: Record<string, readonly Applog[]>\n}\n\n/**\n * Converts an {@link EpochSnapshot} into a {@link SerializedEpochSnapshot}\n * that can be persisted as JSON.\n *\n * @param snapshot - The live snapshot to serialize.\n * @param metadata - Identifying metadata to attach.\n * @returns A plain, JSON-safe object.\n */\nexport function serializeEpochSnapshot(\n\tsnapshot: EpochSnapshot,\n\tmetadata: {\n\t\t/** The attribute this purge targets */\n\t\treadonly attribute: string\n\t\t/** The time range of the snapshot */\n\t\treadonly timeRange: { readonly start: Timestamp; readonly end: Timestamp }\n\t},\n): SerializedEpochSnapshot {\n\tconst groups: Record<string, readonly Applog[]> = {}\n\tlet logCount = 0\n\tfor (const [attr, logs] of snapshot) {\n\t\tgroups[attr] = logs\n\t\tlogCount += logs.length\n\t}\n\n\treturn {\n\t\tformat: 'wovin-epoch-snapshot-v1',\n\t\tattribute: metadata.attribute,\n\t\ttimeRange: metadata.timeRange,\n\t\tcreatedAt: new Date().toISOString(),\n\t\tlogCount,\n\t\tgroups,\n\t}\n}\n\n// ─── History Purge ────────────────────────────────────────────────\n\n/**\n * Options for {@link purgeHistory}.\n */\nexport interface PurgeHistoryOptions {\n\t/**\n\t * Callback invoked with the serialized snapshot **before** any logs are\n\t * purged. Use this to persist the snapshot to a file, database, IPFS, or\n\t * any other storage you choose.\n\t *\n\t * If omitted, the snapshot is created and returned in the result but not\n\t * automatically persisted anywhere.\n\t */\n\tpersistSnapshot?: (serialized: SerializedEpochSnapshot) => Promise<void> | void\n\t/**\n\t * Override \"now\" for deterministic tests. Defaults to `new Date()`.\n\t */\n\tnow?: Date\n}\n\n/**\n * The result of a successful {@link purgeHistory} call.\n */\nexport interface PurgeHistoryResult {\n\t/**\n\t * The {@link EpochSnapshot} that was taken **before** the purge.\n\t * Contains all logs (across all attributes) within the time window, even\n\t * though only one attribute was purged.\n\t */\n\treadonly snapshot: EpochSnapshot\n\t/** Number of applogs that were removed from the thread. */\n\treadonly purgedCount: number\n\t/** Number of applogs kept as LWW winners for the purged attribute. */\n\treadonly keptCount: number\n\t/** The resolved time range that was processed. */\n\treadonly timeRange: { readonly start: Timestamp; readonly end: Timestamp }\n}\n\n/**\n * For a given **attribute** and **time range**:\n *\n * 1. Creates an {@link EpochSnapshot} capturing all app logs within\n * `[start, end)` across **all** attributes.\n * 2. Calls `options.persistSnapshot` with a serialized copy of the snapshot\n * (so you can store the pre-purge state before any data is removed).\n * 3. For the target attribute, groups the captured logs by entity and keeps\n * only the **latest** (last-write-wins) applog per `(en, at)` pair.\n * All older logs for that pair are purged from the thread.\n *\n * Logs for **other** attributes are never touched.\n *\n * @param thread - A writable thread to purge from.\n * @param attribute - Only logs carrying this attribute are candidates for\n * removal. Logs with other attributes are left untouched.\n * @param start - Start of the time window (see {@link TimeSpec}).\n * @param end - End of the time window, exclusive (see {@link TimeSpec}).\n * @param options - Optional persistence callback and time override.\n * @returns A {@link PurgeHistoryResult} with the snapshot and counts.\n *\n * @throws {RangeError} If the resolved `start` is not before `end`.\n *\n * @example\n * ```typescript\n * // Purge all but the latest block/content per entity from the beginning\n * // up to yesterday, saving a snapshot first.\n * const result = await purgeHistory(thread, 'block/content', -1, '-1d', {\n * persistSnapshot: async (snap) => {\n * await writeFile('purge-archive.json', JSON.stringify(snap, null, 2))\n * },\n * })\n * console.log(`Purged ${result.purgedCount}, kept ${result.keptCount}`)\n * ```\n */\nexport async function purgeHistory(\n\tthread: WriteableThread,\n\tattribute: Attribute,\n\tstart: TimeSpec,\n\tend: TimeSpec,\n\toptions?: PurgeHistoryOptions,\n): Promise<PurgeHistoryResult> {\n\tconst now = options?.now ?? new Date()\n\n\t// 1. Resolve time specs\n\tconst resolvedStart = resolveTimeSpec(start, thread, now)\n\tconst resolvedEnd = resolveTimeSpec(end, thread, now)\n\tconst timeRange = { start: resolvedStart, end: resolvedEnd }\n\n\t// 2. Create full EpochSnapshot for the window\n\tconst snapshot = createEpochSnapshot(thread, timeRange)\n\n\t// 3. Persist snapshot (user-supplied callback — e.g. write to file, IPFS, …)\n\tif (options?.persistSnapshot) {\n\t\tconst serialized = serializeEpochSnapshot(snapshot, {\n\t\t\tattribute,\n\t\t\ttimeRange,\n\t\t})\n\t\tawait options.persistSnapshot(serialized)\n\t}\n\n\t// 4. Collect CIDs to purge for the target attribute\n\tconst targetLogs = snapshot.get(attribute)\n\tif (!targetLogs || targetLogs.length === 0) {\n\t\treturn { snapshot, purgedCount: 0, keptCount: 0, timeRange }\n\t}\n\n\t// Group by entity ID (at is the target attribute, so en is the discriminator)\n\tconst byEntity = new Map<EntityID, Applog[]>()\n\tfor (const log of targetLogs) {\n\t\tlet bucket = byEntity.get(log.en)\n\t\tif (!bucket) {\n\t\t\tbucket = []\n\t\t\tbyEntity.set(log.en, bucket)\n\t\t}\n\t\tbucket.push(log)\n\t}\n\n\tconst cidsToPurge: CidString[] = []\n\tlet keptCount = 0\n\n\tfor (const [, logs] of byEntity) {\n\t\t// Logs are sorted by ts ascending (createEpochSnapshot guarantees).\n\t\t// The LWW winner is the LAST log. All earlier logs are stale.\n\t\tif (logs.length <= 1) {\n\t\t\tkeptCount += logs.length\n\t\t\tcontinue\n\t\t}\n\n\t\t// Keep the latest (last in the sorted array), drop the rest\n\t\tconst winner = logs[logs.length - 1]\n\t\tkeptCount += 1\n\t\tfor (let i = 0; i < logs.length - 1; i++) {\n\t\t\tcidsToPurge.push(logs[i].cid)\n\t\t}\n\t}\n\n\t// 5. Execute purge on the thread\n\tconst purgedCount = thread.purge(cidsToPurge)\n\n\treturn { snapshot, purgedCount, keptCount, timeRange }\n}\n"],"mappings":"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","names":["Logger","DEBUG","Logger","liveEntityCollection"]}
|