@inox-tools/request-state 0.0.0-transitive-runtime-20240819000048
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/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/events.js +5 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.js +10 -0
- package/dist/plugin.js.map +1 -0
- package/dist/runtime/clientState.js +8 -0
- package/dist/runtime/clientState.js.map +1 -0
- package/dist/runtime/middleware.js +9 -0
- package/dist/runtime/middleware.js.map +1 -0
- package/dist/runtime/serverState.js +8 -0
- package/dist/runtime/serverState.js.map +1 -0
- package/package.json +53 -0
- package/src/events.ts +24 -0
- package/src/index.ts +29 -0
- package/src/plugin.ts +29 -0
- package/src/runtime/clientState.ts +57 -0
- package/src/runtime/middleware.ts +31 -0
- package/src/runtime/serverState.ts +58 -0
- package/virtual.d.ts +12 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Luiz Ferraz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img alt="InoxTools" width="350px" src="https://github.com/Fryuni/inox-tools/blob/main/assets/shield.png?raw=true"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# Request State
|
|
6
|
+
|
|
7
|
+
Astro provides [`Astro.locals`/`context.locals`](https://docs.astro.build/en/reference/api-reference/#astrolocals) for shared state between different components being rendered in a page.
|
|
8
|
+
|
|
9
|
+
This state can only be accessed from an Astro component using the `Astro` constant or from middlewares from the context. Sharing state between framework components is not provided and inconvenient. State using `Astro.locals` is also not shared with the client after the request completes, resulting in problems with the server and the client rendering different contents.
|
|
10
|
+
|
|
11
|
+
This library provides a solution for those problems:
|
|
12
|
+
|
|
13
|
+
- You can share state between any component used in a request, even between frameworks.
|
|
14
|
+
- The final state formed in the server while rendering a request is available for all client code running in the rendered page.
|
|
15
|
+
- UI framework components can keep their state from server to client.
|
|
16
|
+
|
|
17
|
+
## Installing the dependency
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
npm i @inox-tools/request-state
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Add the integration to your `astro.config.mjs`:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
// astro.config.mjs
|
|
27
|
+
import { defineConfig } from 'astro';
|
|
28
|
+
import requestState from '@inox-tools/request-state';
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
integrations: [requestState({})],
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Prerequisites
|
|
36
|
+
|
|
37
|
+
When using the Cloudflare adapter, you'll need to [enable AsyncLocalStorage manually](https://developers.cloudflare.com/workers/runtime-apis/nodejs/#enable-only-asynclocalstorage).
|
|
38
|
+
|
|
39
|
+
## How to use
|
|
40
|
+
|
|
41
|
+
Import the two functions anywhere in your code:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { getState, setState } from '@it-astro:state';
|
|
45
|
+
|
|
46
|
+
setState('some key', 'some value');
|
|
47
|
+
|
|
48
|
+
const state = getState('some key');
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Alternatively, provide an initial value to be used if not present in the state:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { getState } from '@it-astro:state';
|
|
55
|
+
|
|
56
|
+
// `'initial value'` will be used if `'some key'` is not in the state
|
|
57
|
+
const state = getState('some key', 'initial value');
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The state can be any value supported by the [`devalue` library](https://www.npmjs.com/package/devalue) plus:
|
|
61
|
+
|
|
62
|
+
- `Date`s
|
|
63
|
+
- `URL`s
|
|
64
|
+
- Global symbols (`Symbol.for`)
|
|
65
|
+
- Well-known symbols (`Symbol.XXX`)
|
|
66
|
+
|
|
67
|
+
### `setState`
|
|
68
|
+
|
|
69
|
+
Params:
|
|
70
|
+
|
|
71
|
+
- `key` (`string`): The key of a value in the state
|
|
72
|
+
- `value` (`unknown`): The value to be set
|
|
73
|
+
|
|
74
|
+
### `getState`
|
|
75
|
+
|
|
76
|
+
Params:
|
|
77
|
+
|
|
78
|
+
- `key` (`string`): The key of a value in the state
|
|
79
|
+
- `valueIfMissing` (`unknown`): An optional value to set if there is no value in the state for the given key.
|
|
80
|
+
|
|
81
|
+
Returns the value of the state for the given key, if present. If now, sets the value to `valueIfMissing` and returns it.
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
Astro Request State is available under the MIT license.
|
package/dist/events.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/events.ts"],"names":["ServerStateLoaded","previousState","serverState","options"],"mappings":"AAEO,MAAMA,CAAAA,SAA0B,KAAM,CAGrC,WAAA,CAQGC,EAKAC,CACTC,CAAAA,CAAAA,CACC,CACD,KAAMH,CAAAA,CAAAA,CAAkB,KAAMG,CAAO,CAAA,CAR5B,mBAAAF,CAKA,CAAA,IAAA,CAAA,WAAA,CAAAC,EAIV,CAnBA,OAAc,KAAO,+BAoBtB","file":"events.js","sourcesContent":["export type State = Map<string, unknown>;\n\nexport class ServerStateLoaded extends Event {\n\tpublic static NAME = '@it-astro:server-state-loaded' as const;\n\n\tpublic constructor(\n\t\t/**\n\t\t * The client state before loading the server state.\n\t\t *\n\t\t * On first load, this will be an empty map.\n\t\t * When using View Transitions and navigating to another page,\n\t\t * this will be state of the previous page.\n\t\t */\n\t\treadonly previousState: State,\n\n\t\t/**\n\t\t * The server state that will be loaded.\n\t\t */\n\t\treadonly serverState: State,\n\t\toptions?: EventInit\n\t) {\n\t\tsuper(ServerStateLoaded.NAME, options);\n\t}\n}\n"]}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineIntegration, createResolver, addVitePlugin } from 'astro-integration-kit';
|
|
2
|
+
import { plugin } from './plugin.js';
|
|
3
|
+
|
|
4
|
+
var g=defineIntegration({name:"@inox-tools/request-state",setup(){const{resolve:t}=createResolver(import.meta.url);return {hooks:{"astro:config:setup":e=>{const{addMiddleware:r}=e;e.logger.debug("Adding request-state middleware"),r({order:"pre",entrypoint:t("runtime/middleware.js")}),e.logger.debug("Adding request-state virtual module"),addVitePlugin(e,{warnDuplicated:!0,plugin:plugin()});}}}}});
|
|
5
|
+
|
|
6
|
+
export { g as default };
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["src_default","defineIntegration","resolve","createResolver","params","addMiddleware","addVitePlugin","plugin"],"mappings":";;;AAGA,IAAOA,CAAQC,CAAAA,iBAAAA,CAAkB,CAChC,IAAM,CAAA,2BAAA,CACN,KAAQ,EAAA,CACP,KAAM,CAAE,OAAA,CAAAC,CAAQ,CAAA,CAAIC,eAAe,MAAY,CAAA,IAAA,CAAA,GAAG,CAElD,CAAA,OAAO,CACN,KAAO,CAAA,CACN,oBAAuBC,CAAAA,CAAAA,EAAW,CACjC,KAAM,CAAE,aAAAC,CAAAA,CAAc,EAAID,CAE1BA,CAAAA,CAAAA,CAAO,MAAO,CAAA,KAAA,CAAM,iCAAiC,CACrDC,CAAAA,CAAAA,CAAc,CACb,KAAA,CAAO,MACP,UAAYH,CAAAA,CAAAA,CAAQ,uBAAuB,CAC5C,CAAC,CAEDE,CAAAA,CAAAA,CAAO,MAAO,CAAA,KAAA,CAAM,qCAAqC,CACzDE,CAAAA,aAAAA,CAAcF,CAAQ,CAAA,CACrB,eAAgB,CAChB,CAAA,CAAA,MAAA,CAAQG,MAAO,EAChB,CAAC,EACF,CACD,CACD,CACD,CACD,CAAC","file":"index.js","sourcesContent":["import { defineIntegration, addVitePlugin, createResolver } from 'astro-integration-kit';\nimport { plugin } from './plugin.js';\n\nexport default defineIntegration({\n\tname: '@inox-tools/request-state',\n\tsetup() {\n\t\tconst { resolve } = createResolver(import.meta.url);\n\n\t\treturn {\n\t\t\thooks: {\n\t\t\t\t'astro:config:setup': (params) => {\n\t\t\t\t\tconst { addMiddleware } = params;\n\n\t\t\t\t\tparams.logger.debug('Adding request-state middleware');\n\t\t\t\t\taddMiddleware({\n\t\t\t\t\t\torder: 'pre',\n\t\t\t\t\t\tentrypoint: resolve('runtime/middleware.js'),\n\t\t\t\t\t});\n\n\t\t\t\t\tparams.logger.debug('Adding request-state virtual module');\n\t\t\t\t\taddVitePlugin(params, {\n\t\t\t\t\t\twarnDuplicated: true,\n\t\t\t\t\t\tplugin: plugin(),\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t},\n});\n"]}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createResolver } from 'astro-integration-kit';
|
|
2
|
+
|
|
3
|
+
const r="@it-astro:state",o=`\0${r}`,u=()=>{const{resolve:e}=createResolver(import.meta.url);return {name:"@inox-tools/request-state/vite-plugin",resolveId(t){if(t===r)return o},load(t,s){if(t!==o)return;const n=s?.ssr?"serverState.js":"clientState.js";return `
|
|
4
|
+
export {setState, getState} from '${e("runtime",n)}';
|
|
5
|
+
export {ServerStateLoaded} from '${e("events.js")}';
|
|
6
|
+
`.trim()}}};
|
|
7
|
+
|
|
8
|
+
export { u as plugin };
|
|
9
|
+
//# sourceMappingURL=plugin.js.map
|
|
10
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts"],"names":["MODULE_ID","RESOLVED_MODULE_ID","plugin","resolve","createResolver","id","options","stateSource"],"mappings":";;AAGA,MAAMA,EAAY,iBACZC,CAAAA,CAAAA,CAAqB,CAAOD,EAAAA,EAAAA,CAAS,CAE9BE,CAAAA,CAAAA,CAAAA,CAAS,IAAc,CACnC,KAAM,CAAE,OAAAC,CAAAA,CAAQ,EAAIC,cAAe,CAAA,MAAA,CAAA,IAAA,CAAY,GAAG,CAAA,CAElD,OAAO,CACN,KAAM,uCACN,CAAA,SAAA,CAAUC,EAAI,CACb,GAAIA,IAAOL,CACV,CAAA,OAAOC,CAET,CAAA,CACA,IAAKI,CAAAA,CAAAA,CAAIC,EAAS,CACjB,GAAID,CAAOJ,GAAAA,CAAAA,CAAoB,OAE/B,MAAMM,EAAcD,CAAS,EAAA,GAAA,CAAM,gBAAmB,CAAA,gBAAA,CAGtD,OAAO,CAAA;AAAA,kCAFYH,EAAAA,CAAAA,CAAQ,SAAWI,CAAAA,CAAW,CAGN,CAAA;AAAA,iCACXJ,EAAAA,CAAAA,CAAQ,WAAW,CAAC,CAAA;AAAA,CACrD,CAAA,IAAA,EACA,CACD,CACD","file":"plugin.js","sourcesContent":["import { createResolver } from 'astro-integration-kit';\nimport type { Plugin } from 'vite';\n\nconst MODULE_ID = '@it-astro:state';\nconst RESOLVED_MODULE_ID = `\\x00${MODULE_ID}`;\n\nexport const plugin = (): Plugin => {\n\tconst { resolve } = createResolver(import.meta.url);\n\n\treturn {\n\t\tname: '@inox-tools/request-state/vite-plugin',\n\t\tresolveId(id) {\n\t\t\tif (id === MODULE_ID) {\n\t\t\t\treturn RESOLVED_MODULE_ID;\n\t\t\t}\n\t\t},\n\t\tload(id, options) {\n\t\t\tif (id !== RESOLVED_MODULE_ID) return;\n\n\t\t\tconst stateSource = options?.ssr ? 'serverState.js' : 'clientState.js';\n\t\t\tconst importPath = resolve('runtime', stateSource);\n\n\t\t\treturn `\nexport {setState, getState} from '${importPath}';\nexport {ServerStateLoaded} from '${resolve('events.js')}';\n`.trim();\n\t\t},\n\t};\n};\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { parse } from 'devalue';
|
|
2
|
+
import { ServerStateLoaded } from '../events.js';
|
|
3
|
+
|
|
4
|
+
const i={URL:e=>new URL(e),Date:e=>new Date(e),GlobalSymbol:e=>Symbol.for(e),WellKnownSymbol:e=>Symbol[e]},a=e=>{const t=e.getElementById("it-astro-state");if(t?.textContent){const o=parse(t.textContent,i);return t.remove(),o}};let r=a(document);const n=new Map,s=()=>{const e=new ServerStateLoaded(new Map(n),r??new Map);if(document.dispatchEvent(e)){n.clear();for(const[t,o]of e.serverState.entries())n.set(t,o);}};s(),document.addEventListener("astro:before-swap",e=>{r=a(e.newDocument);}),document.addEventListener("astro:after-swap",s);const f=(e,t)=>(n.has(e)||t!==void 0&&n.set(e,t),n.get(e)),p=(e,t)=>{t===void 0?n.delete(e):n.set(e,t);};
|
|
5
|
+
|
|
6
|
+
export { f as getState, p as setState };
|
|
7
|
+
//# sourceMappingURL=clientState.js.map
|
|
8
|
+
//# sourceMappingURL=clientState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/clientState.ts"],"names":["revivers","value","loadState","doc","element","state","parse","nextState","applyState","event","ServerStateLoaded","key","getState","valueIfMissing","setState"],"mappings":";;;AAGA,MAAMA,CAAgD,CAAA,CACrD,IAAMC,CAAU,EAAA,IAAI,IAAIA,CAAK,CAAA,CAC7B,KAAOA,CAAU,EAAA,IAAI,IAAKA,CAAAA,CAAK,EAC/B,YAAeA,CAAAA,CAAAA,EAAU,MAAO,CAAA,GAAA,CAAIA,CAAK,CACzC,CAAA,eAAA,CAAkBA,CAAU,EAAA,MAAA,CAAOA,CAA4B,CAChE,CAAA,CAEMC,EAAaC,CAAqC,EAAA,CACvD,MAAMC,CAAUD,CAAAA,CAAAA,CAAI,cAAe,CAAA,gBAAgB,EAEnD,GAAIC,CAAAA,EAAS,WAAa,CAAA,CACzB,MAAMC,CAAQC,CAAAA,KAAAA,CAAMF,CAAQ,CAAA,WAAA,CAAaJ,CAAQ,CACjD,CAAA,OAAAI,EAAQ,MAAO,EAAA,CACRC,CACR,CACD,CAAA,CAEA,IAAIE,CAAAA,CAAYL,EAAU,QAAQ,CAAA,CAClC,MAAMG,CAAAA,CAAQ,IAAI,GAEZG,CAAAA,CAAAA,CAAa,IAAM,CACxB,MAAMC,CAAQ,CAAA,IAAIC,kBAAkB,IAAI,GAAA,CAAIL,CAAK,CAAGE,CAAAA,CAAAA,EAAa,IAAI,GAAK,EAE1E,GAAI,QAAA,CAAS,aAAcE,CAAAA,CAAK,EAAG,CAClCJ,CAAAA,CAAM,KAAM,EAAA,CACZ,SAAW,CAACM,CAAAA,CAAKV,CAAK,CAAKQ,GAAAA,CAAAA,CAAM,YAAY,OAAQ,EAAA,CACpDJ,CAAM,CAAA,GAAA,CAAIM,EAAKV,CAAK,EAEtB,CACD,CAAA,CAEAO,GAEA,CAAA,QAAA,CAAS,gBAAiB,CAAA,mBAAA,CAAsBC,GAAU,CACzDF,CAAAA,CAAYL,EAAUO,CAAM,CAAA,WAAW,EACxC,CAAC,CAAA,CACD,QAAS,CAAA,gBAAA,CAAiB,mBAAoBD,CAAU,CAAA,CAE3CI,MAAAA,CAAAA,CAAW,CAACD,CAAaE,CAAAA,CAAAA,IAChCR,CAAM,CAAA,GAAA,CAAIM,CAAG,CACbE,EAAAA,CAAAA,GAAmB,QACtBR,CAAM,CAAA,GAAA,CAAIM,EAAKE,CAAc,CAAA,CAGxBR,CAAM,CAAA,GAAA,CAAIM,CAAG,CAGRG,CAAAA,CAAAA,CAAAA,CAAW,CAACH,CAAAA,CAAaV,IAAyB,CAC1DA,CAAAA,GAAU,KACbI,CAAAA,CAAAA,CAAAA,CAAM,OAAOM,CAAG,CAAA,CAEhBN,EAAM,GAAIM,CAAAA,CAAAA,CAAKV,CAAK,EAEtB","file":"clientState.js","sourcesContent":["import { parse } from 'devalue';\nimport { type State, ServerStateLoaded } from '../events.js';\n\nconst revivers: Record<string, (value: any) => any> = {\n\tURL: (value) => new URL(value),\n\tDate: (value) => new Date(value),\n\tGlobalSymbol: (value) => Symbol.for(value),\n\tWellKnownSymbol: (value) => Symbol[value as keyof typeof Symbol],\n};\n\nconst loadState = (doc: Document): State | undefined => {\n\tconst element = doc.getElementById('it-astro-state');\n\n\tif (element?.textContent) {\n\t\tconst state = parse(element.textContent, revivers);\n\t\telement.remove();\n\t\treturn state;\n\t}\n};\n\nlet nextState = loadState(document);\nconst state = new Map();\n\nconst applyState = () => {\n\tconst event = new ServerStateLoaded(new Map(state), nextState ?? new Map());\n\n\tif (document.dispatchEvent(event)) {\n\t\tstate.clear();\n\t\tfor (const [key, value] of event.serverState.entries()) {\n\t\t\tstate.set(key, value);\n\t\t}\n\t}\n};\n\napplyState();\n\ndocument.addEventListener('astro:before-swap', (event) => {\n\tnextState = loadState(event.newDocument);\n});\ndocument.addEventListener('astro:after-swap', applyState);\n\nexport const getState = (key: string, valueIfMissing?: unknown): unknown => {\n\tif (!state.has(key)) {\n\t\tif (valueIfMissing !== undefined) {\n\t\t\tstate.set(key, valueIfMissing);\n\t\t}\n\t}\n\treturn state.get(key);\n};\n\nexport const setState = (key: string, value: unknown): void => {\n\tif (value === undefined) {\n\t\tstate.delete(key);\n\t} else {\n\t\tstate.set(key, value);\n\t}\n};\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineMiddleware } from 'astro/middleware';
|
|
2
|
+
import { collectState } from './serverState.js';
|
|
3
|
+
import { parse } from 'content-type';
|
|
4
|
+
|
|
5
|
+
const u=defineMiddleware(async(l,o)=>{const{getState:n,result:t}=await collectState(o),e=t.headers.get("Content-Type");if(e===null)return t;const{type:r}=parse(e);if(r!=="text/html"&&!r.startsWith("text/html+"))return t;async function*s(){for await(const i of t.body)yield i;const a=n();a&&(yield `<script id="it-astro-state" type="application/json+devalue">${a}</script>`);}return new Response(s(),t)});
|
|
6
|
+
|
|
7
|
+
export { u as onRequest };
|
|
8
|
+
//# sourceMappingURL=middleware.js.map
|
|
9
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/middleware.ts"],"names":["onRequest","defineMiddleware","_","next","getState","result","collectState","contentType","mediaType","parse","render","chunk","state"],"mappings":";;;;MAKaA,CAAYC,CAAAA,gBAAAA,CAAiB,MAAOC,CAAGC,CAAAA,CAAAA,GAAS,CAC5D,KAAM,CAAE,QAAAC,CAAAA,CAAAA,CAAU,OAAAC,CAAO,CAAA,CAAI,MAAMC,YAAaH,CAAAA,CAAI,EAE9CI,CAAcF,CAAAA,CAAAA,CAAO,OAAQ,CAAA,GAAA,CAAI,cAAc,CAErD,CAAA,GAAIE,IAAgB,IAAM,CAAA,OAAOF,EAEjC,KAAM,CAAE,IAAMG,CAAAA,CAAU,EAAIC,KAAMF,CAAAA,CAAW,EAE7C,GAAIC,CAAAA,GAAc,aAAe,CAACA,CAAAA,CAAU,UAAW,CAAA,YAAY,EAAG,OAAOH,CAAAA,CAE7E,eAAgBK,CAAS,EAAA,CACxB,gBAAiBC,CAASN,IAAAA,CAAAA,CAAO,IAChC,CAAA,MAAMM,EAGP,MAAMC,CAAAA,CAAQR,GAEVQ,CAAAA,CAAAA,GACH,MAAM,CAA+DA,4DAAAA,EAAAA,CAAK,CAE5E,SAAA,CAAA,EAAA,CAGA,OAAO,IAAI,QAAA,CAASF,GAAUL,CAAAA,CAAM,CACrC,CAAC","file":"middleware.js","sourcesContent":["import type { ReadableStream } from 'node:stream/web';\nimport { defineMiddleware } from 'astro/middleware';\nimport { collectState } from './serverState.js';\nimport { parse } from 'content-type';\n\nexport const onRequest = defineMiddleware(async (_, next) => {\n\tconst { getState, result } = await collectState(next);\n\n\tconst contentType = result.headers.get('Content-Type');\n\n\tif (contentType === null) return result;\n\n\tconst { type: mediaType } = parse(contentType);\n\n\tif (mediaType !== 'text/html' && !mediaType.startsWith('text/html+')) return result;\n\n\tasync function* render() {\n\t\tfor await (const chunk of result.body as ReadableStream<ArrayBuffer>) {\n\t\t\tyield chunk;\n\t\t}\n\n\t\tconst state = getState();\n\n\t\tif (state) {\n\t\t\tyield `<script id=\"it-astro-state\" type=\"application/json+devalue\">${state}</script>`;\n\t\t}\n\t}\n\n\t// @ts-expect-error generator not assignable to ReadableStream\n\treturn new Response(render(), result);\n});\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { stringify } from 'devalue';
|
|
3
|
+
|
|
4
|
+
const o=new AsyncLocalStorage,l=(t,e)=>{const n=o.getStore();if(n!==void 0)return n.has(t)||e!==void 0&&n.set(t,e),n.get(t)},p=(t,e)=>{const n=o.getStore();e===void 0?n?.delete(t):n?.set(t,e);},i=new Map(Object.entries(Symbol).filter(([t,e])=>typeof e=="symbol"&&typeof t=="string").map(([t,e])=>[e,t])),c={URL:t=>t instanceof URL&&t.href,Date:t=>t instanceof Date&&t.valueOf(),GlobalSymbol:t=>typeof t=="symbol"&&t.description!==void 0&&t===Symbol.for(t.description)&&t.description,WellKnownSymbol:t=>typeof t=="symbol"&&i.get(t)},d=async t=>{const e=new Map;return {result:await o.run(e,t),getState:()=>e.size>0&&stringify(e,c)}};
|
|
5
|
+
|
|
6
|
+
export { d as collectState, l as getState, p as setState };
|
|
7
|
+
//# sourceMappingURL=serverState.js.map
|
|
8
|
+
//# sourceMappingURL=serverState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/serverState.ts"],"names":["store","AsyncLocalStorage","getState","key","valueIfMissing","state","setState","value","wellKnownSymbols","reducers","collectState","cb","stringify"],"mappings":";;;AAKA,MAAMA,CAAQ,CAAA,IAAIC,kBAELC,CAAW,CAAA,CAACC,CAAaC,CAAAA,CAAAA,GAAsC,CAC3E,MAAMC,CAAAA,CAAQL,CAAM,CAAA,QAAA,GACpB,GAAIK,CAAAA,GAAU,KAEd,CAAA,CAAA,OAAKA,EAAM,GAAIF,CAAAA,CAAG,CACbC,EAAAA,CAAAA,GAAmB,QACtBC,CAAM,CAAA,GAAA,CAAIF,EAAKC,CAAc,CAAA,CAGxBC,EAAM,GAAIF,CAAAA,CAAG,CACrB,CAAA,CAEaG,EAAW,CAACH,CAAAA,CAAaI,CAAyB,GAAA,CAC9D,MAAMF,CAAQL,CAAAA,CAAAA,CAAM,QAAS,EAAA,CACzBO,IAAU,KACbF,CAAAA,CAAAA,CAAAA,EAAO,OAAOF,CAAG,CAAA,CAEjBE,GAAO,GAAIF,CAAAA,CAAAA,CAAKI,CAAK,EAEvB,EAOMC,CAAmB,CAAA,IAAI,GAC5B,CAAA,MAAA,CAAO,QAAQ,MAAM,CAAA,CACnB,MAAO,CAAA,CAAC,CAACL,CAAKI,CAAAA,CAAK,IAAM,OAAOA,CAAAA,EAAU,UAAY,OAAOJ,CAAAA,EAAQ,QAAQ,CAAA,CAC7E,IAAI,CAAC,CAACA,CAAKI,CAAAA,CAAK,IAAM,CAACA,CAAAA,CAAOJ,CAAG,CAAC,CACrC,CAEMM,CAAAA,CAAAA,CAAgD,CACrD,GAAA,CAAMF,GAAUA,CAAiB,YAAA,GAAA,EAAOA,CAAM,CAAA,IAAA,CAC9C,KAAOA,CAAUA,EAAAA,CAAAA,YAAiB,IAAQA,EAAAA,CAAAA,CAAM,SAChD,CAAA,YAAA,CAAeA,CACd,EAAA,OAAOA,GAAU,QACjBA,EAAAA,CAAAA,CAAM,cAAgB,KACtBA,CAAAA,EAAAA,CAAAA,GAAU,OAAO,GAAIA,CAAAA,CAAAA,CAAM,WAAW,CAAA,EACtCA,EAAM,WACP,CAAA,eAAA,CAAkBA,CAAU,EAAA,OAAOA,GAAU,QAAYC,EAAAA,CAAAA,CAAiB,GAAID,CAAAA,CAAK,CACpF,CAEaG,CAAAA,CAAAA,CAAe,MAAUC,CAAqD,EAAA,CAC1F,MAAMN,CAAQ,CAAA,IAAI,GAElB,CAAA,OAAO,CACN,MAFc,CAAA,MAAML,CAAM,CAAA,GAAA,CAAIK,EAAOM,CAAE,CAAA,CAGvC,QAAU,CAAA,IAAMN,EAAM,IAAO,CAAA,CAAA,EAAKO,UAAUP,CAAOI,CAAAA,CAAQ,CAC5D,CACD","file":"serverState.js","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { stringify } from 'devalue';\n\ntype State = Map<string, unknown>;\n\nconst store = new AsyncLocalStorage<State>();\n\nexport const getState = (key: string, valueIfMissing?: unknown): unknown => {\n\tconst state = store.getStore();\n\tif (state === undefined) return;\n\n\tif (!state.has(key)) {\n\t\tif (valueIfMissing !== undefined) {\n\t\t\tstate.set(key, valueIfMissing);\n\t\t}\n\t}\n\treturn state.get(key);\n};\n\nexport const setState = (key: string, value: unknown): void => {\n\tconst state = store.getStore();\n\tif (value === undefined) {\n\t\tstate?.delete(key);\n\t} else {\n\t\tstate?.set(key, value);\n\t}\n};\n\nexport type CollectedState<T> = {\n\tgetState: () => string | false;\n\tresult: T;\n};\n\nconst wellKnownSymbols = new Map(\n\tObject.entries(Symbol)\n\t\t.filter(([key, value]) => typeof value === 'symbol' && typeof key === 'string')\n\t\t.map(([key, value]) => [value, key])\n);\n\nconst reducers: Record<string, (value: any) => any> = {\n\tURL: (value) => value instanceof URL && value.href,\n\tDate: (value) => value instanceof Date && value.valueOf(),\n\tGlobalSymbol: (value) =>\n\t\ttypeof value === 'symbol' &&\n\t\tvalue.description !== undefined &&\n\t\tvalue === Symbol.for(value.description) &&\n\t\tvalue.description,\n\tWellKnownSymbol: (value) => typeof value === 'symbol' && wellKnownSymbols.get(value),\n};\n\nexport const collectState = async <R>(cb: () => Promise<R>): Promise<CollectedState<R>> => {\n\tconst state = new Map();\n\tconst result = await store.run(state, cb);\n\treturn {\n\t\tresult,\n\t\tgetState: () => state.size > 0 && stringify(state, reducers),\n\t};\n};\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@inox-tools/request-state",
|
|
3
|
+
"version": "0.0.0-transitive-runtime-20240819000048",
|
|
4
|
+
"description": "Shared request state between server and client",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"astro-integration",
|
|
7
|
+
"withastro",
|
|
8
|
+
"astro"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"author": "Luiz Ferraz <luiz@lferraz.com>",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"src",
|
|
22
|
+
"virtual.d.ts"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"astro-integration-kit": "~0.16.0",
|
|
26
|
+
"content-type": "^1.0.5",
|
|
27
|
+
"devalue": "^5.0.0",
|
|
28
|
+
"@inox-tools/utils": "^0.1.3"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/content-type": "^1.1.8",
|
|
32
|
+
"@types/node": "^22.4.0",
|
|
33
|
+
"@vitest/coverage-v8": "^2.0.5",
|
|
34
|
+
"@vitest/ui": "^2.0.5",
|
|
35
|
+
"astro": "^4.14.2",
|
|
36
|
+
"jest-extended": "^4.0.2",
|
|
37
|
+
"tsup": "8.2.4",
|
|
38
|
+
"typescript": "^5.5.4",
|
|
39
|
+
"vite": "^5.4.1",
|
|
40
|
+
"vitest": "^2.0.5"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"astro": "^4"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup",
|
|
47
|
+
"dev": "tsup --watch",
|
|
48
|
+
"prepublish": "pnpm run build",
|
|
49
|
+
"test": "echo 'No tests for now :('",
|
|
50
|
+
"test:dev": "vitest --coverage.enabled=true",
|
|
51
|
+
"test:run": "vitest run --coverage"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type State = Map<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export class ServerStateLoaded extends Event {
|
|
4
|
+
public static NAME = '@it-astro:server-state-loaded' as const;
|
|
5
|
+
|
|
6
|
+
public constructor(
|
|
7
|
+
/**
|
|
8
|
+
* The client state before loading the server state.
|
|
9
|
+
*
|
|
10
|
+
* On first load, this will be an empty map.
|
|
11
|
+
* When using View Transitions and navigating to another page,
|
|
12
|
+
* this will be state of the previous page.
|
|
13
|
+
*/
|
|
14
|
+
readonly previousState: State,
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The server state that will be loaded.
|
|
18
|
+
*/
|
|
19
|
+
readonly serverState: State,
|
|
20
|
+
options?: EventInit
|
|
21
|
+
) {
|
|
22
|
+
super(ServerStateLoaded.NAME, options);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineIntegration, addVitePlugin, createResolver } from 'astro-integration-kit';
|
|
2
|
+
import { plugin } from './plugin.js';
|
|
3
|
+
|
|
4
|
+
export default defineIntegration({
|
|
5
|
+
name: '@inox-tools/request-state',
|
|
6
|
+
setup() {
|
|
7
|
+
const { resolve } = createResolver(import.meta.url);
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
hooks: {
|
|
11
|
+
'astro:config:setup': (params) => {
|
|
12
|
+
const { addMiddleware } = params;
|
|
13
|
+
|
|
14
|
+
params.logger.debug('Adding request-state middleware');
|
|
15
|
+
addMiddleware({
|
|
16
|
+
order: 'pre',
|
|
17
|
+
entrypoint: resolve('runtime/middleware.js'),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
params.logger.debug('Adding request-state virtual module');
|
|
21
|
+
addVitePlugin(params, {
|
|
22
|
+
warnDuplicated: true,
|
|
23
|
+
plugin: plugin(),
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
});
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createResolver } from 'astro-integration-kit';
|
|
2
|
+
import type { Plugin } from 'vite';
|
|
3
|
+
|
|
4
|
+
const MODULE_ID = '@it-astro:state';
|
|
5
|
+
const RESOLVED_MODULE_ID = `\x00${MODULE_ID}`;
|
|
6
|
+
|
|
7
|
+
export const plugin = (): Plugin => {
|
|
8
|
+
const { resolve } = createResolver(import.meta.url);
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
name: '@inox-tools/request-state/vite-plugin',
|
|
12
|
+
resolveId(id) {
|
|
13
|
+
if (id === MODULE_ID) {
|
|
14
|
+
return RESOLVED_MODULE_ID;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
load(id, options) {
|
|
18
|
+
if (id !== RESOLVED_MODULE_ID) return;
|
|
19
|
+
|
|
20
|
+
const stateSource = options?.ssr ? 'serverState.js' : 'clientState.js';
|
|
21
|
+
const importPath = resolve('runtime', stateSource);
|
|
22
|
+
|
|
23
|
+
return `
|
|
24
|
+
export {setState, getState} from '${importPath}';
|
|
25
|
+
export {ServerStateLoaded} from '${resolve('events.js')}';
|
|
26
|
+
`.trim();
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { parse } from 'devalue';
|
|
2
|
+
import { type State, ServerStateLoaded } from '../events.js';
|
|
3
|
+
|
|
4
|
+
const revivers: Record<string, (value: any) => any> = {
|
|
5
|
+
URL: (value) => new URL(value),
|
|
6
|
+
Date: (value) => new Date(value),
|
|
7
|
+
GlobalSymbol: (value) => Symbol.for(value),
|
|
8
|
+
WellKnownSymbol: (value) => Symbol[value as keyof typeof Symbol],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const loadState = (doc: Document): State | undefined => {
|
|
12
|
+
const element = doc.getElementById('it-astro-state');
|
|
13
|
+
|
|
14
|
+
if (element?.textContent) {
|
|
15
|
+
const state = parse(element.textContent, revivers);
|
|
16
|
+
element.remove();
|
|
17
|
+
return state;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let nextState = loadState(document);
|
|
22
|
+
const state = new Map();
|
|
23
|
+
|
|
24
|
+
const applyState = () => {
|
|
25
|
+
const event = new ServerStateLoaded(new Map(state), nextState ?? new Map());
|
|
26
|
+
|
|
27
|
+
if (document.dispatchEvent(event)) {
|
|
28
|
+
state.clear();
|
|
29
|
+
for (const [key, value] of event.serverState.entries()) {
|
|
30
|
+
state.set(key, value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
applyState();
|
|
36
|
+
|
|
37
|
+
document.addEventListener('astro:before-swap', (event) => {
|
|
38
|
+
nextState = loadState(event.newDocument);
|
|
39
|
+
});
|
|
40
|
+
document.addEventListener('astro:after-swap', applyState);
|
|
41
|
+
|
|
42
|
+
export const getState = (key: string, valueIfMissing?: unknown): unknown => {
|
|
43
|
+
if (!state.has(key)) {
|
|
44
|
+
if (valueIfMissing !== undefined) {
|
|
45
|
+
state.set(key, valueIfMissing);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return state.get(key);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const setState = (key: string, value: unknown): void => {
|
|
52
|
+
if (value === undefined) {
|
|
53
|
+
state.delete(key);
|
|
54
|
+
} else {
|
|
55
|
+
state.set(key, value);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ReadableStream } from 'node:stream/web';
|
|
2
|
+
import { defineMiddleware } from 'astro/middleware';
|
|
3
|
+
import { collectState } from './serverState.js';
|
|
4
|
+
import { parse } from 'content-type';
|
|
5
|
+
|
|
6
|
+
export const onRequest = defineMiddleware(async (_, next) => {
|
|
7
|
+
const { getState, result } = await collectState(next);
|
|
8
|
+
|
|
9
|
+
const contentType = result.headers.get('Content-Type');
|
|
10
|
+
|
|
11
|
+
if (contentType === null) return result;
|
|
12
|
+
|
|
13
|
+
const { type: mediaType } = parse(contentType);
|
|
14
|
+
|
|
15
|
+
if (mediaType !== 'text/html' && !mediaType.startsWith('text/html+')) return result;
|
|
16
|
+
|
|
17
|
+
async function* render() {
|
|
18
|
+
for await (const chunk of result.body as ReadableStream<ArrayBuffer>) {
|
|
19
|
+
yield chunk;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const state = getState();
|
|
23
|
+
|
|
24
|
+
if (state) {
|
|
25
|
+
yield `<script id="it-astro-state" type="application/json+devalue">${state}</script>`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// @ts-expect-error generator not assignable to ReadableStream
|
|
30
|
+
return new Response(render(), result);
|
|
31
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { stringify } from 'devalue';
|
|
3
|
+
|
|
4
|
+
type State = Map<string, unknown>;
|
|
5
|
+
|
|
6
|
+
const store = new AsyncLocalStorage<State>();
|
|
7
|
+
|
|
8
|
+
export const getState = (key: string, valueIfMissing?: unknown): unknown => {
|
|
9
|
+
const state = store.getStore();
|
|
10
|
+
if (state === undefined) return;
|
|
11
|
+
|
|
12
|
+
if (!state.has(key)) {
|
|
13
|
+
if (valueIfMissing !== undefined) {
|
|
14
|
+
state.set(key, valueIfMissing);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return state.get(key);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const setState = (key: string, value: unknown): void => {
|
|
21
|
+
const state = store.getStore();
|
|
22
|
+
if (value === undefined) {
|
|
23
|
+
state?.delete(key);
|
|
24
|
+
} else {
|
|
25
|
+
state?.set(key, value);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type CollectedState<T> = {
|
|
30
|
+
getState: () => string | false;
|
|
31
|
+
result: T;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const wellKnownSymbols = new Map(
|
|
35
|
+
Object.entries(Symbol)
|
|
36
|
+
.filter(([key, value]) => typeof value === 'symbol' && typeof key === 'string')
|
|
37
|
+
.map(([key, value]) => [value, key])
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const reducers: Record<string, (value: any) => any> = {
|
|
41
|
+
URL: (value) => value instanceof URL && value.href,
|
|
42
|
+
Date: (value) => value instanceof Date && value.valueOf(),
|
|
43
|
+
GlobalSymbol: (value) =>
|
|
44
|
+
typeof value === 'symbol' &&
|
|
45
|
+
value.description !== undefined &&
|
|
46
|
+
value === Symbol.for(value.description) &&
|
|
47
|
+
value.description,
|
|
48
|
+
WellKnownSymbol: (value) => typeof value === 'symbol' && wellKnownSymbols.get(value),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const collectState = async <R>(cb: () => Promise<R>): Promise<CollectedState<R>> => {
|
|
52
|
+
const state = new Map();
|
|
53
|
+
const result = await store.run(state, cb);
|
|
54
|
+
return {
|
|
55
|
+
result,
|
|
56
|
+
getState: () => state.size > 0 && stringify(state, reducers),
|
|
57
|
+
};
|
|
58
|
+
};
|
package/virtual.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare module '@it-astro:state' {
|
|
2
|
+
export const getState: (key: string, valueIfMissing?: unknown) => unknown;
|
|
3
|
+
export const setState: (key: string, value: unknown) => void;
|
|
4
|
+
|
|
5
|
+
export { ServerStateLoaded } from './src/events.js';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface DocumentEventMap {
|
|
10
|
+
[ServerStateLoaded.NAME]: import('./src/events.js').ServerStateLoaded;
|
|
11
|
+
}
|
|
12
|
+
}
|