solidstep 0.1.6 → 0.1.8

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
@@ -364,6 +364,10 @@ export const options = {
364
364
  cache: {
365
365
  ttl: 60000, // Cache for 60 seconds
366
366
  },
367
+ responseHeaders: { // Custom headers for pages
368
+ 'X-Custom-Header': 'MyValue',
369
+ 'Cache-Control': 'public, max-age=60', // Client-side caching
370
+ },
367
371
  };
368
372
  ```
369
373
 
@@ -406,6 +410,45 @@ const template = await fs.promises.readFile(TEMPLATE_PATH, 'utf-8');
406
410
 
407
411
  ## Utilities
408
412
 
413
+ ### Cache (Server-Side)
414
+ - Every page can be cached by setting the `options.cache` property in the page.
415
+ - You can also manually invalidate the cache for specific routes.
416
+ - Invalidation can be done in two ways:
417
+ 1. Using the `invalidateCache` utility to only invalidate paths.
418
+ ```tsx
419
+ import { invalidateCache } from 'solidstep/utils/cache';
420
+
421
+ const action = async () => {
422
+ 'use server';
423
+
424
+ ...
425
+
426
+ // Invalidate cache after data mutation
427
+ await invalidateCache('/some-route');
428
+
429
+ ...
430
+
431
+ return { success: true };
432
+ };
433
+ ```
434
+ 2. Using the `revalidatePath` utility to revalidate specific paths and revalidate the frontend DOM - signaling the server action as a Single Flight Mutation query.
435
+ ```tsx
436
+ import { revalidatePath } from 'solidstep/utils/cache';
437
+
438
+ const action = async () => {
439
+ 'use server';
440
+
441
+ ...
442
+
443
+ // Revalidate path after data mutation
444
+ await revalidatePath('/some-route');
445
+
446
+ ...
447
+
448
+ return { success: true };
449
+ };
450
+ ```
451
+
409
452
  ### Cookies
410
453
  ```tsx
411
454
  import { getCookie, setCookie } from 'solidstep/utils/cookies';
