@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.
- package/README.md +0 -12
- package/dist/applog/applog-helpers.d.ts +12 -12
- package/dist/applog/applog-helpers.d.ts.map +1 -1
- package/dist/applog/applog-utils.d.ts +40 -6
- package/dist/applog/applog-utils.d.ts.map +1 -1
- package/dist/applog/datom-types.d.ts +67 -12
- package/dist/applog/datom-types.d.ts.map +1 -1
- package/dist/applog.d.ts +3 -3
- package/dist/applog.d.ts.map +1 -1
- package/dist/{applog.min.js → applog.js} +12 -7
- package/dist/blockstore.d.ts +1 -1
- package/dist/blockstore.d.ts.map +1 -1
- package/dist/{blockstore.min.js → blockstore.js} +1 -3
- package/dist/{blockstore.min.js.map → blockstore.js.map} +1 -1
- package/dist/chunk-22WDFLXO.js +138 -0
- package/dist/chunk-22WDFLXO.js.map +1 -0
- package/dist/chunk-3SUFNJEZ.js +1026 -0
- package/dist/chunk-3SUFNJEZ.js.map +1 -0
- package/dist/chunk-6ALNRM3J.js +435 -0
- package/dist/chunk-6ALNRM3J.js.map +1 -0
- package/dist/chunk-7Z5YDQKK.js +1 -0
- package/dist/{chunk-KXMTKPF4.min.js → chunk-BLF5MAWU.js} +8 -8
- package/dist/chunk-BLF5MAWU.js.map +1 -0
- package/dist/chunk-E46VTKTZ.js +1 -0
- package/dist/{chunk-H3VQJP56.min.js → chunk-HUIQ54TT.js} +9 -9
- package/dist/chunk-HUIQ54TT.js.map +1 -0
- package/dist/{chunk-BRC7LSM6.min.js → chunk-OC6Z6CQW.js} +5 -5
- package/dist/chunk-OC6Z6CQW.js.map +1 -0
- package/dist/chunk-SHUHRHOT.js +1923 -0
- package/dist/chunk-SHUHRHOT.js.map +1 -0
- package/dist/{chunk-QPGEBDMJ.min.js → chunk-YDAKBU6Q.js} +1 -1
- package/dist/chunk-YDAKBU6Q.js.map +1 -0
- package/dist/chunk-ZAADLBSB.js +36 -0
- package/dist/chunk-ZAADLBSB.js.map +1 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/{index.min.js → index.js} +81 -46
- package/dist/ipfs/car.d.ts +11 -11
- package/dist/ipfs/car.d.ts.map +1 -1
- package/dist/ipfs/ipfs-utils.d.ts +2 -2
- package/dist/ipfs/ipfs-utils.d.ts.map +1 -1
- package/dist/ipfs.d.ts +3 -3
- package/dist/ipfs.d.ts.map +1 -1
- package/dist/{ipfs.min.js → ipfs.js} +7 -10
- package/dist/ipns.d.ts +1 -1
- package/dist/ipns.d.ts.map +1 -1
- package/dist/ipns.js +64 -0
- package/dist/ipns.js.map +1 -0
- package/dist/pubsub/pub-pull.d.ts +3 -3
- package/dist/pubsub/pub-pull.d.ts.map +1 -1
- package/dist/pubsub/pubsub-types.d.ts +3 -3
- package/dist/pubsub/pubsub-types.d.ts.map +1 -1
- package/dist/pubsub/snap-push.d.ts +4 -4
- package/dist/pubsub/snap-push.d.ts.map +1 -1
- package/dist/pubsub/ucan.d.ts +1 -1
- package/dist/pubsub/ucan.d.ts.map +1 -1
- package/dist/pubsub.d.ts +4 -4
- package/dist/pubsub.d.ts.map +1 -1
- package/dist/{pubsub.min.js → pubsub.js} +7 -10
- package/dist/query/attr-helpers.d.ts +5 -0
- package/dist/query/attr-helpers.d.ts.map +1 -0
- package/dist/query/basic.d.ts +87 -23
- package/dist/query/basic.d.ts.map +1 -1
- package/dist/query/divergences.d.ts +5 -5
- package/dist/query/divergences.d.ts.map +1 -1
- package/dist/query/entity-collection.d.ts +19 -0
- package/dist/query/entity-collection.d.ts.map +1 -0
- package/dist/query/matchers.d.ts +12 -1
- package/dist/query/matchers.d.ts.map +1 -1
- package/dist/query/memoized.d.ts +66 -0
- package/dist/query/memoized.d.ts.map +1 -0
- package/dist/query/situations.d.ts +2 -1
- package/dist/query/situations.d.ts.map +1 -1
- package/dist/query/subscribable.d.ts +111 -0
- package/dist/query/subscribable.d.ts.map +1 -0
- package/dist/query/types.d.ts +54 -14
- package/dist/query/types.d.ts.map +1 -1
- package/dist/query.d.ts +9 -5
- package/dist/query.d.ts.map +1 -1
- package/dist/{query.min.js → query.js} +55 -34
- package/dist/retrieve/index.d.ts +1 -1
- package/dist/retrieve/index.d.ts.map +1 -1
- package/dist/retrieve/update-thread.d.ts +3 -3
- package/dist/retrieve/update-thread.d.ts.map +1 -1
- package/dist/retrieve.d.ts +1 -1
- package/dist/retrieve.d.ts.map +1 -1
- package/dist/retrieve.js +14 -0
- package/dist/thread/basic.d.ts +15 -19
- package/dist/thread/basic.d.ts.map +1 -1
- package/dist/thread/filters.d.ts +8 -10
- package/dist/thread/filters.d.ts.map +1 -1
- package/dist/thread/indexes.d.ts +57 -0
- package/dist/thread/indexes.d.ts.map +1 -0
- package/dist/thread/mapped.d.ts +40 -11
- package/dist/thread/mapped.d.ts.map +1 -1
- package/dist/thread/utils.d.ts +5 -5
- package/dist/thread/utils.d.ts.map +1 -1
- package/dist/thread/writeable.d.ts +2 -2
- package/dist/thread/writeable.d.ts.map +1 -1
- package/dist/thread.d.ts +6 -5
- package/dist/thread.d.ts.map +1 -1
- package/dist/{thread.min.js → thread.js} +9 -6
- package/dist/types/typescript-utils.d.ts +6 -5
- package/dist/types/typescript-utils.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/{types.min.js → types.js} +3 -4
- package/dist/utils/debug-name.d.ts +13 -0
- package/dist/utils/debug-name.d.ts.map +1 -0
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +9 -0
- package/package.json +32 -23
- package/src/applog/applog-helpers.ts +155 -0
- package/src/applog/applog-utils.test.ts +108 -0
- package/src/applog/applog-utils.ts +551 -0
- package/src/applog/datom-types.ts +167 -0
- package/src/applog/object-values.test.ts +106 -0
- package/src/applog.ts +3 -0
- package/src/blockstore/index.ts +36 -0
- package/src/blockstore.ts +1 -0
- package/src/index.ts +8 -0
- package/src/ipfs/car.ts +291 -0
- package/src/ipfs/fetch-snapshot-chain.ts +135 -0
- package/src/ipfs/ipfs-utils.ts +132 -0
- package/src/ipfs.ts +3 -0
- package/src/ipns/ipns-record.ts +115 -0
- package/src/ipns.ts +1 -0
- package/src/pubsub/UCAN Specs Overview.md +217 -0
- package/src/pubsub/connector.ts +9 -0
- package/src/pubsub/pub-pull.ts +31 -0
- package/src/pubsub/pubsub-types.ts +90 -0
- package/src/pubsub/snap-push.ts +278 -0
- package/src/pubsub/ucan-example.ts +61 -0
- package/src/pubsub/ucan.ts +56 -0
- package/src/pubsub.ts +4 -0
- package/src/query/attr-helpers.ts +5 -0
- package/src/query/basic.ts +1245 -0
- package/src/query/divergences.ts +50 -0
- package/src/query/entity-collection.ts +132 -0
- package/src/query/liveFilterAndMap.test.ts +102 -0
- package/src/query/matchers.ts +30 -0
- package/src/query/memoized.test.ts +151 -0
- package/src/query/memoized.ts +180 -0
- package/src/query/query-steps.ts +4 -0
- package/src/query/query.test.ts +538 -0
- package/src/query/situations.ts +261 -0
- package/src/query/subscribable.test.ts +245 -0
- package/src/query/subscribable.ts +234 -0
- package/src/query/types.ts +155 -0
- package/src/query/withoutDeleted.test.ts +204 -0
- package/src/query.ts +9 -0
- package/src/retrieve/index.ts +1 -0
- package/src/retrieve/update-thread.ts +248 -0
- package/src/retrieve.ts +1 -0
- package/src/test/perf/query.1m.perf.test.ts +94 -0
- package/src/test/perf/query.perf.test.ts +389 -0
- package/src/test/perf/query.realdata.perf.test.ts +182 -0
- package/src/thread/basic.ts +209 -0
- package/src/thread/filters.ts +227 -0
- package/src/thread/indexes.ts +256 -0
- package/src/thread/joinThreads.test.ts +304 -0
- package/src/thread/mapped.ts +226 -0
- package/src/thread/utils.ts +144 -0
- package/src/thread/writeable.ts +163 -0
- package/src/thread.ts +6 -0
- package/src/types/typescript-utils.ts +64 -0
- package/src/types.ts +1 -0
- package/src/utils/debug-name.ts +54 -0
- package/src/utils.ts +4 -0
- package/dist/chunk-2Y2PYHGR.min.js +0 -65
- package/dist/chunk-2Y2PYHGR.min.js.map +0 -1
- package/dist/chunk-5MMGBK2U.min.js +0 -1
- package/dist/chunk-7IDQIMQO.min.js +0 -1
- package/dist/chunk-BRC7LSM6.min.js.map +0 -1
- package/dist/chunk-COXXILXC.min.js +0 -512
- package/dist/chunk-COXXILXC.min.js.map +0 -1
- package/dist/chunk-GDX2OO7L.min.js +0 -9080
- package/dist/chunk-GDX2OO7L.min.js.map +0 -1
- package/dist/chunk-H3VQJP56.min.js.map +0 -1
- package/dist/chunk-HYMC7W6S.min.js +0 -1549
- package/dist/chunk-HYMC7W6S.min.js.map +0 -1
- package/dist/chunk-KEHU7HGZ.min.js +0 -5216
- package/dist/chunk-KEHU7HGZ.min.js.map +0 -1
- package/dist/chunk-KXMTKPF4.min.js.map +0 -1
- package/dist/chunk-PHITDXZT.min.js +0 -36
- package/dist/chunk-QO2KMGDN.min.js +0 -3771
- package/dist/chunk-QO2KMGDN.min.js.map +0 -1
- package/dist/chunk-QPGEBDMJ.min.js.map +0 -1
- package/dist/chunk-WXLCBTHX.min.js +0 -1606
- package/dist/chunk-WXLCBTHX.min.js.map +0 -1
- package/dist/ipns.min.js +0 -6419
- package/dist/ipns.min.js.map +0 -1
- package/dist/mobx/mobx-utils.d.ts +0 -82
- package/dist/mobx/mobx-utils.d.ts.map +0 -1
- package/dist/mobx.d.ts +0 -2
- package/dist/mobx.d.ts.map +0 -1
- package/dist/mobx.min.js +0 -141
- package/dist/retrieve.min.js +0 -17
- package/dist/types.min.js.map +0 -1
- package/dist/utils.min.js +0 -10
- package/dist/utils.min.js.map +0 -1
- /package/dist/{applog.min.js.map → applog.js.map} +0 -0
- /package/dist/{chunk-5MMGBK2U.min.js.map → chunk-7Z5YDQKK.js.map} +0 -0
- /package/dist/{chunk-7IDQIMQO.min.js.map → chunk-E46VTKTZ.js.map} +0 -0
- /package/dist/{chunk-PHITDXZT.min.js.map → index.js.map} +0 -0
- /package/dist/{index.min.js.map → ipfs.js.map} +0 -0
- /package/dist/{ipfs.min.js.map → pubsub.js.map} +0 -0
- /package/dist/{mobx.min.js.map → query.js.map} +0 -0
- /package/dist/{pubsub.min.js.map → retrieve.js.map} +0 -0
- /package/dist/{query.min.js.map → thread.js.map} +0 -0
- /package/dist/{retrieve.min.js.map → types.js.map} +0 -0
- /package/dist/{thread.min.js.map → utils.js.map} +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// import type { AgentHash } from '../pubsub/pubsub-types.ts'
|
|
2
|
+
// import type { CID } from '@oddjs/odd'
|
|
3
|
+
import { FormatRegistry, Static, TSchema, Type } from '@sinclair/typebox'
|
|
4
|
+
import { TypeCompiler } from '@sinclair/typebox/compiler'
|
|
5
|
+
import { CID } from 'multiformats/cid'
|
|
6
|
+
import type { PartialBy, Tagged } from '../types/typescript-utils.ts'
|
|
7
|
+
|
|
8
|
+
export const Nullable = <T extends TSchema>(schema: T) => Type.Union([schema, Type.Null()])
|
|
9
|
+
export const EntityID_LENGTH = 7
|
|
10
|
+
// const bagu = 'baguqeerav3h4b46j2pyxikqhtm5si5vhzsyrba2duhrtltfutrlmj42anmvq'
|
|
11
|
+
// const k51q = 'k51qzi5uqu5dhe1bxxjxj144bj2a225o1681yobevns26xlxtsfidjgnpwknfd'
|
|
12
|
+
const isCID = /^(k51qz|baguq)[0-9a-z]{56,57}$/ // FIXME: k51 is not really a CID, is it?
|
|
13
|
+
const isShortHash = /^[0-9A-Fa-f]{7,8}$/g // TODO awkward why are some 7 and some 8 long
|
|
14
|
+
// engine level: min 6 (lenient within reason)
|
|
15
|
+
// note3 TBD... either fixed for all entity types VS 7 for pub/sub, 8 for tags, 9 for blocks, 10 for relations etc...
|
|
16
|
+
|
|
17
|
+
FormatRegistry.Set('EntityID', (value) => !!value.match(isShortHash) || !!value.match(isCID))
|
|
18
|
+
export const EntityID = Type.String() // HACK how to configure ID format?
|
|
19
|
+
/*{ format: 'EntityID' }*/
|
|
20
|
+
export type EntityID = Static<typeof EntityID>
|
|
21
|
+
|
|
22
|
+
export type AgentHash = Tagged<string, 'AgentHash'>
|
|
23
|
+
export type DatomPart = string // TODO refactor
|
|
24
|
+
export type CidString = Tagged<string, CID>
|
|
25
|
+
export type IpnsString = Tagged<CidString, 'IPNS'>
|
|
26
|
+
export type AgentID = EntityID
|
|
27
|
+
export type Attribute = string
|
|
28
|
+
// JSON-serializable value space for a datom's `vl` field.
|
|
29
|
+
export type JsonPrimitive = string | boolean | number | null
|
|
30
|
+
export interface JsonObject {
|
|
31
|
+
[key: string]: JsonValue
|
|
32
|
+
}
|
|
33
|
+
export type JsonArray = JsonValue[]
|
|
34
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonArray
|
|
35
|
+
export type ApplogValue = JsonValue // TODO: use Tagged types
|
|
36
|
+
|
|
37
|
+
export interface Atom {
|
|
38
|
+
en: EntityID
|
|
39
|
+
at: Attribute
|
|
40
|
+
vl: ApplogValue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type Timestamp = string
|
|
44
|
+
export interface Applog extends Atom {
|
|
45
|
+
cid: CidString
|
|
46
|
+
pv: CidString | null // ? | CID
|
|
47
|
+
ts: Timestamp
|
|
48
|
+
ag: AgentHash
|
|
49
|
+
}
|
|
50
|
+
export type ApplogNoCid = Omit<Applog, 'cid'>
|
|
51
|
+
export type ApplogOptionalCid = PartialBy<Applog, 'cid'>
|
|
52
|
+
export type ApplogForInsert = PartialBy<ApplogNoCid, 'ts' | 'pv'>
|
|
53
|
+
export type ApplogForInsertOptionalAgent = PartialBy<ApplogForInsert, 'ag'>
|
|
54
|
+
|
|
55
|
+
export interface ApplogEnc {
|
|
56
|
+
cid: CidString
|
|
57
|
+
enc: Uint8Array
|
|
58
|
+
iv?: Uint8Array // iv may be needed if we prefer a different strategy for transmitting iv (odd appends a random iv to the payload via keystoreAES.(en|de)cryptBytes)
|
|
59
|
+
}
|
|
60
|
+
export type ApplogEncNoCid = Omit<ApplogEnc, 'cid'>
|
|
61
|
+
|
|
62
|
+
export type ApplogArrayMaybeEncrypted = (Applog | ApplogEnc)[]
|
|
63
|
+
export type ApplogArrayMaybeEncryptedRO = readonly (Applog | ApplogEnc)[]
|
|
64
|
+
export type ApplogArrayNoCIDMaybeEncryptedRO = readonly (ApplogNoCid | ApplogEncNoCid)[]
|
|
65
|
+
|
|
66
|
+
export type ApplogOfSomeSort = Applog | ApplogEnc | ApplogNoCid | ApplogEncNoCid | ApplogForInsert
|
|
67
|
+
|
|
68
|
+
export const isEncryptedApplog = (l: ApplogOfSomeSort): l is ApplogEnc => (l as ApplogEnc)?.enc instanceof Uint8Array
|
|
69
|
+
|
|
70
|
+
export type AtomPattern = Atom | Applog
|
|
71
|
+
|
|
72
|
+
export interface DatalogStateIdentifier {
|
|
73
|
+
lastTS: Timestamp
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// New generic type for fields that can be a value, an array of that, or a function
|
|
77
|
+
export type ValueOrMatcher<T> = T | readonly T[] | ReadonlySet<T> | ((value: T) => boolean)
|
|
78
|
+
// Generic type that applies ValueOrMatcher to each field of T
|
|
79
|
+
export type WithMatchers<T extends Record<string, any>> = {
|
|
80
|
+
[K in keyof T & string as `${K}` | `!${K}`]?: ValueOrMatcher<T[K]>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type DatalogQueryPattern = Partial<WithMatchers<Applog>>
|
|
84
|
+
export type DatalogQueryPatternArray = DatalogQueryPattern[]
|
|
85
|
+
export interface DatalogQuery<SELECT extends string> {
|
|
86
|
+
find: readonly SELECT[] // see: https://github.com/microsoft/TypeScript/issues/20965#issuecomment-868981458
|
|
87
|
+
where: DatalogQueryPatternArray
|
|
88
|
+
onlyLatest?: boolean
|
|
89
|
+
}
|
|
90
|
+
export type DatalogQueryResultEntry<SELECT extends string> = Record<
|
|
91
|
+
// SELECT,
|
|
92
|
+
StripPrefix<'?', SELECT>,
|
|
93
|
+
DatomPart
|
|
94
|
+
>
|
|
95
|
+
export type DatalogQueryResultRows<SELECT extends string> = DatalogQueryResultEntry<SELECT>[]
|
|
96
|
+
// export type StripTest = StripPrefix<'?', '?A' | '?B'>
|
|
97
|
+
// export type DatalogQueryResultEntryTEST = DatalogQueryResultEntry<'?A' | '?B'>
|
|
98
|
+
// export type DatalogQueryResultEntryTESTX = MapKeysStripPrefix<'?A' | '?B', '?'>
|
|
99
|
+
|
|
100
|
+
export interface SearchContext {
|
|
101
|
+
[key: string]: ApplogValue
|
|
102
|
+
}
|
|
103
|
+
export interface SearchContextWithLog {
|
|
104
|
+
context: SearchContext
|
|
105
|
+
applog?: Applog
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type ResultContext = SearchContext | null
|
|
109
|
+
|
|
110
|
+
/* https://stackoverflow.com/a/72497461 */
|
|
111
|
+
type StripPrefix<
|
|
112
|
+
TPrefix extends string,
|
|
113
|
+
T extends string,
|
|
114
|
+
> = T extends `${TPrefix}${infer R}` ? R : never
|
|
115
|
+
|
|
116
|
+
type MapKeysStripPrefix<SELECT extends string, TPrefix extends string> = {
|
|
117
|
+
[K in SELECT as StripPrefix<TPrefix, K>]: DatomPart
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
FormatRegistry.Set('CID', (value) => !!value.match(isCID))
|
|
121
|
+
export const CIDTB = Type.String({ format: 'CID' })
|
|
122
|
+
export type CIDTB = Static<typeof EntityID>
|
|
123
|
+
|
|
124
|
+
const isURL = /^http([s]?):\/\/.*\..*/
|
|
125
|
+
FormatRegistry.Set('URL', (value) => !!value.match(isURL))
|
|
126
|
+
export const URL = Type.String({ format: 'URL' })
|
|
127
|
+
export type URL = Static<typeof URL>
|
|
128
|
+
|
|
129
|
+
// Recursive JSON value: primitives plus nested arrays/objects (mirrors ApplogValue / JsonValue).
|
|
130
|
+
export const JsonValueTB = Type.Recursive(This =>
|
|
131
|
+
Type.Union([
|
|
132
|
+
Type.String(),
|
|
133
|
+
Type.Number(),
|
|
134
|
+
Type.Boolean(),
|
|
135
|
+
Type.Null(),
|
|
136
|
+
Type.Array(This),
|
|
137
|
+
Type.Record(Type.String(), This),
|
|
138
|
+
])
|
|
139
|
+
)
|
|
140
|
+
export type JsonValueTB = Static<typeof JsonValueTB>
|
|
141
|
+
|
|
142
|
+
export const AppLogNoCidTB = Type.Object({
|
|
143
|
+
en: EntityID, // EntityID
|
|
144
|
+
at: Type.String(), // Attribute
|
|
145
|
+
vl: JsonValueTB, // ApplogValue (JSON-serializable: primitives, arrays, objects)
|
|
146
|
+
ts: Type.String(), // Timestamp
|
|
147
|
+
ag: Type.String(), // AgentHash
|
|
148
|
+
pv: Nullable(CIDTB), // CidString
|
|
149
|
+
})
|
|
150
|
+
export type AppLogNoCidTB = Static<typeof AppLogNoCidTB> // type T = {
|
|
151
|
+
|
|
152
|
+
export const AppLogNoCidTBC = TypeCompiler.Compile(AppLogNoCidTB)
|
|
153
|
+
export const getApplogNoCidTypeErrors = (obj: any) => Array.from(AppLogNoCidTBC.Errors(obj))
|
|
154
|
+
export const isValidApplogNoCid = AppLogNoCidTBC.Check.bind(AppLogNoCidTBC) // ? Include CID
|
|
155
|
+
|
|
156
|
+
export const AppLogTB = Type.Composite([
|
|
157
|
+
Type.Object({
|
|
158
|
+
cid: CIDTB,
|
|
159
|
+
}),
|
|
160
|
+
AppLogNoCidTB,
|
|
161
|
+
])
|
|
162
|
+
export type AppLogTB = Static<typeof AppLogTB> // type T = {
|
|
163
|
+
|
|
164
|
+
export const AppLogTBC = TypeCompiler.Compile(AppLogTB)
|
|
165
|
+
export const getApplogTypeErrors = (obj: any) => Array.from(AppLogTBC.Errors(obj))
|
|
166
|
+
export const isValidApplog = AppLogTBC.Check.bind(AppLogTBC) // ? Include CID
|
|
167
|
+
// maybe useful for defaulting https://github.com/sinclairzx81/typebox#cast
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for JSON object/array support in a datom's `vl` field.
|
|
3
|
+
*
|
|
4
|
+
* Covers: schema validation, deep-equality dedup, value-index keying,
|
|
5
|
+
* matcher semantics (deep-eq literal match, anyOf membership, predicate
|
|
6
|
+
* for nested access) and the fail-loud behaviour for bare-array patterns.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, it } from 'vitest'
|
|
9
|
+
import { isValidApplog } from './datom-types.ts'
|
|
10
|
+
import { matchPartStatic, valueEq, valueKey } from './applog-utils.ts'
|
|
11
|
+
import { anyOf } from '../query/matchers.ts'
|
|
12
|
+
import { finalizeApplogForInsert } from './applog-helpers.ts'
|
|
13
|
+
import { applogsByAttrValue } from '../thread/indexes.ts'
|
|
14
|
+
import { ThreadInMemory } from '../thread/writeable.ts'
|
|
15
|
+
import type { Applog, ApplogForInsert } from './datom-types.ts'
|
|
16
|
+
|
|
17
|
+
let tsCounter = 0
|
|
18
|
+
function makeLog(spec: Pick<ApplogForInsert, 'en' | 'at' | 'vl'> & Partial<Applog>): Applog {
|
|
19
|
+
tsCounter++
|
|
20
|
+
return finalizeApplogForInsert({
|
|
21
|
+
ts: new Date(1700000000000 + tsCounter * 1000).toISOString(),
|
|
22
|
+
pv: null,
|
|
23
|
+
ag: 'testAgent',
|
|
24
|
+
...spec,
|
|
25
|
+
} as ApplogForInsert, {})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('object/array applog values', () => {
|
|
29
|
+
it('schema accepts nested objects and arrays as vl', () => {
|
|
30
|
+
const obj = makeLog({ en: 'e1', at: 'config', vl: { theme: 'dark', nested: { n: [1, 2, 3] } } })
|
|
31
|
+
const arr = makeLog({ en: 'e1', at: 'tags', vl: ['a', 'b', { x: 1 }] })
|
|
32
|
+
expect(isValidApplog(obj)).toBe(true)
|
|
33
|
+
expect(isValidApplog(arr)).toBe(true)
|
|
34
|
+
// primitives still valid
|
|
35
|
+
expect(isValidApplog(makeLog({ en: 'e1', at: 'name', vl: 'hi' }))).toBe(true)
|
|
36
|
+
expect(isValidApplog(makeLog({ en: 'e1', at: 'n', vl: null }))).toBe(true)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('valueEq compares object/array values by structure', () => {
|
|
40
|
+
expect(valueEq({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true)
|
|
41
|
+
expect(valueEq([1, 2, 3], [1, 2, 3])).toBe(true)
|
|
42
|
+
expect(valueEq({ a: 1 }, { a: 2 })).toBe(false)
|
|
43
|
+
expect(valueEq('x', 'x')).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('valueKey canonicalizes objects (key-order independent) without colliding with strings', () => {
|
|
47
|
+
expect(valueKey({ a: 1, b: 2 })).toBe(valueKey({ b: 2, a: 1 }))
|
|
48
|
+
// a literal string equal to an object's serialization must NOT collide
|
|
49
|
+
expect(valueKey('{"a":1}')).not.toBe(valueKey({ a: 1 }))
|
|
50
|
+
// primitives pass through as their own value
|
|
51
|
+
expect(valueKey('hi')).toBe('hi')
|
|
52
|
+
expect(valueKey(42)).toBe(42)
|
|
53
|
+
expect(valueKey(null)).toBe(null)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('hasApplogWithDiffTs matches structurally-equal object values (deep, key-order independent)', () => {
|
|
57
|
+
const existing = makeLog({ en: 'e1', at: 'config', vl: { a: 1, b: 2 } })
|
|
58
|
+
const thread = ThreadInMemory.fromArray([existing], 'dedup')
|
|
59
|
+
// same en/at/ag, deep-equal vl with different key order → recognised as the same datom
|
|
60
|
+
expect(thread.hasApplogWithDiffTs({ en: 'e1', at: 'config', vl: { b: 2, a: 1 }, ag: 'testAgent' } as any)).toBeTruthy()
|
|
61
|
+
// a structurally different value is NOT matched
|
|
62
|
+
expect(thread.hasApplogWithDiffTs({ en: 'e1', at: 'config', vl: { a: 9 }, ag: 'testAgent' } as any)).toBeFalsy()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('applogsByAttrValue groups structurally-equal object values into one bucket', () => {
|
|
66
|
+
const logs = [
|
|
67
|
+
makeLog({ en: 'e1', at: 'config', vl: { a: 1, b: 2 } }),
|
|
68
|
+
makeLog({ en: 'e2', at: 'config', vl: { b: 2, a: 1 } }),
|
|
69
|
+
makeLog({ en: 'e3', at: 'config', vl: { a: 9 } }),
|
|
70
|
+
]
|
|
71
|
+
const thread = ThreadInMemory.fromArray(logs, 'idx')
|
|
72
|
+
const index = applogsByAttrValue(thread, 'config').value
|
|
73
|
+
expect(index.get(valueKey({ a: 1, b: 2 }))).toHaveLength(2)
|
|
74
|
+
expect(index.get(valueKey({ a: 9 }))).toHaveLength(1)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('matchPartStatic matches a literal object value by deep equality', () => {
|
|
78
|
+
expect(matchPartStatic('vl', { a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true)
|
|
79
|
+
expect(matchPartStatic('vl', { a: 1 }, { a: 2 })).toBe(false)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('matching a literal array value requires a predicate (a bare array throws)', () => {
|
|
83
|
+
// bare arrays are reserved/rejected, so an array value is matched via a predicate
|
|
84
|
+
expect(matchPartStatic('vl', (v: any) => valueEq(v, [1, 2, 3]), [1, 2, 3])).toBe(true)
|
|
85
|
+
expect(() => matchPartStatic('vl', [1, 2, 3] as any, [1, 2, 3])).toThrow(/anyOf/)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('anyOf(...) provides set-membership and matches via matchPartStatic', () => {
|
|
89
|
+
expect(matchPartStatic('at', anyOf('a', 'b', 'c'), 'b')).toBe(true)
|
|
90
|
+
expect(matchPartStatic('at', anyOf('a', 'b'), 'z')).toBe(false)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('a predicate matcher can reach into nested object values', () => {
|
|
94
|
+
const isDark = (v: any) => v?.theme === 'dark'
|
|
95
|
+
expect(matchPartStatic('vl', isDark, { theme: 'dark' })).toBe(true)
|
|
96
|
+
expect(matchPartStatic('vl', isDark, { theme: 'light' })).toBe(false)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('a bare array pattern fails loudly rather than matching ambiguously', () => {
|
|
100
|
+
expect(() => matchPartStatic('at', ['a', 'b'] as any, 'a')).toThrow(/anyOf/)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('anyOf rejects object/array members (would silently never match)', () => {
|
|
104
|
+
expect(() => anyOf({ a: 1 } as any)).toThrow(/object\/array/)
|
|
105
|
+
})
|
|
106
|
+
})
|
package/src/applog.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CID } from 'multiformats/cid'
|
|
2
|
+
|
|
3
|
+
/** Minimal async block store interface — get/put/has. */
|
|
4
|
+
export interface BlockStore {
|
|
5
|
+
get(cid: CID): Promise<Uint8Array>
|
|
6
|
+
put(cid: CID, bytes: Uint8Array): Promise<void>
|
|
7
|
+
has(cid: CID): Promise<boolean>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A block store that reads locally first, with optional remote fallback.
|
|
12
|
+
* On get: local hit → return; local miss + remote → fetch, write-back, return.
|
|
13
|
+
* put/has always operate on local only.
|
|
14
|
+
*/
|
|
15
|
+
export class LocalFirstBlockStore implements BlockStore {
|
|
16
|
+
constructor(
|
|
17
|
+
private local: BlockStore,
|
|
18
|
+
private remote?: Pick<BlockStore, 'get'>,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
async get(cid: CID): Promise<Uint8Array> {
|
|
22
|
+
if (await this.local.has(cid)) return this.local.get(cid)
|
|
23
|
+
if (!this.remote) throw new Error(`Block not found: ${cid}`)
|
|
24
|
+
const bytes = await this.remote.get(cid)
|
|
25
|
+
await this.local.put(cid, bytes)
|
|
26
|
+
return bytes
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
put(cid: CID, bytes: Uint8Array): Promise<void> {
|
|
30
|
+
return this.local.put(cid, bytes)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
has(cid: CID): Promise<boolean> {
|
|
34
|
+
return this.local.has(cid)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './blockstore/index.ts'
|
package/src/index.ts
ADDED
package/src/ipfs/car.ts
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { CarReader, CarWriter } from '@ipld/car'
|
|
2
|
+
import * as dagJson from '@ipld/dag-json'
|
|
3
|
+
import { Logger } from 'besonders-logger'
|
|
4
|
+
import { BlockView, CID } from 'multiformats'
|
|
5
|
+
import { sortApplogsByTs } from '../applog/applog-utils.ts'
|
|
6
|
+
import { Applog, ApplogArrayMaybeEncrypted, CidString } from '../applog/datom-types.ts'
|
|
7
|
+
import { unchunkApplogsBlock } from '../pubsub/snap-push.ts'
|
|
8
|
+
import { SnapBlockLogs, SnapBlockLogsOrChunks, SnapRootBlock } from '../pubsub/pubsub-types.ts'
|
|
9
|
+
import { areCidsEqual, containsCid } from './ipfs-utils.ts'
|
|
10
|
+
|
|
11
|
+
const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars
|
|
12
|
+
|
|
13
|
+
export type CIDForCar = CID // Exclude<Parameters<(typeof CarWriter)['create']>[0], void>
|
|
14
|
+
export type BlockForCar = Parameters<CarWriter['put']>[0]
|
|
15
|
+
|
|
16
|
+
export interface BlockStoreish {
|
|
17
|
+
get(cid: CID): PromiseLike<Uint8Array> // (i) not using decoded version to be similar to blockstore-idb
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DecodedCar {
|
|
21
|
+
rootCID: CID
|
|
22
|
+
// blocks: Map<CidString, any>
|
|
23
|
+
blockStore: BlockStoreish
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Warning: unsorted & maybe encrypted */
|
|
27
|
+
export async function decodePubFromCar(car: CarReader) {
|
|
28
|
+
const decoded = await getBlocksOfCar(car)
|
|
29
|
+
return await decodePubFromBlocks(decoded)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function decodePubFromBlocks(
|
|
33
|
+
{ rootCID, blockStore }: DecodedCar,
|
|
34
|
+
_recursionTrace: CID[] = [], // DEPRECATED: kept for API compat, unused in iterative version
|
|
35
|
+
stopAtCID?: CID // NEW: stop iteration when we hit this CID
|
|
36
|
+
) {
|
|
37
|
+
if (!rootCID || !blockStore) {
|
|
38
|
+
throw ERROR('Empty roots/blocks', { rootCID, blockStore })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let allApplogs: ApplogArrayMaybeEncrypted = []
|
|
42
|
+
let firstInfo: { logs: CID[] } | null = null
|
|
43
|
+
let currentCID: CID | undefined = rootCID
|
|
44
|
+
const visited = new Set<string>() // Loop detection (replaces recursionTrace)
|
|
45
|
+
let applogsCID: CID | null = null // From first snapshot only
|
|
46
|
+
|
|
47
|
+
while (currentCID) {
|
|
48
|
+
const cidStr = currentCID.toString()
|
|
49
|
+
|
|
50
|
+
// Loop detection
|
|
51
|
+
if (visited.has(cidStr)) {
|
|
52
|
+
throw ERROR('[decodePubFromBlocks] pub chain has a loop', {
|
|
53
|
+
currentCID: cidStr,
|
|
54
|
+
visited: [...visited]
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
visited.add(cidStr)
|
|
58
|
+
|
|
59
|
+
// Decode current snapshot
|
|
60
|
+
const root = (await getDecodedBlock(blockStore, currentCID)) as SnapRootBlock
|
|
61
|
+
VERBOSE(`[decodePubFromBlocks] root:`, cidStr, root, { blockStore })
|
|
62
|
+
if (!root) {
|
|
63
|
+
throw ERROR('[decodePubFromBlocks] root not found in blockStore', { blockStore, currentCID: cidStr })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Decode applogs for this snapshot
|
|
67
|
+
let pubLogsArray: CID[]
|
|
68
|
+
if (root?.info) {
|
|
69
|
+
// New(er) format
|
|
70
|
+
if (!applogsCID) applogsCID = root.applogs // Save from first snapshot
|
|
71
|
+
const applogsBlock = (await getDecodedBlock(blockStore, root.applogs)) as SnapBlockLogsOrChunks
|
|
72
|
+
pubLogsArray = await unchunkApplogsBlock(applogsBlock, blockStore)
|
|
73
|
+
// Info only from first (most recent) snapshot
|
|
74
|
+
if (!firstInfo) {
|
|
75
|
+
firstInfo = (await getDecodedBlock(blockStore, root.info)) as SnapBlockLogs
|
|
76
|
+
DEBUG(`new format - infoLogs`, firstInfo.logs.map(l => ({ [l.toString()]: l })))
|
|
77
|
+
}
|
|
78
|
+
// TODO: verify signatures
|
|
79
|
+
} else {
|
|
80
|
+
// Old format
|
|
81
|
+
pubLogsArray = root.applogs as any as CID[]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const resolveLogFromCidLink = async (cidOrLink: CID) => {
|
|
85
|
+
const cid = cidOrLink
|
|
86
|
+
const applog = (await getDecodedBlock(blockStore, cid)) as Applog
|
|
87
|
+
if (!applog) {
|
|
88
|
+
ERROR(`Could not find applog CID in pub blocks:`, cid.toString(), { cid, root, blockStore })
|
|
89
|
+
throw new Error(`Could not find applog CID in pub blocks: ${cid.toString()}`)
|
|
90
|
+
}
|
|
91
|
+
if ((applog.pv as any) instanceof CID) applog.pv = (applog.pv as any as CID).toV1().toString()
|
|
92
|
+
return {
|
|
93
|
+
...applog,
|
|
94
|
+
cid: cid.toV1().toString(),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const snapshotApplogs = await Promise.all(pubLogsArray.map(resolveLogFromCidLink))
|
|
99
|
+
allApplogs = allApplogs.concat(snapshotApplogs)
|
|
100
|
+
|
|
101
|
+
// Check if we should stop
|
|
102
|
+
if (!root.prev) {
|
|
103
|
+
break // End of chain
|
|
104
|
+
}
|
|
105
|
+
if (stopAtCID && areCidsEqual(root.prev, stopAtCID)) {
|
|
106
|
+
DEBUG('[decodePubFromBlocks] stopping at stopAtCID:', stopAtCID.toString())
|
|
107
|
+
break // Reached already-pulled snapshot
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Verify prev exists before continuing
|
|
111
|
+
const prevBytes = await blockStore.get(root.prev)
|
|
112
|
+
if (!prevBytes) {
|
|
113
|
+
throw ERROR('[decodePubFromBlocks] prev snapshot missing from blockStore', {
|
|
114
|
+
currentCID: cidStr,
|
|
115
|
+
prev: root.prev.toString(),
|
|
116
|
+
stopAtCID: stopAtCID?.toString(),
|
|
117
|
+
visited: [...visited]
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
currentCID = root.prev // Move to previous snapshot
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const result = {
|
|
125
|
+
cid: rootCID,
|
|
126
|
+
info: firstInfo ? {
|
|
127
|
+
...firstInfo,
|
|
128
|
+
logs: await Promise.all(firstInfo.logs.map(async (cidOrLink: CID) => {
|
|
129
|
+
const cid = cidOrLink
|
|
130
|
+
const applog = (await getDecodedBlock(blockStore, cid)) as Applog
|
|
131
|
+
if (!applog) {
|
|
132
|
+
ERROR(`Could not find info log CID in pub blocks:`, cid.toString(), { cid, blockStore })
|
|
133
|
+
throw new Error(`Could not find info log CID in pub blocks: ${cid.toString()}`)
|
|
134
|
+
}
|
|
135
|
+
if ((applog.pv as any) instanceof CID) applog.pv = (applog.pv as any as CID).toV1().toString()
|
|
136
|
+
return {
|
|
137
|
+
...applog,
|
|
138
|
+
cid: cid.toV1().toString(),
|
|
139
|
+
}
|
|
140
|
+
})),
|
|
141
|
+
} : null,
|
|
142
|
+
applogsCID,
|
|
143
|
+
applogs: allApplogs,
|
|
144
|
+
}
|
|
145
|
+
DEBUG('[decodePubFromBlocks] result:', result, { rootCID: rootCID.toString(), blockStore, applogs: allApplogs })
|
|
146
|
+
return result
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function getBlocksOfCar(car: CarReader) {
|
|
150
|
+
const rootsFromCar = await car.getRoots()
|
|
151
|
+
const roots = rootsFromCar.map(c => ((typeof c.toV1 === 'function') ? c : CID.decode(c.bytes)).toV1().toString() as CidString) // HACK
|
|
152
|
+
const blocks = new Map<CidString, any>()
|
|
153
|
+
for await (const { cid: cidFromCarblocks, bytes } of car.blocks()) {
|
|
154
|
+
const cid = (typeof cidFromCarblocks.toV1 === 'function') ? cidFromCarblocks : CID.decode(cidFromCarblocks.bytes)
|
|
155
|
+
VERBOSE({ cidFromCarblocks, cid })
|
|
156
|
+
// 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
|
|
157
|
+
blocks.set(cid.toV1().toString(), bytes) // HACK: tried using CID as map key, but because it's based on referential equality it's not working
|
|
158
|
+
}
|
|
159
|
+
if (roots.length !== 1) {
|
|
160
|
+
WARN('Unexpected roots count:', roots)
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
rootCID: CID.parse(roots[0]),
|
|
164
|
+
blockStore: {
|
|
165
|
+
get: (cid) => blocks.get(cid.toV1().toString()),
|
|
166
|
+
},
|
|
167
|
+
} satisfies DecodedCar
|
|
168
|
+
}
|
|
169
|
+
export async function getDecodedBlock(blockStore: BlockStoreish, cid: CID) {
|
|
170
|
+
try {
|
|
171
|
+
var blob = await blockStore.get(cid)
|
|
172
|
+
if (!blob) {
|
|
173
|
+
WARN('returning null')
|
|
174
|
+
return null // I don't think this ever happens actually
|
|
175
|
+
}
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if ((err as any).message === 'Not Found') return null
|
|
178
|
+
throw err
|
|
179
|
+
}
|
|
180
|
+
return dagJson.decode(blob)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// make out in the car... been a while but also sounds nice
|
|
184
|
+
export async function makeCarOut(roots: CIDForCar, blocks: BlockForCar[]) {
|
|
185
|
+
const { writer, out } = CarWriter.create(Array.isArray(roots) ? roots : [roots])
|
|
186
|
+
|
|
187
|
+
// add the blocks to the CAR and close it
|
|
188
|
+
VERBOSE(`Writing ${blocks.length} blocks to CAR`, { roots, blocks })
|
|
189
|
+
blocks.forEach(b => writer.put(b))
|
|
190
|
+
writer.close()
|
|
191
|
+
// VERBOSE(`Wrote ${blocks.length} blocks to CAR`, writer)
|
|
192
|
+
return out
|
|
193
|
+
} /** create a new CarWriter, with the encoded block as the root */
|
|
194
|
+
|
|
195
|
+
// export async function makeCarReader(roots: CIDForCar, blocks: BlockForCar[]) {
|
|
196
|
+
// const out = await makeCarOut(roots, blocks)
|
|
197
|
+
|
|
198
|
+
// // create a new CarReader we can hand to web3.storage.putCar
|
|
199
|
+
// const reader = await CarReader.fromIterable(out)
|
|
200
|
+
// VERBOSE(`CAR reader`, reader)
|
|
201
|
+
// return reader
|
|
202
|
+
// } /** create a new CarWriter, with the encoded block as the root */
|
|
203
|
+
|
|
204
|
+
export async function makeCarBlob(roots: CIDForCar, blocks: BlockForCar[]) {
|
|
205
|
+
const carOut = await makeCarOut(roots, blocks)
|
|
206
|
+
const chunks = []
|
|
207
|
+
for await (const chunk of carOut) {
|
|
208
|
+
chunks.push(chunk)
|
|
209
|
+
}
|
|
210
|
+
const blob = new Blob(chunks)
|
|
211
|
+
return blob
|
|
212
|
+
}
|
|
213
|
+
export async function carFromBlob(blob: Blob | File): Promise<CarReader> {
|
|
214
|
+
return CarReader.fromBytes(new Uint8Array(await blob.arrayBuffer()))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function extractCids(value: unknown): CID[] {
|
|
218
|
+
if (value instanceof CID) return [value]
|
|
219
|
+
if (Array.isArray(value)) return value.flatMap(extractCids)
|
|
220
|
+
if (value && typeof value === 'object') return Object.values(value).flatMap(extractCids)
|
|
221
|
+
return []
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const MAX_COLLECT_BLOCKS = 1_000_000
|
|
225
|
+
|
|
226
|
+
export async function collectDagBlocks(
|
|
227
|
+
startCID: CID,
|
|
228
|
+
blockStore: BlockStoreish,
|
|
229
|
+
): Promise<BlockForCar[]> {
|
|
230
|
+
const visited = new Set<string>()
|
|
231
|
+
const blocks: BlockForCar[] = []
|
|
232
|
+
const queue: CID[] = [startCID]
|
|
233
|
+
|
|
234
|
+
while (queue.length > 0) {
|
|
235
|
+
if (blocks.length >= MAX_COLLECT_BLOCKS) {
|
|
236
|
+
WARN(`[collectDagBlocks] hit ${MAX_COLLECT_BLOCKS} block limit, returning partial result`)
|
|
237
|
+
break
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const cid = queue.shift()!
|
|
241
|
+
const cidStr = cid.toString()
|
|
242
|
+
if (visited.has(cidStr)) continue
|
|
243
|
+
visited.add(cidStr)
|
|
244
|
+
|
|
245
|
+
let bytes: Uint8Array
|
|
246
|
+
try {
|
|
247
|
+
bytes = await blockStore.get(cid)
|
|
248
|
+
} catch {
|
|
249
|
+
WARN(`[collectDagBlocks] block not found: ${cidStr}, stopping this branch`)
|
|
250
|
+
continue
|
|
251
|
+
}
|
|
252
|
+
if (!bytes) {
|
|
253
|
+
WARN(`[collectDagBlocks] block not found: ${cidStr}, stopping this branch`)
|
|
254
|
+
continue
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
blocks.push({ cid, bytes })
|
|
258
|
+
|
|
259
|
+
if (blocks.length % 1000 === 0) {
|
|
260
|
+
LOG(`[collectDagBlocks] collected ${blocks.length} blocks...`)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const decoded = dagJson.decode(bytes)
|
|
265
|
+
const childCids = extractCids(decoded)
|
|
266
|
+
for (const child of childCids) {
|
|
267
|
+
if (!visited.has(child.toString())) {
|
|
268
|
+
queue.push(child)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} catch {
|
|
272
|
+
// Not dag-json — leaf block, no children to walk
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
DEBUG(`[collectDagBlocks] collected ${blocks.length} blocks from ${startCID.toString()}`)
|
|
277
|
+
return blocks
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function streamReaderToIterable(bodyReader: ReadableStreamDefaultReader<Uint8Array>): AsyncIterable<Uint8Array> {
|
|
281
|
+
return (async function*() {
|
|
282
|
+
while (true) {
|
|
283
|
+
const { done, value } = await bodyReader.read()
|
|
284
|
+
VERBOSE(`[car] chunk`, { done, value })
|
|
285
|
+
if (done) {
|
|
286
|
+
break
|
|
287
|
+
}
|
|
288
|
+
yield value
|
|
289
|
+
}
|
|
290
|
+
})()
|
|
291
|
+
}
|