@graffiti-garden/wrapper-vue 0.0.3 → 0.0.4

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 CHANGED
@@ -1,5 +1,65 @@
1
1
  # Graffiti Wrapper for Vue.js
2
2
 
3
- ## TODO
3
+ This is a wrapper around the [Graffiti API](https://api.graffiti.garden/classes/Graffiti.html)
4
+ as [Vue.js](https://vuejs.org/) plugin.
5
+ It provides the following features:
6
+ - A global Graffiti instance via the `$graffiti`
7
+ [global property](https://vuejs.org/api/application.html#app-config-globalproperties)
8
+ in templates and the Options API, or the `useGraffiti` [composable](https://vuejs.org/guide/reusability/composables.html)
9
+ in the Composition API.
10
+ - A `useGraffitiDiscover` [composable](https://vuejs.org/guide/reusability/composables.html)
11
+ and a `GraffitiDiscover` [renderless component](https://vuejs.org/guide/components/slots#renderless-components)
12
+ that produce a reactive arrays of objects from the results of [`discover`](https://api.graffiti.garden/classes/Graffiti.html#discover).
13
+ - A reactive [`GraffitiSession`](https://api.graffiti.garden/interfaces/GraffitiSession.html) object
14
+ that holds the user's most recent log-in state announced
15
+ from [`sessionEvents`](https://api.graffiti.garden/classes/Graffiti.html#sessionevents).
16
+ It is accessible via the `$graffitiSession` global property in templates and the Options API,
17
+ or the `useGraffitiSession` composable in the Composition API.
4
18
 
5
- - When repolling use `lastModified`.
19
+ ## Installation
20
+
21
+ You must install this package along with Vue.js and an implementation of the Graffiti API.
22
+ In this example, we will use the [PouchDB implementation](https://github.com/graffiti-garden/implementation-pouchdb)
23
+ of the Graffiti API, but any other would be similar.
24
+ In node.js, simply install them with npm:
25
+
26
+ ```bash
27
+ npm install vue
28
+ npm install @graffiti-garden/implementation-pouchdb
29
+ npm install @graffiti-garden/wrapper-vue
30
+ ```
31
+
32
+ In the browser, you can use a CDN like
33
+ [jsDelivr](https://www.jsdelivr.com/).
34
+ Add an import map the the `<head>` of your HTML file:
35
+
36
+ ```html
37
+ <head>
38
+ <script type="importmap">
39
+ {
40
+ "imports": {
41
+ "vue": "https://cdn.jsdelivr.net/npm/vue/dist/vue.esm-browser.js",
42
+ "@graffiti-garden/implementation-pouchdb": "https://cdn.jsdelivr.net/npm/@graffiti-garden/implementation-pouchdb/dist/index.js",
43
+ "@graffiti-garden/wrapper-vue": "https://cdn.jsdelivr.net/npm/@graffiti-garden/wrapper-vue/dist/plugin.js"
44
+ }
45
+ }
46
+ </script>
47
+ </head>
48
+ ```
49
+
50
+ In ether case install the plugin in your Vue app as follows:
51
+
52
+ ```typescript
53
+ import { createApp } from "vue";
54
+ import { GraffitiPouchDB } from "@graffiti-garden/implementation-pouchdb";
55
+ import { GraffitiPlugin } from "@graffiti-garden/wrapper-vue";
56
+
57
+ createApp({})
58
+ .use(GraffitiPlugin, {
59
+ useGraffiti: () => new GraffitiPouchDB(),
60
+ });
61
+ .mount("#app");
62
+ ```
63
+
64
+ See full examples of both with both methods in the [examples](./examples) directory,
65
+ which are live [here](https://graffiti.garden/wrapper-vue/).
package/dist/plugin.js CHANGED
@@ -1,124 +1,139 @@
1
- import { inject as b, ref as y, watch as j, onScopeDispose as L, toValue as h, defineComponent as R, toRef as G, renderSlot as $, unref as w } from "vue";
2
- const M = Symbol(), P = Symbol();
3
- function x() {
4
- const o = b(M);
5
- if (!o)
1
+ import { inject as x, ref as S, watch as F, onScopeDispose as K, toValue as E, defineComponent as N, toRef as b, renderSlot as V, unref as R } from "vue";
2
+ const L = Symbol(), j = Symbol();
3
+ function z() {
4
+ const e = x(L);
5
+ if (!e)
6
6
  throw new Error("No Graffiti instance provided");
7
- return o;
7
+ return e;
8
8
  }
9
- function K() {
10
- const o = b(P);
11
- if (!o)
9
+ function B() {
10
+ const e = x(j);
11
+ if (!e)
12
12
  throw new Error("No Graffiti session provided");
13
- return o;
13
+ return e;
14
14
  }
15
- function N(o, f, t) {
16
- const s = x(), n = K(), r = y([]), c = /* @__PURE__ */ new Map();
17
- function d() {
18
- r.value = Array.from(c.values()).reduce((e, u) => {
19
- const { tombstone: l, value: _ } = u;
20
- return l || e.push({ ...u, tombstone: l, value: _ }), e;
15
+ const C = 100;
16
+ function H(e, a, l) {
17
+ const r = z(), s = B(), n = S([]), p = /* @__PURE__ */ new Map();
18
+ function P() {
19
+ n.value = Array.from(p.values()).reduce((t, i) => {
20
+ const { tombstone: o, value: m } = i;
21
+ return o || t.push({ ...i, tombstone: o, value: m }), t;
21
22
  }, []);
22
23
  }
23
- function E(e) {
24
- const u = s.objectToUri(e), l = c.get(u);
25
- l && (l.lastModified > e.lastModified || l.lastModified === e.lastModified && !l.tombstone) || c.set(u, e);
24
+ let v;
25
+ function D(t) {
26
+ const i = r.objectToUri(t), o = p.get(i);
27
+ o && (o.lastModified > t.lastModified || o.lastModified === t.lastModified && !o.tombstone) || (p.set(i, t), v || (v = setTimeout(() => {
28
+ P(), v = void 0;
29
+ }, C)));
26
30
  }
27
- const v = () => h(o), m = () => h(f), p = () => h(t) ?? (n == null ? void 0 : n.value);
28
- let i;
29
- async function D() {
30
- i == null || i.return(), i = s.synchronize(
31
- v(),
32
- m(),
33
- p()
31
+ const M = () => E(e), w = () => E(a), y = () => E(l) ?? (s == null ? void 0 : s.value);
32
+ let f;
33
+ async function $() {
34
+ f == null || f.return(), f = r.synchronize(
35
+ M(),
36
+ w(),
37
+ y()
34
38
  );
35
- for await (const e of i) {
36
- if (e.error) {
37
- console.error(e.error);
39
+ for await (const t of f) {
40
+ if (t.error) {
41
+ console.error(t.error);
38
42
  continue;
39
43
  }
40
- E(e.value), d();
44
+ D(t.value);
41
45
  }
42
46
  }
43
- const g = y(!1);
44
- let a;
45
- async function S() {
46
- a == null || a.return(), g.value = !0;
47
+ const G = S(!1);
48
+ let u, h, d;
49
+ async function T() {
50
+ var _;
51
+ const t = (/* @__PURE__ */ new Date()).getTime(), i = { ...w() };
52
+ u && h && h > t && (!i.properties || !("lastModified" in i.properties) || !("minimum" in i.properties.lastModified)) && (console.log(typeof u), i.properties = {
53
+ ...i.properties,
54
+ lastModified: {
55
+ ...(_ = i.properties) == null ? void 0 : _.lastModified,
56
+ minimum: u
57
+ }
58
+ });
59
+ let o;
47
60
  try {
48
- a = s.discover(
49
- v(),
50
- m(),
51
- p()
52
- );
53
- } catch (e) {
54
- console.error(e), d(), g.value = !1;
61
+ o = r.discover(M(), i, y());
62
+ } catch (g) {
63
+ console.error(g);
55
64
  return;
56
65
  }
57
- for await (const e of a) {
58
- if (e.error) {
59
- console.error(e.error);
66
+ d == null || d.return({ tombstoneRetention: 0 }), d = o, G.value = !0;
67
+ let m = u, c = await o.next();
68
+ for (; !c.done; ) {
69
+ if (c.value.error) {
70
+ console.error(c.value.error), c = await o.next();
60
71
  continue;
61
72
  }
62
- E(e.value), d();
73
+ const g = c.value.value;
74
+ (!m || g.lastModified > m) && (m = g.lastModified), D(g), c = await o.next();
63
75
  }
64
- g.value = !1;
76
+ if (d !== o) return;
77
+ d = void 0, G.value = !1;
78
+ const { tombstoneRetention: A } = c.value;
79
+ u = m, h = t + A;
65
80
  }
66
- return j(
67
- [v, m, p],
81
+ return F(
82
+ [M, w, y],
68
83
  () => {
69
- c.clear(), d(), S(), D();
84
+ p.clear(), u = void 0, h = void 0, clearTimeout(v), P(), T(), $();
70
85
  },
71
86
  {
72
87
  immediate: !0
73
88
  }
74
- ), L(() => i == null ? void 0 : i.return()), {
75
- results: r,
76
- poll: S,
77
- isPolling: g
89
+ ), K(() => f == null ? void 0 : f.return()), {
90
+ results: n,
91
+ poll: T,
92
+ isPolling: G
78
93
  };
79
94
  }
80
- const V = /* @__PURE__ */ R({
95
+ const O = /* @__PURE__ */ N({
81
96
  __name: "Discover",
82
97
  props: {
83
98
  channels: {},
84
99
  schema: {},
85
100
  session: {}
86
101
  },
87
- setup(o) {
88
- const f = o, { results: t, poll: s, isPolling: n } = N(
89
- G(f, "channels"),
90
- G(f, "schema"),
91
- G(f, "session")
102
+ setup(e) {
103
+ const a = e, { results: l, poll: r, isPolling: s } = H(
104
+ b(a, "channels"),
105
+ b(a, "schema"),
106
+ b(a, "session")
92
107
  );
93
- return (r, c) => $(r.$slots, "default", {
94
- results: w(t),
95
- poll: w(s),
96
- isPolling: w(n)
108
+ return (n, p) => V(n.$slots, "default", {
109
+ results: R(l),
110
+ poll: R(r),
111
+ isPolling: R(s)
97
112
  });
98
113
  }
99
- }), A = {
100
- install(o, f) {
101
- const t = f.useGraffiti(), s = y(void 0);
102
- t.sessionEvents.addEventListener("login", (n) => {
103
- const r = n.detail;
104
- if (r.error) {
105
- console.error("Error logging in:"), console.error(r.error);
114
+ }), k = {
115
+ install(e, a) {
116
+ const l = a.useGraffiti(), r = S(void 0);
117
+ l.sessionEvents.addEventListener("login", (s) => {
118
+ const n = s.detail;
119
+ if (n.error) {
120
+ console.error("Error logging in:"), console.error(n.error);
106
121
  return;
107
122
  } else
108
- s.value = r.session;
109
- }), t.sessionEvents.addEventListener("logout", (n) => {
110
- const r = n.detail;
111
- r.error ? (console.error("Error logging out:"), console.error(r.error)) : s.value = void 0;
112
- }), o.provide(M, t), o.provide(P, s), o.component("GraffitiDiscover", V), o.config.globalProperties.$graffiti = t, o.config.globalProperties.$graffitiSession = s;
123
+ r.value = n.session;
124
+ }), l.sessionEvents.addEventListener("logout", (s) => {
125
+ const n = s.detail;
126
+ n.error ? (console.error("Error logging out:"), console.error(n.error)) : r.value = void 0;
127
+ }), e.provide(L, l), e.provide(j, r), e.component("GraffitiDiscover", O), e.config.globalProperties.$graffiti = l, e.config.globalProperties.$graffitiSession = r;
113
128
  }
114
129
  };
115
130
  export {
116
- V as GraffitiDiscover,
117
- A as GraffitiPlugin,
118
- M as graffitiInjectKey,
119
- P as graffitiSessionInjectKey,
120
- x as useGraffiti,
121
- N as useGraffitiDiscover,
122
- K as useGraffitiSession
131
+ O as GraffitiDiscover,
132
+ k as GraffitiPlugin,
133
+ L as graffitiInjectKey,
134
+ j as graffitiSessionInjectKey,
135
+ z as useGraffiti,
136
+ H as useGraffitiDiscover,
137
+ B as useGraffitiSession
123
138
  };
124
139
  //# sourceMappingURL=plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["../src/injections.ts","../src/composables.ts","../src/Discover.vue","../src/plugin.ts"],"sourcesContent":["import { inject } from \"vue\";\nimport type { InjectionKey, Ref } from \"vue\";\nimport type { Graffiti, GraffitiSession } from \"@graffiti-garden/api\";\n\nexport const graffitiInjectKey = Symbol() as InjectionKey<Graffiti>;\nexport const graffitiSessionInjectKey = Symbol() as InjectionKey<\n Ref<GraffitiSession | undefined>\n>;\n\nexport function useGraffiti() {\n const graffiti = inject(graffitiInjectKey);\n if (!graffiti) {\n throw new Error(\"No Graffiti instance provided\");\n }\n return graffiti;\n}\n\nexport function useGraffitiSession() {\n const session = inject(graffitiSessionInjectKey);\n if (!session) {\n throw new Error(\"No Graffiti session provided\");\n }\n return session;\n}\n","import {\n onScopeDispose,\n ref,\n toValue,\n watch,\n type MaybeRefOrGetter,\n} from \"vue\";\nimport type {\n GraffitiObject,\n GraffitiSession,\n JSONSchema4,\n} from \"@graffiti-garden/api\";\nimport { useGraffiti, useGraffitiSession } from \"./injections\";\n\n/**\n * A reactive version of the [`Graffiti.discover`](https://api.graffiti.garden/classes/Graffiti.html#discover)\n * method.\n *\n * @returns An object containing\n * - `results`: a reactive array of Graffiti objects\n * - `poll`: a method to poll for new results\n * - `isPolling`: a boolean ref indicating if the poll is currently running\n */\nexport function useGraffitiDiscover<Schema extends JSONSchema4>(\n /**\n * A list of channels to discover objects from.\n * It may be a Vue ref or getter.\n */\n channels: MaybeRefOrGetter<string[]>,\n /**\n * A [JSON Schema](https://json-schema.org/) object describing the schema\n * of the objects to discover. All other objects will be filtered out\n * and the output will be typed as `GraffitiObject<Schema>`.\n */\n schema: MaybeRefOrGetter<Schema>,\n /**\n * A Graffiti session object. If not provided, the\n * global plugin session will be used.\n */\n session?: MaybeRefOrGetter<GraffitiSession | undefined>,\n) {\n const graffiti = useGraffiti();\n const sessionInjected = useGraffitiSession();\n\n const results = ref<(GraffitiObject<Schema> & { tombstone: false })[]>([]);\n const resultsRaw = new Map<string, GraffitiObject<Schema>>();\n function flattenResults() {\n results.value = Array.from(resultsRaw.values()).reduce<\n (GraffitiObject<Schema> & { tombstone: false })[]\n >((acc, o) => {\n const { tombstone, value } = o;\n if (!tombstone) {\n acc.push({ ...o, tombstone, value });\n }\n return acc;\n }, []);\n }\n\n function onValue(value: GraffitiObject<Schema>) {\n const url = graffiti.objectToUri(value);\n const existing = resultsRaw.get(url);\n if (\n existing &&\n (existing.lastModified > value.lastModified ||\n (existing.lastModified === value.lastModified && !existing.tombstone))\n ) {\n return;\n }\n resultsRaw.set(url, value);\n }\n\n const channelsGetter = () => toValue(channels);\n const schemaGetter = () => toValue(schema);\n const sessionGetter = () => toValue(session) ?? sessionInjected?.value;\n\n let localIterator:\n | ReturnType<typeof graffiti.synchronize<Schema>>\n | undefined = undefined;\n async function pollLocalModifications() {\n localIterator?.return();\n localIterator = graffiti.synchronize(\n channelsGetter(),\n schemaGetter(),\n sessionGetter(),\n );\n for await (const value of localIterator) {\n if (value.error) {\n console.error(value.error);\n continue;\n }\n onValue(value.value);\n flattenResults();\n }\n }\n\n const isPolling = ref(false);\n let iterator: ReturnType<typeof graffiti.discover<Schema>> | undefined =\n undefined;\n async function poll() {\n iterator?.return();\n isPolling.value = true;\n\n try {\n iterator = graffiti.discover(\n channelsGetter(),\n schemaGetter(),\n sessionGetter(),\n );\n } catch (e) {\n console.error(e);\n flattenResults();\n isPolling.value = false;\n return;\n }\n\n for await (const result of iterator) {\n if (result.error) {\n console.error(result.error);\n continue;\n }\n onValue(result.value);\n flattenResults();\n }\n\n isPolling.value = false;\n }\n\n watch(\n [channelsGetter, schemaGetter, sessionGetter],\n () => {\n resultsRaw.clear();\n flattenResults();\n poll();\n pollLocalModifications();\n },\n\n {\n immediate: true,\n },\n );\n onScopeDispose(() => localIterator?.return());\n\n return {\n results,\n poll,\n isPolling,\n };\n}\n","<script setup lang=\"ts\" generic=\"Schema extends JSONSchema4\">\nimport { toRef } from \"vue\";\nimport type { GraffitiSession, JSONSchema4 } from \"@graffiti-garden/api\";\nimport { useGraffitiDiscover } from \"./composables\";\n\nconst props = defineProps<{\n channels: string[];\n schema: Schema;\n session?: GraffitiSession;\n}>();\n\nconst { results, poll, isPolling } = useGraffitiDiscover<Schema>(\n toRef(props, \"channels\"),\n toRef(props, \"schema\"),\n toRef(props, \"session\"),\n);\n</script>\n\n<template>\n <slot :results=\"results\" :poll=\"poll\" :isPolling=\"isPolling\"></slot>\n</template>\n","import type { App, Plugin, Ref } from \"vue\";\nimport { ref } from \"vue\";\nimport Discover from \"./Discover.vue\";\nimport type {\n GraffitiFactory,\n Graffiti,\n GraffitiSession,\n GraffitiLoginEvent,\n GraffitiLogoutEvent,\n} from \"@graffiti-garden/api\";\nimport { graffitiInjectKey, graffitiSessionInjectKey } from \"./injections\";\n\ndeclare module \"vue\" {\n export interface ComponentCustomProperties {\n $graffiti: Graffiti;\n $graffitiSession: Ref<GraffitiSession | undefined>;\n }\n\n export interface GlobalComponents {\n GraffitiDiscover: typeof Discover;\n }\n}\n\nexport interface GraffitiPluginOptions {\n useGraffiti: GraffitiFactory;\n}\n\nexport const GraffitiPlugin: Plugin<GraffitiPluginOptions> = {\n install(app: App, options: GraffitiPluginOptions) {\n const graffiti = options.useGraffiti();\n const graffitiSession = ref<GraffitiSession | undefined>(undefined);\n graffiti.sessionEvents.addEventListener(\"login\", (evt) => {\n const detail = (evt as GraffitiLoginEvent).detail;\n if (detail.error) {\n console.error(\"Error logging in:\");\n console.error(detail.error);\n return;\n } else {\n graffitiSession.value = detail.session;\n }\n });\n graffiti.sessionEvents.addEventListener(\"logout\", (evt) => {\n const detail = (evt as GraffitiLogoutEvent).detail;\n if (detail.error) {\n console.error(\"Error logging out:\");\n console.error(detail.error);\n } else {\n graffitiSession.value = undefined;\n }\n });\n\n app.provide(graffitiInjectKey, graffiti);\n app.provide(graffitiSessionInjectKey, graffitiSession);\n\n app.component(\"GraffitiDiscover\", Discover);\n app.config.globalProperties.$graffiti = graffiti;\n app.config.globalProperties.$graffitiSession = graffitiSession;\n },\n};\n\nexport * from \"./composables\";\nexport * from \"./injections\";\nexport { Discover as GraffitiDiscover };\n"],"names":["graffitiInjectKey","graffitiSessionInjectKey","useGraffiti","graffiti","inject","useGraffitiSession","session","useGraffitiDiscover","channels","schema","sessionInjected","results","ref","resultsRaw","flattenResults","acc","o","tombstone","value","onValue","url","existing","channelsGetter","toValue","schemaGetter","sessionGetter","localIterator","pollLocalModifications","isPolling","iterator","poll","result","watch","onScopeDispose","props","__props","toRef","GraffitiPlugin","app","options","graffitiSession","evt","detail","Discover"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sources":["../src/injections.ts","../src/composables.ts","../src/Discover.vue","../src/plugin.ts"],"sourcesContent":["import { inject } from \"vue\";\nimport type { InjectionKey, Ref } from \"vue\";\nimport type { Graffiti, GraffitiSession } from \"@graffiti-garden/api\";\n\nexport const graffitiInjectKey = Symbol() as InjectionKey<Graffiti>;\nexport const graffitiSessionInjectKey = Symbol() as InjectionKey<\n Ref<GraffitiSession | undefined>\n>;\n\nexport function useGraffiti() {\n const graffiti = inject(graffitiInjectKey);\n if (!graffiti) {\n throw new Error(\"No Graffiti instance provided\");\n }\n return graffiti;\n}\n\nexport function useGraffitiSession() {\n const session = inject(graffitiSessionInjectKey);\n if (!session) {\n throw new Error(\"No Graffiti session provided\");\n }\n return session;\n}\n","import {\n onScopeDispose,\n ref,\n toValue,\n watch,\n type MaybeRefOrGetter,\n} from \"vue\";\nimport type {\n GraffitiObject,\n GraffitiSession,\n JSONSchema4,\n} from \"@graffiti-garden/api\";\nimport { useGraffiti, useGraffitiSession } from \"./injections\";\n\nconst REFRESH_RATE = 100; // milliseconds\n\n/**\n * A reactive version of the [`Graffiti.discover`](https://api.graffiti.garden/classes/Graffiti.html#discover)\n * method. Its arguments are the same, but now they can be\n * reactive Vue refs or getters. As they change the output will\n * automatically update.\n *\n * Rather than returning a stream of Graffiti objects, this\n * function returns a reactive array of objects. It also\n * provides a method to poll for new results and a boolean\n * ref indicating if the poll is currently running.\n *\n * @returns An object containing\n * - `results`: a reactive array of Graffiti objects\n * - `poll`: a method to poll for new results\n * - `isPolling`: a boolean ref indicating if the poll is currently running\n */\nexport function useGraffitiDiscover<Schema extends JSONSchema4>(\n /**\n * A list of channels to discover objects from.\n * It may be a Vue ref or getter.\n */\n channels: MaybeRefOrGetter<string[]>,\n /**\n * A [JSON Schema](https://json-schema.org/) object describing the schema\n * of the objects to discover. All other objects will be filtered out\n * and the output will be typed as `GraffitiObject<Schema>`.\n */\n schema: MaybeRefOrGetter<Schema>,\n /**\n * A Graffiti session object. If not provided, the\n * global plugin session will be used.\n */\n session?: MaybeRefOrGetter<GraffitiSession | undefined>,\n) {\n const graffiti = useGraffiti();\n const sessionInjected = useGraffitiSession();\n\n const results = ref<(GraffitiObject<Schema> & { tombstone: false })[]>([]);\n const resultsRaw = new Map<string, GraffitiObject<Schema>>();\n function flattenResults() {\n results.value = Array.from(resultsRaw.values()).reduce<\n (GraffitiObject<Schema> & { tombstone: false })[]\n >((acc, o) => {\n const { tombstone, value } = o;\n if (!tombstone) {\n acc.push({ ...o, tombstone, value });\n }\n return acc;\n }, []);\n }\n\n let batchFlattenTimer: ReturnType<typeof setTimeout> | undefined = undefined;\n function onValue(value: GraffitiObject<Schema>) {\n const url = graffiti.objectToUri(value);\n const existing = resultsRaw.get(url);\n if (\n existing &&\n (existing.lastModified > value.lastModified ||\n (existing.lastModified === value.lastModified && !existing.tombstone))\n ) {\n return;\n }\n resultsRaw.set(url, value);\n\n // Don't flatten the results all at once,\n // because we may get a lot of results\n // and we don't want the interface to\n // freeze up\n if (!batchFlattenTimer) {\n batchFlattenTimer = setTimeout(() => {\n flattenResults();\n batchFlattenTimer = undefined;\n }, REFRESH_RATE);\n }\n }\n\n const channelsGetter = () => toValue(channels);\n const schemaGetter = () => toValue(schema);\n const sessionGetter = () => toValue(session) ?? sessionInjected?.value;\n\n let localIterator:\n | ReturnType<typeof graffiti.synchronize<Schema>>\n | undefined = undefined;\n async function pollLocalModifications() {\n localIterator?.return();\n localIterator = graffiti.synchronize(\n channelsGetter(),\n schemaGetter(),\n sessionGetter(),\n );\n for await (const value of localIterator) {\n if (value.error) {\n console.error(value.error);\n continue;\n }\n onValue(value.value);\n }\n }\n\n const isPolling = ref(false);\n let lastModified: number | undefined = undefined;\n let fullRepollBy: number | undefined = undefined;\n let iterator: ReturnType<typeof graffiti.discover<Schema>> | undefined =\n undefined;\n async function poll() {\n const startOfPoll = new Date().getTime();\n\n // Add a query for lastModified if it's not in the schema\n const schema = { ...schemaGetter() };\n if (\n lastModified &&\n fullRepollBy &&\n fullRepollBy > startOfPoll &&\n (!schema.properties ||\n !(\"lastModified\" in schema.properties) ||\n !(\"minimum\" in schema.properties.lastModified))\n ) {\n console.log(typeof lastModified);\n schema.properties = {\n ...schema.properties,\n lastModified: {\n ...schema.properties?.lastModified,\n minimum: lastModified,\n },\n };\n }\n\n let myIterator: ReturnType<typeof graffiti.discover<Schema>>;\n try {\n myIterator = graffiti.discover(channelsGetter(), schema, sessionGetter());\n } catch (e) {\n console.error(e);\n return;\n }\n\n // Kill the previous iterator if its\n // still running and claim its spot\n iterator?.return({ tombstoneRetention: 0 });\n iterator = myIterator;\n isPolling.value = true;\n\n // Keep track of the latest lastModified value\n // while streaming results\n let myLastModified = lastModified;\n let result = await myIterator.next();\n while (!result.done) {\n if (result.value.error) {\n console.error(result.value.error);\n result = await myIterator.next();\n continue;\n }\n\n const value = result.value.value;\n if (!myLastModified || value.lastModified > myLastModified) {\n myLastModified = value.lastModified;\n }\n\n onValue(value);\n\n result = await myIterator.next();\n }\n\n // Make sure we're still the current iterator\n if (iterator !== myIterator) return;\n\n // We've successfully polled all results\n // without getting overridden\n iterator = undefined;\n isPolling.value = false;\n\n // Only now do we update the cache parameters\n // because the results may have appeared out\n // of order\n const { tombstoneRetention } = result.value;\n lastModified = myLastModified;\n fullRepollBy = startOfPoll + tombstoneRetention;\n }\n\n watch(\n [channelsGetter, schemaGetter, sessionGetter],\n () => {\n // Clear all the state\n resultsRaw.clear();\n lastModified = undefined;\n fullRepollBy = undefined;\n clearTimeout(batchFlattenTimer);\n flattenResults();\n\n // Repoll\n poll();\n pollLocalModifications();\n },\n {\n immediate: true,\n },\n );\n onScopeDispose(() => localIterator?.return());\n\n return {\n results,\n poll,\n isPolling,\n };\n}\n","<script setup lang=\"ts\" generic=\"Schema extends JSONSchema4\">\nimport { toRef } from \"vue\";\nimport type { GraffitiSession, JSONSchema4 } from \"@graffiti-garden/api\";\nimport { useGraffitiDiscover } from \"./composables\";\n\nconst props = defineProps<{\n channels: string[];\n schema: Schema;\n session?: GraffitiSession;\n}>();\n\nconst { results, poll, isPolling } = useGraffitiDiscover<Schema>(\n toRef(props, \"channels\"),\n toRef(props, \"schema\"),\n toRef(props, \"session\"),\n);\n</script>\n\n<template>\n <slot :results=\"results\" :poll=\"poll\" :isPolling=\"isPolling\"></slot>\n</template>\n","import type { App, Plugin, Ref } from \"vue\";\nimport { ref } from \"vue\";\nimport Discover from \"./Discover.vue\";\nimport type {\n GraffitiFactory,\n Graffiti,\n GraffitiSession,\n GraffitiLoginEvent,\n GraffitiLogoutEvent,\n} from \"@graffiti-garden/api\";\nimport { graffitiInjectKey, graffitiSessionInjectKey } from \"./injections\";\n\ndeclare module \"vue\" {\n export interface ComponentCustomProperties {\n $graffiti: Graffiti;\n $graffitiSession: Ref<GraffitiSession | undefined>;\n }\n\n export interface GlobalComponents {\n GraffitiDiscover: typeof Discover;\n }\n}\n\nexport interface GraffitiPluginOptions {\n useGraffiti: GraffitiFactory;\n}\n\nexport const GraffitiPlugin: Plugin<GraffitiPluginOptions> = {\n install(app: App, options: GraffitiPluginOptions) {\n const graffiti = options.useGraffiti();\n const graffitiSession = ref<GraffitiSession | undefined>(undefined);\n graffiti.sessionEvents.addEventListener(\"login\", (evt) => {\n const detail = (evt as GraffitiLoginEvent).detail;\n if (detail.error) {\n console.error(\"Error logging in:\");\n console.error(detail.error);\n return;\n } else {\n graffitiSession.value = detail.session;\n }\n });\n graffiti.sessionEvents.addEventListener(\"logout\", (evt) => {\n const detail = (evt as GraffitiLogoutEvent).detail;\n if (detail.error) {\n console.error(\"Error logging out:\");\n console.error(detail.error);\n } else {\n graffitiSession.value = undefined;\n }\n });\n\n app.provide(graffitiInjectKey, graffiti);\n app.provide(graffitiSessionInjectKey, graffitiSession);\n\n app.component(\"GraffitiDiscover\", Discover);\n app.config.globalProperties.$graffiti = graffiti;\n app.config.globalProperties.$graffitiSession = graffitiSession;\n },\n};\n\nexport * from \"./composables\";\nexport * from \"./injections\";\nexport { Discover as GraffitiDiscover };\n"],"names":["graffitiInjectKey","graffitiSessionInjectKey","useGraffiti","graffiti","inject","useGraffitiSession","session","REFRESH_RATE","useGraffitiDiscover","channels","schema","sessionInjected","results","ref","resultsRaw","flattenResults","acc","o","tombstone","value","batchFlattenTimer","onValue","url","existing","channelsGetter","toValue","schemaGetter","sessionGetter","localIterator","pollLocalModifications","isPolling","lastModified","fullRepollBy","iterator","poll","startOfPoll","myIterator","e","myLastModified","result","tombstoneRetention","watch","onScopeDispose","props","__props","toRef","GraffitiPlugin","app","options","graffitiSession","evt","detail","Discover"],"mappings":"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"}