Skip to content
This repository was archived by the owner on Oct 25, 2023. It is now read-only.

Commit 494c625

Browse files
committed
implement node.* endpoints
1 parent 7bc227b commit 494c625

File tree

2 files changed

+118
-29
lines changed

2 files changed

+118
-29
lines changed

archaeologist/src/storage_api_local.ts

Lines changed: 116 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,31 @@
77
* for more information.
88
*/
99

10-
import type {
10+
import {
11+
Ack,
1112
CreateNodeArgs,
1213
Eid,
14+
GetNodeSliceArgs,
1315
NewNodeResponse,
1416
Nid,
17+
NodeBatch,
18+
NodeBatchRequestBody,
1519
NodeCreatedVia,
20+
NodePatchRequest,
21+
NodeUtil,
1622
OriginId,
1723
StorageApi,
1824
TEdgeJson,
25+
TNode,
1926
TNodeJson,
27+
TNodeSliceIterator,
2028
} from 'smuggler-api'
2129
import { NodeType } from 'smuggler-api'
2230
import { v4 as uuidv4 } from 'uuid'
2331
import base32Encode from 'base32-encode'
2432

2533
import browser from 'webextension-polyfill'
26-
import { unixtime } from 'armoury'
34+
import { MimeType, unixtime } from 'armoury'
2735
import lodash from 'lodash'
2836

2937
// TODO[snikitin@outlook.com] Describe that "yek" is "key" in reverse,
@@ -90,7 +98,19 @@ class YekLavStore {
9098
get(yek: NidYek): Promise<NidLav | undefined>
9199
get(yek: OriginToNidYek): Promise<OriginToNidLav | undefined>
92100
get(yek: NidToEdgeYek): Promise<NidToEdgeLav | undefined>
93-
get(yek: Yek): Promise<Lav | undefined> {
101+
get(yek: NidYek[]): Promise<NidLav[]>
102+
get(yek: Yek | Yek[]): Promise<Lav | Lav[] | undefined> {
103+
if (Array.isArray(yek)) {
104+
const keys: string[] = yek.map((value: Yek) => this.stringify(value))
105+
const records: Promise<Record<string, any>> = this.store.get(keys)
106+
return records.then((records: Record<string, any>): Promise<Lav[]> => {
107+
const lavs: Lav[] = Object.keys(records).map(
108+
(key: string) => records[key] as Lav
109+
)
110+
return Promise.resolve(lavs)
111+
})
112+
}
113+
94114
const key = this.stringify(yek)
95115
const records: Promise<Record<string, any>> = this.store.get(key)
96116
return records.then(
@@ -138,16 +158,9 @@ function generateEid(): Eid {
138158

139159
async function createNode(
140160
store: YekLavStore,
141-
args: CreateNodeArgs,
142-
_signal?: AbortSignal
161+
args: CreateNodeArgs
143162
): Promise<NewNodeResponse> {
144163
// TODO[snikitin@outlook.com] Below keys must become functional somehow.
145-
// Since they only need to function for search-like actions I can store
146-
// create addition kv-pairs just for them where their value is the key
147-
// (potentially prefixed) and a list of Nid is the value. When a node is added,
148-
// an element gets added into the list and when a node is deleted then an
149-
// element is removed from a list
150-
const origin: OriginId | undefined = args.origin
151164
const created_via: NodeCreatedVia | undefined = args.created_via
152165

153166
// TODO[snikitin@outlook.com] This graph structure has to work somehow
@@ -174,8 +187,10 @@ async function createNode(
174187
},
175188
]
176189

177-
if (origin) {
178-
const yek: OriginToNidYek = { yek: { kind: 'origin->nid', key: origin } }
190+
if (args.origin) {
191+
const yek: OriginToNidYek = {
192+
yek: { kind: 'origin->nid', key: args.origin },
193+
}
179194
let lav: OriginToNidLav | undefined = await store.get(yek)
180195
lav = lav ?? { lav: { kind: 'origin->nid', value: [] } }
181196
const nidsWithThisOrigin: Nid[] = lav.lav.value
@@ -224,30 +239,103 @@ async function createNode(
224239
return { nid: node.nid }
225240
}
226241

242+
async function getNode({
243+
store,
244+
nid,
245+
}: {
246+
store: YekLavStore
247+
nid: Nid
248+
}): Promise<TNode> {
249+
const yek: NidYek = { yek: { kind: 'nid', key: nid } }
250+
const lav: NidLav | undefined = await store.get(yek)
251+
if (lav == null) {
252+
throw new Error(`Failed to get node ${nid} because it wasn't found`)
253+
}
254+
const value: TNodeJson = lav.lav.value
255+
return NodeUtil.fromJson(value)
256+
}
257+
258+
async function getNodeBatch(
259+
store: YekLavStore,
260+
req: NodeBatchRequestBody
261+
): Promise<NodeBatch> {
262+
const yeks: NidYek[] = req.nids.map((nid: Nid): NidYek => {
263+
return { yek: { kind: 'nid', key: nid } }
264+
})
265+
const lavs: NidLav[] = await store.get(yeks)
266+
return { nodes: lavs.map((lav: NidLav) => NodeUtil.fromJson(lav.lav.value)) }
267+
}
268+
269+
async function updateNode(
270+
store: YekLavStore,
271+
args: { nid: Nid } & NodePatchRequest
272+
): Promise<Ack> {
273+
const yek: NidYek = { yek: { kind: 'nid', key: args.nid } }
274+
const lav: NidLav | undefined = await store.get(yek)
275+
if (lav == null) {
276+
throw new Error(`Failed to update node ${args.nid} because it wasn't found`)
277+
}
278+
const value: TNodeJson = lav.lav.value
279+
value.text = args.text != null ? args.text : value.text
280+
value.index_text =
281+
args.index_text != null ? args.index_text : value.index_text
282+
if (!args.preserve_update_time) {
283+
value.updated_at = unixtime.now()
284+
}
285+
await store.set([{ yek, lav }])
286+
return { ack: true }
287+
}
288+
227289
export function makeLocalStorageApi(
228-
store: browser.Storage.StorageArea
290+
browserStore: browser.Storage.StorageArea
229291
): StorageApi {
292+
const store = new YekLavStore(browserStore)
293+
294+
const throwUnimplementedError = (endpoint: string) => {
295+
return (..._: any[]): never => {
296+
throw new Error(
297+
`Attempted to call an ${endpoint} endpoint of local StorageApi which hasn't been implemented yet`
298+
)
299+
}
300+
}
301+
230302
return {
231303
node: {
232-
get: createNode,
233-
// update: updateNode,
234-
// create: createNode,
235-
// slice: _getNodesSliceIter,
236-
// delete: deleteNode,
237-
// bulkDelete: bulkDeleteNodes,
238-
// batch: {
239-
// get: getNodeBatch,
240-
// },
241-
// url: makeDirectUrl,
304+
get: ({ nid }: { nid: string; signal?: AbortSignal }) =>
305+
getNode({ store, nid }),
306+
update: (
307+
args: { nid: string } & NodePatchRequest,
308+
_signal?: AbortSignal
309+
) => updateNode(store, args),
310+
create: (args: CreateNodeArgs, _signal?: AbortSignal) =>
311+
createNode(store, args),
312+
// TODO[snikitin@outlook.com] Local-hosted slicing implementation is a
313+
// problem because the datacenter-hosted version depends entirely on
314+
// time range search which is easy with in SQL, but with a KV-store
315+
// requires to load all nodes from memory on every "iteration"
316+
slice: throwUnimplementedError('node.slice'),
317+
delete: throwUnimplementedError('node.delete'),
318+
bulkDelete: throwUnimplementedError('node.bulkdDelete'),
319+
batch: {
320+
get: (req: NodeBatchRequestBody, _signal?: AbortSignal) =>
321+
getNodeBatch(store, req),
322+
},
323+
url: throwUnimplementedError('node.url'),
242324
},
325+
// TODO[snikitin@outlook.com] At the time of this writing blob.upload and
326+
// blob_index.build are used together to create a single searchable blob node.
327+
// blob.upload is easy to implement for local storage while blob_index.build
328+
// is more difficult. Given how important search is for Mazed goals at the
329+
// time of writing, it makes little sense to implement any of them until
330+
// blob_index.build is usable.
243331
blob: {
244-
// upload: uploadFiles,
245-
// sourceUrl: makeBlobSourceUrl,
332+
upload: throwUnimplementedError('blob.upload'),
333+
sourceUrl: throwUnimplementedError('blob.sourceUrl'),
246334
},
247335
blob_index: {
248-
// build: buildFilesSearchIndex,
336+
build: throwUnimplementedError('blob_index.build'),
249337
cfg: {
250-
// supportsMime: mimeTypeIsSupportedByBuildIndex,
338+
supportsMime: (_mimeType: MimeType) => false,
251339
},
252340
},
253341
edge: {

smuggler-api/src/api_datacenter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
UploadMultipartResponse,
3030
UserExternalPipelineId,
3131
UserExternalPipelineIngestionProgress,
32+
Nid,
3233
} from './types'
3334
import type {
3435
CreateNodeArgs,
@@ -247,7 +248,7 @@ async function getNodeBatch(
247248
}
248249

249250
async function updateNode(
250-
args: { nid: string } & NodePatchRequest,
251+
args: { nid: Nid } & NodePatchRequest,
251252
signal?: AbortSignal
252253
): Promise<Ack> {
253254
const { nid, text, index_text, preserve_update_time } = args

0 commit comments

Comments
 (0)