@wovin/core 0.1.36 → 0.2.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.
Files changed (213) hide show
  1. package/README.md +0 -12
  2. package/dist/applog/applog-helpers.d.ts +12 -12
  3. package/dist/applog/applog-helpers.d.ts.map +1 -1
  4. package/dist/applog/applog-utils.d.ts +40 -6
  5. package/dist/applog/applog-utils.d.ts.map +1 -1
  6. package/dist/applog/datom-types.d.ts +67 -12
  7. package/dist/applog/datom-types.d.ts.map +1 -1
  8. package/dist/applog.d.ts +3 -3
  9. package/dist/applog.d.ts.map +1 -1
  10. package/dist/{applog.min.js → applog.js} +12 -7
  11. package/dist/blockstore.d.ts +1 -1
  12. package/dist/blockstore.d.ts.map +1 -1
  13. package/dist/{blockstore.min.js → blockstore.js} +1 -3
  14. package/dist/{blockstore.min.js.map → blockstore.js.map} +1 -1
  15. package/dist/chunk-22WDFLXO.js +138 -0
  16. package/dist/chunk-22WDFLXO.js.map +1 -0
  17. package/dist/chunk-3SUFNJEZ.js +1026 -0
  18. package/dist/chunk-3SUFNJEZ.js.map +1 -0
  19. package/dist/chunk-6ALNRM3J.js +435 -0
  20. package/dist/chunk-6ALNRM3J.js.map +1 -0
  21. package/dist/chunk-7Z5YDQKK.js +1 -0
  22. package/dist/{chunk-KXMTKPF4.min.js → chunk-BLF5MAWU.js} +8 -8
  23. package/dist/chunk-BLF5MAWU.js.map +1 -0
  24. package/dist/chunk-E46VTKTZ.js +1 -0
  25. package/dist/{chunk-H3VQJP56.min.js → chunk-HUIQ54TT.js} +9 -9
  26. package/dist/chunk-HUIQ54TT.js.map +1 -0
  27. package/dist/{chunk-BRC7LSM6.min.js → chunk-OC6Z6CQW.js} +5 -5
  28. package/dist/chunk-OC6Z6CQW.js.map +1 -0
  29. package/dist/chunk-SHUHRHOT.js +1923 -0
  30. package/dist/chunk-SHUHRHOT.js.map +1 -0
  31. package/dist/{chunk-QPGEBDMJ.min.js → chunk-YDAKBU6Q.js} +1 -1
  32. package/dist/chunk-YDAKBU6Q.js.map +1 -0
  33. package/dist/chunk-ZAADLBSB.js +36 -0
  34. package/dist/chunk-ZAADLBSB.js.map +1 -0
  35. package/dist/index.d.ts +7 -7
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/{index.min.js → index.js} +81 -46
  38. package/dist/ipfs/car.d.ts +11 -11
  39. package/dist/ipfs/car.d.ts.map +1 -1
  40. package/dist/ipfs/ipfs-utils.d.ts +2 -2
  41. package/dist/ipfs/ipfs-utils.d.ts.map +1 -1
  42. package/dist/ipfs.d.ts +3 -3
  43. package/dist/ipfs.d.ts.map +1 -1
  44. package/dist/{ipfs.min.js → ipfs.js} +7 -10
  45. package/dist/ipns.d.ts +1 -1
  46. package/dist/ipns.d.ts.map +1 -1
  47. package/dist/ipns.js +64 -0
  48. package/dist/ipns.js.map +1 -0
  49. package/dist/pubsub/pub-pull.d.ts +3 -3
  50. package/dist/pubsub/pub-pull.d.ts.map +1 -1
  51. package/dist/pubsub/pubsub-types.d.ts +3 -3
  52. package/dist/pubsub/pubsub-types.d.ts.map +1 -1
  53. package/dist/pubsub/snap-push.d.ts +4 -4
  54. package/dist/pubsub/snap-push.d.ts.map +1 -1
  55. package/dist/pubsub/ucan.d.ts +1 -1
  56. package/dist/pubsub/ucan.d.ts.map +1 -1
  57. package/dist/pubsub.d.ts +4 -4
  58. package/dist/pubsub.d.ts.map +1 -1
  59. package/dist/{pubsub.min.js → pubsub.js} +7 -10
  60. package/dist/query/attr-helpers.d.ts +5 -0
  61. package/dist/query/attr-helpers.d.ts.map +1 -0
  62. package/dist/query/basic.d.ts +87 -23
  63. package/dist/query/basic.d.ts.map +1 -1
  64. package/dist/query/divergences.d.ts +5 -5
  65. package/dist/query/divergences.d.ts.map +1 -1
  66. package/dist/query/entity-collection.d.ts +19 -0
  67. package/dist/query/entity-collection.d.ts.map +1 -0
  68. package/dist/query/matchers.d.ts +12 -1
  69. package/dist/query/matchers.d.ts.map +1 -1
  70. package/dist/query/memoized.d.ts +66 -0
  71. package/dist/query/memoized.d.ts.map +1 -0
  72. package/dist/query/situations.d.ts +2 -1
  73. package/dist/query/situations.d.ts.map +1 -1
  74. package/dist/query/subscribable.d.ts +111 -0
  75. package/dist/query/subscribable.d.ts.map +1 -0
  76. package/dist/query/types.d.ts +54 -14
  77. package/dist/query/types.d.ts.map +1 -1
  78. package/dist/query.d.ts +9 -5
  79. package/dist/query.d.ts.map +1 -1
  80. package/dist/{query.min.js → query.js} +55 -34
  81. package/dist/retrieve/index.d.ts +1 -1
  82. package/dist/retrieve/index.d.ts.map +1 -1
  83. package/dist/retrieve/update-thread.d.ts +3 -3
  84. package/dist/retrieve/update-thread.d.ts.map +1 -1
  85. package/dist/retrieve.d.ts +1 -1
  86. package/dist/retrieve.d.ts.map +1 -1
  87. package/dist/retrieve.js +14 -0
  88. package/dist/thread/basic.d.ts +15 -19
  89. package/dist/thread/basic.d.ts.map +1 -1
  90. package/dist/thread/filters.d.ts +8 -10
  91. package/dist/thread/filters.d.ts.map +1 -1
  92. package/dist/thread/indexes.d.ts +57 -0
  93. package/dist/thread/indexes.d.ts.map +1 -0
  94. package/dist/thread/mapped.d.ts +40 -11
  95. package/dist/thread/mapped.d.ts.map +1 -1
  96. package/dist/thread/utils.d.ts +5 -5
  97. package/dist/thread/utils.d.ts.map +1 -1
  98. package/dist/thread/writeable.d.ts +2 -2
  99. package/dist/thread/writeable.d.ts.map +1 -1
  100. package/dist/thread.d.ts +6 -5
  101. package/dist/thread.d.ts.map +1 -1
  102. package/dist/{thread.min.js → thread.js} +9 -6
  103. package/dist/types/typescript-utils.d.ts +6 -5
  104. package/dist/types/typescript-utils.d.ts.map +1 -1
  105. package/dist/types.d.ts +1 -1
  106. package/dist/types.d.ts.map +1 -1
  107. package/dist/{types.min.js → types.js} +3 -4
  108. package/dist/utils/debug-name.d.ts +13 -0
  109. package/dist/utils/debug-name.d.ts.map +1 -0
  110. package/dist/utils.d.ts +1 -1
  111. package/dist/utils.d.ts.map +1 -1
  112. package/dist/utils.js +9 -0
  113. package/package.json +32 -23
  114. package/src/applog/applog-helpers.ts +155 -0
  115. package/src/applog/applog-utils.test.ts +108 -0
  116. package/src/applog/applog-utils.ts +551 -0
  117. package/src/applog/datom-types.ts +167 -0
  118. package/src/applog/object-values.test.ts +106 -0
  119. package/src/applog.ts +3 -0
  120. package/src/blockstore/index.ts +36 -0
  121. package/src/blockstore.ts +1 -0
  122. package/src/index.ts +8 -0
  123. package/src/ipfs/car.ts +291 -0
  124. package/src/ipfs/fetch-snapshot-chain.ts +135 -0
  125. package/src/ipfs/ipfs-utils.ts +132 -0
  126. package/src/ipfs.ts +3 -0
  127. package/src/ipns/ipns-record.ts +115 -0
  128. package/src/ipns.ts +1 -0
  129. package/src/pubsub/UCAN Specs Overview.md +217 -0
  130. package/src/pubsub/connector.ts +9 -0
  131. package/src/pubsub/pub-pull.ts +31 -0
  132. package/src/pubsub/pubsub-types.ts +90 -0
  133. package/src/pubsub/snap-push.ts +278 -0
  134. package/src/pubsub/ucan-example.ts +61 -0
  135. package/src/pubsub/ucan.ts +56 -0
  136. package/src/pubsub.ts +4 -0
  137. package/src/query/attr-helpers.ts +5 -0
  138. package/src/query/basic.ts +1245 -0
  139. package/src/query/divergences.ts +50 -0
  140. package/src/query/entity-collection.ts +132 -0
  141. package/src/query/liveFilterAndMap.test.ts +102 -0
  142. package/src/query/matchers.ts +30 -0
  143. package/src/query/memoized.test.ts +151 -0
  144. package/src/query/memoized.ts +180 -0
  145. package/src/query/query-steps.ts +4 -0
  146. package/src/query/query.test.ts +538 -0
  147. package/src/query/situations.ts +261 -0
  148. package/src/query/subscribable.test.ts +245 -0
  149. package/src/query/subscribable.ts +234 -0
  150. package/src/query/types.ts +155 -0
  151. package/src/query/withoutDeleted.test.ts +204 -0
  152. package/src/query.ts +9 -0
  153. package/src/retrieve/index.ts +1 -0
  154. package/src/retrieve/update-thread.ts +248 -0
  155. package/src/retrieve.ts +1 -0
  156. package/src/test/perf/query.1m.perf.test.ts +94 -0
  157. package/src/test/perf/query.perf.test.ts +389 -0
  158. package/src/test/perf/query.realdata.perf.test.ts +182 -0
  159. package/src/thread/basic.ts +209 -0
  160. package/src/thread/filters.ts +227 -0
  161. package/src/thread/indexes.ts +256 -0
  162. package/src/thread/joinThreads.test.ts +304 -0
  163. package/src/thread/mapped.ts +226 -0
  164. package/src/thread/utils.ts +144 -0
  165. package/src/thread/writeable.ts +163 -0
  166. package/src/thread.ts +6 -0
  167. package/src/types/typescript-utils.ts +64 -0
  168. package/src/types.ts +1 -0
  169. package/src/utils/debug-name.ts +54 -0
  170. package/src/utils.ts +4 -0
  171. package/dist/chunk-2Y2PYHGR.min.js +0 -65
  172. package/dist/chunk-2Y2PYHGR.min.js.map +0 -1
  173. package/dist/chunk-5MMGBK2U.min.js +0 -1
  174. package/dist/chunk-7IDQIMQO.min.js +0 -1
  175. package/dist/chunk-BRC7LSM6.min.js.map +0 -1
  176. package/dist/chunk-COXXILXC.min.js +0 -512
  177. package/dist/chunk-COXXILXC.min.js.map +0 -1
  178. package/dist/chunk-GDX2OO7L.min.js +0 -9080
  179. package/dist/chunk-GDX2OO7L.min.js.map +0 -1
  180. package/dist/chunk-H3VQJP56.min.js.map +0 -1
  181. package/dist/chunk-HYMC7W6S.min.js +0 -1549
  182. package/dist/chunk-HYMC7W6S.min.js.map +0 -1
  183. package/dist/chunk-KEHU7HGZ.min.js +0 -5216
  184. package/dist/chunk-KEHU7HGZ.min.js.map +0 -1
  185. package/dist/chunk-KXMTKPF4.min.js.map +0 -1
  186. package/dist/chunk-PHITDXZT.min.js +0 -36
  187. package/dist/chunk-QO2KMGDN.min.js +0 -3771
  188. package/dist/chunk-QO2KMGDN.min.js.map +0 -1
  189. package/dist/chunk-QPGEBDMJ.min.js.map +0 -1
  190. package/dist/chunk-WXLCBTHX.min.js +0 -1606
  191. package/dist/chunk-WXLCBTHX.min.js.map +0 -1
  192. package/dist/ipns.min.js +0 -6419
  193. package/dist/ipns.min.js.map +0 -1
  194. package/dist/mobx/mobx-utils.d.ts +0 -82
  195. package/dist/mobx/mobx-utils.d.ts.map +0 -1
  196. package/dist/mobx.d.ts +0 -2
  197. package/dist/mobx.d.ts.map +0 -1
  198. package/dist/mobx.min.js +0 -141
  199. package/dist/retrieve.min.js +0 -17
  200. package/dist/types.min.js.map +0 -1
  201. package/dist/utils.min.js +0 -10
  202. package/dist/utils.min.js.map +0 -1
  203. /package/dist/{applog.min.js.map → applog.js.map} +0 -0
  204. /package/dist/{chunk-5MMGBK2U.min.js.map → chunk-7Z5YDQKK.js.map} +0 -0
  205. /package/dist/{chunk-7IDQIMQO.min.js.map → chunk-E46VTKTZ.js.map} +0 -0
  206. /package/dist/{chunk-PHITDXZT.min.js.map → index.js.map} +0 -0
  207. /package/dist/{index.min.js.map → ipfs.js.map} +0 -0
  208. /package/dist/{ipfs.min.js.map → pubsub.js.map} +0 -0
  209. /package/dist/{mobx.min.js.map → query.js.map} +0 -0
  210. /package/dist/{pubsub.min.js.map → retrieve.js.map} +0 -0
  211. /package/dist/{query.min.js.map → thread.js.map} +0 -0
  212. /package/dist/{retrieve.min.js.map → types.js.map} +0 -0
  213. /package/dist/{thread.min.js.map → utils.js.map} +0 -0
