crelte 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/Crelte.d.ts +49 -9
  2. package/dist/Crelte.d.ts.map +1 -1
  3. package/dist/Crelte.js +30 -10
  4. package/dist/CrelteRequest.d.ts +3 -7
  5. package/dist/CrelteRequest.d.ts.map +1 -1
  6. package/dist/CrelteRequest.js +9 -15
  7. package/dist/graphql/GraphQl.d.ts +7 -0
  8. package/dist/graphql/GraphQl.d.ts.map +1 -1
  9. package/dist/graphql/GraphQl.js +16 -3
  10. package/dist/index.d.ts +15 -4
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +19 -1
  13. package/dist/init/client.d.ts +0 -19
  14. package/dist/init/client.d.ts.map +1 -1
  15. package/dist/init/client.js +9 -12
  16. package/dist/init/server.d.ts +0 -4
  17. package/dist/init/server.d.ts.map +1 -1
  18. package/dist/init/server.js +2 -5
  19. package/dist/init/shared.d.ts.map +1 -1
  20. package/dist/init/shared.js +8 -8
  21. package/dist/loadData/Globals.d.ts +15 -31
  22. package/dist/loadData/Globals.d.ts.map +1 -1
  23. package/dist/loadData/Globals.js +65 -72
  24. package/dist/routing/InnerRouter.d.ts.map +1 -1
  25. package/dist/routing/InnerRouter.js +8 -2
  26. package/dist/routing/Router.d.ts +27 -2
  27. package/dist/routing/Router.d.ts.map +1 -1
  28. package/dist/routing/Router.js +31 -1
  29. package/dist/ssr/SsrCache.d.ts.map +1 -1
  30. package/dist/ssr/SsrCache.js +6 -1
  31. package/package.json +2 -2
  32. package/src/Crelte.ts +80 -13
  33. package/src/CrelteRequest.ts +14 -23
  34. package/src/graphql/GraphQl.ts +25 -6
  35. package/src/index.ts +30 -8
  36. package/src/init/client.ts +9 -40
  37. package/src/init/server.ts +2 -13
  38. package/src/init/shared.ts +8 -9
  39. package/src/loadData/Globals.ts +76 -93
  40. package/src/routing/InnerRouter.ts +9 -2
  41. package/src/routing/Router.ts +43 -9
  42. package/src/ssr/SsrCache.ts +6 -1
@@ -4,25 +4,30 @@ const emergency = getGlobal('emergency');
4
4
 
5
5
  // returns the data based on the current site (no store)
6
6
  cr.getGlobal('emergency')
7
+
7
8
  */
8
9
 
9
10
  import { Writable } from 'crelte-std/stores';
10
11
 
11
- export type GlobalWaiters = [(g: Global<any> | null) => void];
12
+ export type GlobalWaiters<T> = [(g: T | null) => void];
12
13
 
