crelte 0.4.8 → 0.5.0-alpha.2

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.
Files changed (110) hide show
  1. package/dist/Crelte.d.ts +7 -6
  2. package/dist/Crelte.d.ts.map +1 -1
  3. package/dist/Crelte.js +5 -13
  4. package/dist/CrelteRequest.d.ts +9 -0
  5. package/dist/CrelteRequest.d.ts.map +1 -1
  6. package/dist/CrelteRequest.js +16 -2
  7. package/dist/blocks/Blocks.svelte +2 -2
  8. package/dist/blocks/Blocks.svelte.d.ts +3 -19
  9. package/dist/blocks/Blocks.svelte.d.ts.map +1 -1
  10. package/dist/cookies/ClientCookies.d.ts +0 -1
  11. package/dist/cookies/ClientCookies.d.ts.map +1 -1
  12. package/dist/cookies/ClientCookies.js +0 -1
  13. package/dist/cookies/ServerCookies.d.ts +1 -2
  14. package/dist/cookies/ServerCookies.d.ts.map +1 -1
  15. package/dist/cookies/ServerCookies.js +2 -6
  16. package/dist/cookies/index.d.ts +0 -2
  17. package/dist/cookies/index.d.ts.map +1 -1
  18. package/dist/graphql/GraphQl.d.ts +2 -2
  19. package/dist/graphql/GraphQl.d.ts.map +1 -1
  20. package/dist/index.d.ts +9 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +14 -6
  23. package/dist/init/InternalApp.d.ts +30 -0
  24. package/dist/init/InternalApp.d.ts.map +1 -0
  25. package/dist/init/InternalApp.js +71 -0
  26. package/dist/init/client.d.ts +0 -5
  27. package/dist/init/client.d.ts.map +1 -1
  28. package/dist/init/client.js +88 -75
  29. package/dist/init/crelte-vite-plugin.d.ts +5 -0
  30. package/dist/init/server.d.ts +0 -5
  31. package/dist/init/server.d.ts.map +1 -1
  32. package/dist/init/server.js +49 -20
  33. package/dist/init/shared.d.ts +7 -18
  34. package/dist/init/shared.d.ts.map +1 -1
  35. package/dist/init/shared.js +97 -154
  36. package/dist/init/svelteComponents.d.ts +3 -0
  37. package/dist/init/svelteComponents.d.ts.map +1 -0
  38. package/dist/init/svelteComponents.js +7 -0
  39. package/dist/loadData/Globals.d.ts +40 -33
  40. package/dist/loadData/Globals.d.ts.map +1 -1
  41. package/dist/loadData/Globals.js +99 -88
  42. package/dist/loadData/index.d.ts +3 -2
  43. package/dist/loadData/index.d.ts.map +1 -1
  44. package/dist/loadData/index.js +2 -0
  45. package/dist/plugins/Events.d.ts +11 -13
  46. package/dist/plugins/Events.d.ts.map +1 -1
  47. package/dist/plugins/Events.js +10 -3
  48. package/dist/routing/BaseRoute.d.ts +255 -0
  49. package/dist/routing/BaseRoute.d.ts.map +1 -0
  50. package/dist/routing/BaseRoute.js +349 -0
  51. package/dist/routing/BaseRouter.d.ts +210 -0
  52. package/dist/routing/BaseRouter.d.ts.map +1 -0
  53. package/dist/routing/BaseRouter.js +444 -0
  54. package/dist/routing/ClientRouter.d.ts +32 -0
  55. package/dist/routing/ClientRouter.d.ts.map +1 -0
  56. package/dist/routing/ClientRouter.js +259 -0
  57. package/dist/routing/LoadRunner.d.ts +39 -0
  58. package/dist/routing/LoadRunner.d.ts.map +1 -0
  59. package/dist/routing/{PageLoader.js → LoadRunner.js} +32 -20
  60. package/dist/routing/Request.d.ts +35 -3
  61. package/dist/routing/Request.d.ts.map +1 -1
  62. package/dist/routing/Request.js +64 -5
  63. package/dist/routing/Route.d.ts +24 -223
  64. package/dist/routing/Route.d.ts.map +1 -1
  65. package/dist/routing/Route.js +26 -315
  66. package/dist/routing/Router.d.ts +49 -73
  67. package/dist/routing/Router.d.ts.map +1 -1
  68. package/dist/routing/Router.js +85 -251
  69. package/dist/routing/ServerRouter.d.ts +23 -0
  70. package/dist/routing/ServerRouter.d.ts.map +1 -0
  71. package/dist/routing/ServerRouter.js +57 -0
  72. package/dist/routing/utils.d.ts +5 -0
  73. package/dist/routing/utils.d.ts.map +1 -1
  74. package/dist/routing/utils.js +39 -0
  75. package/dist/utils.d.ts +1 -0
  76. package/dist/utils.d.ts.map +1 -1
  77. package/dist/utils.js +3 -0
  78. package/package.json +7 -6
  79. package/src/Crelte.ts +12 -18
  80. package/src/CrelteRequest.ts +21 -2
  81. package/src/cookies/ClientCookies.ts +0 -2
  82. package/src/cookies/ServerCookies.ts +2 -7
  83. package/src/cookies/index.ts +0 -3
  84. package/src/graphql/GraphQl.ts +2 -1
  85. package/src/index.ts +17 -9
  86. package/src/init/InternalApp.ts +134 -0
  87. package/src/init/client.ts +104 -93
  88. package/src/init/crelte-vite-plugin.d.ts +5 -0
  89. package/src/init/server.ts +67 -35
  90. package/src/init/shared.ts +107 -227
  91. package/src/init/svelteComponents.ts +12 -0
  92. package/src/loadData/Globals.ts +121 -102
  93. package/src/loadData/index.ts +3 -2
  94. package/src/plugins/Events.ts +40 -42
  95. package/src/routing/BaseRoute.ts +422 -0
  96. package/src/routing/BaseRouter.ts +528 -0
  97. package/src/routing/ClientRouter.ts +329 -0
  98. package/src/routing/{PageLoader.ts → LoadRunner.ts} +43 -30
  99. package/src/routing/Request.ts +97 -12
  100. package/src/routing/Route.ts +56 -376
  101. package/src/routing/Router.ts +100 -359
  102. package/src/routing/ServerRouter.ts +78 -0
  103. package/src/routing/utils.ts +53 -0
  104. package/src/utils.ts +4 -0
  105. package/dist/routing/InnerRouter.d.ts +0 -113
  106. package/dist/routing/InnerRouter.d.ts.map +0 -1
  107. package/dist/routing/InnerRouter.js +0 -417
  108. package/dist/routing/PageLoader.d.ts +0 -36
  109. package/dist/routing/PageLoader.d.ts.map +0 -1
  110. package/src/routing/InnerRouter.ts +0 -498