@@ -746,8 +789,10 @@ export const RootLayout = (props) => {
746
789
  ```
747
790
  - **Custom Preloading Logic**: Write custom logic to preload data for specific routes or components based on user behavior or application state.
748
791
 
792
+ ## Environment Variables
793
+ As SolidStep is built using Vite, it follows the same guide as stated in [Vite docs](https://vite.dev/guide/env-and-mode) regarding environment variables.
794
+
749
795
  ## Future Plans
750
- - Revalidate on demand
751
796
  - Support for dynamic site.webmanifest, robots.txt, sitemap.xml, manifest.json, and llms.txt
752
797
  - Support loading and error pages for parallel routes
753
798
  - Support deferring loaders
package/client.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../client.ts"],"names":[],"mappings":"AACA,OAAO,cAAc,CAAC;AAYtB,eAAO,MAAM,IAAI,GACb,YAAY,MAAM,EAClB,cAAa,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACxC,eAAc,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACzC,qBAAoB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,kBAsF/C,CAAC;AAEF,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../client.ts"],"names":[],"mappings":"AACA,OAAO,cAAc,CAAC;AAwCtB,eAAO,MAAM,IAAI,GACb,YAAY,MAAM,EAClB,cAAa,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACxC,eAAc,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACzC,qBAAoB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,kBAsF/C,CAAC;AAEF,eAAe,IAAI,CAAC"}
package/client.js CHANGED
@@ -2,6 +2,32 @@ import { hydrate } from 'solid-js/web';
2
2
  import 'vinxi/client';
3
3
  import fileRoutes from 'vinxi/routes';
4
4
  import { getManifest } from 'vinxi/manifest';
5
+ import { createDiffDOM } from './utils/diff-dom';
6
+ window.onpageshow = () => {
7
+ const state = window.history.state;
8
+ const key = `${window.location.pathname}:build-time`;
9
+ const buildTimeMeta = document.querySelector('meta[name="x-build-time"]');
10
+ const buildTime = buildTimeMeta ? Number.parseInt(buildTimeMeta.getAttribute('content') || '0', 10) : 0;
11
+ const lastBuildTime = Number.parseInt(sessionStorage.getItem(key) || '0', 10);
12
+ const diffString = sessionStorage.getItem(window.location.pathname);
13
+ if (state?.revalidated && buildTime === lastBuildTime && diffString) {
14
+ // we need to re-apply the diff from session storage
15
+ const diff = JSON.parse(diffString);
16
+ const dd = createDiffDOM();
17
+ const didApply = dd.apply(document.body, diff);
18
+ if (didApply) {
19
+ const key = window.location.pathname;
20
+ sessionStorage.setItem(key, JSON.stringify(diff));
21
+ window.history.pushState({ revalidated: true }, '', window.location.href);
22
+ }
23
+ if (import.meta.env.DEV && !didApply) {
24
+ console.error('The mutation was not applied, this seems to be an edge case.');
25
+ console.error('Please raise an issue on GitHub describing your case.');
26
+ console.error('The diff calculated:', diff);
27
+ }
28
+ }
29
+ sessionStorage.setItem(key, buildTime.toString());
30
+ };
5
31
  const importModule = async (routeModule) => {
6
32
  const manifest = getManifest('client');
7
33
  if (import.meta.env.DEV) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solidstep",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Next Step SolidJS Framework for building web applications.",
5
5
  "type": "module",
6
6
  "author": "HamzaKV <hamzakv333@gmail.com>",
package/server.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":"AAygBA,QAAA,MAAM,OAAO,+FAqUX,CAAC;AAEH,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":"AAyhBA,QAAA,MAAM,OAAO,+FAgVX,CAAC;AAEH,eAAe,OAAO,CAAC"}
package/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { eventHandler, toWebRequest } from 'vinxi/http';
1
+ import { eventHandler, toWebRequest, setResponseHeader, getEvent } from 'vinxi/http';
2
2
  import { getManifest } from 'vinxi/manifest';
3
3
  import { generateHydrationScript, renderToString } from 'solid-js/web';
4
4
  import fileRoutes, {} from 'vinxi/routes';
@@ -215,9 +215,18 @@ const sendNodeResponse = async (res, response) => {
215
215
  }
216
216
  };
217
217
  const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonce, }) => {
218
- const url = req.url || '/';
219
- const cachedEntry = getCache(url);
218
+ const url = new URL(req.url);
219
+ const path = url.pathname;
220
+ const cachedEntry = getCache(path);
220
221
  if (cachedEntry && toRender === 'main') {
222
+ const { options } = entry.mainPage.options ? await entry.mainPage.options.import() : { options: {} };
223
+ if (options?.responseHeaders) {
224
+ const headers = options.responseHeaders;
225
+ const event = getEvent();
226
+ for (const [key, value] of Object.entries(headers)) {
227
+ setResponseHeader(event, key, value);
228
+ }
229
+ }
221
230
  return {
222
231
  rendered: cachedEntry.rendered,
223
232
  documentMeta: cachedEntry.documentMeta,
@@ -316,6 +325,13 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
316
325
  if (options?.cache) {
317
326
  cachingOptions = options.cache;
318
327
  }
328
+ if (options?.responseHeaders) {
329
+ const headers = options.responseHeaders;
330
+ const event = getEvent();
331
+ for (const [key, value] of Object.entries(headers)) {
332
+ setResponseHeader(event, key, value);
333
+ }
334
+ }
319
335
  let data = {};
320
336
  if (pageLoader) {
321
337
  const result = await pageLoader.loader(req);
@@ -345,13 +361,13 @@ const render = async ({ toRender, entry, routeParams, searchParams, req, cspNonc
345
361
  });
346
362
  const composed = await compose();
347
363
  const rendered = await renderToString(() => composed());
348
- if (cachingOptions && toRender === 'main') {
349
- setCache(url, {
364
+ if (toRender === 'main') {
365
+ setCache(path, {
350
366
  rendered: rendered,
351
367
  documentMeta: meta,
352
368
  documentAssets: assets,
353
369
  loaderData: loaderData,
354
- }, cachingOptions.ttl);
370
+ }, cachingOptions ? cachingOptions.ttl : 0);
355
371
  }
356
372
  return {
357
373
  rendered: rendered,
@@ -458,6 +474,14 @@ const handler = eventHandler(async (event) => {
458
474
  type: 'title',
459
475
  attributes: {},
460
476
  content: 'SolidStep'
477
+ },
478
+ build_time: {
479
+ type: 'meta',
480
+ attributes: {
481
+ name: 'x-build-time',
482
+ content: Date.now().toString(),
483
+ description: 'IMPORTANT: This tag indicates the build time of the application and should not be removed.'
484
+ },
461
485
  }
462
486
  };
463
487
  const assets = await clientManifest.inputs[clientManifest.handler].assets();
@@ -501,6 +525,9 @@ const handler = eventHandler(async (event) => {
501
525
  }
502
526
  else {
503
527
  try {
528
+ if (!matched.loadingPage) {
529
+ throw new Error('No loading page');
530
+ }
504
531
  const { rendered, documentMeta, documentAssets, loaderData, } = await render({
505
532
  toRender: 'loading',
506
533
  entry: matched,
package/utils/cache.d.ts CHANGED
@@ -2,4 +2,5 @@ export declare const getCache: <T>(key: string) => T | null;
2
2
  export declare const setCache: <T>(key: string, value: T, ttlMs?: number) => void;
3
3
  export declare const invalidateCache: (key: string) => void;
4
4
  export declare const clearAllCache: () => void;
5
+ export declare const revalidatePath: (path: string) => void;
5
6
  //# sourceMappingURL=cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../utils/cache.ts"],"names":[],"mappings":"AA6CA,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,KAAK,MAAM,KAAG,CAAC,GAAG,IAe7C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,KAAK,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,MAAM,SA0BhE,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,SAU1C,CAAC;AAEF,eAAO,MAAM,aAAa,YAGzB,CAAC"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../utils/cache.ts"],"names":[],"mappings":"AA+CA,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,KAAK,MAAM,KAAG,CAAC,GAAG,IAe7C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,KAAK,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,MAAM,SA0BhE,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,SAU1C,CAAC;AAEF,eAAO,MAAM,aAAa,YAGzB,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,SAS1C,CAAC"}
package/utils/cache.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { getEvent, setResponseHeader } from 'vinxi/http';
1
2
  const MAX_CACHE_ENTRIES = 1000;
2
3
  const cacheMap = new Map();
3
4
  let head;
@@ -95,3 +96,12 @@ export const clearAllCache = () => {
95
96
  cacheMap.clear();
96
97
  head = tail = undefined;
97
98
  };
99
+ export const revalidatePath = (path) => {
100
+ // get and verify the event
101
+ const event = getEvent();
102
+ if (!event.path.includes('_server')) {
103
+ throw new Error('This function can only be used in server functions.');
104
+ }
105
+ // add the revalidate header as a flag for the server action to do diffing
106
+ setResponseHeader(event, 'X-Revalidate', path);
107
+ };
@@ -0,0 +1,72 @@
1
+ declare const SKIP_MODES: {
2
+ readonly CHILDREN: "children";
3
+ readonly FULL: "full";
4
+ };
5
+ type SkipMode = (typeof SKIP_MODES)[keyof typeof SKIP_MODES];
6
+ type SkipPredicate = (domNode: Node | VirtualNode, virtualNode: VirtualNode) => boolean | SkipMode;
7
+ type PreDiffApplyHook = (info: {
8
+ diff: DiffResult;
9
+ node: Node;
10
+ }) => boolean | undefined;
11
+ type PostDiffApplyHook = (info: {
12
+ diff: DiffResult;
13
+ node: Node;
14
+ }) => void;
15
+ type FilterOuterDiffHook = (oldNode: VirtualNode, newNode: VirtualNode, diffs: DiffResult[]) => DiffResult[] | undefined;
16
+ type TextDiffHook = (target: Node, currentData: string, oldValue: string, newValue: string) => void;
17
+ interface DiffOptions {
18
+ skipSelector: string | null;
19
+ skipPredicate: SkipPredicate | null;
20
+ skipAttributes: string[];
21
+ skipChildren: boolean;
22
+ skipMode: SkipMode;
23
+ debug: boolean;
24
+ diffcap: number;
25
+ valueDiffing: boolean;
26
+ caseSensitive: boolean;
27
+ preVirtualDiffApply: PreDiffApplyHook | null;
28
+ postVirtualDiffApply: PostDiffApplyHook | null;
29
+ preDiffApply: PreDiffApplyHook | null;
30
+ postDiffApply: PostDiffApplyHook | null;
31
+ filterOuterDiff: FilterOuterDiffHook | null;
32
+ textDiff: TextDiffHook | null;
33
+ document: Document | null;
34
+ }
35
+ interface VirtualNode {
36
+ nodeType: number;
37
+ nodeName: string;
38
+ route?: number[];
39
+ data?: string;
40
+ attributes?: Record<string, string>;
41
+ value?: string;
42
+ checked?: boolean;
43
+ selected?: boolean;
44
+ childNodes?: VirtualNode[];
45
+ innerDone?: boolean;
46
+ skipFull?: boolean;
47
+ }
48
+ interface DiffResult {
49
+ action: string;
50
+ route: number[];
51
+ from?: number[];
52
+ to?: number[];
53
+ element?: VirtualNode;
54
+ index?: number;
55
+ name?: string;
56
+ value?: string;
57
+ oldValue?: string | boolean | VirtualNode;
58
+ newValue?: string | boolean | VirtualNode;
59
+ }
60
+ export declare const nodeToObj: (node: Node, options?: Partial<DiffOptions>) => VirtualNode | null;
61
+ export declare const stringToObj: (htmlString: string, options?: Partial<DiffOptions>) => VirtualNode;
62
+ export declare const objToNode: (obj: VirtualNode, options?: Partial<DiffOptions>) => Node | null;
63
+ export declare const diff: (elementA: string | Node | VirtualNode, elementB: string | Node | VirtualNode, options?: Partial<DiffOptions>) => DiffResult[];
64
+ export declare const apply: (element: Node, diffs: DiffResult[], options?: Partial<DiffOptions>) => boolean;
65
+ export declare const undo: (element: Node, diffs: DiffResult[], options?: Partial<DiffOptions>) => boolean;
66
+ export declare const createDiffDOM: (userOptions?: Partial<DiffOptions>) => {
67
+ diff: (elementA: string | Node | VirtualNode, elementB: string | Node | VirtualNode) => DiffResult[];
68
+ apply: (element: Node, diffs: DiffResult[]) => boolean;
69
+ undo: (element: Node, diffs: DiffResult[]) => boolean;
70
+ };
71
+ export {};
72
+ //# sourceMappingURL=diff-dom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-dom.d.ts","sourceRoot":"","sources":["../../utils/diff-dom.ts"],"names":[],"mappings":"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"}