@real-router/ssr-data-plugin 0.2.1 → 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.
- package/README.md +10 -9
- package/dist/cjs/index.d.ts +15 -4
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.mts +15 -4
- package/dist/esm/index.d.mts.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/constants.ts +1 -1
- package/src/factory.ts +29 -5
- package/src/index.ts +5 -1
- package/src/types.ts +20 -2
- package/src/validation.ts +7 -3
package/README.md
CHANGED
|
@@ -32,10 +32,11 @@ npm install @real-router/ssr-data-plugin
|
|
|
32
32
|
import { createRouter } from "@real-router/core";
|
|
33
33
|
import { cloneRouter } from "@real-router/core/api";
|
|
34
34
|
import { ssrDataPluginFactory } from "@real-router/ssr-data-plugin";
|
|
35
|
+
import type { DataLoaderFactoryMap } from "@real-router/ssr-data-plugin";
|
|
35
36
|
|
|
36
|
-
const loaders = {
|
|
37
|
-
"users.profile": async (params) => fetchUser(params.id),
|
|
38
|
-
"users.list": async () => fetchUsers(),
|
|
37
|
+
const loaders: DataLoaderFactoryMap = {
|
|
38
|
+
"users.profile": () => async (params) => fetchUser(params.id),
|
|
39
|
+
"users.list": () => async () => fetchUsers(),
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
// Base router — created once
|
|
@@ -54,15 +55,15 @@ router.dispose();
|
|
|
54
55
|
|
|
55
56
|
## Configuration
|
|
56
57
|
|
|
57
|
-
Loaders are keyed by **route name** (not path). Each loader receives route `params` and returns a `Promise`:
|
|
58
|
+
Loaders are keyed by **route name** (not path). Each value is a **factory function** `(router, getDependency) => loaderFn` that receives the router instance and a dependency getter. The factory runs once at plugin registration; the returned loader is cached. Each loader receives route `params` and returns a `Promise`:
|
|
58
59
|
|
|
59
60
|
```typescript
|
|
60
|
-
import type {
|
|
61
|
+
import type { DataLoaderFactoryMap } from "@real-router/ssr-data-plugin";
|
|
61
62
|
|
|
62
|
-
const loaders:
|
|
63
|
-
home: async () => ({ featured: await fetchFeatured() }),
|
|
64
|
-
"users.profile": async (params) => ({ user: await fetchUser(params.id) }),
|
|
65
|
-
"users.list": async () => ({ users: await fetchUsers() }),
|
|
63
|
+
const loaders: DataLoaderFactoryMap = {
|
|
64
|
+
home: () => async () => ({ featured: await fetchFeatured() }),
|
|
65
|
+
"users.profile": () => async (params) => ({ user: await fetchUser(params.id) }),
|
|
66
|
+
"users.list": () => async () => ({ users: await fetchUsers() }),
|
|
66
67
|
};
|
|
67
68
|
```
|
|
68
69
|
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
import { Params,
|
|
1
|
+
import { DefaultDependencies, Params, Router } from "@real-router/types";
|
|
2
|
+
import { PluginFactory } from "@real-router/core";
|
|
2
3
|
|
|
3
4
|
//#region src/types.d.ts
|
|
4
5
|
type DataLoaderFn = (params: Params) => Promise<unknown>;
|
|
5
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Factory function for creating data loaders.
|
|
8
|
+
* Receives the router instance and a dependency getter (same pattern as GuardFnFactory).
|
|
9
|
+
* Factory runs once when the plugin starts; the returned loader is cached.
|
|
10
|
+
*
|
|
11
|
+
* @template Dependencies - Router dependency map for typed `getDependency()` access.
|
|
12
|
+
* Defaults to `DefaultDependencies`. Pass your app's dependency interface for
|
|
13
|
+
* type-safe DI: `DataLoaderFnFactory<AppDependencies>`.
|
|
14
|
+
*/
|
|
15
|
+
type DataLoaderFnFactory<Dependencies extends DefaultDependencies = DefaultDependencies> = (router: Router<Dependencies>, getDependency: <K extends keyof Dependencies>(key: K) => Dependencies[K]) => DataLoaderFn;
|
|
16
|
+
type DataLoaderFactoryMap<Dependencies extends DefaultDependencies = DefaultDependencies> = Record<string, DataLoaderFnFactory<Dependencies>>;
|
|
6
17
|
//#endregion
|
|
7
18
|
//#region src/factory.d.ts
|
|
8
|
-
declare function ssrDataPluginFactory(loaders:
|
|
19
|
+
declare function ssrDataPluginFactory(loaders: DataLoaderFactoryMap): PluginFactory;
|
|
9
20
|
//#endregion
|
|
10
21
|
//#region src/index.d.ts
|
|
11
22
|
declare module "@real-router/types" {
|
|
@@ -14,5 +25,5 @@ declare module "@real-router/types" {
|
|
|
14
25
|
}
|
|
15
26
|
} //# sourceMappingURL=index.d.ts.map
|
|
16
27
|
//#endregion
|
|
17
|
-
export { type DataLoaderFn, type
|
|
28
|
+
export { type DataLoaderFactoryMap, type DataLoaderFn, type DataLoaderFnFactory, ssrDataPluginFactory };
|
|
18
29
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;;KAEY,YAAA,IAAgB,MAAA,EAAQ,MAAA,KAAW,OAAA;;;AAA/C;;;;;;;KAWY,mBAAA,sBACW,mBAAA,GAAsB,mBAAA,KAE3C,MAAA,EAAQ,MAAA,CAAO,YAAA,GACf,aAAA,mBAAgC,YAAA,EAAc,GAAA,EAAK,CAAA,KAAM,YAAA,CAAa,CAAA,MACnE,YAAA;AAAA,KAEO,oBAAA,sBACW,mBAAA,GAAsB,mBAAA,IACzC,MAAA,SAAe,mBAAA,CAAoB,YAAA;;;iBCdvB,oBAAA,CACd,OAAA,EAAS,oBAAA,GACR,aAAA;;;;YCDS,YAAA;IACR,IAAA;EAAA;AAAA"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@real-router/core/api`);const t=`[@real-router/ssr-data-plugin]`;function n(e){if(typeof e!=`object`||!e)throw TypeError(`${t} loaders must be a non-null object`);for(let[n,r]of Object.entries(e))if(typeof r!=`function`)throw TypeError(`${t} loader for route "${n}" must be a function`)}function r(
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@real-router/core/api`);const t=`[@real-router/ssr-data-plugin]`;function n(e){if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`${t} loaders must be a non-null object`);for(let[n,r]of Object.entries(e))if(typeof r!=`function`)throw TypeError(`${t} loader for route "${n}" must be a function`)}function r(r){return n(r),(n,i)=>{let a=(0,e.getPluginApi)(n),o=a.claimContextNamespace(`data`),s=new Map;try{for(let[e,a]of Object.entries(r)){let r=a(n,i);if(typeof r!=`function`)throw TypeError(`${t} factory for route "${e}" must return a function`);s.set(e,r)}}catch(e){throw o.release(),e}let c=a.addInterceptor(`start`,async(e,t)=>{let n=await e(t),r=s.get(n.name);return r&&o.write(n,await r(n.params)),n});return{teardown(){c(),o.release()}}}}exports.ssrDataPluginFactory=r;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/constants.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/constants.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["const LOGGER_CONTEXT = \"ssr-data-plugin\";\n\nexport const ERROR_PREFIX = `[@real-router/${LOGGER_CONTEXT}]`;\n","import { ERROR_PREFIX } from \"./constants\";\n\nimport type { DataLoaderFactoryMap } from \"./types\";\n\nexport function validateLoaders(\n loaders: unknown,\n): asserts loaders is DataLoaderFactoryMap {\n if (\n loaders === null ||\n typeof loaders !== \"object\" ||\n Array.isArray(loaders)\n ) {\n throw new TypeError(`${ERROR_PREFIX} loaders must be a non-null object`);\n }\n\n for (const [key, value] of Object.entries(\n loaders as Record<string, unknown>,\n )) {\n if (typeof value !== \"function\") {\n throw new TypeError(\n `${ERROR_PREFIX} loader for route \"${key}\" must be a function`,\n );\n }\n }\n}\n","import { getPluginApi } from \"@real-router/core/api\";\n\nimport { ERROR_PREFIX } from \"./constants\";\nimport { validateLoaders } from \"./validation\";\n\nimport type { DataLoaderFn, DataLoaderFactoryMap } from \"./types\";\nimport type { PluginFactory, Plugin } from \"@real-router/core\";\n\nexport function ssrDataPluginFactory(\n loaders: DataLoaderFactoryMap,\n): PluginFactory {\n validateLoaders(loaders);\n\n return (router, getDependency): Plugin => {\n const api = getPluginApi(router);\n const claim = api.claimContextNamespace(\"data\");\n\n const compiledLoaders = new Map<string, DataLoaderFn>();\n\n try {\n for (const [name, factory] of Object.entries(loaders)) {\n const loader = factory(router, getDependency);\n\n if (typeof loader !== \"function\") {\n throw new TypeError(\n `${ERROR_PREFIX} factory for route \"${name}\" must return a function`,\n );\n }\n\n compiledLoaders.set(name, loader);\n }\n } catch (error) {\n claim.release();\n\n throw error;\n }\n\n const removeStartInterceptor = api.addInterceptor(\n \"start\",\n async (next, path) => {\n const state = await next(path);\n const loader = compiledLoaders.get(state.name);\n\n if (loader) {\n claim.write(state, await loader(state.params));\n }\n\n return state;\n },\n );\n\n return {\n teardown() {\n removeStartInterceptor();\n claim.release();\n },\n };\n };\n}\n"],"mappings":"0GAAA,MAEa,EAAe,iCCE5B,SAAgB,EACd,EACyC,CACzC,GAEE,OAAO,GAAY,WADnB,GAEA,MAAM,QAAQ,EAAQ,CAEtB,MAAU,UAAU,GAAG,EAAa,oCAAoC,CAG1E,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAChC,EACD,CACC,GAAI,OAAO,GAAU,WACnB,MAAU,UACR,GAAG,EAAa,qBAAqB,EAAI,sBAC1C,CCbP,SAAgB,EACd,EACe,CAGf,OAFA,EAAgB,EAAQ,EAEhB,EAAQ,IAA0B,CACxC,IAAM,GAAA,EAAA,EAAA,cAAmB,EAAO,CAC1B,EAAQ,EAAI,sBAAsB,OAAO,CAEzC,EAAkB,IAAI,IAE5B,GAAI,CACF,IAAK,GAAM,CAAC,EAAM,KAAY,OAAO,QAAQ,EAAQ,CAAE,CACrD,IAAM,EAAS,EAAQ,EAAQ,EAAc,CAE7C,GAAI,OAAO,GAAW,WACpB,MAAU,UACR,GAAG,EAAa,sBAAsB,EAAK,0BAC5C,CAGH,EAAgB,IAAI,EAAM,EAAO,QAE5B,EAAO,CAGd,MAFA,EAAM,SAAS,CAET,EAGR,IAAM,EAAyB,EAAI,eACjC,QACA,MAAO,EAAM,IAAS,CACpB,IAAM,EAAQ,MAAM,EAAK,EAAK,CACxB,EAAS,EAAgB,IAAI,EAAM,KAAK,CAM9C,OAJI,GACF,EAAM,MAAM,EAAO,MAAM,EAAO,EAAM,OAAO,CAAC,CAGzC,GAEV,CAED,MAAO,CACL,UAAW,CACT,GAAwB,CACxB,EAAM,SAAS,EAElB"}
|
package/dist/esm/index.d.mts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
import { Params,
|
|
1
|
+
import { DefaultDependencies, Params, Router } from "@real-router/types";
|
|
2
|
+
import { PluginFactory } from "@real-router/core";
|
|
2
3
|
|
|
3
4
|
//#region src/types.d.ts
|
|
4
5
|
type DataLoaderFn = (params: Params) => Promise<unknown>;
|
|
5
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Factory function for creating data loaders.
|
|
8
|
+
* Receives the router instance and a dependency getter (same pattern as GuardFnFactory).
|
|
9
|
+
* Factory runs once when the plugin starts; the returned loader is cached.
|
|
10
|
+
*
|
|
11
|
+
* @template Dependencies - Router dependency map for typed `getDependency()` access.
|
|
12
|
+
* Defaults to `DefaultDependencies`. Pass your app's dependency interface for
|
|
13
|
+
* type-safe DI: `DataLoaderFnFactory<AppDependencies>`.
|
|
14
|
+
*/
|
|
15
|
+
type DataLoaderFnFactory<Dependencies extends DefaultDependencies = DefaultDependencies> = (router: Router<Dependencies>, getDependency: <K extends keyof Dependencies>(key: K) => Dependencies[K]) => DataLoaderFn;
|
|
16
|
+
type DataLoaderFactoryMap<Dependencies extends DefaultDependencies = DefaultDependencies> = Record<string, DataLoaderFnFactory<Dependencies>>;
|
|
6
17
|
//#endregion
|
|
7
18
|
//#region src/factory.d.ts
|
|
8
|
-
declare function ssrDataPluginFactory(loaders:
|
|
19
|
+
declare function ssrDataPluginFactory(loaders: DataLoaderFactoryMap): PluginFactory;
|
|
9
20
|
//#endregion
|
|
10
21
|
//#region src/index.d.ts
|
|
11
22
|
declare module "@real-router/types" {
|
|
@@ -14,5 +25,5 @@ declare module "@real-router/types" {
|
|
|
14
25
|
}
|
|
15
26
|
} //# sourceMappingURL=index.d.ts.map
|
|
16
27
|
//#endregion
|
|
17
|
-
export { type DataLoaderFn, type
|
|
28
|
+
export { type DataLoaderFactoryMap, type DataLoaderFn, type DataLoaderFnFactory, ssrDataPluginFactory };
|
|
18
29
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/esm/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;;KAEY,YAAA,IAAgB,MAAA,EAAQ,MAAA,KAAW,OAAA;;;AAA/C;;;;;;;KAWY,mBAAA,sBACW,mBAAA,GAAsB,mBAAA,KAE3C,MAAA,EAAQ,MAAA,CAAO,YAAA,GACf,aAAA,mBAAgC,YAAA,EAAc,GAAA,EAAK,CAAA,KAAM,YAAA,CAAa,CAAA,MACnE,YAAA;AAAA,KAEO,oBAAA,sBACW,mBAAA,GAAsB,mBAAA,IACzC,MAAA,SAAe,mBAAA,CAAoB,YAAA;;;iBCdvB,oBAAA,CACd,OAAA,EAAS,oBAAA,GACR,aAAA;;;;YCDS,YAAA;IACR,IAAA;EAAA;AAAA"}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getPluginApi as e}from"@real-router/core/api";const t=`[@real-router/ssr-data-plugin]`;function n(e){if(typeof e!=`object`||!e)throw TypeError(`${t} loaders must be a non-null object`);for(let[n,r]of Object.entries(e))if(typeof r!=`function`)throw TypeError(`${t} loader for route "${n}" must be a function`)}function r(
|
|
1
|
+
import{getPluginApi as e}from"@real-router/core/api";const t=`[@real-router/ssr-data-plugin]`;function n(e){if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`${t} loaders must be a non-null object`);for(let[n,r]of Object.entries(e))if(typeof r!=`function`)throw TypeError(`${t} loader for route "${n}" must be a function`)}function r(r){return n(r),(n,i)=>{let a=e(n),o=a.claimContextNamespace(`data`),s=new Map;try{for(let[e,a]of Object.entries(r)){let r=a(n,i);if(typeof r!=`function`)throw TypeError(`${t} factory for route "${e}" must return a function`);s.set(e,r)}}catch(e){throw o.release(),e}let c=a.addInterceptor(`start`,async(e,t)=>{let n=await e(t),r=s.get(n.name);return r&&o.write(n,await r(n.params)),n});return{teardown(){c(),o.release()}}}}export{r as ssrDataPluginFactory};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/constants.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/constants.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["const LOGGER_CONTEXT = \"ssr-data-plugin\";\n\nexport const ERROR_PREFIX = `[@real-router/${LOGGER_CONTEXT}]`;\n","import { ERROR_PREFIX } from \"./constants\";\n\nimport type { DataLoaderFactoryMap } from \"./types\";\n\nexport function validateLoaders(\n loaders: unknown,\n): asserts loaders is DataLoaderFactoryMap {\n if (\n loaders === null ||\n typeof loaders !== \"object\" ||\n Array.isArray(loaders)\n ) {\n throw new TypeError(`${ERROR_PREFIX} loaders must be a non-null object`);\n }\n\n for (const [key, value] of Object.entries(\n loaders as Record<string, unknown>,\n )) {\n if (typeof value !== \"function\") {\n throw new TypeError(\n `${ERROR_PREFIX} loader for route \"${key}\" must be a function`,\n );\n }\n }\n}\n","import { getPluginApi } from \"@real-router/core/api\";\n\nimport { ERROR_PREFIX } from \"./constants\";\nimport { validateLoaders } from \"./validation\";\n\nimport type { DataLoaderFn, DataLoaderFactoryMap } from \"./types\";\nimport type { PluginFactory, Plugin } from \"@real-router/core\";\n\nexport function ssrDataPluginFactory(\n loaders: DataLoaderFactoryMap,\n): PluginFactory {\n validateLoaders(loaders);\n\n return (router, getDependency): Plugin => {\n const api = getPluginApi(router);\n const claim = api.claimContextNamespace(\"data\");\n\n const compiledLoaders = new Map<string, DataLoaderFn>();\n\n try {\n for (const [name, factory] of Object.entries(loaders)) {\n const loader = factory(router, getDependency);\n\n if (typeof loader !== \"function\") {\n throw new TypeError(\n `${ERROR_PREFIX} factory for route \"${name}\" must return a function`,\n );\n }\n\n compiledLoaders.set(name, loader);\n }\n } catch (error) {\n claim.release();\n\n throw error;\n }\n\n const removeStartInterceptor = api.addInterceptor(\n \"start\",\n async (next, path) => {\n const state = await next(path);\n const loader = compiledLoaders.get(state.name);\n\n if (loader) {\n claim.write(state, await loader(state.params));\n }\n\n return state;\n },\n );\n\n return {\n teardown() {\n removeStartInterceptor();\n claim.release();\n },\n };\n };\n}\n"],"mappings":"qDAAA,MAEa,EAAe,iCCE5B,SAAgB,EACd,EACyC,CACzC,GAEE,OAAO,GAAY,WADnB,GAEA,MAAM,QAAQ,EAAQ,CAEtB,MAAU,UAAU,GAAG,EAAa,oCAAoC,CAG1E,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAChC,EACD,CACC,GAAI,OAAO,GAAU,WACnB,MAAU,UACR,GAAG,EAAa,qBAAqB,EAAI,sBAC1C,CCbP,SAAgB,EACd,EACe,CAGf,OAFA,EAAgB,EAAQ,EAEhB,EAAQ,IAA0B,CACxC,IAAM,EAAM,EAAa,EAAO,CAC1B,EAAQ,EAAI,sBAAsB,OAAO,CAEzC,EAAkB,IAAI,IAE5B,GAAI,CACF,IAAK,GAAM,CAAC,EAAM,KAAY,OAAO,QAAQ,EAAQ,CAAE,CACrD,IAAM,EAAS,EAAQ,EAAQ,EAAc,CAE7C,GAAI,OAAO,GAAW,WACpB,MAAU,UACR,GAAG,EAAa,sBAAsB,EAAK,0BAC5C,CAGH,EAAgB,IAAI,EAAM,EAAO,QAE5B,EAAO,CAGd,MAFA,EAAM,SAAS,CAET,EAGR,IAAM,EAAyB,EAAI,eACjC,QACA,MAAO,EAAM,IAAS,CACpB,IAAM,EAAQ,MAAM,EAAK,EAAK,CACxB,EAAS,EAAgB,IAAI,EAAM,KAAK,CAM9C,OAJI,GACF,EAAM,MAAM,EAAO,MAAM,EAAO,EAAM,OAAO,CAAC,CAGzC,GAEV,CAED,MAAO,CACL,UAAW,CACT,GAAwB,CACxB,EAAM,SAAS,EAElB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@real-router/ssr-data-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "SSR per-route data loading plugin for Real-Router",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"test": "vitest",
|
|
47
47
|
"test:properties": "vitest run --config vitest.config.properties.mts",
|
|
48
48
|
"test:stress": "vitest --config vitest.config.stress.mts --run",
|
|
49
|
-
"build": "tsdown --config-loader unrun",
|
|
50
49
|
"type-check": "tsc --noEmit",
|
|
51
50
|
"lint": "eslint --cache --ext .ts src/ tests/ --fix --max-warnings 0",
|
|
52
51
|
"lint:package": "publint",
|
|
53
|
-
"lint:types": "attw --pack ."
|
|
52
|
+
"lint:types": "attw --pack .",
|
|
53
|
+
"bundle": "tsdown --config-loader unrun"
|
|
54
54
|
}
|
|
55
55
|
}
|
package/src/constants.ts
CHANGED
package/src/factory.ts
CHANGED
|
@@ -1,24 +1,48 @@
|
|
|
1
1
|
import { getPluginApi } from "@real-router/core/api";
|
|
2
2
|
|
|
3
|
+
import { ERROR_PREFIX } from "./constants";
|
|
3
4
|
import { validateLoaders } from "./validation";
|
|
4
5
|
|
|
5
|
-
import type {
|
|
6
|
+
import type { DataLoaderFn, DataLoaderFactoryMap } from "./types";
|
|
6
7
|
import type { PluginFactory, Plugin } from "@real-router/core";
|
|
7
8
|
|
|
8
|
-
export function ssrDataPluginFactory(
|
|
9
|
+
export function ssrDataPluginFactory(
|
|
10
|
+
loaders: DataLoaderFactoryMap,
|
|
11
|
+
): PluginFactory {
|
|
9
12
|
validateLoaders(loaders);
|
|
10
13
|
|
|
11
|
-
return (router): Plugin => {
|
|
14
|
+
return (router, getDependency): Plugin => {
|
|
12
15
|
const api = getPluginApi(router);
|
|
13
16
|
const claim = api.claimContextNamespace("data");
|
|
14
17
|
|
|
18
|
+
const compiledLoaders = new Map<string, DataLoaderFn>();
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
for (const [name, factory] of Object.entries(loaders)) {
|
|
22
|
+
const loader = factory(router, getDependency);
|
|
23
|
+
|
|
24
|
+
if (typeof loader !== "function") {
|
|
25
|
+
throw new TypeError(
|
|
26
|
+
`${ERROR_PREFIX} factory for route "${name}" must return a function`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
compiledLoaders.set(name, loader);
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
claim.release();
|
|
34
|
+
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
const removeStartInterceptor = api.addInterceptor(
|
|
16
39
|
"start",
|
|
17
40
|
async (next, path) => {
|
|
18
41
|
const state = await next(path);
|
|
42
|
+
const loader = compiledLoaders.get(state.name);
|
|
19
43
|
|
|
20
|
-
if (
|
|
21
|
-
claim.write(state, await
|
|
44
|
+
if (loader) {
|
|
45
|
+
claim.write(state, await loader(state.params));
|
|
22
46
|
}
|
|
23
47
|
|
|
24
48
|
return state;
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
import type { Params } from "@real-router/
|
|
1
|
+
import type { DefaultDependencies, Params, Router } from "@real-router/types";
|
|
2
2
|
|
|
3
3
|
export type DataLoaderFn = (params: Params) => Promise<unknown>;
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Factory function for creating data loaders.
|
|
7
|
+
* Receives the router instance and a dependency getter (same pattern as GuardFnFactory).
|
|
8
|
+
* Factory runs once when the plugin starts; the returned loader is cached.
|
|
9
|
+
*
|
|
10
|
+
* @template Dependencies - Router dependency map for typed `getDependency()` access.
|
|
11
|
+
* Defaults to `DefaultDependencies`. Pass your app's dependency interface for
|
|
12
|
+
* type-safe DI: `DataLoaderFnFactory<AppDependencies>`.
|
|
13
|
+
*/
|
|
14
|
+
export type DataLoaderFnFactory<
|
|
15
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
16
|
+
> = (
|
|
17
|
+
router: Router<Dependencies>,
|
|
18
|
+
getDependency: <K extends keyof Dependencies>(key: K) => Dependencies[K],
|
|
19
|
+
) => DataLoaderFn;
|
|
20
|
+
|
|
21
|
+
export type DataLoaderFactoryMap<
|
|
22
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
23
|
+
> = Record<string, DataLoaderFnFactory<Dependencies>>;
|
package/src/validation.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { ERROR_PREFIX } from "./constants";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { DataLoaderFactoryMap } from "./types";
|
|
4
4
|
|
|
5
5
|
export function validateLoaders(
|
|
6
6
|
loaders: unknown,
|
|
7
|
-
): asserts loaders is
|
|
8
|
-
if (
|
|
7
|
+
): asserts loaders is DataLoaderFactoryMap {
|
|
8
|
+
if (
|
|
9
|
+
loaders === null ||
|
|
10
|
+
typeof loaders !== "object" ||
|
|
11
|
+
Array.isArray(loaders)
|
|
12
|
+
) {
|
|
9
13
|
throw new TypeError(`${ERROR_PREFIX} loaders must be a non-null object`);
|
|
10
14
|
}
|
|
11
15
|
|