@@ -0,0 +1,90 @@
1
+ import { CID } from 'multiformats/cid'
2
+ import { cyrb53hash } from './../applog/applog-utils.ts'
3
+ import { AgentHash, AgentID, CidString } from '../applog/datom-types.ts'
4
+ import { Tagged } from '../types.ts'
5
+ import { UCANCapMap } from './ucan.ts'
6
+ type AgentString = Tagged<string, 'AgentString'>
7
+ type DIDString = Tagged<string, 'DID'>
8
+ export type { AgentHash, AgentString, DIDString }
9
+
10
+
11
+ export interface AppAgent {
12
+ ag: AgentHash
13
+ agentString: AgentString
14
+ did: DIDString
15
+ sign? (data: Uint8Array): Promise<Uint8Array>
16
+ ucan?: UCANCapMap
17
+ }
18
+ export interface AppAgentForWorker extends AppAgent {
19
+ signerSecret?: Uint8Array
20
+ }
21
+ export interface SnapRootBlock {
22
+ applogs: CID
23
+ applogsSignature: Uint8Array
24
+ info: CID
25
+ infoSignature: Uint8Array
26
+ prev?: CID
27
+ prevCounter?: number | null // Sequential counter: 0 for first, then +1 for each. null = unknown (legacy/error).
28
+ }
29
+ export interface SnapBlockLogs {
30
+ logs: CID[]
31
+ }
32
+ export interface SnapBlockChunks {
33
+ chunks: CID[]
34
+ }
35
+ export type SnapBlockLogsOrChunks = SnapBlockLogs | SnapBlockChunks
36
+
37
+ // HACK: this is actually note3 types, not wovin
38
+ export interface IShare {
39
+ id?: string // string hash of pub (used as unique id in IDB) `W3Name.create().toString()` starts with k51qzi5uqu5d
40
+ createdAt: string // ISO timestamp of creation
41
+ name: string // nick name for the pub
42
+ isDeleted?: boolean
43
+ purgeBeforePush?: boolean
44
+
45
+ pk: Uint8Array // exported privatekey - needed to create WritableName for publishing //TODO: store as non-extractable / encrypted?
46
+
47
+ autopush: boolean
48
+ lastPush: string | null
49
+ lastCID?: string
50
+ latestLogTs?: string
51
+ pubCounter?: number
52
+
53
+ publishedBy: string // local user appAgent
54
+ selectors?: string[] // to be used as a filter for which applogs to pub
55
+ encryptedFor?: string | null // short agentHash
56
+ encryptedWith?: CryptoKey | null // AES-GCM derived key from ECDH keys (local private and remote public)
57
+
58
+ // HACK WIP #39 - shared encryption
59
+ sharedKey?: CryptoKey | null // AES-GCM derived key from ECDH keys (local private and ipns public)
60
+ sharedAgents?: AgentID[] | null // array of string EntityIDs for the chosen agents (we need public jwkd atoms for each of them)
61
+ sharedKeyMap?: Map<AgentID, string> | null // uses public key from each agent to derive an aes key that is used to encrypt and btoa the sharedKey that is actually used to encrypt and decrypt the applogs
62
+ }
63
+ export interface ISubscription {
64
+ id: string // string hash of pub (used as unique id in IDB) `W3Name.create().toString()` starts with k51qzi5uqu5d
65
+ createdAt: string // ISO timestamp of creation
66
+ name: string // nick name for the pub
67
+ isDeleted: boolean
68
+
69
+ lastPull?: string | null
70
+ lastPullAttempt?: string | null
71
+ autopull: boolean
72
+ realtime?: boolean // enable real-time WebSocket updates via IPNS watcher
73
+ lastCID?: string // ? why not CidString
74
+ lastApplogCID?: string
75
+ publishedBy?: string // remote publisher short agentHash
76
+ encryptedFor?: string | undefined // short agentHash
77
+ encryptedWith?: CryptoKey | undefined // AES-GCM derived key from ECDH keys (local private and remote public)
78
+ }
79
+ export function isShare(obj: any): obj is IShare {
80
+ return obj?.pk !== undefined && obj?.lastPush !== undefined
81
+ }
82
+ export function isSubscription(obj: any): obj is ISubscription {
83
+ return obj?.lastPull !== undefined
84
+ }
85
+
86
+ export type TShareSub = IShare | ISubscription
87
+
88
+ export function agentToShortHash(agentString: string) {
89
+ return cyrb53hash(agentString, 31, 7) as string
90
+ }
@@ -0,0 +1,278 @@
1
+ import * as dagJson from '@ipld/dag-json'
2
+ import { Logger } from 'besonders-logger'
3
+ import { CID } from 'multiformats/cid'
4
+ import stringify from 'safe-stable-stringify'
5
+ import { ensureTsPvAndFinalizeApplog } from '../applog/applog-helpers.ts'
6
+ import type {
7
+ Applog,
8
+ ApplogArrayMaybeEncrypted,
9
+ ApplogArrayMaybeEncryptedRO,
10
+ ApplogArrayNoCIDMaybeEncryptedRO,
11
+ ApplogEnc,
12
+ ApplogEncNoCid,
13
+ CidString,
14
+ } from '../applog/datom-types.ts'
15
+ import { BlockStoreish, DecodedCar, getDecodedBlock, makeCarBlob } from '../ipfs/car.ts'
16
+ import { encodeBlockOriginal, prepareForPub } from '../ipfs/ipfs-utils.ts'
17
+ import { lastWriteWins } from './../query/basic.ts'
18
+ import { anyOf } from '../query/matchers.ts'
19
+ import { ApplogsOrThread, getLogsFromThread, Thread } from '../thread.ts'
20
+ import { rollingFilter } from '../thread/filters.ts'
21
+ import { keepTruthy } from '../utils.ts'
22
+ import type { AppAgent, IShare, SnapBlockChunks, SnapBlockLogs, SnapBlockLogsOrChunks } from './pubsub-types.ts'
23
+
24
+ const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars
25
+
26
+ // export const neverEncryptAttrs = [
27
+ // 'agent/jwkd',
28
+ // 'agent/appAgent',
29
+ // 'pub/encryptedFor',
30
+ // 'pub/sharedKey',
31
+ // ]
32
+
33
+ // export interface WovinPublicationInfo {
34
+ // id: string
35
+ // }
36
+
37
+ export async function prepareSnapshotForPush(
38
+ agent: AppAgent,
39
+ appThread: Thread,
40
+ threadToPublish: ApplogsOrThread,
41
+ share: IShare,
42
+ prevSnapCID: CID | null,
43
+ prevCounter: number | null,
44
+ ) {
45
+ if (prevCounter !== null && !prevSnapCID) {
46
+ throw ERROR('[prepareSnapshotForPush] prevCounter provided without prevSnapCID')
47
+ }
48
+ // TODO prevent publish if there is no new info
49
+ let logsToPublish = getLogsFromThread(threadToPublish)
50
+
51
+ // const logsFromLastPush = await getLogsFromPub(publication)
52
+ // logsToPublish = logsToPublish.filter(eachLog => !logsFromLastPush.includes(eachLog.cid)) // TODO deep compare includes
53
+ // const prevPushCIDs = [
54
+ // publication.lastCID,
55
+ // //TODO add this one and update the publication data after push
56
+ // ]
57
+ // const includedLogCIDs = [
58
+ // 'full array of CIDS from all previous pushes'
59
+ // ]
60
+
61
+ DEBUG(`[preparePubForPush] Collected ${logsToPublish.length} logs :`, {
62
+ logsToPublish,
63
+ threadOrLogsCount: (threadToPublish as any).nameAndSizeUntracked || (`[${(threadToPublish as any).length}]`),
64
+ })
65
+
66
+ const { sharedAgents, sharedKeyMap, sharedKey, pubCounter } = share ?? {}
67
+
68
+ const getExistingOrNewLog = (thread: Thread, share: IShare, ag: string, at: string, vl) => {
69
+ let logInQuestion = rollingFilter(lastWriteWins(thread), { en: share.id, at }).latestLog
70
+ if (!logInQuestion && vl !== undefined) {
71
+ logInQuestion = ensureTsPvAndFinalizeApplog({ ag, en: share.id, at, vl }, thread)
72
+ }
73
+ return logInQuestion // can be undefined if the passed vl is undefined and the log is not found
74
+ }
75
+ const shareNameLog = getExistingOrNewLog(appThread, share, agent.ag, 'share/name', share.name)
76
+
77
+ // ? using did as it is derived from the same ecdh in note3 and part of the minimal AppAgent type required here in wovin core
78
+ const shareCounterLog = getExistingOrNewLog(appThread, share, agent.ag, 'share/counter', `${agent.did}<::>${pubCounter}`)
79
+ // ? 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
80
+
81
+ const encryptApplog = async (applog: Applog, keyToUse: CryptoKey): Promise<Uint8Array> => {
82
+ const { log: eachLog, cid } = prepareForPub(applog) // without cid
83
+ const enc = new TextEncoder()
84
+ const stringified = stringify(eachLog)
85
+ const stringifiedEncodedAppLogPayload = enc.encode(stringified) // TODO: consider encodeToDagJson instead
86
+ VERBOSE('[odd]', { eachLog, stringified, stringifiedEncodedAppLogPayload })
87
+
88
+ try {
89
+ // @ts-expect-error
90
+ const encPayload = await agent.crypto?.aes.encrypt(stringifiedEncodedAppLogPayload, keyToUse, 'AES-GCM')
91
+ // TODO get rid of odd down here
92
+ VERBOSE('[odd] encrypted length:', stringifiedEncodedAppLogPayload.length, { encPayload })
93
+ return encPayload
94
+ } catch (err) {
95
+ throw ERROR('FAILED TO ENC payload length:', stringifiedEncodedAppLogPayload.length, { err })
96
+ }
97
+
98
+ // const decrypted = await decryptWithAesSharedKey(encPayload, keyToUse, 'string')
99
+ }
100
+
101
+ let maybeEncryptedApplogs: ApplogEncNoCid[] | readonly Applog[]
102
+ const encryptedApplogs = [] as { enc: Uint8Array }[]
103
+ const agentSharedKeyLogs = []
104
+ if (sharedAgents) { // encrypt all Applogs
105
+ if (!sharedKey || !sharedKeyMap) {
106
+ throw ERROR('sharedAgents but no Keys/Map', { sharedAgents, sharedKeyMap, sharedKey })
107
+ }
108
+ VERBOSE('encrypting', { sharedAgents, sharedKeyMap })
109
+
110
+ for (const [eachAgent, eachEncKey] of Array.from(sharedKeyMap.entries())) {
111
+ VERBOSE('adding key', { eachAgent, eachEncKey })
112
+ agentSharedKeyLogs.push({
113
+ ag: agent.ag,
114
+ en: eachAgent,
115
+ at: 'share/sharedKey',
116
+ vl: eachEncKey, // these are encrypted with the derived key from the local agent private and remote agent public keys
117
+ })
118
+ }
119
+ // const encryptedForLogs = await insertApplogsInAppDB(agentSharedKeyLogs)
120
+ // DEBUG(`[publish] adding agentSharedKeyLogs:`, encryptedForLogs)
121
+ const CIDlist: { cid: CidString; encCID?: CidString }[] = []
122
+ const pubCIDmap: Record<CidString, typeof CIDlist> = {}
123
+ // TODO ensure that all needed keys are in
124
+ for (const eachLog of logsToPublish) {
125
+ VERBOSE('[crypto] encrypting ', { eachLog, sharedKey })
126
+ // try {
127
+ const encPayload = await encryptApplog(eachLog, sharedKey)
128
+ DEBUG('[crypto] encrypted ', { eachLog, encPayload, sharedKey })
129
+ // } catch (err) {
130
+ // // its already traced in encryptAndTestDecrypt
131
+ // // continue
132
+ // }
133
+ encryptedApplogs.push({ enc: encPayload })
134
+ }
135
+ maybeEncryptedApplogs = encryptedApplogs
136
+ } else {
137
+ maybeEncryptedApplogs = logsToPublish // publish nonEncrypted
138
+ }
139
+
140
+ DEBUG('adding all agent info and shareAtoms', {
141
+ share,
142
+ agent,
143
+ logsToPublish,
144
+ // threadToPublish, - very verbose
145
+ agentSharedKeyLogs,
146
+ })
147
+ const infoLogs = [
148
+ ...rollingFilter(lastWriteWins(appThread), { // TODO: use static filter for performance
149
+ en: agent.ag,
150
+ at: anyOf('agent/ecdh', 'agent/jwkd', 'agent/appAgent'),
151
+ }).applogs,
152
+ ...(shareNameLog ? [shareNameLog] : []),
153
+ ...(shareCounterLog ? [shareCounterLog] : []),
154
+ ...agentSharedKeyLogs,
155
+ ]
156
+ DEBUG(`[prepareSnapshotForPush] info logs:`, infoLogs)
157
+ if (!infoLogs.find(({ at }) => at === 'agent/appAgent')) throw ERROR(`[prepareSnapshotForPush] appThread missing agent/appAgent log`)
158
+
159
+ const applogsToEncode = keepTruthy(maybeEncryptedApplogs)
160
+ const infologsToEncode = keepTruthy(infoLogs)
161
+ if (!applogsToEncode.length) {
162
+ throw ERROR('no valid applogs', { agent, maybeEncryptedApplogs, infoLogs, applogsToEncode, infologsToEncode, prevSnapCID })
163
+ }
164
+ if (!infologsToEncode.length) {
165
+ throw ERROR('no valid infologs', { agent, maybeEncryptedApplogs, infoLogs, applogsToEncode, infologsToEncode, prevSnapCID })
166
+ }
167
+ const encodedSnapshot = await encodeSnapshotAsCar(agent, applogsToEncode, infologsToEncode, prevSnapCID, prevCounter)
168
+ DEBUG('inPrepareSnapshotForPush', { encodedSnapshot })
169
+ return encodedSnapshot
170
+ }
171
+
172
+ /**
173
+ * @param applogs Encrypted or plain applogs
174
+ * @returns Car file
175
+ */
176
+ export async function encodeSnapshotAsCar(
177
+ agent: AppAgent,
178
+ applogs: ApplogArrayNoCIDMaybeEncryptedRO,
179
+ infoLogs: readonly Applog[],
180
+ prevSnapCID: CID | null,
181
+ prevCounter: number | null,
182
+ ) {
183
+ DEBUG(`[encodeSnapshotAsCar] encoding`, { agent, applogs, infoLogs })
184
+ const { cids: infoLogCids, encodedApplogs: encodedInfoLogs } = await encodeApplogsAsIPLD(infoLogs)
185
+ const { cids: applogCids, encodedApplogs } = await encodeApplogsAsIPLD(applogs)
186
+ let blocks = encodedApplogs.concat(encodedInfoLogs)
187
+ // We need to wrap the array to get a CID
188
+ const infoLogsWrap = await encodeBlockOriginal({ logs: infoLogCids })
189
+ blocks.push(infoLogsWrap)
190
+ const { rootCID: chunkRootCID, blocks: chunkBlocks } = await chunkApplogs(applogCids)
191
+ blocks = blocks.concat(chunkBlocks) // (i) concat bc. https://stackoverflow.com/a/51860949
192
+ const infoSignature = await agent.sign(infoLogsWrap.cid.bytes)
193
+ const applogsSignature = await agent.sign(chunkRootCID.bytes)
194
+ const root = {
195
+ info: infoLogsWrap.cid,
196
+ applogs: chunkRootCID,
197
+ infoSignature,
198
+ applogsSignature,
199
+ prev: prevSnapCID,
200
+ prevCounter: !prevSnapCID ? 0 : prevCounter !== null ? prevCounter + 1 : null,
201
+ }
202
+ DEBUG('[encodeSnapshotAsCar] encoding root', { root, logCids: applogCids, infoLogCids })
203
+ const encodedRoot = await encodeBlockOriginal(root)
204
+ blocks.push(encodedRoot)
205
+ DEBUG('[encodeSnapshotAsCar] => root', { encodedRoot })
206
+
207
+ return {
208
+ cid: encodedRoot.cid,
209
+ blob: await makeCarBlob(encodedRoot.cid, blocks), // TODO: create CarBuilder (incl .encodeAndAdd({...}))
210
+ blocks,
211
+ infoLogCids,
212
+ applogCids,
213
+ }
214
+ }
215
+
216
+ /** (i) IPFS has a block size limit of 1MB - which is about 15K CIDs */
217
+ export async function chunkApplogs(applogCids: CID<unknown, 297, 18, 1>[], size = 10000) {
218
+ if (!applogCids.length) throw ERROR(`[chunkApplogs] called with empty array`)
219
+ const chunks = []
220
+ // TODO: chunk by stable btree based on size or something like that
221
+ for (let i = 0; i < applogCids.length; i += size) {
222
+ const chunk = await encodeBlockOriginal({ logs: applogCids.slice(i, Math.min(i + applogCids.length, i + size)) })
223
+ chunks.push(chunk)
224
+ }
225
+ if (chunks.length === 1) return { rootCID: chunks[0].cid, blocks: chunks }
226
+ const root = await encodeBlockOriginal({ chunks: chunks.map(chunk => chunk.cid) })
227
+ const blocks = [root, ...chunks]
228
+ DEBUG(`[chunkApplogs] ${applogCids.length} logs chunked into ${chunks.length}`, { applogCids, root, blocks, chunks, dagJson })
229
+ return { rootCID: root.cid, blocks, chunks }
230
+ }
231
+ export async function unchunkApplogsBlock(block: SnapBlockLogsOrChunks, blockStore: BlockStoreish): Promise<CID[]> {
232
+ if (isSnapBlockChunks(block)) {
233
+ return (await Promise.all(
234
+ block.chunks.map(async (chunkCid) => {
235
+ const block = (await getDecodedBlock(blockStore, chunkCid)) as SnapBlockLogs
236
+ if (!block.logs) throw ERROR(`Weird chunk`, block)
237
+ return block.logs
238
+ }),
239
+ )).flat()
240
+ } else {
241
+ return block.logs
242
+ }
243
+ }
244
+ export function isSnapBlockChunks(block: SnapBlockLogsOrChunks): block is SnapBlockChunks {
245
+ return (block as any).chunks
246
+ }
247
+ /**
248
+ * @param applogs Encrypted or plain applogs
249
+ * @returns Car file
250
+ */
251
+ export async function encodeSnapshotApplogsAsCar(
252
+ applogs: ApplogArrayMaybeEncryptedRO,
253
+ ) {
254
+ const encoded = await encodeApplogsAsIPLD(applogs)
255
+ if (!encoded) throw ERROR('invalid applogs cannot continue', { applogs, encoded })
256
+ const { cids, encodedApplogs } = encoded
257
+ const root = { applogs: cids }
258
+ const encodedRoot = await encodeBlockOriginal(root)
259
+ DEBUG('[encodeSnapshotApplogsAsCar] encoded root', { cids, encodedRoot })
260
+
261
+ return await makeCarBlob(encodedRoot.cid, [encodedRoot, ...encodedApplogs])
262
+ }
263
+
264
+ async function encodeApplogsAsIPLD(applogs: ApplogArrayNoCIDMaybeEncryptedRO) {
265
+ DEBUG({ applogs })
266
+ const validApplogs = applogs.filter(eachLog => !!eachLog)
267
+ DEBUG({ validApplogs })
268
+ if (!validApplogs.length) throw ERROR('no valid applogs')
269
+ const preppedLogs = validApplogs.map(log => prepareForPub(log as Applog).log)
270
+ const encodedApplogs = await Promise.all(preppedLogs.map(encodeBlockOriginal))
271
+ DEBUG('[encodeApplogsAsIpld] encoded applogs', { preppedLogs, encodedApplogs })
272
+
273
+ const cids = encodedApplogs.map(b => {
274
+ if (!b.cid) throw ERROR(`[publish] no cid for encoded log:`, b)
275
+ return b.cid
276
+ })
277
+ return { cids, encodedApplogs }
278
+ }
@@ -0,0 +1,61 @@
1
+ import { Capability } from 'iso-ucan/capability'
2
+ import { Store } from 'iso-ucan/store'
3
+ import { MemoryDriver } from 'iso-kv/drivers/memory.js'
4
+ import { EdDSASigner } from 'iso-signatures/signers/eddsa.js'
5
+ import { z } from 'zod'
6
+ import { verify } from 'iso-signatures/verifiers/eddsa.js'
7
+ import { Resolver } from 'iso-signatures/verifiers/resolver.js'
8
+
9
+ export const verifierResolver = new Resolver({
10
+ Ed25519: verify,
11
+ })
12
+ // from:
13
+ //https://ucan.xyz/guides/examples/
14
+
15
+ // Initialize delegation store for tracking capability chains
16
+ // In production, this might be backed by a database or persistent storage
17
+ const store = new Store(new MemoryDriver())
18
+
19
+ // Define file read capability with path validation schema
20
+ // The schema ensures all invocations include a valid file path
21
+ const FileReadCap = Capability.from({
22
+ schema: z.object({
23
+ path: z.string(), // Required: file path to read
24
+ }),
25
+ cmd: '/file/read', // UCAN v1 command identifier
26
+ verifierResolver,
27
+ })
28
+
29
+ // Create cryptographic identities for resource owner and accessor
30
+ // In a real system, these would be persistent identity keypairs
31
+ const alice = await EdDSASigner.generate() // Resource owner
32
+ const bob = await EdDSASigner.generate() // Requesting access
33
+
34
+ const nowInSeconds = Math.floor(Date.now() / 1000)
35
+
36
+ // Alice grants Bob permission to read files
37
+ // This delegation can be stored, transmitted, or embedded in applications
38
+ const delegation = await FileReadCap.delegate({
39
+ iss: alice, // Alice issues this capability
40
+ aud: bob, // Bob is authorized to use it
41
+ sub: alice, // Alice's resources are the subject
42
+ pol: [], // No additional policy constraints
43
+ exp: nowInSeconds + 3600, // Expires in 1 hour for security
44
+ store,
45
+ })
46
+
47
+ // Store delegation to enable later invocation validation
48
+ // The store enables automatic delegation chain resolution
49
+ // await store.set(delegation)
50
+
51
+ // Bob exercises the delegated capability to read a specific file
52
+ // This creates a cryptographically verifiable access request
53
+ const invocation = await FileReadCap.invoke({
54
+ iss: bob, // Bob is invoking the capability
55
+ sub: alice, // Alice's system will process the request
56
+ args: {
57
+ path: '/documents/report.pdf' // Specific file Bob wants to read
58
+ },
59
+ store, // Store containing the delegation proof
60
+ exp: nowInSeconds + 300, // Invocation expires in 5 minutes
61
+ })
@@ -0,0 +1,56 @@
1
+ import type { DelegationOptions, DelegationPayload, PayloadBase, StandardSchemaV1 } from 'iso-ucan/types'
2
+ import { Delegation } from 'iso-ucan/delegation'
3
+ import { AgentID } from '../applog/datom-types.ts'
4
+ import { Capability } from 'iso-ucan/capability'
5
+ import { z } from 'zod/mini'
6
+ import { verify } from 'iso-signatures/verifiers/eddsa.js'
7
+ import { Resolver } from 'iso-signatures/verifiers/resolver.js'
8
+
9
+ export const verifierResolver = new Resolver({
10
+ Ed25519: verify,// TODO review if iso-signatures is better than web crypto (or if it even uses it)
11
+ })
12
+
13
+
14
+ const WovinRootCap = Capability.from({
15
+ cmd: '/wovin',
16
+ schema: z.object({
17
+ agentString: z.string(),
18
+ agentID: z.string(),
19
+ type: z.string(),
20
+ }),
21
+ verifierResolver,
22
+ })
23
+
24
+ const WovinPublishCap = Capability.from({
25
+ cmd: '/wovin/publish',
26
+ schema: z.object({
27
+ agentString: z.string(),
28
+ agentID: z.string(),
29
+ type: z.string(),
30
+ }),
31
+ verifierResolver,
32
+ })
33
+ const WovinDelegateCap = Capability.from({
34
+ cmd: '/wovin/delegate',
35
+ schema: z.object({
36
+ agentString: z.string(),
37
+ agentID: z.string(),
38
+ type: z.string(),
39
+ }),
40
+ verifierResolver,
41
+ })
42
+ export enum KnownCaps {
43
+ publish = 'WovinPublishCap',
44
+ delegate = 'cap_delegate',
45
+ }
46
+
47
+ type DelegationPayloadSchema = StandardSchemaV1<unknown, DelegationPayload<unknown>>
48
+ export type DelegationOptionsStandard = DelegationOptions<DelegationPayloadSchema>
49
+ export type UCANCapMap = Partial<Record<KnownCaps, DelegationOptionsStandard[]>>
50
+
51
+ export type AgentCapMap = Record<AgentID, UCANCapMap>
52
+
53
+ export const createDelegation = (delegationPayload:DelegationOptionsStandard) => {
54
+ // delegationPayload.
55
+ return Delegation.create(delegationPayload)
56
+ }
package/src/pubsub.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './pubsub/connector.ts'
2
+ export * from './pubsub/pub-pull.ts'
3
+ export * from './pubsub/snap-push.ts'
4
+ export * from './pubsub/pubsub-types.ts'
@@ -0,0 +1,5 @@
1
+ /** Strip everything up to and including the first `/` */
2
+ export type StripFirstPrefix<S extends string> = S extends `${string}/${infer Rest}` ? Rest : S
3
+
4
+ /** Strip a specific prefix `P/` from attribute string */
5
+ export type StripExplicitPrefix<S extends string, P extends string> = S extends `${P}/${infer Rest}` ? Rest : S