@graffiti-garden/implementation-local 0.6.4 → 1.0.0
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 -1
- package/dist/browser/ajv-IY2ZY7VT.js +9 -0
- package/dist/browser/ajv-IY2ZY7VT.js.map +7 -0
- package/dist/browser/{chunk-KNUPPOQC.js → chunk-GE6AZATH.js} +2 -2
- package/dist/browser/{chunk-KNUPPOQC.js.map → chunk-GE6AZATH.js.map} +1 -1
- package/dist/browser/{index-browser.es-G37SKL53.js → index-browser.es-UXYPGJ2M.js} +2 -2
- package/dist/browser/{index-browser.es-G37SKL53.js.map → index-browser.es-UXYPGJ2M.js.map} +1 -1
- package/dist/browser/index.js +11 -2
- package/dist/browser/index.js.map +4 -4
- package/dist/cjs/identity.js +112 -0
- package/dist/cjs/identity.js.map +7 -0
- package/dist/cjs/index.js +43 -22
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/media.js +111 -0
- package/dist/cjs/media.js.map +7 -0
- package/dist/cjs/objects.js +307 -0
- package/dist/cjs/objects.js.map +7 -0
- package/dist/cjs/tests.spec.js +1 -2
- package/dist/cjs/tests.spec.js.map +2 -2
- package/dist/cjs/utilities.js +68 -43
- package/dist/cjs/utilities.js.map +2 -2
- package/dist/esm/identity.js +92 -0
- package/dist/esm/identity.js.map +7 -0
- package/dist/esm/index.js +43 -24
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/media.js +91 -0
- package/dist/esm/media.js.map +7 -0
- package/dist/esm/objects.js +285 -0
- package/dist/esm/objects.js.map +7 -0
- package/dist/esm/tests.spec.js +2 -4
- package/dist/esm/tests.spec.js.map +2 -2
- package/dist/esm/utilities.js +69 -48
- package/dist/esm/utilities.js.map +2 -2
- package/dist/{session-manager.d.ts → identity.d.ts} +7 -5
- package/dist/identity.d.ts.map +1 -0
- package/dist/index.d.ts +15 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/media.d.ts +9 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/objects.d.ts +63 -0
- package/dist/objects.d.ts.map +1 -0
- package/dist/utilities.d.ts +19 -8
- package/dist/utilities.d.ts.map +1 -1
- package/package.json +31 -19
- package/src/identity.ts +131 -0
- package/src/index.ts +44 -29
- package/src/media.ts +106 -0
- package/src/objects.ts +431 -0
- package/src/tests.spec.ts +2 -4
- package/src/utilities.ts +67 -87
- package/dist/browser/ajv-6AI3HK2A.js +0 -9
- package/dist/browser/ajv-6AI3HK2A.js.map +0 -7
- package/dist/browser/fast-json-patch-ZE7SZEYK.js +0 -19
- package/dist/browser/fast-json-patch-ZE7SZEYK.js.map +0 -7
- package/dist/cjs/database.js +0 -626
- package/dist/cjs/database.js.map +0 -7
- package/dist/cjs/session-manager.js +0 -107
- package/dist/cjs/session-manager.js.map +0 -7
- package/dist/database.d.ts +0 -106
- package/dist/database.d.ts.map +0 -1
- package/dist/esm/database.js +0 -608
- package/dist/esm/database.js.map +0 -7
- package/dist/esm/session-manager.js +0 -87
- package/dist/esm/session-manager.js.map +0 -7
- package/dist/session-manager.d.ts.map +0 -1
- package/src/database.ts +0 -921
- package/src/session-manager.ts +0 -123
package/dist/esm/database.js
DELETED
|
@@ -1,608 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
GraffitiErrorNotFound,
|
|
3
|
-
GraffitiErrorSchemaMismatch,
|
|
4
|
-
GraffitiErrorForbidden,
|
|
5
|
-
GraffitiErrorPatchError
|
|
6
|
-
} from "@graffiti-garden/api";
|
|
7
|
-
import {
|
|
8
|
-
randomBase64,
|
|
9
|
-
applyGraffitiPatch,
|
|
10
|
-
maskGraffitiObject,
|
|
11
|
-
isActorAllowedGraffitiObject,
|
|
12
|
-
compileGraffitiObjectSchema,
|
|
13
|
-
unpackObjectUrl
|
|
14
|
-
} from "./utilities.js";
|
|
15
|
-
const DEFAULT_ORIGIN = "graffiti:local:";
|
|
16
|
-
const LAST_MODIFIED_BUFFER = 6e4;
|
|
17
|
-
class GraffitiLocalDatabase {
|
|
18
|
-
db_;
|
|
19
|
-
applyPatch_;
|
|
20
|
-
ajv_;
|
|
21
|
-
options;
|
|
22
|
-
origin;
|
|
23
|
-
get db() {
|
|
24
|
-
if (!this.db_) {
|
|
25
|
-
this.db_ = (async () => {
|
|
26
|
-
const { default: PouchDB } = await import("pouchdb");
|
|
27
|
-
const pouchDbOptions = {
|
|
28
|
-
name: "graffitiDb",
|
|
29
|
-
...this.options.pouchDBOptions
|
|
30
|
-
};
|
|
31
|
-
const db = new PouchDB(
|
|
32
|
-
pouchDbOptions.name,
|
|
33
|
-
pouchDbOptions
|
|
34
|
-
);
|
|
35
|
-
await db.put({
|
|
36
|
-
_id: "_design/indexes",
|
|
37
|
-
views: {
|
|
38
|
-
objectsPerChannelAndLastModified: {
|
|
39
|
-
map: function(object) {
|
|
40
|
-
const paddedLastModified = object.lastModified.toString().padStart(15, "0");
|
|
41
|
-
object.channels.forEach(function(channel) {
|
|
42
|
-
const id = encodeURIComponent(channel) + "/" + paddedLastModified;
|
|
43
|
-
emit(id);
|
|
44
|
-
});
|
|
45
|
-
}.toString()
|
|
46
|
-
},
|
|
47
|
-
orphansPerActorAndLastModified: {
|
|
48
|
-
map: function(object) {
|
|
49
|
-
if (object.channels.length === 0) {
|
|
50
|
-
const paddedLastModified = object.lastModified.toString().padStart(15, "0");
|
|
51
|
-
const id = encodeURIComponent(object.actor) + "/" + paddedLastModified;
|
|
52
|
-
emit(id);
|
|
53
|
-
}
|
|
54
|
-
}.toString()
|
|
55
|
-
},
|
|
56
|
-
channelStatsPerActor: {
|
|
57
|
-
map: function(object) {
|
|
58
|
-
if (object.tombstone) return;
|
|
59
|
-
object.channels.forEach(function(channel) {
|
|
60
|
-
const id = encodeURIComponent(object.actor) + "/" + encodeURIComponent(channel);
|
|
61
|
-
emit(id, object.lastModified);
|
|
62
|
-
});
|
|
63
|
-
}.toString(),
|
|
64
|
-
reduce: "_stats"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}).catch((error) => {
|
|
68
|
-
if (error && typeof error === "object" && "name" in error && error.name === "conflict") {
|
|
69
|
-
return;
|
|
70
|
-
} else {
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
return db;
|
|
75
|
-
})();
|
|
76
|
-
}
|
|
77
|
-
return this.db_;
|
|
78
|
-
}
|
|
79
|
-
get applyPatch() {
|
|
80
|
-
if (!this.applyPatch_) {
|
|
81
|
-
this.applyPatch_ = (async () => {
|
|
82
|
-
const imported = await import("fast-json-patch");
|
|
83
|
-
return imported.applyPatch || imported.default.applyPatch;
|
|
84
|
-
})();
|
|
85
|
-
}
|
|
86
|
-
return this.applyPatch_;
|
|
87
|
-
}
|
|
88
|
-
get ajv() {
|
|
89
|
-
if (!this.ajv_) {
|
|
90
|
-
this.ajv_ = this.options.ajv ? Promise.resolve(this.options.ajv) : (async () => {
|
|
91
|
-
const { default: Ajv } = await import("ajv");
|
|
92
|
-
return new Ajv({ strict: false });
|
|
93
|
-
})();
|
|
94
|
-
}
|
|
95
|
-
return this.ajv_;
|
|
96
|
-
}
|
|
97
|
-
extractGraffitiObject(object) {
|
|
98
|
-
const { value, channels, allowed, url, actor, lastModified } = object;
|
|
99
|
-
return {
|
|
100
|
-
value,
|
|
101
|
-
channels,
|
|
102
|
-
allowed,
|
|
103
|
-
url,
|
|
104
|
-
actor,
|
|
105
|
-
lastModified
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
constructor(options) {
|
|
109
|
-
this.options = options ?? {};
|
|
110
|
-
this.origin = this.options.origin ?? DEFAULT_ORIGIN;
|
|
111
|
-
if (!this.origin.endsWith(":") && !this.origin.endsWith("/")) {
|
|
112
|
-
this.origin += "/";
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
async allDocsAtLocation(objectUrl) {
|
|
116
|
-
const url = unpackObjectUrl(objectUrl) + "/";
|
|
117
|
-
const results = await (await this.db).allDocs({
|
|
118
|
-
startkey: url,
|
|
119
|
-
endkey: url + "\uFFFF",
|
|
120
|
-
// \uffff is the last unicode character
|
|
121
|
-
include_docs: true
|
|
122
|
-
});
|
|
123
|
-
const docs = results.rows.map((row) => row.doc).reduce((acc, doc) => {
|
|
124
|
-
if (doc) acc.push(doc);
|
|
125
|
-
return acc;
|
|
126
|
-
}, []);
|
|
127
|
-
return docs;
|
|
128
|
-
}
|
|
129
|
-
docId(objectUrl) {
|
|
130
|
-
return objectUrl.url + "/" + randomBase64();
|
|
131
|
-
}
|
|
132
|
-
get = async (...args) => {
|
|
133
|
-
const [urlObject, schema, session] = args;
|
|
134
|
-
const docsAll = await this.allDocsAtLocation(urlObject);
|
|
135
|
-
const docs = docsAll.filter(
|
|
136
|
-
(doc2) => isActorAllowedGraffitiObject(doc2, session)
|
|
137
|
-
);
|
|
138
|
-
if (!docs.length)
|
|
139
|
-
throw new GraffitiErrorNotFound(
|
|
140
|
-
"The object you are trying to get either does not exist or you are not allowed to see it"
|
|
141
|
-
);
|
|
142
|
-
const doc = docs.reduce(
|
|
143
|
-
(a, b) => a.lastModified > b.lastModified || a.lastModified === b.lastModified && !a.tombstone && b.tombstone ? a : b
|
|
144
|
-
);
|
|
145
|
-
if (doc.tombstone) {
|
|
146
|
-
throw new GraffitiErrorNotFound(
|
|
147
|
-
"The object you are trying to get either does not exist or you are not allowed to see it"
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
const object = this.extractGraffitiObject(doc);
|
|
151
|
-
maskGraffitiObject(object, [], session);
|
|
152
|
-
const validate = compileGraffitiObjectSchema(await this.ajv, schema);
|
|
153
|
-
if (!validate(object)) {
|
|
154
|
-
throw new GraffitiErrorSchemaMismatch();
|
|
155
|
-
}
|
|
156
|
-
return object;
|
|
157
|
-
};
|
|
158
|
-
/**
|
|
159
|
-
* Deletes all docs at a particular location.
|
|
160
|
-
* If the `keepLatest` flag is set to true,
|
|
161
|
-
* the doc with the most recent timestamp will be
|
|
162
|
-
* spared. If there are multiple docs with the same
|
|
163
|
-
* timestamp, the one with the highest `_id` will be
|
|
164
|
-
* spared.
|
|
165
|
-
*/
|
|
166
|
-
async deleteAtLocation(url, options = {
|
|
167
|
-
keepLatest: false
|
|
168
|
-
}) {
|
|
169
|
-
const docsAtLocationAll = await this.allDocsAtLocation(url);
|
|
170
|
-
const docsAtLocationAllowed = options.session ? docsAtLocationAll.filter(
|
|
171
|
-
(doc) => isActorAllowedGraffitiObject(doc, options.session)
|
|
172
|
-
) : docsAtLocationAll;
|
|
173
|
-
if (!docsAtLocationAllowed.length) {
|
|
174
|
-
throw new GraffitiErrorNotFound(
|
|
175
|
-
"The object you are trying to delete either does not exist or you are not allowed to see it"
|
|
176
|
-
);
|
|
177
|
-
} else if (options.session && docsAtLocationAllowed.some((doc) => doc.actor !== options.session?.actor)) {
|
|
178
|
-
throw new GraffitiErrorForbidden(
|
|
179
|
-
"You cannot delete an object owned by another actor"
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
const docsAtLocation = docsAtLocationAllowed.filter(
|
|
183
|
-
(doc) => !doc.tombstone
|
|
184
|
-
);
|
|
185
|
-
if (!docsAtLocation.length) return void 0;
|
|
186
|
-
const latestModified = docsAtLocation.map((doc) => doc.lastModified).reduce((a, b) => a > b ? a : b);
|
|
187
|
-
const docsToDelete = docsAtLocation.filter(
|
|
188
|
-
(doc) => !options.keepLatest || doc.lastModified < latestModified
|
|
189
|
-
);
|
|
190
|
-
const concurrentDocsAll = docsAtLocation.filter(
|
|
191
|
-
(doc) => options.keepLatest && doc.lastModified === latestModified
|
|
192
|
-
);
|
|
193
|
-
if (concurrentDocsAll.length) {
|
|
194
|
-
const keepDocId = concurrentDocsAll.map((doc) => doc._id).reduce((a, b) => a > b ? a : b);
|
|
195
|
-
const concurrentDocsToDelete = concurrentDocsAll.filter(
|
|
196
|
-
(doc) => doc._id !== keepDocId
|
|
197
|
-
);
|
|
198
|
-
docsToDelete.push(...concurrentDocsToDelete);
|
|
199
|
-
}
|
|
200
|
-
const lastModified = options.keepLatest ? latestModified : (/* @__PURE__ */ new Date()).getTime();
|
|
201
|
-
const deleteResults = await (await this.db).bulkDocs(
|
|
202
|
-
docsToDelete.map((doc) => ({
|
|
203
|
-
...doc,
|
|
204
|
-
tombstone: true,
|
|
205
|
-
lastModified
|
|
206
|
-
}))
|
|
207
|
-
);
|
|
208
|
-
let deletedObject = void 0;
|
|
209
|
-
for (const resultOrError of deleteResults) {
|
|
210
|
-
if ("ok" in resultOrError) {
|
|
211
|
-
const { id } = resultOrError;
|
|
212
|
-
const deletedDoc = docsToDelete.find((doc) => doc._id === id);
|
|
213
|
-
if (deletedDoc) {
|
|
214
|
-
deletedObject = {
|
|
215
|
-
...this.extractGraffitiObject(deletedDoc),
|
|
216
|
-
lastModified
|
|
217
|
-
};
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return deletedObject;
|
|
223
|
-
}
|
|
224
|
-
delete = async (...args) => {
|
|
225
|
-
const [url, session] = args;
|
|
226
|
-
const deletedObject = await this.deleteAtLocation(url, {
|
|
227
|
-
session
|
|
228
|
-
});
|
|
229
|
-
if (!deletedObject) {
|
|
230
|
-
throw new GraffitiErrorNotFound("The object has already been deleted");
|
|
231
|
-
}
|
|
232
|
-
return deletedObject;
|
|
233
|
-
};
|
|
234
|
-
put = async (...args) => {
|
|
235
|
-
const [objectPartial, session] = args;
|
|
236
|
-
if (objectPartial.actor && objectPartial.actor !== session.actor) {
|
|
237
|
-
throw new GraffitiErrorForbidden(
|
|
238
|
-
"Cannot put an object with a different actor than the session actor"
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
if (objectPartial.url) {
|
|
242
|
-
let oldObject;
|
|
243
|
-
try {
|
|
244
|
-
oldObject = await this.get(objectPartial.url, {}, session);
|
|
245
|
-
} catch (e) {
|
|
246
|
-
if (e instanceof GraffitiErrorNotFound) {
|
|
247
|
-
if (!this.options.allowSettingArbitraryUrls) {
|
|
248
|
-
throw new GraffitiErrorNotFound(
|
|
249
|
-
"The object you are trying to replace does not exist or you are not allowed to see it"
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
} else {
|
|
253
|
-
throw e;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (oldObject?.actor !== session.actor) {
|
|
257
|
-
throw new GraffitiErrorForbidden(
|
|
258
|
-
"The object you are trying to replace is owned by another actor"
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
const lastModified = (this.options.allowSettinngLastModified ?? false) && objectPartial.lastModified || (/* @__PURE__ */ new Date()).getTime();
|
|
263
|
-
const object = {
|
|
264
|
-
value: objectPartial.value,
|
|
265
|
-
channels: objectPartial.channels,
|
|
266
|
-
allowed: objectPartial.allowed,
|
|
267
|
-
url: objectPartial.url ?? this.origin + randomBase64(),
|
|
268
|
-
actor: session.actor,
|
|
269
|
-
tombstone: false,
|
|
270
|
-
lastModified
|
|
271
|
-
};
|
|
272
|
-
await (await this.db).put({
|
|
273
|
-
_id: this.docId(object),
|
|
274
|
-
...object
|
|
275
|
-
});
|
|
276
|
-
const previousObject = await this.deleteAtLocation(object, {
|
|
277
|
-
keepLatest: true
|
|
278
|
-
});
|
|
279
|
-
if (previousObject) {
|
|
280
|
-
return previousObject;
|
|
281
|
-
} else {
|
|
282
|
-
return {
|
|
283
|
-
...object,
|
|
284
|
-
value: {},
|
|
285
|
-
channels: [],
|
|
286
|
-
allowed: [],
|
|
287
|
-
tombstone: true
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
patch = async (...args) => {
|
|
292
|
-
const [patch, url, session] = args;
|
|
293
|
-
let originalObject;
|
|
294
|
-
try {
|
|
295
|
-
originalObject = await this.get(url, {}, session);
|
|
296
|
-
} catch (e) {
|
|
297
|
-
if (e instanceof GraffitiErrorNotFound) {
|
|
298
|
-
throw new GraffitiErrorNotFound(
|
|
299
|
-
"The object you are trying to patch does not exist or you are not allowed to see it"
|
|
300
|
-
);
|
|
301
|
-
} else {
|
|
302
|
-
throw e;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
if (originalObject.actor !== session.actor) {
|
|
306
|
-
throw new GraffitiErrorForbidden(
|
|
307
|
-
"The object you are trying to patch is owned by another actor"
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
const patchObject = { ...originalObject };
|
|
311
|
-
for (const prop of ["value", "channels", "allowed"]) {
|
|
312
|
-
applyGraffitiPatch(await this.applyPatch, prop, patch, patchObject);
|
|
313
|
-
}
|
|
314
|
-
if (typeof patchObject.value !== "object" || Array.isArray(patchObject.value) || !patchObject.value) {
|
|
315
|
-
throw new GraffitiErrorPatchError("value is no longer an object");
|
|
316
|
-
}
|
|
317
|
-
if (!Array.isArray(patchObject.channels) || !patchObject.channels.every((channel) => typeof channel === "string")) {
|
|
318
|
-
throw new GraffitiErrorPatchError(
|
|
319
|
-
"channels are no longer an array of strings"
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
if (patchObject.allowed && (!Array.isArray(patchObject.allowed) || !patchObject.allowed.every((allowed) => typeof allowed === "string"))) {
|
|
323
|
-
throw new GraffitiErrorPatchError(
|
|
324
|
-
"allowed list is not an array of strings"
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
patchObject.lastModified = (/* @__PURE__ */ new Date()).getTime();
|
|
328
|
-
await (await this.db).put({
|
|
329
|
-
...patchObject,
|
|
330
|
-
tombstone: false,
|
|
331
|
-
_id: this.docId(patchObject)
|
|
332
|
-
});
|
|
333
|
-
await this.deleteAtLocation(patchObject, {
|
|
334
|
-
keepLatest: true
|
|
335
|
-
});
|
|
336
|
-
return {
|
|
337
|
-
...originalObject,
|
|
338
|
-
lastModified: patchObject.lastModified
|
|
339
|
-
};
|
|
340
|
-
};
|
|
341
|
-
queryLastModifiedSuffixes(schema, lastModified) {
|
|
342
|
-
let startKeySuffix = "";
|
|
343
|
-
let endKeySuffix = "\uFFFF";
|
|
344
|
-
if (typeof schema === "object" && schema.properties?.lastModified && typeof schema.properties.lastModified === "object") {
|
|
345
|
-
const lastModifiedSchema = schema.properties.lastModified;
|
|
346
|
-
const minimum = lastModified && lastModifiedSchema.minimum ? Math.max(lastModified, lastModifiedSchema.minimum) : lastModified ?? lastModifiedSchema.minimum;
|
|
347
|
-
const exclusiveMinimum = lastModifiedSchema.exclusiveMinimum;
|
|
348
|
-
let intMinimum;
|
|
349
|
-
if (exclusiveMinimum !== void 0) {
|
|
350
|
-
intMinimum = Math.ceil(exclusiveMinimum);
|
|
351
|
-
intMinimum === exclusiveMinimum && intMinimum++;
|
|
352
|
-
} else if (minimum !== void 0) {
|
|
353
|
-
intMinimum = Math.ceil(minimum);
|
|
354
|
-
}
|
|
355
|
-
if (intMinimum !== void 0) {
|
|
356
|
-
startKeySuffix = intMinimum.toString().padStart(15, "0");
|
|
357
|
-
}
|
|
358
|
-
const maximum = lastModifiedSchema.maximum;
|
|
359
|
-
const exclusiveMaximum = lastModifiedSchema.exclusiveMaximum;
|
|
360
|
-
let intMaximum;
|
|
361
|
-
if (exclusiveMaximum !== void 0) {
|
|
362
|
-
intMaximum = Math.floor(exclusiveMaximum);
|
|
363
|
-
intMaximum === exclusiveMaximum && intMaximum--;
|
|
364
|
-
} else if (maximum !== void 0) {
|
|
365
|
-
intMaximum = Math.floor(maximum);
|
|
366
|
-
}
|
|
367
|
-
if (intMaximum !== void 0) {
|
|
368
|
-
endKeySuffix = intMaximum.toString().padStart(15, "0");
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return {
|
|
372
|
-
startKeySuffix,
|
|
373
|
-
endKeySuffix
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
async *streamObjects(index, startkey, endkey, validate, session, ifModifiedSince, channels, processedIds) {
|
|
377
|
-
if (ifModifiedSince !== void 0) {
|
|
378
|
-
ifModifiedSince -= LAST_MODIFIED_BUFFER;
|
|
379
|
-
}
|
|
380
|
-
const result = await (await this.db).query(index, {
|
|
381
|
-
startkey,
|
|
382
|
-
endkey,
|
|
383
|
-
include_docs: true
|
|
384
|
-
});
|
|
385
|
-
for (const row of result.rows) {
|
|
386
|
-
const doc = row.doc;
|
|
387
|
-
if (!doc) continue;
|
|
388
|
-
if (processedIds?.has(doc._id)) continue;
|
|
389
|
-
processedIds?.add(doc._id);
|
|
390
|
-
if (ifModifiedSince === void 0 && doc.tombstone) continue;
|
|
391
|
-
const object = this.extractGraffitiObject(doc);
|
|
392
|
-
if (channels) {
|
|
393
|
-
if (!isActorAllowedGraffitiObject(object, session)) continue;
|
|
394
|
-
maskGraffitiObject(object, channels, session);
|
|
395
|
-
}
|
|
396
|
-
if (!validate(object)) continue;
|
|
397
|
-
yield doc.tombstone ? {
|
|
398
|
-
tombstone: true,
|
|
399
|
-
object: {
|
|
400
|
-
url: object.url,
|
|
401
|
-
lastModified: object.lastModified
|
|
402
|
-
}
|
|
403
|
-
} : { object };
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
async waitToContinue(ifModifiedSince) {
|
|
407
|
-
if (ifModifiedSince === void 0) return;
|
|
408
|
-
const continueBuffer = this.options.continueBuffer ?? 1e3;
|
|
409
|
-
const timeElapsedSinceContinue = Date.now() - ifModifiedSince;
|
|
410
|
-
if (timeElapsedSinceContinue < continueBuffer) {
|
|
411
|
-
await new Promise(
|
|
412
|
-
(resolve) => setTimeout(resolve, continueBuffer - timeElapsedSinceContinue)
|
|
413
|
-
);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
async *discoverMeta(args, ifModifiedSince) {
|
|
417
|
-
await this.waitToContinue(ifModifiedSince);
|
|
418
|
-
const [channels, schema, session] = args;
|
|
419
|
-
const validate = compileGraffitiObjectSchema(await this.ajv, schema);
|
|
420
|
-
const { startKeySuffix, endKeySuffix } = this.queryLastModifiedSuffixes(
|
|
421
|
-
schema,
|
|
422
|
-
ifModifiedSince
|
|
423
|
-
);
|
|
424
|
-
const processedIds = /* @__PURE__ */ new Set();
|
|
425
|
-
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
426
|
-
for (const channel of channels) {
|
|
427
|
-
const keyPrefix = encodeURIComponent(channel) + "/";
|
|
428
|
-
const startkey = keyPrefix + startKeySuffix;
|
|
429
|
-
const endkey = keyPrefix + endKeySuffix;
|
|
430
|
-
const iterator = this.streamObjects(
|
|
431
|
-
"indexes/objectsPerChannelAndLastModified",
|
|
432
|
-
startkey,
|
|
433
|
-
endkey,
|
|
434
|
-
validate,
|
|
435
|
-
session,
|
|
436
|
-
ifModifiedSince,
|
|
437
|
-
channels,
|
|
438
|
-
processedIds
|
|
439
|
-
);
|
|
440
|
-
for await (const result of iterator) yield result;
|
|
441
|
-
}
|
|
442
|
-
return startTime;
|
|
443
|
-
}
|
|
444
|
-
async *recoverOrphansMeta(args, ifModifiedSince) {
|
|
445
|
-
await this.waitToContinue(ifModifiedSince);
|
|
446
|
-
const [schema, session] = args;
|
|
447
|
-
const { startKeySuffix, endKeySuffix } = this.queryLastModifiedSuffixes(
|
|
448
|
-
schema,
|
|
449
|
-
ifModifiedSince
|
|
450
|
-
);
|
|
451
|
-
const keyPrefix = encodeURIComponent(session.actor) + "/";
|
|
452
|
-
const startkey = keyPrefix + startKeySuffix;
|
|
453
|
-
const endkey = keyPrefix + endKeySuffix;
|
|
454
|
-
const validate = compileGraffitiObjectSchema(await this.ajv, schema);
|
|
455
|
-
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
456
|
-
const iterator = this.streamObjects(
|
|
457
|
-
"indexes/orphansPerActorAndLastModified",
|
|
458
|
-
startkey,
|
|
459
|
-
endkey,
|
|
460
|
-
validate,
|
|
461
|
-
session,
|
|
462
|
-
ifModifiedSince
|
|
463
|
-
);
|
|
464
|
-
for await (const result of iterator) yield result;
|
|
465
|
-
return startTime;
|
|
466
|
-
}
|
|
467
|
-
discoverCursor(args, ifModifiedSince) {
|
|
468
|
-
return "discover:" + JSON.stringify({
|
|
469
|
-
channels: args[0],
|
|
470
|
-
schema: args[1],
|
|
471
|
-
actor: args[2]?.actor,
|
|
472
|
-
ifModifiedSince
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
async *discoverContinue(args, ifModifiedSince) {
|
|
476
|
-
const iterator = this.discoverMeta(args, ifModifiedSince);
|
|
477
|
-
while (true) {
|
|
478
|
-
const result = await iterator.next();
|
|
479
|
-
if (result.done) {
|
|
480
|
-
const ifModifiedSince2 = result.value;
|
|
481
|
-
return {
|
|
482
|
-
continue: () => this.discoverContinue(args, ifModifiedSince2),
|
|
483
|
-
cursor: this.discoverCursor(args, ifModifiedSince2)
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
yield result.value;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
discover = (...args) => {
|
|
490
|
-
const iterator = this.discoverMeta(args);
|
|
491
|
-
const this_ = this;
|
|
492
|
-
return async function* () {
|
|
493
|
-
while (true) {
|
|
494
|
-
const result = await iterator.next();
|
|
495
|
-
if (result.done) {
|
|
496
|
-
return {
|
|
497
|
-
continue: () => this_.discoverContinue(args, result.value),
|
|
498
|
-
cursor: this_.discoverCursor(args, result.value)
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
if (result.value.tombstone) continue;
|
|
502
|
-
yield result.value;
|
|
503
|
-
}
|
|
504
|
-
}();
|
|
505
|
-
};
|
|
506
|
-
recoverOrphansCursor(args, ifModifiedSince) {
|
|
507
|
-
return "orphans:" + JSON.stringify({
|
|
508
|
-
schema: args[0],
|
|
509
|
-
actor: args[1]?.actor,
|
|
510
|
-
ifModifiedSince
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
async *recoverOrphansContinue(args, ifModifiedSince) {
|
|
514
|
-
const iterator = this.recoverOrphansMeta(args, ifModifiedSince);
|
|
515
|
-
while (true) {
|
|
516
|
-
const result = await iterator.next();
|
|
517
|
-
if (result.done) {
|
|
518
|
-
const ifModifiedSince2 = result.value;
|
|
519
|
-
return {
|
|
520
|
-
continue: () => this.recoverOrphansContinue(args, ifModifiedSince2),
|
|
521
|
-
cursor: this.recoverOrphansCursor(args, ifModifiedSince2)
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
yield result.value;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
recoverOrphans = (...args) => {
|
|
528
|
-
const iterator = this.recoverOrphansMeta(args);
|
|
529
|
-
const this_ = this;
|
|
530
|
-
return async function* () {
|
|
531
|
-
while (true) {
|
|
532
|
-
const result = await iterator.next();
|
|
533
|
-
if (result.done) {
|
|
534
|
-
return {
|
|
535
|
-
continue: () => this_.recoverOrphansContinue(
|
|
536
|
-
args,
|
|
537
|
-
result.value
|
|
538
|
-
),
|
|
539
|
-
cursor: this_.recoverOrphansCursor(args, result.value)
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
if (result.value.tombstone) continue;
|
|
543
|
-
yield result.value;
|
|
544
|
-
}
|
|
545
|
-
}();
|
|
546
|
-
};
|
|
547
|
-
channelStats = (session) => {
|
|
548
|
-
const this_ = this;
|
|
549
|
-
return async function* () {
|
|
550
|
-
const keyPrefix = encodeURIComponent(session.actor) + "/";
|
|
551
|
-
const result = await (await this_.db).query("indexes/channelStatsPerActor", {
|
|
552
|
-
startkey: keyPrefix,
|
|
553
|
-
endkey: keyPrefix + "\uFFFF",
|
|
554
|
-
reduce: true,
|
|
555
|
-
group: true
|
|
556
|
-
});
|
|
557
|
-
for (const row of result.rows) {
|
|
558
|
-
const channelEncoded = row.key.split("/")[1];
|
|
559
|
-
if (typeof channelEncoded !== "string") continue;
|
|
560
|
-
const { count, max: lastModified } = row.value;
|
|
561
|
-
if (typeof count !== "number" || typeof lastModified !== "number")
|
|
562
|
-
continue;
|
|
563
|
-
yield {
|
|
564
|
-
value: {
|
|
565
|
-
channel: decodeURIComponent(channelEncoded),
|
|
566
|
-
count,
|
|
567
|
-
lastModified
|
|
568
|
-
}
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
}();
|
|
572
|
-
};
|
|
573
|
-
continueObjectStream = (cursor, session) => {
|
|
574
|
-
if (cursor.startsWith("discover:")) {
|
|
575
|
-
const { channels, schema, actor, ifModifiedSince } = JSON.parse(
|
|
576
|
-
cursor.slice("discover:".length)
|
|
577
|
-
);
|
|
578
|
-
if (actor && actor !== session?.actor) {
|
|
579
|
-
throw new GraffitiErrorForbidden(
|
|
580
|
-
"Cannot continue a cursor for another actor"
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
return this.discoverContinue(
|
|
584
|
-
[channels, schema, session],
|
|
585
|
-
ifModifiedSince
|
|
586
|
-
);
|
|
587
|
-
} else if (cursor.startsWith("orphans:")) {
|
|
588
|
-
const { schema, actor, ifModifiedSince } = JSON.parse(
|
|
589
|
-
cursor.slice("orphans:".length)
|
|
590
|
-
);
|
|
591
|
-
if (!session || actor !== session?.actor) {
|
|
592
|
-
throw new GraffitiErrorForbidden(
|
|
593
|
-
"Cannot continue a cursor for another actor"
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
return this.recoverOrphansContinue(
|
|
597
|
-
[schema, session],
|
|
598
|
-
ifModifiedSince
|
|
599
|
-
);
|
|
600
|
-
} else {
|
|
601
|
-
throw new GraffitiErrorNotFound("Cursor not found");
|
|
602
|
-
}
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
export {
|
|
606
|
-
GraffitiLocalDatabase
|
|
607
|
-
};
|
|
608
|
-
//# sourceMappingURL=database.js.map
|