@@ -1,34 +1,13 @@
1
1
  import Crelte from '../Crelte.js';
2
2
  import CrelteRequest from '../CrelteRequest.js';
3
- import EntryRouter, { EntryRoutes } from '../entry/EntryRouter.js';
4
- import { EntryQueryVars } from '../entry/index.js';
5
- import { GraphQlQuery } from '../graphql/GraphQl.js';
3
+ import { GraphQlQuery, isGraphQlQuery } from '../graphql/GraphQl.js';
6
4
  import { Entry } from '../index.js';
7
- import { LoadData, callLoadData } from '../loadData/index.js';
5
+ import { callLoadData } from '../loadData/index.js';
8
6
  import { PluginCreator } from '../plugins/Plugins.js';
9
- import { LoadOptions } from '../routing/PageLoader.js';
10
-
11
- interface App {
12
- loadGlobalData?: LoadData<null>;
13
-
14
- // todo: add a generic
15
- loadEntryData?: LoadData<Entry>;
16
-
17
- templates?: Record<string, LazyTemplateModule>;
18
-
19
- entryRoutes?: EntryRoutes;
20
-
21
- init?: (crelte: Crelte) => void;
22
- }
23
-
24
- interface TemplateModule {
25
- // svelte component
26
- default: any;
27
-
28
- loadData?: LoadData<Entry>;
29
- }
30
-
31
- type LazyTemplateModule = (() => Promise<TemplateModule>) | TemplateModule;
7
+ import { LoadOptions } from '../routing/LoadRunner.js';
8
+ import { isPromise } from '../utils.js';
9
+ import InternalApp, { TemplateModule } from './InternalApp.js';
10
+ import Route from '../routing/Route.js';
32
11
 
