@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.
@@ -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
- unpackLocationOrUri: () => unpackLocationOrUri
26
+ unpackObjectUrl: () => unpackObjectUrl
28
27
  });
29
28
  module.exports = __toCommonJS(utilities_exports);
30
29
  var import_api = require("@graffiti-garden/api");
31
- function unpackLocationOrUri(locationOrUri) {
32
- return typeof locationOrUri === "string" ? locationOrUri : locationOrUri.url;
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 unpackLocationOrUri(locationOrUri: GraffitiObjectUrl | string) {\n return typeof locationOrUri === \"string\" ? locationOrUri : locationOrUri.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 isObjectNewer(\n left: GraffitiObjectBase,\n right: GraffitiObjectBase,\n) {\n return (\n left.lastModified > right.lastModified ||\n (left.lastModified === right.lastModified &&\n !left.tombstone &&\n right.tombstone)\n );\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;AAAA,iBAKO;AAYA,SAAS,oBAAoB,eAA2C;AAC7E,SAAO,OAAO,kBAAkB,WAAW,gBAAgB,cAAc;AAC3E;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,cACd,MACA,OACA;AACA,SACE,KAAK,eAAe,MAAM,gBACzB,KAAK,iBAAiB,MAAM,gBAC3B,CAAC,KAAK,aACN,MAAM;AAEZ;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;",
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
  }
@@ -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 URIs put in the system. Defaults to `graffiti:local`.
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 URIs, i.e.
23
- * URIs that are *not* prefixed with the origin or not generated
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
- allowSettingArbitraryUris?: boolean;
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 Pick<Graffiti, "get" | "put" | "patch" | "delete" | "discover" | "recoverOrphans" | "channelStats"> {
55
- protected db_: Promise<PouchDB.Database<GraffitiObjectBase>> | undefined;
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<GraffitiObjectBase>>;
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(locationOrUri: GraffitiObjectUrl | string): Promise<PouchDB.Core.ExistingDocument<GraffitiObjectBase & PouchDB.Core.AllDocsMeta>[]>;
65
- protected docId(location: GraffitiObjectUrl): string;
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(locationOrUri: GraffitiObjectUrl | string, options?: {
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
@@ -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,EAChB,MAAM,sBAAsB,CAAC;AAiB9B,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAC;IAC7D;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;;;OAMG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,GAAG,CAAC,EAAE,GAAG,CAAC;CACX;AAKD;;;GAGG;AACH,qBAAa,qBACX,YACE,IAAI,CACF,QAAQ,EACN,KAAK,GACL,KAAK,GACL,OAAO,GACP,QAAQ,GACR,UAAU,GACV,gBAAgB,GAChB,cAAc,CACjB;IAEH,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,GAAG,SAAS,CAAC;IACzE,SAAS,CAAC,WAAW,EAAE,OAAO,CAAC,OAAO,UAAU,CAAC,GAAG,SAAS,CAAC;IAC9D,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACzC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IACjD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAElC,IAAI,EAAE,kDA+EL;IAED,IAAI,UAAU,+BAQb;IAED,IAAI,GAAG,iBAUN;gBAEW,OAAO,CAAC,EAAE,oBAAoB;cAQ1B,iBAAiB,CAAC,aAAa,EAAE,iBAAiB,GAAG,MAAM;IAuB3E,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,iBAAiB;IAI3C,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CA6BlB;IAEF;;;;;;;OAOG;cACa,gBAAgB,CAC9B,aAAa,EAAE,iBAAiB,GAAG,MAAM,EACzC,OAAO,GAAE;QACP,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,OAAO,CAAC,EAAE,eAAe,CAAC;KAG3B;IAsFH,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CASxB;IAEF,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAmElB;IAEF,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CA8EtB;IAEF,SAAS,CAAC,yBAAyB,CAAC,MAAM,EAAE,UAAU;;;;IA+CtD,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAyD5B;IAEF,cAAc,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAwCxC;IAEF,YAAY,EAAE,QAAQ,CAAC,cAAc,CAAC,CA8BpC;CACH"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"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"}
@@ -9,13 +9,11 @@ import {
9
9
  applyGraffitiPatch,
10
10
  maskGraffitiObject,
11
11
  isActorAllowedGraffitiObject,
12
- isObjectNewer,
13
12
  compileGraffitiObjectSchema,
14
- unpackLocationOrUri
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(locationOrUri) {
107
- const uri = unpackLocationOrUri(locationOrUri) + "/";
115
+ async allDocsAtLocation(objectUrl) {
116
+ const url = unpackObjectUrl(objectUrl) + "/";
108
117
  const results = await (await this.db).allDocs({
109
- startkey: uri,
110
- endkey: uri + "\uFFFF",
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(location) {
121
- return location.url + "/" + randomBase64();
129
+ docId(objectUrl) {
130
+ return objectUrl.url + "/" + randomBase64();
122
131
  }
123
132
  get = async (...args) => {
124
- const [locationOrUri, schema, session] = args;
125
- const docsAll = await this.allDocsAtLocation(locationOrUri);
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((a, b) => isObjectNewer(a, b) ? a : b);
134
- const { _id, _rev, _conflicts, _attachments, ...object } = doc;
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(locationOrUri, options = {
166
+ async deleteAtLocation(url, options = {
151
167
  keepLatest: false
152
168
  }) {
153
- const docsAtLocationAll = await this.allDocsAtLocation(locationOrUri);
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
- ...object,
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 [locationOrUri, session] = args;
212
- const deletedObject = await this.deleteAtLocation(locationOrUri, {
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.allowSettingArbitraryUris) {
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, locationOrUri, session] = args;
292
+ const [patch, url, session] = args;
279
293
  let originalObject;
280
294
  try {
281
- originalObject = await this.get(locationOrUri, {}, session);
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
- discover = (...args) => {
367
- const [channels, schema, session] = args;
368
- const { startKeySuffix, endKeySuffix } = this.queryLastModifiedSuffixes(schema);
369
- const repeater = new Repeater(async (push, stop) => {
370
- const validate = compileGraffitiObjectSchema(await this.ajv, schema);
371
- const processedIds = /* @__PURE__ */ new Set();
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
- return repeater;
399
- };
400
- recoverOrphans = (schema, session) => {
401
- const { startKeySuffix, endKeySuffix } = this.queryLastModifiedSuffixes(schema);
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 repeater = new Repeater(async (push, stop) => {
406
- const validate = compileGraffitiObjectSchema(await this.ajv, schema);
407
- const result = await (await this.db).query("indexes/orphansPerActorAndLastModified", {
408
- startkey,
409
- endkey,
410
- include_docs: true
411
- });
412
- for (const row of result.rows) {
413
- const doc = row.doc;
414
- if (!doc) continue;
415
- const { _id, _rev, ...object } = doc;
416
- if (validate(object)) {
417
- await push({ value: object });
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
- stop();
421
- return {
422
- tombstoneRetention: this.options.tombstoneRetention ?? DEFAULT_TOMBSTONE_RETENTION
423
- };
424
- });
425
- return repeater;
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 repeater = new Repeater(async (push, stop) => {
516
+ const this_ = this;
517
+ return async function* () {
429
518
  const keyPrefix = encodeURIComponent(session.actor) + "/";
430
- const result = await (await this.db).query("indexes/channelStatsPerActor", {
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
- await push({
531
+ yield {
443
532
  value: {
444
533
  channel: decodeURIComponent(channelEncoded),
445
534
  count,
446
535
  lastModified
447
536
  }
448
- });
537
+ };
449
538
  }
450
- stop();
451
- });
452
- return repeater;
539
+ }();
540
+ };
541
+ continueObjectStream = (cursor, session) => {
542
+ throw new GraffitiErrorNotFound("Cursor not found");
453
543
  };
454
544
  }
455
545
  export {