@graffiti-garden/implementation-local 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.js +2 -20
- package/dist/browser/index.js.map +4 -4
- package/dist/cjs/database.js +181 -90
- package/dist/cjs/database.js.map +3 -3
- package/dist/cjs/index.js +11 -0
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/utilities.js +3 -7
- package/dist/cjs/utilities.js.map +2 -2
- package/dist/database.d.ts +28 -20
- package/dist/database.d.ts.map +1 -1
- package/dist/esm/database.js +182 -92
- package/dist/esm/database.js.map +3 -3
- package/dist/esm/index.js +11 -0
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/utilities.js +3 -7
- package/dist/esm/utilities.js.map +2 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/utilities.d.ts +1 -2
- package/dist/utilities.d.ts.map +1 -1
- package/package.json +2 -3
- package/src/database.ts +285 -164
- package/src/index.ts +17 -1
- package/src/utilities.ts +2 -14
package/dist/cjs/utilities.js
CHANGED
|
@@ -21,15 +21,14 @@ __export(utilities_exports, {
|
|
|
21
21
|
applyGraffitiPatch: () => applyGraffitiPatch,
|
|
22
22
|
compileGraffitiObjectSchema: () => compileGraffitiObjectSchema,
|
|
23
23
|
isActorAllowedGraffitiObject: () => isActorAllowedGraffitiObject,
|
|
24
|
-
isObjectNewer: () => isObjectNewer,
|
|
25
24
|
maskGraffitiObject: () => maskGraffitiObject,
|
|
26
25
|
randomBase64: () => randomBase64,
|
|
27
|
-
|
|
26
|
+
unpackObjectUrl: () => unpackObjectUrl
|
|
28
27
|
});
|
|
29
28
|
module.exports = __toCommonJS(utilities_exports);
|
|
30
29
|
var import_api = require("@graffiti-garden/api");
|
|
31
|
-
function
|
|
32
|
-
return typeof
|
|
30
|
+
function unpackObjectUrl(url) {
|
|
31
|
+
return typeof url === "string" ? url : url.url;
|
|
33
32
|
}
|
|
34
33
|
function randomBase64(numBytes = 24) {
|
|
35
34
|
const bytes = new Uint8Array(numBytes);
|
|
@@ -37,9 +36,6 @@ function randomBase64(numBytes = 24) {
|
|
|
37
36
|
const base64 = btoa(String.fromCodePoint(...bytes));
|
|
38
37
|
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, "");
|
|
39
38
|
}
|
|
40
|
-
function isObjectNewer(left, right) {
|
|
41
|
-
return left.lastModified > right.lastModified || left.lastModified === right.lastModified && !left.tombstone && right.tombstone;
|
|
42
|
-
}
|
|
43
39
|
function applyGraffitiPatch(apply, prop, patch, object) {
|
|
44
40
|
const ops = patch[prop];
|
|
45
41
|
if (!ops || !ops.length) return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utilities.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n GraffitiErrorInvalidSchema,\n GraffitiErrorInvalidUri,\n GraffitiErrorPatchError,\n GraffitiErrorPatchTestFailed,\n} from \"@graffiti-garden/api\";\nimport type {\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiPatch,\n JSONSchema,\n GraffitiSession,\n GraffitiObjectUrl,\n} from \"@graffiti-garden/api\";\nimport type { Ajv } from \"ajv\";\nimport type { applyPatch } from \"fast-json-patch\";\n\nexport function
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA
|
|
4
|
+
"sourcesContent": ["import {\n GraffitiErrorInvalidSchema,\n GraffitiErrorInvalidUri,\n GraffitiErrorPatchError,\n GraffitiErrorPatchTestFailed,\n} from \"@graffiti-garden/api\";\nimport type {\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiPatch,\n JSONSchema,\n GraffitiSession,\n GraffitiObjectUrl,\n} from \"@graffiti-garden/api\";\nimport type { Ajv } from \"ajv\";\nimport type { applyPatch } from \"fast-json-patch\";\n\nexport function unpackObjectUrl(url: string | GraffitiObjectUrl) {\n return typeof url === \"string\" ? url : url.url;\n}\n\nexport function randomBase64(numBytes: number = 24) {\n const bytes = new Uint8Array(numBytes);\n crypto.getRandomValues(bytes);\n // Convert it to base64\n const base64 = btoa(String.fromCodePoint(...bytes));\n // Make sure it is url safe\n return base64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/\\=+$/, \"\");\n}\n\nexport function applyGraffitiPatch<Prop extends keyof GraffitiPatch>(\n apply: typeof applyPatch,\n prop: Prop,\n patch: GraffitiPatch,\n object: GraffitiObjectBase,\n): void {\n const ops = patch[prop];\n if (!ops || !ops.length) return;\n try {\n object[prop] = apply(object[prop], ops, true, false).newDocument;\n } catch (e) {\n if (\n typeof e === \"object\" &&\n e &&\n \"name\" in e &&\n typeof e.name === \"string\" &&\n \"message\" in e &&\n typeof e.message === \"string\"\n ) {\n if (e.name === \"TEST_OPERATION_FAILED\") {\n throw new GraffitiErrorPatchTestFailed(e.message);\n } else {\n throw new GraffitiErrorPatchError(e.name + \": \" + e.message);\n }\n } else {\n throw e;\n }\n }\n}\n\nexport function compileGraffitiObjectSchema<Schema extends JSONSchema>(\n ajv: Ajv,\n schema: Schema,\n) {\n try {\n // Force the validation guard because\n // it is too big for the type checker.\n // Fortunately json-schema-to-ts is\n // well tested against ajv.\n return ajv.compile(schema) as (\n data: GraffitiObjectBase,\n ) => data is GraffitiObject<Schema>;\n } catch (error) {\n throw new GraffitiErrorInvalidSchema(\n error instanceof Error ? error.message : undefined,\n );\n }\n}\n\nexport function maskGraffitiObject(\n object: GraffitiObjectBase,\n channels: string[],\n session?: GraffitiSession | null,\n): void {\n if (object.actor !== session?.actor) {\n object.allowed = object.allowed && session ? [session.actor] : undefined;\n object.channels = object.channels.filter((channel) =>\n channels.includes(channel),\n );\n }\n}\nexport function isActorAllowedGraffitiObject(\n object: GraffitiObjectBase,\n session?: GraffitiSession | null,\n) {\n return (\n object.allowed === undefined ||\n object.allowed === null ||\n (!!session?.actor &&\n (object.actor === session.actor ||\n object.allowed.includes(session.actor)))\n );\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAKO;AAYA,SAAS,gBAAgB,KAAiC;AAC/D,SAAO,OAAO,QAAQ,WAAW,MAAM,IAAI;AAC7C;AAEO,SAAS,aAAa,WAAmB,IAAI;AAClD,QAAM,QAAQ,IAAI,WAAW,QAAQ;AACrC,SAAO,gBAAgB,KAAK;AAE5B,QAAM,SAAS,KAAK,OAAO,cAAc,GAAG,KAAK,CAAC;AAElD,SAAO,OAAO,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,QAAQ,EAAE;AAC1E;AAEO,SAAS,mBACd,OACA,MACA,OACA,QACM;AACN,QAAM,MAAM,MAAM,IAAI;AACtB,MAAI,CAAC,OAAO,CAAC,IAAI,OAAQ;AACzB,MAAI;AACF,WAAO,IAAI,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,MAAM,KAAK,EAAE;AAAA,EACvD,SAAS,GAAG;AACV,QACE,OAAO,MAAM,YACb,KACA,UAAU,KACV,OAAO,EAAE,SAAS,YAClB,aAAa,KACb,OAAO,EAAE,YAAY,UACrB;AACA,UAAI,EAAE,SAAS,yBAAyB;AACtC,cAAM,IAAI,wCAA6B,EAAE,OAAO;AAAA,MAClD,OAAO;AACL,cAAM,IAAI,mCAAwB,EAAE,OAAO,OAAO,EAAE,OAAO;AAAA,MAC7D;AAAA,IACF,OAAO;AACL,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,SAAS,4BACd,KACA,QACA;AACA,MAAI;AAKF,WAAO,IAAI,QAAQ,MAAM;AAAA,EAG3B,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAC3C;AAAA,EACF;AACF;AAEO,SAAS,mBACd,QACA,UACA,SACM;AACN,MAAI,OAAO,UAAU,SAAS,OAAO;AACnC,WAAO,UAAU,OAAO,WAAW,UAAU,CAAC,QAAQ,KAAK,IAAI;AAC/D,WAAO,WAAW,OAAO,SAAS;AAAA,MAAO,CAAC,YACxC,SAAS,SAAS,OAAO;AAAA,IAC3B;AAAA,EACF;AACF;AACO,SAAS,6BACd,QACA,SACA;AACA,SACE,OAAO,YAAY,UACnB,OAAO,YAAY,QAClB,CAAC,CAAC,SAAS,UACT,OAAO,UAAU,QAAQ,SACxB,OAAO,QAAQ,SAAS,QAAQ,KAAK;AAE7C;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/database.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Graffiti, GraffitiObjectBase, GraffitiObjectUrl, JSONSchema, GraffitiSession } from "@graffiti-garden/api";
|
|
1
|
+
import type { Graffiti, GraffitiObjectBase, GraffitiObjectUrl, JSONSchema, GraffitiSession, GraffitiObjectStreamContinue, GraffitiObjectStreamContinueEntry } from "@graffiti-garden/api";
|
|
2
|
+
import { compileGraffitiObjectSchema } from "./utilities.js";
|
|
2
3
|
import type Ajv from "ajv";
|
|
3
4
|
import type { applyPatch } from "fast-json-patch";
|
|
4
5
|
/**
|
|
@@ -15,18 +16,18 @@ export interface GraffitiLocalOptions {
|
|
|
15
16
|
pouchDBOptions?: PouchDB.Configuration.DatabaseConfiguration;
|
|
16
17
|
/**
|
|
17
18
|
* Includes the scheme and other information (possibly domain name)
|
|
18
|
-
* to prefix prefixes all
|
|
19
|
+
* to prefix prefixes all URLs put in the system. Defaults to `graffiti:local`.
|
|
19
20
|
*/
|
|
20
21
|
origin?: string;
|
|
21
22
|
/**
|
|
22
|
-
* Whether to allow putting objects at arbtirary
|
|
23
|
-
*
|
|
23
|
+
* Whether to allow putting objects at arbtirary URLs, i.e.
|
|
24
|
+
* URLs that are *not* prefixed with the origin or not generated
|
|
24
25
|
* by the system. Defaults to `false`.
|
|
25
26
|
*
|
|
26
27
|
* Allows this implementation to be used as a client-side cache
|
|
27
28
|
* for remote sources.
|
|
28
29
|
*/
|
|
29
|
-
|
|
30
|
+
allowSettingArbitraryUrls?: boolean;
|
|
30
31
|
/**
|
|
31
32
|
* Whether to allow the user to set the lastModified field
|
|
32
33
|
* when putting objects. Defaults to `false`.
|
|
@@ -35,34 +36,34 @@ export interface GraffitiLocalOptions {
|
|
|
35
36
|
* for remote sources.
|
|
36
37
|
*/
|
|
37
38
|
allowSettinngLastModified?: boolean;
|
|
38
|
-
/**
|
|
39
|
-
* The time in milliseconds to keep tombstones before deleting them.
|
|
40
|
-
* See the {@link https://api.graffiti.garden/classes/Graffiti.html#discover | `discover` }
|
|
41
|
-
* documentation for more information.
|
|
42
|
-
*/
|
|
43
|
-
tombstoneRetention?: number;
|
|
44
39
|
/**
|
|
45
40
|
* An optional Ajv instance to use for schema validation.
|
|
46
41
|
* If not provided, an internal instance will be created.
|
|
47
42
|
*/
|
|
48
43
|
ajv?: Ajv;
|
|
49
44
|
}
|
|
45
|
+
type GraffitiObjectWithTombstone = GraffitiObjectBase & {
|
|
46
|
+
tombstone: boolean;
|
|
47
|
+
};
|
|
50
48
|
/**
|
|
51
49
|
* An implementation of only the database operations of the
|
|
52
50
|
* GraffitiAPI without synchronization or session management.
|
|
53
51
|
*/
|
|
54
|
-
export declare class GraffitiLocalDatabase implements
|
|
55
|
-
protected db_: Promise<PouchDB.Database<
|
|
52
|
+
export declare class GraffitiLocalDatabase implements Omit<Graffiti, "login" | "logout" | "sessionEvents"> {
|
|
53
|
+
protected db_: Promise<PouchDB.Database<GraffitiObjectWithTombstone>> | undefined;
|
|
56
54
|
protected applyPatch_: Promise<typeof applyPatch> | undefined;
|
|
57
55
|
protected ajv_: Promise<Ajv> | undefined;
|
|
58
56
|
protected readonly options: GraffitiLocalOptions;
|
|
59
57
|
protected readonly origin: string;
|
|
60
|
-
get db(): Promise<PouchDB.Database<
|
|
61
|
-
get applyPatch(): Promise<typeof applyPatch>;
|
|
62
|
-
get ajv(): Promise<Ajv>;
|
|
58
|
+
get db(): Promise<PouchDB.Database<GraffitiObjectWithTombstone>>;
|
|
59
|
+
protected get applyPatch(): Promise<typeof applyPatch>;
|
|
60
|
+
protected get ajv(): Promise<Ajv>;
|
|
61
|
+
protected extractGraffitiObject(object: GraffitiObjectWithTombstone): GraffitiObjectBase;
|
|
63
62
|
constructor(options?: GraffitiLocalOptions);
|
|
64
|
-
protected allDocsAtLocation(
|
|
65
|
-
|
|
63
|
+
protected allDocsAtLocation(objectUrl: string | GraffitiObjectUrl): Promise<PouchDB.Core.ExistingDocument<GraffitiObjectBase & {
|
|
64
|
+
tombstone: boolean;
|
|
65
|
+
} & PouchDB.Core.AllDocsMeta>[]>;
|
|
66
|
+
protected docId(objectUrl: GraffitiObjectUrl): string;
|
|
66
67
|
get: Graffiti["get"];
|
|
67
68
|
/**
|
|
68
69
|
* Deletes all docs at a particular location.
|
|
@@ -72,19 +73,26 @@ export declare class GraffitiLocalDatabase implements Pick<Graffiti, "get" | "pu
|
|
|
72
73
|
* timestamp, the one with the highest `_id` will be
|
|
73
74
|
* spared.
|
|
74
75
|
*/
|
|
75
|
-
protected deleteAtLocation(
|
|
76
|
+
protected deleteAtLocation(url: GraffitiObjectUrl | string, options?: {
|
|
76
77
|
keepLatest?: boolean;
|
|
77
78
|
session?: GraffitiSession;
|
|
78
79
|
}): Promise<GraffitiObjectBase | undefined>;
|
|
79
80
|
delete: Graffiti["delete"];
|
|
80
81
|
put: Graffiti["put"];
|
|
81
82
|
patch: Graffiti["patch"];
|
|
82
|
-
protected queryLastModifiedSuffixes(schema: JSONSchema): {
|
|
83
|
+
protected queryLastModifiedSuffixes(schema: JSONSchema, lastModified?: number): {
|
|
83
84
|
startKeySuffix: string;
|
|
84
85
|
endKeySuffix: string;
|
|
85
86
|
};
|
|
87
|
+
protected streamObjects<Schema extends JSONSchema>(index: string, startkey: string, endkey: string, validate: ReturnType<typeof compileGraffitiObjectSchema<Schema>>, session: GraffitiSession | undefined | null, ifModifiedSince: number | undefined, channels?: string[], processedIds?: Set<string>): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>>;
|
|
88
|
+
protected discoverMeta<Schema extends JSONSchema>(args: Parameters<typeof Graffiti.prototype.discover<Schema>>, ifModifiedSince?: number): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>, number | undefined>;
|
|
89
|
+
protected recoverOrphansMeta<Schema extends JSONSchema>(args: Parameters<typeof Graffiti.prototype.recoverOrphans<Schema>>, ifModifiedSince?: number): AsyncGenerator<GraffitiObjectStreamContinueEntry<Schema>, number | undefined>;
|
|
90
|
+
protected discoverContinue<Schema extends JSONSchema>(args: Parameters<typeof Graffiti.prototype.discover<Schema>>, ifModifiedSince?: number): GraffitiObjectStreamContinue<Schema>;
|
|
86
91
|
discover: Graffiti["discover"];
|
|
92
|
+
protected recoverContinue<Schema extends JSONSchema>(args: Parameters<typeof Graffiti.prototype.recoverOrphans<Schema>>, ifModifiedSince?: number): GraffitiObjectStreamContinue<Schema>;
|
|
87
93
|
recoverOrphans: Graffiti["recoverOrphans"];
|
|
88
94
|
channelStats: Graffiti["channelStats"];
|
|
95
|
+
continueObjectStream: Graffiti["continueObjectStream"];
|
|
89
96
|
}
|
|
97
|
+
export {};
|
|
90
98
|
//# sourceMappingURL=database.d.ts.map
|
package/dist/database.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,eAAe,
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/database.js
CHANGED
|
@@ -9,13 +9,11 @@ import {
|
|
|
9
9
|
applyGraffitiPatch,
|
|
10
10
|
maskGraffitiObject,
|
|
11
11
|
isActorAllowedGraffitiObject,
|
|
12
|
-
isObjectNewer,
|
|
13
12
|
compileGraffitiObjectSchema,
|
|
14
|
-
|
|
13
|
+
unpackObjectUrl
|
|
15
14
|
} from "./utilities.js";
|
|
16
|
-
import { Repeater } from "@repeaterjs/repeater";
|
|
17
|
-
const DEFAULT_TOMBSTONE_RETENTION = 864e5;
|
|
18
15
|
const DEFAULT_ORIGIN = "graffiti:local:";
|
|
16
|
+
const LAST_MODIFIED_BUFFER = 6e4;
|
|
19
17
|
class GraffitiLocalDatabase {
|
|
20
18
|
db_;
|
|
21
19
|
applyPatch_;
|
|
@@ -96,6 +94,17 @@ class GraffitiLocalDatabase {
|
|
|
96
94
|
}
|
|
97
95
|
return this.ajv_;
|
|
98
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
|
+
}
|
|
99
108
|
constructor(options) {
|
|
100
109
|
this.options = options ?? {};
|
|
101
110
|
this.origin = this.options.origin ?? DEFAULT_ORIGIN;
|
|
@@ -103,11 +112,11 @@ class GraffitiLocalDatabase {
|
|
|
103
112
|
this.origin += "/";
|
|
104
113
|
}
|
|
105
114
|
}
|
|
106
|
-
async allDocsAtLocation(
|
|
107
|
-
const
|
|
115
|
+
async allDocsAtLocation(objectUrl) {
|
|
116
|
+
const url = unpackObjectUrl(objectUrl) + "/";
|
|
108
117
|
const results = await (await this.db).allDocs({
|
|
109
|
-
startkey:
|
|
110
|
-
endkey:
|
|
118
|
+
startkey: url,
|
|
119
|
+
endkey: url + "\uFFFF",
|
|
111
120
|
// \uffff is the last unicode character
|
|
112
121
|
include_docs: true
|
|
113
122
|
});
|
|
@@ -117,12 +126,12 @@ class GraffitiLocalDatabase {
|
|
|
117
126
|
}, []);
|
|
118
127
|
return docs;
|
|
119
128
|
}
|
|
120
|
-
docId(
|
|
121
|
-
return
|
|
129
|
+
docId(objectUrl) {
|
|
130
|
+
return objectUrl.url + "/" + randomBase64();
|
|
122
131
|
}
|
|
123
132
|
get = async (...args) => {
|
|
124
|
-
const [
|
|
125
|
-
const docsAll = await this.allDocsAtLocation(
|
|
133
|
+
const [urlObject, schema, session] = args;
|
|
134
|
+
const docsAll = await this.allDocsAtLocation(urlObject);
|
|
126
135
|
const docs = docsAll.filter(
|
|
127
136
|
(doc2) => isActorAllowedGraffitiObject(doc2, session)
|
|
128
137
|
);
|
|
@@ -130,8 +139,15 @@ class GraffitiLocalDatabase {
|
|
|
130
139
|
throw new GraffitiErrorNotFound(
|
|
131
140
|
"The object you are trying to get either does not exist or you are not allowed to see it"
|
|
132
141
|
);
|
|
133
|
-
const doc = docs.reduce(
|
|
134
|
-
|
|
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);
|
|
135
151
|
maskGraffitiObject(object, [], session);
|
|
136
152
|
const validate = compileGraffitiObjectSchema(await this.ajv, schema);
|
|
137
153
|
if (!validate(object)) {
|
|
@@ -147,10 +163,10 @@ class GraffitiLocalDatabase {
|
|
|
147
163
|
* timestamp, the one with the highest `_id` will be
|
|
148
164
|
* spared.
|
|
149
165
|
*/
|
|
150
|
-
async deleteAtLocation(
|
|
166
|
+
async deleteAtLocation(url, options = {
|
|
151
167
|
keepLatest: false
|
|
152
168
|
}) {
|
|
153
|
-
const docsAtLocationAll = await this.allDocsAtLocation(
|
|
169
|
+
const docsAtLocationAll = await this.allDocsAtLocation(url);
|
|
154
170
|
const docsAtLocationAllowed = options.session ? docsAtLocationAll.filter(
|
|
155
171
|
(doc) => isActorAllowedGraffitiObject(doc, options.session)
|
|
156
172
|
) : docsAtLocationAll;
|
|
@@ -195,10 +211,8 @@ class GraffitiLocalDatabase {
|
|
|
195
211
|
const { id } = resultOrError;
|
|
196
212
|
const deletedDoc = docsToDelete.find((doc) => doc._id === id);
|
|
197
213
|
if (deletedDoc) {
|
|
198
|
-
const { _id, _rev, _conflicts, _attachments, ...object } = deletedDoc;
|
|
199
214
|
deletedObject = {
|
|
200
|
-
...
|
|
201
|
-
tombstone: true,
|
|
215
|
+
...this.extractGraffitiObject(deletedDoc),
|
|
202
216
|
lastModified
|
|
203
217
|
};
|
|
204
218
|
break;
|
|
@@ -208,8 +222,8 @@ class GraffitiLocalDatabase {
|
|
|
208
222
|
return deletedObject;
|
|
209
223
|
}
|
|
210
224
|
delete = async (...args) => {
|
|
211
|
-
const [
|
|
212
|
-
const deletedObject = await this.deleteAtLocation(
|
|
225
|
+
const [url, session] = args;
|
|
226
|
+
const deletedObject = await this.deleteAtLocation(url, {
|
|
213
227
|
session
|
|
214
228
|
});
|
|
215
229
|
if (!deletedObject) {
|
|
@@ -230,7 +244,7 @@ class GraffitiLocalDatabase {
|
|
|
230
244
|
oldObject = await this.get(objectPartial.url, {}, session);
|
|
231
245
|
} catch (e) {
|
|
232
246
|
if (e instanceof GraffitiErrorNotFound) {
|
|
233
|
-
if (!this.options.
|
|
247
|
+
if (!this.options.allowSettingArbitraryUrls) {
|
|
234
248
|
throw new GraffitiErrorNotFound(
|
|
235
249
|
"The object you are trying to replace does not exist or you are not allowed to see it"
|
|
236
250
|
);
|
|
@@ -275,10 +289,10 @@ class GraffitiLocalDatabase {
|
|
|
275
289
|
}
|
|
276
290
|
};
|
|
277
291
|
patch = async (...args) => {
|
|
278
|
-
const [patch,
|
|
292
|
+
const [patch, url, session] = args;
|
|
279
293
|
let originalObject;
|
|
280
294
|
try {
|
|
281
|
-
originalObject = await this.get(
|
|
295
|
+
originalObject = await this.get(url, {}, session);
|
|
282
296
|
} catch (e) {
|
|
283
297
|
if (e instanceof GraffitiErrorNotFound) {
|
|
284
298
|
throw new GraffitiErrorNotFound(
|
|
@@ -292,10 +306,6 @@ class GraffitiLocalDatabase {
|
|
|
292
306
|
throw new GraffitiErrorForbidden(
|
|
293
307
|
"The object you are trying to patch is owned by another actor"
|
|
294
308
|
);
|
|
295
|
-
} else if (originalObject.tombstone) {
|
|
296
|
-
throw new GraffitiErrorNotFound(
|
|
297
|
-
"The object you are trying to patch has been deleted"
|
|
298
|
-
);
|
|
299
309
|
}
|
|
300
310
|
const patchObject = { ...originalObject };
|
|
301
311
|
for (const prop of ["value", "channels", "allowed"]) {
|
|
@@ -317,6 +327,7 @@ class GraffitiLocalDatabase {
|
|
|
317
327
|
patchObject.lastModified = (/* @__PURE__ */ new Date()).getTime();
|
|
318
328
|
await (await this.db).put({
|
|
319
329
|
...patchObject,
|
|
330
|
+
tombstone: false,
|
|
320
331
|
_id: this.docId(patchObject)
|
|
321
332
|
});
|
|
322
333
|
await this.deleteAtLocation(patchObject, {
|
|
@@ -324,16 +335,15 @@ class GraffitiLocalDatabase {
|
|
|
324
335
|
});
|
|
325
336
|
return {
|
|
326
337
|
...originalObject,
|
|
327
|
-
tombstone: true,
|
|
328
338
|
lastModified: patchObject.lastModified
|
|
329
339
|
};
|
|
330
340
|
};
|
|
331
|
-
queryLastModifiedSuffixes(schema) {
|
|
341
|
+
queryLastModifiedSuffixes(schema, lastModified) {
|
|
332
342
|
let startKeySuffix = "";
|
|
333
343
|
let endKeySuffix = "\uFFFF";
|
|
334
344
|
if (typeof schema === "object" && schema.properties?.lastModified && typeof schema.properties.lastModified === "object") {
|
|
335
345
|
const lastModifiedSchema = schema.properties.lastModified;
|
|
336
|
-
const minimum = lastModifiedSchema.minimum;
|
|
346
|
+
const minimum = lastModified && lastModifiedSchema.minimum ? Math.max(lastModified, lastModifiedSchema.minimum) : lastModified ?? lastModifiedSchema.minimum;
|
|
337
347
|
const exclusiveMinimum = lastModifiedSchema.exclusiveMinimum;
|
|
338
348
|
let intMinimum;
|
|
339
349
|
if (exclusiveMinimum !== void 0) {
|
|
@@ -363,71 +373,150 @@ class GraffitiLocalDatabase {
|
|
|
363
373
|
endKeySuffix
|
|
364
374
|
};
|
|
365
375
|
}
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
for (const channel of channels) {
|
|
373
|
-
const keyPrefix = encodeURIComponent(channel) + "/";
|
|
374
|
-
const startkey = keyPrefix + startKeySuffix;
|
|
375
|
-
const endkey = keyPrefix + endKeySuffix;
|
|
376
|
-
const result = await (await this.db).query(
|
|
377
|
-
"indexes/objectsPerChannelAndLastModified",
|
|
378
|
-
{ startkey, endkey, include_docs: true }
|
|
379
|
-
);
|
|
380
|
-
for (const row of result.rows) {
|
|
381
|
-
const doc = row.doc;
|
|
382
|
-
if (!doc) continue;
|
|
383
|
-
const { _id, _rev, ...object } = doc;
|
|
384
|
-
if (processedIds.has(_id)) continue;
|
|
385
|
-
processedIds.add(_id);
|
|
386
|
-
if (!isActorAllowedGraffitiObject(doc, session)) continue;
|
|
387
|
-
maskGraffitiObject(object, channels, session);
|
|
388
|
-
if (validate(object)) {
|
|
389
|
-
await push({ value: object });
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
stop();
|
|
394
|
-
return {
|
|
395
|
-
tombstoneRetention: this.options.tombstoneRetention ?? DEFAULT_TOMBSTONE_RETENTION
|
|
396
|
-
};
|
|
376
|
+
async *streamObjects(index, startkey, endkey, validate, session, ifModifiedSince, channels, processedIds) {
|
|
377
|
+
const showTombstones = ifModifiedSince !== void 0;
|
|
378
|
+
const result = await (await this.db).query(index, {
|
|
379
|
+
startkey,
|
|
380
|
+
endkey,
|
|
381
|
+
include_docs: true
|
|
397
382
|
});
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
383
|
+
for (const row of result.rows) {
|
|
384
|
+
const doc = row.doc;
|
|
385
|
+
if (!doc) continue;
|
|
386
|
+
if (processedIds?.has(doc._id)) continue;
|
|
387
|
+
processedIds?.add(doc._id);
|
|
388
|
+
if (!showTombstones && doc.tombstone) continue;
|
|
389
|
+
const object = this.extractGraffitiObject(doc);
|
|
390
|
+
if (channels) {
|
|
391
|
+
if (!isActorAllowedGraffitiObject(object, session)) continue;
|
|
392
|
+
maskGraffitiObject(object, channels, session);
|
|
393
|
+
}
|
|
394
|
+
if (!validate(object)) continue;
|
|
395
|
+
yield doc.tombstone ? {
|
|
396
|
+
tombstone: true,
|
|
397
|
+
object: {
|
|
398
|
+
url: object.url,
|
|
399
|
+
lastModified: object.lastModified
|
|
400
|
+
}
|
|
401
|
+
} : { object };
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async *discoverMeta(args, ifModifiedSince) {
|
|
405
|
+
const [channels, schema, session] = args;
|
|
406
|
+
const validate = compileGraffitiObjectSchema(await this.ajv, schema);
|
|
407
|
+
const { startKeySuffix, endKeySuffix } = this.queryLastModifiedSuffixes(
|
|
408
|
+
schema,
|
|
409
|
+
ifModifiedSince
|
|
410
|
+
);
|
|
411
|
+
const processedIds = /* @__PURE__ */ new Set();
|
|
412
|
+
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
413
|
+
for (const channel of channels) {
|
|
414
|
+
const keyPrefix = encodeURIComponent(channel) + "/";
|
|
415
|
+
const startkey = keyPrefix + startKeySuffix;
|
|
416
|
+
const endkey = keyPrefix + endKeySuffix;
|
|
417
|
+
const iterator = this.streamObjects(
|
|
418
|
+
"indexes/objectsPerChannelAndLastModified",
|
|
419
|
+
startkey,
|
|
420
|
+
endkey,
|
|
421
|
+
validate,
|
|
422
|
+
session,
|
|
423
|
+
ifModifiedSince,
|
|
424
|
+
channels,
|
|
425
|
+
processedIds
|
|
426
|
+
);
|
|
427
|
+
for await (const result of iterator) yield result;
|
|
428
|
+
}
|
|
429
|
+
return startTime - LAST_MODIFIED_BUFFER;
|
|
430
|
+
}
|
|
431
|
+
async *recoverOrphansMeta(args, ifModifiedSince) {
|
|
432
|
+
const [schema, session] = args;
|
|
433
|
+
const { startKeySuffix, endKeySuffix } = this.queryLastModifiedSuffixes(
|
|
434
|
+
schema,
|
|
435
|
+
ifModifiedSince
|
|
436
|
+
);
|
|
402
437
|
const keyPrefix = encodeURIComponent(session.actor) + "/";
|
|
403
438
|
const startkey = keyPrefix + startKeySuffix;
|
|
404
439
|
const endkey = keyPrefix + endKeySuffix;
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
440
|
+
const validate = compileGraffitiObjectSchema(await this.ajv, schema);
|
|
441
|
+
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
442
|
+
const iterator = this.streamObjects(
|
|
443
|
+
"indexes/orphansPerActorAndLastModified",
|
|
444
|
+
startkey,
|
|
445
|
+
endkey,
|
|
446
|
+
validate,
|
|
447
|
+
session,
|
|
448
|
+
ifModifiedSince
|
|
449
|
+
);
|
|
450
|
+
for await (const result of iterator) yield result;
|
|
451
|
+
return startTime - LAST_MODIFIED_BUFFER;
|
|
452
|
+
}
|
|
453
|
+
async *discoverContinue(args, ifModifiedSince) {
|
|
454
|
+
const iterator = this.discoverMeta(args, ifModifiedSince);
|
|
455
|
+
while (true) {
|
|
456
|
+
const result = await iterator.next();
|
|
457
|
+
if (result.done) {
|
|
458
|
+
const ifModifiedSince2 = result.value;
|
|
459
|
+
return {
|
|
460
|
+
continue: () => this.discoverContinue(args, ifModifiedSince2),
|
|
461
|
+
cursor: ""
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
yield result.value;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
discover = (...args) => {
|
|
468
|
+
const iterator = this.discoverMeta(args);
|
|
469
|
+
const this_ = this;
|
|
470
|
+
return async function* () {
|
|
471
|
+
while (true) {
|
|
472
|
+
const result = await iterator.next();
|
|
473
|
+
if (result.done) {
|
|
474
|
+
return {
|
|
475
|
+
continue: () => this_.discoverContinue(args, result.value),
|
|
476
|
+
cursor: ""
|
|
477
|
+
};
|
|
418
478
|
}
|
|
479
|
+
if (result.value.tombstone) continue;
|
|
480
|
+
yield result.value;
|
|
419
481
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
482
|
+
}();
|
|
483
|
+
};
|
|
484
|
+
async *recoverContinue(args, ifModifiedSince) {
|
|
485
|
+
const iterator = this.recoverOrphansMeta(args, ifModifiedSince);
|
|
486
|
+
while (true) {
|
|
487
|
+
const result = await iterator.next();
|
|
488
|
+
if (result.done) {
|
|
489
|
+
const ifModifiedSince2 = result.value;
|
|
490
|
+
return {
|
|
491
|
+
continue: () => this.recoverContinue(args, ifModifiedSince2),
|
|
492
|
+
cursor: ""
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
yield result.value;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
recoverOrphans = (...args) => {
|
|
499
|
+
const iterator = this.recoverOrphansMeta(args);
|
|
500
|
+
const this_ = this;
|
|
501
|
+
return async function* () {
|
|
502
|
+
while (true) {
|
|
503
|
+
const result = await iterator.next();
|
|
504
|
+
if (result.done) {
|
|
505
|
+
return {
|
|
506
|
+
continue: () => this_.recoverContinue(args, result.value),
|
|
507
|
+
cursor: ""
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
if (result.value.tombstone) continue;
|
|
511
|
+
yield result.value;
|
|
512
|
+
}
|
|
513
|
+
}();
|
|
426
514
|
};
|
|
427
515
|
channelStats = (session) => {
|
|
428
|
-
const
|
|
516
|
+
const this_ = this;
|
|
517
|
+
return async function* () {
|
|
429
518
|
const keyPrefix = encodeURIComponent(session.actor) + "/";
|
|
430
|
-
const result = await (await
|
|
519
|
+
const result = await (await this_.db).query("indexes/channelStatsPerActor", {
|
|
431
520
|
startkey: keyPrefix,
|
|
432
521
|
endkey: keyPrefix + "\uFFFF",
|
|
433
522
|
reduce: true,
|
|
@@ -439,17 +528,18 @@ class GraffitiLocalDatabase {
|
|
|
439
528
|
const { count, max: lastModified } = row.value;
|
|
440
529
|
if (typeof count !== "number" || typeof lastModified !== "number")
|
|
441
530
|
continue;
|
|
442
|
-
|
|
531
|
+
yield {
|
|
443
532
|
value: {
|
|
444
533
|
channel: decodeURIComponent(channelEncoded),
|
|
445
534
|
count,
|
|
446
535
|
lastModified
|
|
447
536
|
}
|
|
448
|
-
}
|
|
537
|
+
};
|
|
449
538
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
539
|
+
}();
|
|
540
|
+
};
|
|
541
|
+
continueObjectStream = (cursor, session) => {
|
|
542
|
+
throw new GraffitiErrorNotFound("Cursor not found");
|
|
453
543
|
};
|
|
454
544
|
}
|
|
455
545
|
export {
|