33
12
  export function setupPlugins(crelte: Crelte, plugins: PluginCreator[]) {
34
13
  for (const plugin of plugins) {
@@ -37,124 +16,94 @@ export function setupPlugins(crelte: Crelte, plugins: PluginCreator[]) {
37
16
  }
38
17
  }
39
18
 
40
- export function pluginsBeforeRender(cr: CrelteRequest): void {
41
- cr.events.trigger('beforeRender', cr);
19
+ export function pluginsBeforeRequest(cr: CrelteRequest): Promise<void> | void {
20
+ const res = cr.events.trigger('beforeRequest', cr);
21
+ // if one of them is a promise we need to wait for it
22
+ if (res.some(isPromise)) {
23
+ return Promise.all(res).then();
24
+ }
42
25
  }
43
26
 
44
- const ERROR_404_ENTRY: Entry = {
45
- sectionHandle: 'error',
46
- typeHandle: '404',
47
- };
48
-
49
- /**
50
- * Get the entry from the page
51
- *
52
- * entries should export sectionHandle and typeHandle
53
- *
54
- * products should alias productTypeHandle with typeHandle,
55
- * sectionHandle will be automatically set to product
56
- */
57
- function getEntry(page: any): Entry | null {
58
- if (page?.entry) return { ...page.entry };
59
- if (page?.product)
60
- return {
61
- sectionHandle: 'product',
62
- ...page.product,
63
- };
64
-
65
- return null;
27
+ export function pluginsBeforeRender(cr: CrelteRequest, route: Route): void {
28
+ cr.events.trigger('beforeRender', cr, route);
66
29
  }
67
30
 
68
- // todo it would be nice to call this only once per server start
69
- export async function prepareLoadFn(
70
- crelte: Crelte,
71
- app: App,
72
- entryQuery: GraphQlQuery,
73
- globalQuery?: GraphQlQuery,
74
- ): Promise<(cr: CrelteRequest, loadOpts?: LoadOptions) => Promise<any>> {
75
- const templateModules = prepareTemplates(app.templates ?? {});
76
- let entryRouter: EntryRouter | null = null;
77
- if (app.entryRoutes) {
78
- entryRouter = new EntryRouter(crelte);
79
- await app.entryRoutes(entryRouter);
80
- }
81
-
82
- return async (cr, loadOpts) => {
83
- return await loadFn(
84
- cr,
85
- app,
86
- templateModules,
87
- entryRouter,
88
- entryQuery,
89
- globalQuery,
90
- loadOpts,
91
- );
92
- };
93
- }
31
+ // This should be onRequest or handleRequest
32
+ //
33
+ // it should also handle the site redirect and stuff like that
34
+ // we should have a onRequest
94
35
 
95
- async function loadFn(
36
+ export async function loadFn(
96
37
  cr: CrelteRequest,
97
- app: App,
98
- templateModules: Map<string, LazyTemplateModule>,
99
- entryRouter: EntryRouter | null,
100
- entryQuery: GraphQlQuery,
101
- globalQuery?: GraphQlQuery,
38
+ app: InternalApp,
102
39
  loadOpts?: LoadOptions,
103
- ): Promise<any> {
104
- let dataProm: Promise<any> | null = null;
105
- // @ts-ignore
106
- if (app.loadData) {
107
- throw new Error(
108
- 'loadData is ambigous, choose loadGlobalData or ' +
109
- 'loadEntryData depending on if you need access to entry or not?',
110
- );
111
- }
40
+ ): Promise<void> {
41
+ const isCanceled = () => !!loadOpts?.isCanceled();
112
42
 
113
- if (app.loadGlobalData) {
114
- dataProm = callLoadData(app.loadGlobalData, cr, null) as any;
115
- }
43
+ // loadGlobalData phase
116
44
 
117
45
  let globalProm: Promise<any> | null = null;
118
- if (globalQuery && !cr.globals._wasLoaded(cr.site.id)) {
46
+
47
+ if (app.loadGlobalData) {
48
+ // we need to set the globals as soon as loadGlobalData completes
49
+ // because other loadGlobalData functions might wait on the result
119
50
  globalProm = (async () => {
120
- const res = await cr.query(globalQuery, {
121
- siteId: cr.site.id,
122
- });
123
- // we need to do this sorcery here and can't wait until all
124
- // globals functions are done, because some global function
125
- // might want to use globals, and for that the function
126
- // getAsync exists on Globals
127
- cr.globals._setData(cr.site.id, res);
128
- return res;
51
+ // todo theoretically we could if the loadData is a an LoadObject
52
+ // assign each to the global as soon as we have it. Which might
53
+ // prevent some deadlocks but i don't think this will happen
54
+ // often
55
+ const globals = await callLoadData(app.loadGlobalData, cr, null);
56
+ if (!globals) return globals;
57
+
58
+ if (typeof globals !== 'object') {
59
+ throw new Error(
60
+ 'loadGlobalData needs to return an object or nothing',
61
+ );
62
+ }
63
+
64
+ for (const [k, v] of Object.entries(globals)) {
65
+ cr.globals.set(k, v);
66
+ }
129
67
  })();
130
68
  }
131
69
 
132
- const entryProm = queryEntry(cr, app, entryRouter, entryQuery);
70
+ // todo maybe each setting of the property on the request should be
71
+ // checked to be empty before doing it
72
+ const entryProm = (async () => {
73
+ let loadEntry = app.loadEntry;
74
+ if (isGraphQlQuery(loadEntry)) {
75
+ const entryQuery = loadEntry;
76
+ loadEntry = cr => queryEntry(cr, entryQuery);
77
+ }
78
+
79
+ let entry: Entry = await callLoadData(loadEntry, cr, null);
80
+ if (isCanceled()) return [];
81
+ cr.req.entry = entry;
82
+
83
+ await Promise.all(cr.events.trigger('afterLoadEntry', cr));
84
+ if (isCanceled()) return [];
85
+ entry = cr.req.entry;
86
+
87
+ const template = await app.loadTemplate(entry);
88
+ if (isCanceled()) return [];
89
+ cr.req.template = template;
90
+
91
+ return [entry, template] as [Entry, TemplateModule];
92
+ })();
133
93
 
134
94
  const pluginsLoadGlobalData = cr.events.trigger('loadGlobalData', cr);
135
95
 
136
96
  // loading progress is at 20%
137
97
  loadOpts?.setProgress(0.2);
138
98
 
139
- const [data, global, entry] = await Promise.all([
140
- dataProm,
99
+ const [_global, [entry, template]] = await Promise.all([
141
100
  globalProm,
142
101
  entryProm,
143
102
  ...pluginsLoadGlobalData,
144
103
  ]);
104
+ if (isCanceled()) return;
145
105
 
146
- // global is only set if !wasLoaded but we need to store something
147
- // even if no globalQuery exists
148
- if (global || !cr.globals._wasLoaded(cr.site.id)) {
149
- cr.globals._setData(cr.site.id, global ?? {});
150
- }
151
-
152
- let template;
153
- if (app.templates) {
154
- template = await loadTemplate(templateModules, entry);
155
- } else {
156
- throw new Error('App must export some templates');
157
- }
106
+ cr.globals._globalsLoaded();
158
107
 
159
108
  // loading progress is at 60%
160
109
  loadOpts?.setProgress(0.6);
@@ -168,7 +117,7 @@ async function loadFn(
168
117
 
169
118
  let entryDataProm: Promise<any> | null = null;
170
119
  if (app.loadEntryData) {
171
- entryDataProm = callLoadData(app.loadEntryData, cr, entry) as any;
120
+ entryDataProm = callLoadData(app.loadEntryData, cr, entry);
172
121
  }
173
122
 
174
123
  const [templateData, entryData] = await Promise.all([
@@ -177,126 +126,57 @@ async function loadFn(
177
126
  ...pluginsLoadData,
178
127
  ]);
179
128
 
180
- // loading progress is at 100%
181
- loadOpts?.setProgress(1);
182
-
183
- return {
184
- ...data,
129
+ cr.req.loadedData = {
130
+ ...templateData,
185
131
  ...entryData,
186
- entry,
187
- template: template.default,
188
- templateData: templateData! as any,
189
132
  };
190
- }
191
-
192
- function parseFilename(path: string): [string, string] {
193
- // get filename with extension
194
- const slash = path.lastIndexOf('/');
195
- const filename = path.substring(slash + 1);
196
133
 
197
- const extPos = filename.lastIndexOf('.');
198
-
199
- const name = filename.substring(0, extPos);
200
- const ext = filename.substring(extPos + 1);
201
-
202
- return [name, ext];
134
+ // loading progress is at 100%
135
+ loadOpts?.setProgress(1);
203
136
  }
204
137
 
205
- async function queryEntry(
138
+ export async function queryEntry(
206
139
  cr: CrelteRequest,
207
- app: App,
208
- entryRouter: EntryRouter | null,
209
140
  entryQuery: GraphQlQuery,
210
141
  ): Promise<Entry> {
211
- let vars: EntryQueryVars | null = null;
212
-
213
- if (cr.req.siteMatches()) {
214
- let uri = decodeURI(cr.req.uri);
215
- if (uri.startsWith('/')) uri = uri.substring(1);
216
- if (uri === '' || uri === '/') uri = '__home__';
217
-
218
- vars = {
219
- uri,
220
- siteId: cr.site.id,
221
- };
222
- }
223
-
224
- if (vars) {
225
- await Promise.all(cr.events.trigger('beforeQueryEntry', cr, vars));
226
- }
227
-
228
- // basic query function
229
- let loadFn = async (vars: EntryQueryVars | null) => {
230
- if (entryRouter) {
231
- const entry = await entryRouter._handle(cr);
232
- if (entry) return entry;
233
- }
234
-
235
- if (vars) {
236
- const page = await cr.query(entryQuery, vars);
142
+ if (!cr.req.siteMatches())
143
+ throw new Error(
144
+ 'to run the entryQuery the request needs to have a matching site',
145
+ );
237
146
 
238
- return getEntry(page);
239
- }
147
+ let uri = decodeURI(cr.req.uri);
148
+ if (uri.startsWith('/')) uri = uri.substring(1);
149
+ if (uri === '' || uri === '/') uri = '__home__';
240
150
 
241
- return null;
151
+ const vars = {
152
+ uri,
153
+ siteId: cr.site.id,
242
154
  };
243
155
 
244
- // check if a plugin wants to override the query
245
- const fns = cr.events.getListeners('queryEntry');
246
- for (const fn of fns) {
247
- const prevLoadFn = loadFn;
248
- loadFn = async vars => {
249
- return await fn(cr, vars, prevLoadFn);
250
- };
251
- }
252
-
253
- const entry = (await loadFn(vars)) ?? ERROR_404_ENTRY;
254
-
255
- await Promise.all(cr.events.trigger('afterQueryEntry', cr, entry));
256
-
257
- return entry;
258
- }
259
-
260
- function prepareTemplates(
261
- rawModules: Record<string, LazyTemplateModule>,
262
- ): Map<string, LazyTemplateModule> {
263
- // parse modules
264
- return new Map(
265
- Object.entries(rawModules)
266
- .map(([path, mod]) => {
267
- const [name, _ext] = parseFilename(path);
268
- return [name, mod] as [string, LazyTemplateModule];
269
- })
270
- .filter(([name, _mod]) => !!name),
271
- );
156
+ const page = await cr.query(entryQuery, vars);
157
+ return getEntry(page) ?? ERROR_404_ENTRY;
272
158
  }
273
159
 
274
- async function loadTemplate(
275
- modules: Map<string, LazyTemplateModule>,
276
- entry: Entry,
277
- ): Promise<TemplateModule> {
278
- const entr = entry as any;
279
- const handle = `${entr.sectionHandle}-${entr.typeHandle}`;
280
-
281
- if (
282
- // @ts-ignore
283
- import.meta.env.DEV &&
284
- !modules.has(handle) &&
285
- !modules.has(entr.sectionHandle)
286
- ) {
287
- console.error(
288
- `Template not found: <${handle}>, expecting file: ${handle}.svelte or ${entr.sectionHandle}.svelte`,
289
- );
290
- }
160
+ const ERROR_404_ENTRY: Entry = {
161
+ sectionHandle: 'error',
162
+ typeHandle: '404',
163
+ };
291
164
 
292
- const loadMod =
293
- modules.get(handle) ??
294
- modules.get(entr.sectionHandle) ??
295
- modules.get('error-404');
296
- if (!loadMod) throw new Error('could not find error-404 template');
165
+ /**
166
+ * Get the entry from the page
167
+ *
168
+ * entries should export sectionHandle and typeHandle
169
+ *
170
+ * products should alias productTypeHandle with typeHandle,
171
+ * sectionHandle will be automatically set to product
172
+ */
173
+ function getEntry(page: any): Entry | null {
174
+ if (page?.entry) return { ...page.entry };
175
+ if (page?.product)
176
+ return {
177
+ sectionHandle: 'product',
178
+ ...page.product,
179
+ };
297
180
 
298
- if (typeof loadMod === 'function') {
299
- return await loadMod();
300
- }
301
- return loadMod;
181
+ return null;
302
182
  }
@@ -0,0 +1,12 @@
1
+ import {
2
+ internalSvelteMount,
3
+ internalSvelteRender,
4
+ } from 'crelte-vite-plugin/svelteComponents.js';
5
+
6
+ export function svelteMount(comp: any, options: any): any {
7
+ return internalSvelteMount(comp, options);
8
+ }
9
+
10
+ export function svelteRender(comp: any, options: any): any {
11
+ return internalSvelteRender(comp, options);
12
+ }
@@ -7,27 +7,63 @@ cr.getGlobal('emergency')
7
7
 
8
8
  */
9
9
 
10
- import { Writable } from 'crelte-std/stores';
10
+ import { Readable, Writable } from 'crelte-std/stores';
11
11
 
12
- export type GlobalWaiters<T> = [(g: T | null) => void];
12
+ export type GlobalWaiters<T> = ((g: T | null) => void)[];
13
13
 
14
+ /**
15
+ * Globals is sort of a queue
16
+ *
17
+ * each time a new request get's started
18
+ * a copy of globals is created which references some properties of the original one
19
+ *
20
+ * then if everything is loaded th original globals is "overriden" with the new one
21
+ * and we get a new state
22
+ */
14
23
  export default class Globals {
15
24
  // while the globals are not loaded if somebody calls
16
25
  // getAsync then we need to store the waiters
17
- private waiters: Map<number, Map<string, GlobalWaiters<any>>>;
18
- private data: Map<number, Map<string, any>>;
19
- private stores: Map<string, Global<any>>;
20
- private currentSiteId: number | null;
21
-
22
- constructor() {
23
- this.waiters = new Map();
24
- this.data = new Map();
25
- this.stores = new Map();
26
- this.currentSiteId = null;
26
+ // this get's created as soon as a request was started
27
+ // and get's deleted as soon as all globals are loaded
28
+ private waiters: Map<string, GlobalWaiters<any>> | null;
29
+
30
+ // this get's created as soon as a request was started
31
+ // and deleted once they are synced to the stores
32
+ private newData: Map<string, any> | null;
33
+
34
+ // contains the current active globals
35
+ private stores: Map<string, Writable<any>>;
36
+
37
+ constructor(stores?: Map<string, Writable<any>>) {
38
+ this.waiters = null;
39
+ this.newData = null;
40
+ this.stores = stores ?? new Map();
27
41
  }
28
42
 
29
- get<T = any>(name: string, siteId: number): T | null {
30
- return this.data.get(siteId)?.get(name) ?? null;
43
+ /**
44
+ * returns a globalValue
45
+ *
46
+ * ## Note
47
+ * This only works in loadData, in loadGlobalData this will
48
+ * throw an error. In that context you should use `.getAsync`
49
+ */
50
+ get<T = any>(name: string): T | null {
51
+ if (this.waiters)
52
+ throw new Error(
53
+ 'calling get in loadGlobalData will not work. call getAsync',
54
+ );
55
+
56
+ if (!this.newData) {
57
+ throw new Error(
58
+ 'calling get outside of a loadData is forbidden. use getStore',
59
+ );
60
+
61
+ // todo do we wan't to allow this?
62
+ // isn't it just a footgun?
63
+ // return this.getStore(name)?.get() ?? null;
64
+ }
65
+
66
+ return this.newData.get(name) ?? null;
31
67
  }
32
68
 
33
69
  /**
@@ -38,8 +74,8 @@ export default class Globals {
38
74
  * always return null. In that context you should use
39
75
  * `.getAsync`
40
76
  */
41
- getStore<T = any>(name: string): Global<T> | null {
42
- return this.stores.get(name) ?? null;
77
+ getStore<T = any>(name: string): Readable<T> | null {
78
+ return this.stores.get(name)?.readonly() ?? null;
43
79
  }
44
80
 
45
81
  /**
@@ -47,83 +83,34 @@ export default class Globals {
47
83
  *
48
84
  * ## Note
49
85
  * This is only useful in loadGlobalData in all other cases
50
- * you can use `.getGlobal` which does return a Promise
86
+ * you can use `.get` which does not return a Promise
51
87
  */
52
- getAsync<T = any>(name: string, siteId: number): Promise<T | null> {
53
- if (this._wasLoaded(siteId))
54
- return Promise.resolve(this.get(name, siteId));
88
+ getAsync<T = any>(name: string): Promise<T | null> | T | null {
89
+ if (this.newData) return this.newData.get(name) ?? null;
55
90
 
56
- let listeners = this.waiters.get(siteId);
57
- if (!listeners) {
58
- listeners = new Map();
59
- this.waiters.set(siteId, listeners);
60
- }
91
+ if (!this.waiters)
92
+ throw new Error(
93
+ 'calling getAsync in non loadGlobalData contexts is pointless. Use getStore instead',
94
+ );
61
95
 
62
- let waiter = listeners.get(name);
63
- if (!waiter) {
64
- waiter = [] as any;
65
- listeners.set(name, waiter!);
96
+ let listeners = this.waiters.get(name);
97
+ if (!listeners) {
98
+ listeners = [];
99
+ this.waiters.set(name, listeners);
66
100
  }
67
101
 
68
- return new Promise(resolve => {
69
- waiter!.push(resolve);
70
- });
71
- }
72
-
73
- /** @hidden */
74
- _wasLoaded(siteId: number): boolean {
75
- return this.data.has(siteId);
102
+ return new Promise(resolve => listeners.push(resolve));
76
103
  }
77
104
 
78
- // data is the data from the global graphql
79
- // so it contains some keys and data which should be parsed
80
- // and created a store for each key
81
- // do not call this if _wasLoaded returns true with the same siteId
82
- /** @hidden */
83
- _setData(siteId: number, data: any) {
84
- const map = new Map(Object.entries(data));
85
- this.data.set(siteId, map);
86
-
87
- this.waiters.get(siteId)?.forEach((waiters, key) => {
88
- waiters.forEach(waiter => waiter(map.get(key)));
89
- });
90
- this.waiters.delete(siteId);
91
- }
92
-
93
- /** @hidden */
94
- _updateSiteId(siteId: number) {
95
- if (this.currentSiteId === siteId) return;
96
-
97
- const data = this.data.get(siteId) ?? new Map();
98
-
99
- // we set all global data to null via setSilent
100
- // then set them all with the new data
101
- // and update all of them
102
-
103
- this.stores.forEach(global => global._setSilent(null));
104
-
105
- data.forEach((value, key) => {
106
- let global = this.stores.get(key);
107
- if (global) {
108
- global._setSilent(value);
109
- } else {
110
- global = new Global(key, value);
111
- this.stores.set(key, global);
112
- }
113
- });
114
-
115
- this.stores.forEach(global => global._notify());
116
- }
117
- }
118
-
119
- /**
120
- * A globalSet store
121
- */
122
- export class Global<T = any> {
123
- /** @hidden */
124
- private inner: Writable<T>;
105
+ /**
106
+ * can only be called in loadGlobalData contexts
107
+ */
108
+ set<T>(name: string, data: T) {
109
+ if (!this.newData) {
110
+ // this is not strictly necessary but
111
+ throw new Error('can only be called in loadGlobalData contexts');
112
+ }
125
113
 
126
- constructor(name: string, data: T) {
127
114
  // todo remove in v1.0
128
115
  // In v0.2, we queried the global data for all sites.
129
116
  // We now check if the siteId is present and notify the user to remove it.
@@ -137,33 +124,65 @@ export class Global<T = any> {
137
124
  );
138
125
  }
139
126
 
140
- this.inner = new Writable(data);
127
+ this.newData?.set(name, data);
128
+
129
+ const listeners = this.waiters?.get(name);
130
+ if (listeners) {
131
+ this.waiters!.delete(name);
132
+ listeners.forEach(fn => fn(data));
133
+ }
141
134
  }
142
135
 
143
136
  /**
144
- * The function get's called once with the current value and then when the
145
- * values changes
146
- *
147
- * @return a function which should be called to unsubscribe
137
+ * @hidden
138
+ * call this before starting the loadGlobalData phase
148
139
  */
149
- subscribe(fn: (val: T) => void): () => void {
150
- return this.inner.subscribe(fn);
140
+ _toRequest() {
141
+ const nGlobals = new Globals(this.stores);
142
+ nGlobals.waiters = new Map();
143
+ nGlobals.newData = new Map();
144
+
145
+ return nGlobals;
151
146
  }
152
147
 
153
148
  /**
154
- * The current value
149
+ * @hidden
150
+ * call this after the loadGlobalData phase
155
151
  */
156
- get(): T {
157
- return this.inner.get();
152
+ _globalsLoaded() {
153
+ // todo should we check if there are still waiters?
154
+ // theoretically this should never happen
155
+ this.waiters = null;
158
156
  }
159
157
 
160
- /** @hidden */
161
- _setSilent(value: T) {
162
- this.inner.setSilent(value);
163
- }
158
+ /**
159
+ * @hidden
160
+ * call this after the loadData phase once the CrelteRequest
161
+ * gets completed
162
+ */
163
+ _syncToStores() {
164
+ const setToNull = new Set(this.stores.keys());
165
+
166
+ for (const [name, data] of this.newData!.entries()) {
167
+ setToNull.delete(name);
168
+
169
+ const store = this.stores.get(name);
170
+ if (store) {
171
+ // todo should we do this check always?
172
+ if (store.get() !== data) store.set(data);
173
+ } else {
174
+ this.stores.set(name, new Writable(data));
175
+ }
176
+ }
177
+
178
+ for (const name of setToNull) {
179
+ console.warn(
180
+ `global ${name} was not modified setting to null and removing it`,
181
+ );
182
+ this.stores.get(name)!.set(null);
183
+ this.stores.delete(name);
184
+ }
164
185
 
165
- /** @hidden */
166
- _notify() {
167
- this.inner.notify();
186
+ this.newData = null;
168
187
  }
169
188
  }