13
14
  export default class Globals {
14
15
  // while the globals are not loaded if somebody calls
15
- // getOrWait then we need to store the waiters
16
- private waiters: Map<string, GlobalWaiters>;
17
- private entries: Map<string, Global<any>>;
18
- private loaded: boolean;
19
- private prevSiteId: number | null;
16
+ // 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;
20
21
 
21
22
  constructor() {
22
23
  this.waiters = new Map();
23
- this.entries = new Map();
24
- this.loaded = false;
25
- this.prevSiteId = null;
24
+ this.data = new Map();
25
+ this.stores = new Map();
26
+ this.currentSiteId = null;
27
+ }
28
+
29
+ get<T = any>(name: string, siteId: number): T | null {
30
+ return this.data.get(siteId)?.get(name) ?? null;
26
31
  }
27
32
 
28
33
  /**
@@ -31,10 +36,10 @@ export default class Globals {
31
36
  * ## Note
32
37
  * This only works in loadData, in loadGlobalData this will
33
38
  * always return null. In that context you should use
34
- * `.getGlobalAsync`
39
+ * `.getAsync`
35
40
  */
36
- get<T extends GlobalData>(name: string): Global<T> | null {
37
- return this.entries.get(name) ?? null;
41
+ getStore<T = any>(name: string): Global<T> | null {
42
+ return this.stores.get(name) ?? null;
38
43
  }
39
44
 
40
45
  /**
@@ -44,15 +49,20 @@ export default class Globals {
44
49
  * This is only useful in loadGlobalData in all other cases
45
50
  * you can use `.getGlobal` which does return a Promise
46
51
  */
47
- getAsync<T extends GlobalData>(
48
- name: string,
49
- ): Promise<Global<T> | null> | Global<T> | null {
50
- if (this.loaded) return this.get(name);
52
+ getAsync<T = any>(name: string, siteId: number): Promise<T | null> {
53
+ if (this._wasLoaded(siteId))
54
+ return Promise.resolve(this.get(name, siteId));
55
+
56
+ let listeners = this.waiters.get(siteId);
57
+ if (!listeners) {
58
+ listeners = new Map();
59
+ this.waiters.set(siteId, listeners);
60
+ }
51
61
 
52
- let waiter = this.waiters.get(name);
62
+ let waiter = listeners.get(name);
53
63
  if (!waiter) {
54
64
  waiter = [] as any;
55
- this.waiters.set(name, waiter!);
65
+ listeners.set(name, waiter!);
56
66
  }
57
67
 
58
68
  return new Promise(resolve => {
@@ -61,88 +71,73 @@ export default class Globals {
61
71
  }
62
72
 
63
73
  /** @hidden */
64
- _wasLoaded(): boolean {
65
- return this.loaded;
74
+ _wasLoaded(siteId: number): boolean {
75
+ return this.data.has(siteId);
66
76
  }
67
77
 
68
78
  // data is the data from the global graphql
69
79
  // so it contains some keys and data which should be parsed
70
80
  // and created a store for each key
81
+ // do not call this if _wasLoaded returns true with the same siteId
71
82
  /** @hidden */
72
83
  _setData(siteId: number, data: any) {
73
- const wasLoaded = this.loaded;
74
- this.loaded = true;
84
+ const map = new Map(Object.entries(data));
85
+ this.data.set(siteId, map);
75
86
 
76
- for (const [key, value] of Object.entries(data)) {
77
- this.entries.set(key, new Global(key, value as any, siteId));
78
- }
79
-
80
- if (!wasLoaded) {
81
- this.waiters.forEach((waiters, key) => {
82
- waiters.forEach(waiter => waiter(this.get(key)));
83
- });
84
- this.waiters.clear();
85
- }
87
+ this.waiters.get(siteId)?.forEach((waiters, key) => {
88
+ waiters.forEach(waiter => waiter(map.get(key)));
89
+ });
90
+ this.waiters.delete(siteId);
86
91
  }
87
92
 
88
93
  /** @hidden */
89
- _globalsBySite(siteId: number): Map<string, any> {
90
- const map = new Map();
94
+ _updateSiteId(siteId: number) {
95
+ if (this.currentSiteId === siteId) return;
91
96
 
92
- for (const [key, global] of this.entries) {
93
- map.set(key, global.bySiteId(siteId));
94
- }
97
+ const data = this.data.get(siteId) ?? new Map();
95
98
 
96
- return map;
97
- }
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
98
102
 
99
- /** @hidden */
100
- _updateSiteId(siteId: number) {
101
- // todo we should only trigger
102
- if (this.prevSiteId === siteId) return;
103
+ this.stores.forEach(global => global._setSilent(null));
103
104
 
104
- this.entries.forEach(global => global._updateSiteId(siteId));
105
- }
106
- }
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
+ });
107
114
 
108
- /**
109
- * A globalSet Data
110
- *
111
- * Each global query should contain the siteId
112
- */
113
- export interface GlobalData {
114
- siteId?: number;
115
- [key: string]: any;
115
+ this.stores.forEach(global => global._notify());
116
+ }
116
117
  }
117
118
 
118
119
  /**
119
120
  * A globalSet store
120
121
  */
121
- export class Global<T extends GlobalData> {
122
+ export class Global<T = any> {
123
+ /** @hidden */
122
124
  private inner: Writable<T>;
123
- /// if languages is null this means we always have the same data
124
- private languages: T[] | null;
125
-
126
- constructor(name: string, data: T[] | T, siteId: number) {
127
- this.languages = null;
128
-
129
- let inner: T;
130
- if (Array.isArray(data)) {
131
- // make sure the data contains an object with the property
132
- // siteId
133
- this.languages = data;
134
- inner = data.find(d => d.siteId === siteId)!;
135
-
136
- if (!inner?.siteId) {
137
- throw new Error(
138
- `The global query ${name} does not contain the required siteId property`,
139
- );
140
- }
141
- } else {
142
- inner = data;
125
+
126
+ constructor(name: string, data: T) {
127
+ // todo remove in v1.0
128
+ // In v0.2, we queried the global data for all sites.
129
+ // We now check if the siteId is present and notify the user to remove it.
130
+ if (
131
+ typeof (data as any).siteId === 'number' ||
132
+ (Array.isArray(data) && typeof data[0]?.siteId === 'number')
133
+ ) {
134
+ throw new Error(
135
+ `The global query ${name} should not include the siteId` +
136
+ ` property. Instead, use the siteId as a parameter.`,
137
+ );
143
138
  }
144
139
 
145
- this.inner = new Writable(inner);
140
+ this.inner = new Writable(data);
146
141
  }
147
142
 
148
143
  /**
@@ -162,25 +157,13 @@ export class Global<T extends GlobalData> {
162
157
  return this.inner.get();
163
158
  }
164
159
 
165
- /**
166
- * Get the value based on the siteId
167
- *
168
- * ## Note
169
- * If you pass a siteId which comes from craft
170
- * you will never receive null
171
- */
172
- bySiteId(siteId: number): T | null {
173
- if (this.languages)
174
- return this.languages.find(d => d.siteId === siteId) ?? null;
175
-
176
- return this.inner.get();
160
+ /** @hidden */
161
+ _setSilent(value: T) {
162
+ this.inner.setSilent(value);
177
163
  }
178
164
 
179
165
  /** @hidden */
180
- _updateSiteId(siteId: number) {
181
- if (!this.languages) return;
182
-
183
- const inner = this.languages.find(d => d.siteId === siteId);
184
- this.inner.set(inner!);
166
+ _notify() {
167
+ this.inner.notify();
185
168
  }
186
169
  }
@@ -133,6 +133,7 @@ export default class InnerRouter {
133
133
  return this.sites.find(s => s.id === id) ?? null;
134
134
  }
135
135
 
136
+ // keep this doc in sync with Router.targetToRequest
136
137
  /**
137
138
  * Resolve a url or Route and convert it to a Request
138
139
  *
@@ -150,6 +151,8 @@ export default class InnerRouter {
150
151
  // exists
151
152
  const site = this.route?.site ?? this.defaultSite();
152
153
  target = new URL(site.uri + target, site.url);
154
+ } else if (!target) {
155
+ throw new Error('the url is not allowed to be empty');
153
156
  } else {
154
157
  target = new URL(target);
155
158
  }
@@ -242,7 +245,11 @@ export default class InnerRouter {
242
245
  if (currentMouseOver && link === currentMouseOver) return;
243
246
  if (link && link.target.toLowerCase() === '_blank') return;
244
247
 
245
- if (link && !link.hasAttribute('data-no-preload')) {
248
+ if (
249
+ link &&
250
+ !link.hasAttribute('data-no-preload') &&
251
+ link.href
252
+ ) {
246
253
  this.preload(link.href);
247
254
  }
248
255
 
@@ -288,7 +295,7 @@ export default class InnerRouter {
288
295
  }
289
296
 
290
297
  window.addEventListener('popstate', async e => {
291
- if (!('route' in e.state)) return;
298
+ if (!e.state?.route) return;
292
299
 
293
300
  const req = this.targetToRequest(window.location.href);
294
301
  req._fillFromState(e.state);
@@ -67,13 +67,19 @@ type ServerInited = {
67
67
  export default class Router {
68
68
  /**
69
69
  * The current route
70
+ *
71
+ * ## Note
72
+ * Will always contain a route expect in the first loadData call
70
73
  */
71
- private _route: Writable<Route>;
74
+ private _route: Writable<Route | null>;
72
75
 
73
76
  /**
74
77
  * The current site
78
+ *
79
+ * ## Note
80
+ * Will always contain a site expect in the first loadData call
75
81
  */
76
- private _site: Writable<Site>;
82
+ private _site: Writable<Site | null>;
77
83
 
78
84
  // the next request, just here to destroy it
79
85
  private _request: Request | null;
@@ -117,9 +123,9 @@ export default class Router {
117
123
  this._onRequest = new Listeners();
118
124
 
119
125
  this._internal = {
120
- onLoaded: () => { },
121
- onNothingLoaded: () => { },
122
- onLoad: () => { },
126
+ onLoaded: () => {},
127
+ onNothingLoaded: () => {},
128
+ onLoad: () => {},
123
129
  domReady: req => this.inner.domReady(req),
124
130
  initClient: () => this._initClient(),
125
131
  initServer: (url, acceptLang) => this._initServer(url, acceptLang),
@@ -139,15 +145,25 @@ export default class Router {
139
145
 
140
146
  /**
141
147
  * returns a store with the current route
148
+ *
149
+ * ## Note
150
+ * Will always contain a route expect in the first loadData call
151
+ *
152
+ * Consider to use CrelteRequest instead
142
153
  */
143
- get route(): Readable<Route> {
154
+ get route(): Readable<Route | null> {
144
155
  return this._route.readclone();
145
156
  }
146
157
 
147
158
  /**
148
159
  * returns a store with the current site
160
+ *
161
+ * ## Note
162
+ * Will always contain a site expect in the first loadData call
163
+ *
164
+ * Consider to use CrelteRequest instead
149
165
  */
150
- get site(): Readable<Site> {
166
+ get site(): Readable<Site | null> {
151
167
  return this._site.readonly();
152
168
  }
153
169
 
@@ -242,6 +258,7 @@ export default class Router {
242
258
  * @deprecated use push instead
243
259
  */
244
260
  pushState(route: Route | Request) {
261
+ console.warn('pushState is deprecated, use push instead');
245
262
  this.push(route);
246
263
  }
247
264
 
@@ -284,6 +301,7 @@ export default class Router {
284
301
  * @deprecated use replace instead
285
302
  */
286
303
  replaceState(route: Route | Request) {
304
+ console.warn('replaceState is deprecated, use replace instead');
287
305
  this.replace(route);
288
306
  }
289
307
 
@@ -313,11 +331,13 @@ export default class Router {
313
331
  *
314
332
  * This will trigger every time a new route is set
315
333
  * and is equivalent to router.route.subscribe(fn)
334
+ * expect that it will not trigger instantly
316
335
  *
317
336
  * @returns a function to remove the listener
318
337
  */
319
338
  onRoute(fn: (route: Route) => void): () => void {
320
- return this.route.subscribe(fn);
339
+ let first = true;
340
+ return this.route.subscribe(r => (first ? (first = false) : fn(r!)));
321
341
  }
322
342
 
323
343
  /**
@@ -331,6 +351,20 @@ export default class Router {
331
351
  return this._onRequest.add(fn);
332
352
  }
333
353
 
354
+ /**
355
+ * Resolve a url or Route and convert it to a Request
356
+ *
357
+ * @param target
358
+ * @param opts, any option present will override the value in target
359
+ * @return Returns null if the url does not match our host (the protocol get's ignored)
360
+ */
361
+ targetToRequest(
362
+ target: string | URL | Route | Request,
363
+ opts: RequestOptions = {},
364
+ ): Request {
365
+ return this.inner.targetToRequest(target, opts);
366
+ }
367
+
334
368
  private setNewRoute(route: Route) {
335
369
  this._route.setSilent(route);
336
370
  const siteChanged = this.site.get()?.id !== route.site.id;
@@ -356,7 +390,7 @@ export default class Router {
356
390
  const prom: Promise<ServerInited> = new Promise(resolve => {
357
391
  this._internal.onLoaded = (success, req, ready) => {
358
392
  const props = ready();
359
- this._internal.onLoaded = () => { };
393
+ this._internal.onLoaded = () => {};
360
394
 
361
395
  resolve({
362
396
  success,
@@ -1,7 +1,12 @@
1
1
  export async function calcKey(data: any) {
2
+ const json = JSON.stringify(data);
3
+ // this should only happen in an unsecure context
4
+ // specifically in the craft preview locally
5
+ if (!crypto?.subtle) return json;
6
+
2
7
  // Convert the string data to an ArrayBuffer
3
8
  const encoder = new TextEncoder();
4
- const dataBuffer = encoder.encode(JSON.stringify(data));
9
+ const dataBuffer = encoder.encode(json);
5
10
 
6
11
  // Use the Web Crypto API to hash the data with SHA-1
7
12
  const hashBuffer = await crypto.subtle.digest('SHA-1', dataBuffer);