@real-router/search-schema-plugin 0.3.1 → 0.3.3
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/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +2 -2
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=`[search-schema-plugin]`;function n(e){let t=new Set;for(let n of e)if(n.path&&n.path.length>0){let e=n.path[0],r=typeof e==`object`&&`key`in e?e.key:e;t.add(String(r))}return t}function r(e,t){let n={};for(let
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@real-router/core/api");const t=`[search-schema-plugin]`;function n(e){let t=new Set;for(let n of e)if(n.path&&n.path.length>0){let e=n.path[0],r=typeof e==`object`&&`key`in e?e.key:e;t.add(String(r))}return t}function r(e,t){let n={};for(let[r,i]of Object.entries(e))t.has(r)||(n[r]=i);return n}var i=class{#e;#t;#n;#r;#i;#a;#o;constructor(e,t,n){this.#e=e,this.#t=t,this.#n=n.mode??`development`,this.#r=n.strict??!1,this.#i=n.onError,this.#u(),this.#a=this.#e.addInterceptor(`forwardState`,(e,t,n)=>{let r=e(t,n);return this.#l(r)}),this.#o=this.#n===`development`?this.#t.subscribeChanges(e=>{this.#s(e)}):()=>{}}getPlugin(){return{teardown:()=>{this.#a(),this.#o()}}}#s(e){switch(e.op){case`add`:case`replace`:for(let t of e.added)this.#f(t.name);break;case`update`:e.patch.defaultParams!==void 0&&this.#f(e.name);break}}#c(e){return this.#e.getRouteConfig(e)?.searchSchema}#l(e){let i=this.#c(e.name);if(!i)return e;let a=i[`~standard`].validate(e.params);if(a instanceof Promise)throw TypeError(`${t} Async schema validation is not supported. Route "${e.name}" returned a Promise from ~standard.validate().`);if(`value`in a){let t=this.#r?a.value:{...e.params,...a.value};return{...e,params:t}}if(this.#i)return{...e,params:this.#i(e.name,e.params,a.issues)};this.#n===`development`&&console.error(`${t} Route "${e.name}": invalid search params`,a.issues);let o=n(a.issues),s=r(e.params,o),c=this.#t.get(e.name)?.defaultParams,l=c?{...c,...s}:s;return{...e,params:l}}#u(){if(this.#n!==`development`)return;let e=this.#e.getTree();e&&this.#d(e)}#d(e){if(e.fullName&&this.#f(e.fullName),e.children instanceof Map)for(let t of e.children.values())t&&typeof t==`object`&&this.#d(t)}#f(e){let n=this.#c(e);if(!n)return;let r=this.#t.get(e)?.defaultParams;if(!r)return;let i=n[`~standard`].validate(r);i instanceof Promise||`issues`in i&&console.warn(`${t} Route "${e}": defaultParams do not pass searchSchema`,i.issues)}};const a=new Set([`development`,`production`]);function o(e){if(e.mode!==void 0&&!a.has(e.mode))throw TypeError(`${t} Invalid mode: "${e.mode}". Must be "development" or "production".`);if(e.strict!==void 0&&typeof e.strict!=`boolean`)throw TypeError(`${t} Invalid strict option: expected boolean, got ${typeof e.strict}.`);if(e.onError!==void 0&&typeof e.onError!=`function`)throw TypeError(`${t} Invalid onError: expected function, got ${typeof e.onError}.`)}function s(t={}){o(t);let n=Object.freeze({...t});return t=>new i((0,e.getPluginApi)(t),(0,e.getRoutesApi)(t),n).getPlugin()}exports.searchSchemaPlugin=s;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#pluginApi","#routesApi","#mode","#strict","#onError","#removeForwardStateInterceptor","#removeChangesSubscription","#validateExistingDefaultParams","#validateState","#onTreeChanged","#validateSingleRouteDefaultParams","#getSchema","#walkTree"],"sources":["../../src/constants.ts","../../src/helpers.ts","../../src/plugin.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["// packages/search-schema-plugin/src/constants.ts\n\nexport const ERROR_PREFIX = \"[search-schema-plugin]\";\n","// packages/search-schema-plugin/src/helpers.ts\n\nimport type { StandardSchemaV1Issue } from \"./types\";\nimport type { Params } from \"@real-router/core\";\n\n/**\n * Extract top-level keys from validation issues.\n * Only processes issues with a non-empty path — issues without path\n * affect the whole object and can't be stripped by key.\n */\nexport function getInvalidKeys(\n issues: readonly StandardSchemaV1Issue[],\n): Set<string> {\n const keys = new Set<string>();\n\n for (const issue of issues) {\n if (issue.path && issue.path.length > 0) {\n const segment = issue.path[0];\n const key =\n typeof segment === \"object\" && \"key\" in segment ? segment.key : segment;\n\n keys.add(String(key));\n }\n }\n\n return keys;\n}\n\n/** Create a shallow copy of params without the specified keys. */\nexport function omitKeys(params: Params, keys: Set<string>): Params {\n const result: Params = {};\n\n for (const key of Object.keys(params)) {\n if (!keys.has(key)) {\n result[key] = params[key];\n }\n }\n\n return result;\n}\n","import { ERROR_PREFIX } from \"./constants\";\nimport { getInvalidKeys, omitKeys } from \"./helpers\";\n\nimport type {\n SearchSchemaPluginOptions,\n StandardSchemaV1,\n StandardSchemaV1Issue,\n} from \"./types\";\nimport type { Params, Plugin, TreeChangedEvent } from \"@real-router/core\";\nimport type { PluginApi, RoutesApi } from \"@real-router/core/api\";\n\nexport class SearchSchemaPlugin {\n readonly #pluginApi: PluginApi;\n readonly #routesApi: RoutesApi;\n readonly #mode: \"development\" | \"production\";\n readonly #strict: boolean;\n readonly #onError:\n | ((\n routeName: string,\n params: Params,\n issues: readonly StandardSchemaV1Issue[],\n ) => Params)\n | undefined;\n readonly #removeForwardStateInterceptor: () => void;\n readonly #removeChangesSubscription: () => void;\n\n constructor(\n pluginApi: PluginApi,\n routesApi: RoutesApi,\n options: SearchSchemaPluginOptions,\n ) {\n this.#pluginApi = pluginApi;\n this.#routesApi = routesApi;\n this.#mode = options.mode ?? \"development\";\n this.#strict = options.strict ?? false;\n this.#onError = options.onError;\n\n this.#validateExistingDefaultParams();\n\n this.#removeForwardStateInterceptor = this.#pluginApi.addInterceptor(\n \"forwardState\",\n (next, routeName, routeParams) => {\n const result = next(routeName, routeParams);\n\n return this.#validateState(result);\n },\n );\n\n // Dev-time defaultParams validation for runtime tree mutations. Replaces the\n // old `add` interceptor: TREE_CHANGED additionally covers `update` (changed\n // defaultParams) and `replace` (new route set) — the gap the interceptor\n // could not reach. Production mode skips the subscription entirely.\n this.#removeChangesSubscription =\n this.#mode === \"development\"\n ? this.#routesApi.subscribeChanges((event) => {\n this.#onTreeChanged(event);\n })\n : () => {};\n }\n\n getPlugin(): Plugin {\n return {\n teardown: () => {\n this.#removeForwardStateInterceptor();\n this.#removeChangesSubscription();\n },\n };\n }\n\n #onTreeChanged(event: TreeChangedEvent): void {\n switch (event.op) {\n case \"add\":\n case \"replace\": {\n // `added` is FLAT (full dotted names, descendants included).\n for (const route of event.added) {\n this.#validateSingleRouteDefaultParams(route.name);\n }\n\n break;\n }\n case \"update\": {\n // Only a defaultParams change can newly violate the schema.\n if (event.patch.defaultParams !== undefined) {\n this.#validateSingleRouteDefaultParams(event.name);\n }\n\n break;\n }\n // \"remove\" / \"clear\": the routes are gone — nothing to validate.\n }\n }\n\n #getSchema(routeName: string): StandardSchemaV1 | undefined {\n return this.#pluginApi.getRouteConfig(routeName)?.searchSchema as\n | StandardSchemaV1\n | undefined;\n }\n\n #validateState(result: { name: string; params: Params }): {\n name: string;\n params: Params;\n } {\n const schema = this.#getSchema(result.name);\n\n if (!schema) {\n return result;\n }\n\n const validation = schema[\"~standard\"].validate(result.params);\n\n if (validation instanceof Promise) {\n throw new TypeError(\n `${ERROR_PREFIX} Async schema validation is not supported. Route \"${result.name}\" returned a Promise from ~standard.validate().`,\n );\n }\n\n if (\"value\" in validation) {\n const params = this.#strict\n ? (validation.value as Params)\n : { ...result.params, ...(validation.value as Params) };\n\n return { ...result, params };\n }\n\n if (this.#onError) {\n return {\n ...result,\n params: this.#onError(result.name, result.params, validation.issues),\n };\n }\n\n if (this.#mode === \"development\") {\n console.error(\n `${ERROR_PREFIX} Route \"${result.name}\": invalid search params`,\n validation.issues,\n );\n }\n\n const invalidKeys = getInvalidKeys(validation.issues);\n const stripped = omitKeys(result.params, invalidKeys);\n const route = this.#routesApi.get(result.name);\n const defaults = route?.defaultParams;\n const restored = defaults ? { ...defaults, ...stripped } : stripped;\n\n return { ...result, params: restored };\n }\n\n #validateExistingDefaultParams(): void {\n if (this.#mode !== \"development\") {\n return;\n }\n\n const tree = this.#pluginApi.getTree() as unknown as\n | { fullName?: string; children?: ReadonlyMap<string, unknown> }\n | undefined;\n\n /* v8 ignore next -- @preserve: getTree() always returns a RouteTree, defensive check */\n if (!tree) {\n return;\n }\n\n this.#walkTree(tree);\n }\n\n #walkTree(node: {\n fullName?: string;\n children?: ReadonlyMap<string, unknown>;\n }): void {\n if (node.fullName) {\n this.#validateSingleRouteDefaultParams(node.fullName);\n }\n\n /* v8 ignore next 3 -- @preserve: children is always a Map in RouteTree */\n if (node.children instanceof Map) {\n for (const child of node.children.values()) {\n if (child && typeof child === \"object\") {\n this.#walkTree(\n child as {\n fullName?: string;\n children?: ReadonlyMap<string, unknown>;\n },\n );\n }\n }\n }\n }\n\n #validateSingleRouteDefaultParams(routeName: string): void {\n const schema = this.#getSchema(routeName);\n\n if (!schema) {\n return;\n }\n\n const route = this.#routesApi.get(routeName);\n const defaultParams = route?.defaultParams;\n\n if (!defaultParams) {\n return;\n }\n\n const validation = schema[\"~standard\"].validate(defaultParams);\n\n if (validation instanceof Promise) {\n return;\n }\n\n if (\"issues\" in validation) {\n console.warn(\n `${ERROR_PREFIX} Route \"${routeName}\": defaultParams do not pass searchSchema`,\n validation.issues,\n );\n }\n }\n}\n","import { ERROR_PREFIX } from \"./constants\";\n\nimport type { SearchSchemaPluginOptions } from \"./types\";\n\nconst VALID_MODES = new Set([\"development\", \"production\"]);\n\nexport function validateOptions(options: SearchSchemaPluginOptions): void {\n if (options.mode !== undefined && !VALID_MODES.has(options.mode)) {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid mode: \"${options.mode}\". Must be \"development\" or \"production\".`,\n );\n }\n\n if (options.strict !== undefined && typeof options.strict !== \"boolean\") {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid strict option: expected boolean, got ${typeof options.strict}.`,\n );\n }\n\n if (options.onError !== undefined && typeof options.onError !== \"function\") {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid onError: expected function, got ${typeof options.onError}.`,\n );\n }\n}\n","import { getPluginApi, getRoutesApi } from \"@real-router/core/api\";\n\nimport { SearchSchemaPlugin } from \"./plugin\";\nimport { validateOptions } from \"./validation\";\n\nimport type { SearchSchemaPluginOptions } from \"./types\";\nimport type { PluginFactory, Plugin } from \"@real-router/core\";\n\nexport function searchSchemaPlugin(\n options: SearchSchemaPluginOptions = {},\n): PluginFactory {\n validateOptions(options);\n\n const frozenOptions: SearchSchemaPluginOptions = Object.freeze({\n ...options,\n });\n\n return (router): Plugin => {\n const pluginApi = getPluginApi(router);\n const routesApi = getRoutesApi(router);\n const plugin = new SearchSchemaPlugin(pluginApi, routesApi, frozenOptions);\n\n return plugin.getPlugin();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["#pluginApi","#routesApi","#mode","#strict","#onError","#removeForwardStateInterceptor","#removeChangesSubscription","#validateExistingDefaultParams","#validateState","#onTreeChanged","#validateSingleRouteDefaultParams","#getSchema","#walkTree"],"sources":["../../src/constants.ts","../../src/helpers.ts","../../src/plugin.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["// packages/search-schema-plugin/src/constants.ts\n\nexport const ERROR_PREFIX = \"[search-schema-plugin]\";\n","// packages/search-schema-plugin/src/helpers.ts\n\nimport type { StandardSchemaV1Issue } from \"./types\";\nimport type { Params } from \"@real-router/core\";\n\n/**\n * Extract top-level keys from validation issues.\n * Only processes issues with a non-empty path — issues without path\n * affect the whole object and can't be stripped by key.\n */\nexport function getInvalidKeys(\n issues: readonly StandardSchemaV1Issue[],\n): Set<string> {\n const keys = new Set<string>();\n\n for (const issue of issues) {\n if (issue.path && issue.path.length > 0) {\n const segment = issue.path[0];\n const key =\n typeof segment === \"object\" && \"key\" in segment ? segment.key : segment;\n\n keys.add(String(key));\n }\n }\n\n return keys;\n}\n\n/** Create a shallow copy of params without the specified keys. */\nexport function omitKeys(params: Params, keys: Set<string>): Params {\n const result: Params = {};\n\n for (const [key, value] of Object.entries(params)) {\n if (!keys.has(key)) {\n result[key] = value;\n }\n }\n\n return result;\n}\n","import { ERROR_PREFIX } from \"./constants\";\nimport { getInvalidKeys, omitKeys } from \"./helpers\";\n\nimport type {\n SearchSchemaPluginOptions,\n StandardSchemaV1,\n StandardSchemaV1Issue,\n} from \"./types\";\nimport type { Params, Plugin, TreeChangedEvent } from \"@real-router/core\";\nimport type { PluginApi, RoutesApi } from \"@real-router/core/api\";\n\nexport class SearchSchemaPlugin {\n readonly #pluginApi: PluginApi;\n readonly #routesApi: RoutesApi;\n readonly #mode: \"development\" | \"production\";\n readonly #strict: boolean;\n readonly #onError:\n | ((\n routeName: string,\n params: Params,\n issues: readonly StandardSchemaV1Issue[],\n ) => Params)\n | undefined;\n readonly #removeForwardStateInterceptor: () => void;\n readonly #removeChangesSubscription: () => void;\n\n constructor(\n pluginApi: PluginApi,\n routesApi: RoutesApi,\n options: SearchSchemaPluginOptions,\n ) {\n this.#pluginApi = pluginApi;\n this.#routesApi = routesApi;\n this.#mode = options.mode ?? \"development\";\n this.#strict = options.strict ?? false;\n this.#onError = options.onError;\n\n this.#validateExistingDefaultParams();\n\n this.#removeForwardStateInterceptor = this.#pluginApi.addInterceptor(\n \"forwardState\",\n (next, routeName, routeParams) => {\n const result = next(routeName, routeParams);\n\n return this.#validateState(result);\n },\n );\n\n // Dev-time defaultParams validation for runtime tree mutations. Replaces the\n // old `add` interceptor: TREE_CHANGED additionally covers `update` (changed\n // defaultParams) and `replace` (new route set) — the gap the interceptor\n // could not reach. Production mode skips the subscription entirely.\n this.#removeChangesSubscription =\n this.#mode === \"development\"\n ? this.#routesApi.subscribeChanges((event) => {\n this.#onTreeChanged(event);\n })\n : () => {};\n }\n\n getPlugin(): Plugin {\n return {\n teardown: () => {\n this.#removeForwardStateInterceptor();\n this.#removeChangesSubscription();\n },\n };\n }\n\n #onTreeChanged(event: TreeChangedEvent): void {\n switch (event.op) {\n case \"add\":\n case \"replace\": {\n // `added` is FLAT (full dotted names, descendants included).\n for (const route of event.added) {\n this.#validateSingleRouteDefaultParams(route.name);\n }\n\n break;\n }\n case \"update\": {\n // Only a defaultParams change can newly violate the schema.\n if (event.patch.defaultParams !== undefined) {\n this.#validateSingleRouteDefaultParams(event.name);\n }\n\n break;\n }\n // \"remove\" / \"clear\": the routes are gone — nothing to validate.\n }\n }\n\n #getSchema(routeName: string): StandardSchemaV1 | undefined {\n return this.#pluginApi.getRouteConfig(routeName)?.searchSchema as\n | StandardSchemaV1\n | undefined;\n }\n\n #validateState(result: { name: string; params: Params }): {\n name: string;\n params: Params;\n } {\n const schema = this.#getSchema(result.name);\n\n if (!schema) {\n return result;\n }\n\n const validation = schema[\"~standard\"].validate(result.params);\n\n if (validation instanceof Promise) {\n throw new TypeError(\n `${ERROR_PREFIX} Async schema validation is not supported. Route \"${result.name}\" returned a Promise from ~standard.validate().`,\n );\n }\n\n if (\"value\" in validation) {\n const params = this.#strict\n ? (validation.value as Params)\n : { ...result.params, ...(validation.value as Params) };\n\n return { ...result, params };\n }\n\n if (this.#onError) {\n return {\n ...result,\n params: this.#onError(result.name, result.params, validation.issues),\n };\n }\n\n if (this.#mode === \"development\") {\n console.error(\n `${ERROR_PREFIX} Route \"${result.name}\": invalid search params`,\n validation.issues,\n );\n }\n\n const invalidKeys = getInvalidKeys(validation.issues);\n const stripped = omitKeys(result.params, invalidKeys);\n const route = this.#routesApi.get(result.name);\n const defaults = route?.defaultParams;\n const restored = defaults ? { ...defaults, ...stripped } : stripped;\n\n return { ...result, params: restored };\n }\n\n #validateExistingDefaultParams(): void {\n if (this.#mode !== \"development\") {\n return;\n }\n\n const tree = this.#pluginApi.getTree() as unknown as\n | { fullName?: string; children?: ReadonlyMap<string, unknown> }\n | undefined;\n\n /* v8 ignore next -- @preserve: getTree() always returns a RouteTree, defensive check */\n if (!tree) {\n return;\n }\n\n this.#walkTree(tree);\n }\n\n #walkTree(node: {\n fullName?: string;\n children?: ReadonlyMap<string, unknown>;\n }): void {\n if (node.fullName) {\n this.#validateSingleRouteDefaultParams(node.fullName);\n }\n\n /* v8 ignore next 3 -- @preserve: children is always a Map in RouteTree */\n if (node.children instanceof Map) {\n for (const child of node.children.values()) {\n if (child && typeof child === \"object\") {\n this.#walkTree(\n child as {\n fullName?: string;\n children?: ReadonlyMap<string, unknown>;\n },\n );\n }\n }\n }\n }\n\n #validateSingleRouteDefaultParams(routeName: string): void {\n const schema = this.#getSchema(routeName);\n\n if (!schema) {\n return;\n }\n\n const route = this.#routesApi.get(routeName);\n const defaultParams = route?.defaultParams;\n\n if (!defaultParams) {\n return;\n }\n\n const validation = schema[\"~standard\"].validate(defaultParams);\n\n if (validation instanceof Promise) {\n return;\n }\n\n if (\"issues\" in validation) {\n console.warn(\n `${ERROR_PREFIX} Route \"${routeName}\": defaultParams do not pass searchSchema`,\n validation.issues,\n );\n }\n }\n}\n","import { ERROR_PREFIX } from \"./constants\";\n\nimport type { SearchSchemaPluginOptions } from \"./types\";\n\nconst VALID_MODES = new Set([\"development\", \"production\"]);\n\nexport function validateOptions(options: SearchSchemaPluginOptions): void {\n if (options.mode !== undefined && !VALID_MODES.has(options.mode)) {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid mode: \"${options.mode}\". Must be \"development\" or \"production\".`,\n );\n }\n\n if (options.strict !== undefined && typeof options.strict !== \"boolean\") {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid strict option: expected boolean, got ${typeof options.strict}.`,\n );\n }\n\n if (options.onError !== undefined && typeof options.onError !== \"function\") {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid onError: expected function, got ${typeof options.onError}.`,\n );\n }\n}\n","import { getPluginApi, getRoutesApi } from \"@real-router/core/api\";\n\nimport { SearchSchemaPlugin } from \"./plugin\";\nimport { validateOptions } from \"./validation\";\n\nimport type { SearchSchemaPluginOptions } from \"./types\";\nimport type { PluginFactory, Plugin } from \"@real-router/core\";\n\nexport function searchSchemaPlugin(\n options: SearchSchemaPluginOptions = {},\n): PluginFactory {\n validateOptions(options);\n\n const frozenOptions: SearchSchemaPluginOptions = Object.freeze({\n ...options,\n });\n\n return (router): Plugin => {\n const pluginApi = getPluginApi(router);\n const routesApi = getRoutesApi(router);\n const plugin = new SearchSchemaPlugin(pluginApi, routesApi, frozenOptions);\n\n return plugin.getPlugin();\n };\n}\n"],"mappings":"0GAEA,MAAa,EAAe,yBCQ5B,SAAgB,EACd,EACa,CACb,IAAM,EAAO,IAAI,IAEjB,IAAK,IAAM,KAAS,EAClB,GAAI,EAAM,MAAQ,EAAM,KAAK,OAAS,EAAG,CACvC,IAAM,EAAU,EAAM,KAAK,GACrB,EACJ,OAAO,GAAY,UAAY,QAAS,EAAU,EAAQ,IAAM,EAElE,EAAK,IAAI,OAAO,CAAG,CAAC,CACtB,CAGF,OAAO,CACT,CAGA,SAAgB,EAAS,EAAgB,EAA2B,CAClE,IAAM,EAAiB,CAAC,EAExB,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAM,EACzC,EAAK,IAAI,CAAG,IACf,EAAO,GAAO,GAIlB,OAAO,CACT,CC5BA,IAAa,EAAb,KAAgC,CAC9B,GACA,GACA,GACA,GACA,GAOA,GACA,GAEA,YACE,EACA,EACA,EACA,CACA,KAAKA,GAAa,EAClB,KAAKC,GAAa,EAClB,KAAKC,GAAQ,EAAQ,MAAQ,cAC7B,KAAKC,GAAU,EAAQ,QAAU,GACjC,KAAKC,GAAW,EAAQ,QAExB,KAAKG,GAA+B,EAEpC,KAAKF,GAAiC,KAAKL,GAAW,eACpD,gBACC,EAAM,EAAW,IAAgB,CAChC,IAAM,EAAS,EAAK,EAAW,CAAW,EAE1C,OAAO,KAAKQ,GAAe,CAAM,CACnC,CACF,EAMA,KAAKF,GACH,KAAKJ,KAAU,cACX,KAAKD,GAAW,iBAAkB,GAAU,CAC1C,KAAKQ,GAAe,CAAK,CAC3B,CAAC,MACK,CAAC,CACf,CAEA,WAAoB,CAClB,MAAO,CACL,aAAgB,CACd,KAAKJ,GAA+B,EACpC,KAAKC,GAA2B,CAClC,CACF,CACF,CAEA,GAAe,EAA+B,CAC5C,OAAQ,EAAM,GAAd,CACE,IAAK,MACL,IAAK,UAEH,IAAK,IAAM,KAAS,EAAM,MACxB,KAAKI,GAAkC,EAAM,IAAI,EAGnD,MAEF,IAAK,SAEC,EAAM,MAAM,gBAAkB,IAAA,IAChC,KAAKA,GAAkC,EAAM,IAAI,EAGnD,KAGJ,CACF,CAEA,GAAW,EAAiD,CAC1D,OAAO,KAAKV,GAAW,eAAe,CAAS,CAAC,EAAE,YAGpD,CAEA,GAAe,EAGb,CACA,IAAM,EAAS,KAAKW,GAAW,EAAO,IAAI,EAE1C,GAAI,CAAC,EACH,OAAO,EAGT,IAAM,EAAa,EAAO,YAAY,CAAC,SAAS,EAAO,MAAM,EAE7D,GAAI,aAAsB,QACxB,MAAU,UACR,GAAG,EAAa,oDAAoD,EAAO,KAAK,gDAClF,EAGF,GAAI,UAAW,EAAY,CACzB,IAAM,EAAS,KAAKR,GACf,EAAW,MACZ,CAAE,GAAG,EAAO,OAAQ,GAAI,EAAW,KAAiB,EAExD,MAAO,CAAE,GAAG,EAAQ,QAAO,CAC7B,CAEA,GAAI,KAAKC,GACP,MAAO,CACL,GAAG,EACH,OAAQ,KAAKA,GAAS,EAAO,KAAM,EAAO,OAAQ,EAAW,MAAM,CACrE,EAGE,KAAKF,KAAU,eACjB,QAAQ,MACN,GAAG,EAAa,UAAU,EAAO,KAAK,0BACtC,EAAW,MACb,EAGF,IAAM,EAAc,EAAe,EAAW,MAAM,EAC9C,EAAW,EAAS,EAAO,OAAQ,CAAW,EAE9C,EADQ,KAAKD,GAAW,IAAI,EAAO,IACpB,CAAC,EAAE,cAClB,EAAW,EAAW,CAAE,GAAG,EAAU,GAAG,CAAS,EAAI,EAE3D,MAAO,CAAE,GAAG,EAAQ,OAAQ,CAAS,CACvC,CAEA,IAAuC,CACrC,GAAI,KAAKC,KAAU,cACjB,OAGF,IAAM,EAAO,KAAKF,GAAW,QAAQ,EAKhC,GAIL,KAAKY,GAAU,CAAI,CACrB,CAEA,GAAU,EAGD,CAMP,GALI,EAAK,UACP,KAAKF,GAAkC,EAAK,QAAQ,EAIlD,EAAK,oBAAoB,QACtB,IAAM,KAAS,EAAK,SAAS,OAAO,EACnC,GAAS,OAAO,GAAU,UAC5B,KAAKE,GACH,CAIF,CAIR,CAEA,GAAkC,EAAyB,CACzD,IAAM,EAAS,KAAKD,GAAW,CAAS,EAExC,GAAI,CAAC,EACH,OAIF,IAAM,EADQ,KAAKV,GAAW,IAAI,CACR,CAAC,EAAE,cAE7B,GAAI,CAAC,EACH,OAGF,IAAM,EAAa,EAAO,YAAY,CAAC,SAAS,CAAa,EAEzD,aAAsB,SAItB,WAAY,GACd,QAAQ,KACN,GAAG,EAAa,UAAU,EAAU,2CACpC,EAAW,MACb,CAEJ,CACF,EClNA,MAAM,EAAc,IAAI,IAAI,CAAC,cAAe,YAAY,CAAC,EAEzD,SAAgB,EAAgB,EAA0C,CACxE,GAAI,EAAQ,OAAS,IAAA,IAAa,CAAC,EAAY,IAAI,EAAQ,IAAI,EAC7D,MAAU,UACR,GAAG,EAAa,kBAAkB,EAAQ,KAAK,0CACjD,EAGF,GAAI,EAAQ,SAAW,IAAA,IAAa,OAAO,EAAQ,QAAW,UAC5D,MAAU,UACR,GAAG,EAAa,gDAAgD,OAAO,EAAQ,OAAO,EACxF,EAGF,GAAI,EAAQ,UAAY,IAAA,IAAa,OAAO,EAAQ,SAAY,WAC9D,MAAU,UACR,GAAG,EAAa,2CAA2C,OAAO,EAAQ,QAAQ,EACpF,CAEJ,CChBA,SAAgB,EACd,EAAqC,CAAC,EACvB,CACf,EAAgB,CAAO,EAEvB,IAAM,EAA2C,OAAO,OAAO,CAC7D,GAAG,CACL,CAAC,EAED,MAAQ,IAKC,IAFY,GAAA,EAAA,EAAA,aAAA,CAFY,CAEe,GAAA,EAAA,EAAA,aAAA,CADf,CAC0B,EAAG,CAEhD,CAAC,CAAC,UAAU,CAE5B"}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getPluginApi as e,getRoutesApi as t}from"@real-router/core/api";const n=`[search-schema-plugin]`;function r(e){let t=new Set;for(let n of e)if(n.path&&n.path.length>0){let e=n.path[0],r=typeof e==`object`&&`key`in e?e.key:e;t.add(String(r))}return t}function i(e,t){let n={};for(let
|
|
1
|
+
import{getPluginApi as e,getRoutesApi as t}from"@real-router/core/api";const n=`[search-schema-plugin]`;function r(e){let t=new Set;for(let n of e)if(n.path&&n.path.length>0){let e=n.path[0],r=typeof e==`object`&&`key`in e?e.key:e;t.add(String(r))}return t}function i(e,t){let n={};for(let[r,i]of Object.entries(e))t.has(r)||(n[r]=i);return n}var a=class{#e;#t;#n;#r;#i;#a;#o;constructor(e,t,n){this.#e=e,this.#t=t,this.#n=n.mode??`development`,this.#r=n.strict??!1,this.#i=n.onError,this.#u(),this.#a=this.#e.addInterceptor(`forwardState`,(e,t,n)=>{let r=e(t,n);return this.#l(r)}),this.#o=this.#n===`development`?this.#t.subscribeChanges(e=>{this.#s(e)}):()=>{}}getPlugin(){return{teardown:()=>{this.#a(),this.#o()}}}#s(e){switch(e.op){case`add`:case`replace`:for(let t of e.added)this.#f(t.name);break;case`update`:e.patch.defaultParams!==void 0&&this.#f(e.name);break}}#c(e){return this.#e.getRouteConfig(e)?.searchSchema}#l(e){let t=this.#c(e.name);if(!t)return e;let a=t[`~standard`].validate(e.params);if(a instanceof Promise)throw TypeError(`${n} Async schema validation is not supported. Route "${e.name}" returned a Promise from ~standard.validate().`);if(`value`in a){let t=this.#r?a.value:{...e.params,...a.value};return{...e,params:t}}if(this.#i)return{...e,params:this.#i(e.name,e.params,a.issues)};this.#n===`development`&&console.error(`${n} Route "${e.name}": invalid search params`,a.issues);let o=r(a.issues),s=i(e.params,o),c=this.#t.get(e.name)?.defaultParams,l=c?{...c,...s}:s;return{...e,params:l}}#u(){if(this.#n!==`development`)return;let e=this.#e.getTree();e&&this.#d(e)}#d(e){if(e.fullName&&this.#f(e.fullName),e.children instanceof Map)for(let t of e.children.values())t&&typeof t==`object`&&this.#d(t)}#f(e){let t=this.#c(e);if(!t)return;let r=this.#t.get(e)?.defaultParams;if(!r)return;let i=t[`~standard`].validate(r);i instanceof Promise||`issues`in i&&console.warn(`${n} Route "${e}": defaultParams do not pass searchSchema`,i.issues)}};const o=new Set([`development`,`production`]);function s(e){if(e.mode!==void 0&&!o.has(e.mode))throw TypeError(`${n} Invalid mode: "${e.mode}". Must be "development" or "production".`);if(e.strict!==void 0&&typeof e.strict!=`boolean`)throw TypeError(`${n} Invalid strict option: expected boolean, got ${typeof e.strict}.`);if(e.onError!==void 0&&typeof e.onError!=`function`)throw TypeError(`${n} Invalid onError: expected function, got ${typeof e.onError}.`)}function c(n={}){s(n);let r=Object.freeze({...n});return n=>new a(e(n),t(n),r).getPlugin()}export{c as searchSchemaPlugin};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["#pluginApi","#routesApi","#mode","#strict","#onError","#removeForwardStateInterceptor","#removeChangesSubscription","#validateExistingDefaultParams","#validateState","#onTreeChanged","#validateSingleRouteDefaultParams","#getSchema","#walkTree"],"sources":["../../src/constants.ts","../../src/helpers.ts","../../src/plugin.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["// packages/search-schema-plugin/src/constants.ts\n\nexport const ERROR_PREFIX = \"[search-schema-plugin]\";\n","// packages/search-schema-plugin/src/helpers.ts\n\nimport type { StandardSchemaV1Issue } from \"./types\";\nimport type { Params } from \"@real-router/core\";\n\n/**\n * Extract top-level keys from validation issues.\n * Only processes issues with a non-empty path — issues without path\n * affect the whole object and can't be stripped by key.\n */\nexport function getInvalidKeys(\n issues: readonly StandardSchemaV1Issue[],\n): Set<string> {\n const keys = new Set<string>();\n\n for (const issue of issues) {\n if (issue.path && issue.path.length > 0) {\n const segment = issue.path[0];\n const key =\n typeof segment === \"object\" && \"key\" in segment ? segment.key : segment;\n\n keys.add(String(key));\n }\n }\n\n return keys;\n}\n\n/** Create a shallow copy of params without the specified keys. */\nexport function omitKeys(params: Params, keys: Set<string>): Params {\n const result: Params = {};\n\n for (const key of Object.keys(params)) {\n if (!keys.has(key)) {\n result[key] = params[key];\n }\n }\n\n return result;\n}\n","import { ERROR_PREFIX } from \"./constants\";\nimport { getInvalidKeys, omitKeys } from \"./helpers\";\n\nimport type {\n SearchSchemaPluginOptions,\n StandardSchemaV1,\n StandardSchemaV1Issue,\n} from \"./types\";\nimport type { Params, Plugin, TreeChangedEvent } from \"@real-router/core\";\nimport type { PluginApi, RoutesApi } from \"@real-router/core/api\";\n\nexport class SearchSchemaPlugin {\n readonly #pluginApi: PluginApi;\n readonly #routesApi: RoutesApi;\n readonly #mode: \"development\" | \"production\";\n readonly #strict: boolean;\n readonly #onError:\n | ((\n routeName: string,\n params: Params,\n issues: readonly StandardSchemaV1Issue[],\n ) => Params)\n | undefined;\n readonly #removeForwardStateInterceptor: () => void;\n readonly #removeChangesSubscription: () => void;\n\n constructor(\n pluginApi: PluginApi,\n routesApi: RoutesApi,\n options: SearchSchemaPluginOptions,\n ) {\n this.#pluginApi = pluginApi;\n this.#routesApi = routesApi;\n this.#mode = options.mode ?? \"development\";\n this.#strict = options.strict ?? false;\n this.#onError = options.onError;\n\n this.#validateExistingDefaultParams();\n\n this.#removeForwardStateInterceptor = this.#pluginApi.addInterceptor(\n \"forwardState\",\n (next, routeName, routeParams) => {\n const result = next(routeName, routeParams);\n\n return this.#validateState(result);\n },\n );\n\n // Dev-time defaultParams validation for runtime tree mutations. Replaces the\n // old `add` interceptor: TREE_CHANGED additionally covers `update` (changed\n // defaultParams) and `replace` (new route set) — the gap the interceptor\n // could not reach. Production mode skips the subscription entirely.\n this.#removeChangesSubscription =\n this.#mode === \"development\"\n ? this.#routesApi.subscribeChanges((event) => {\n this.#onTreeChanged(event);\n })\n : () => {};\n }\n\n getPlugin(): Plugin {\n return {\n teardown: () => {\n this.#removeForwardStateInterceptor();\n this.#removeChangesSubscription();\n },\n };\n }\n\n #onTreeChanged(event: TreeChangedEvent): void {\n switch (event.op) {\n case \"add\":\n case \"replace\": {\n // `added` is FLAT (full dotted names, descendants included).\n for (const route of event.added) {\n this.#validateSingleRouteDefaultParams(route.name);\n }\n\n break;\n }\n case \"update\": {\n // Only a defaultParams change can newly violate the schema.\n if (event.patch.defaultParams !== undefined) {\n this.#validateSingleRouteDefaultParams(event.name);\n }\n\n break;\n }\n // \"remove\" / \"clear\": the routes are gone — nothing to validate.\n }\n }\n\n #getSchema(routeName: string): StandardSchemaV1 | undefined {\n return this.#pluginApi.getRouteConfig(routeName)?.searchSchema as\n | StandardSchemaV1\n | undefined;\n }\n\n #validateState(result: { name: string; params: Params }): {\n name: string;\n params: Params;\n } {\n const schema = this.#getSchema(result.name);\n\n if (!schema) {\n return result;\n }\n\n const validation = schema[\"~standard\"].validate(result.params);\n\n if (validation instanceof Promise) {\n throw new TypeError(\n `${ERROR_PREFIX} Async schema validation is not supported. Route \"${result.name}\" returned a Promise from ~standard.validate().`,\n );\n }\n\n if (\"value\" in validation) {\n const params = this.#strict\n ? (validation.value as Params)\n : { ...result.params, ...(validation.value as Params) };\n\n return { ...result, params };\n }\n\n if (this.#onError) {\n return {\n ...result,\n params: this.#onError(result.name, result.params, validation.issues),\n };\n }\n\n if (this.#mode === \"development\") {\n console.error(\n `${ERROR_PREFIX} Route \"${result.name}\": invalid search params`,\n validation.issues,\n );\n }\n\n const invalidKeys = getInvalidKeys(validation.issues);\n const stripped = omitKeys(result.params, invalidKeys);\n const route = this.#routesApi.get(result.name);\n const defaults = route?.defaultParams;\n const restored = defaults ? { ...defaults, ...stripped } : stripped;\n\n return { ...result, params: restored };\n }\n\n #validateExistingDefaultParams(): void {\n if (this.#mode !== \"development\") {\n return;\n }\n\n const tree = this.#pluginApi.getTree() as unknown as\n | { fullName?: string; children?: ReadonlyMap<string, unknown> }\n | undefined;\n\n /* v8 ignore next -- @preserve: getTree() always returns a RouteTree, defensive check */\n if (!tree) {\n return;\n }\n\n this.#walkTree(tree);\n }\n\n #walkTree(node: {\n fullName?: string;\n children?: ReadonlyMap<string, unknown>;\n }): void {\n if (node.fullName) {\n this.#validateSingleRouteDefaultParams(node.fullName);\n }\n\n /* v8 ignore next 3 -- @preserve: children is always a Map in RouteTree */\n if (node.children instanceof Map) {\n for (const child of node.children.values()) {\n if (child && typeof child === \"object\") {\n this.#walkTree(\n child as {\n fullName?: string;\n children?: ReadonlyMap<string, unknown>;\n },\n );\n }\n }\n }\n }\n\n #validateSingleRouteDefaultParams(routeName: string): void {\n const schema = this.#getSchema(routeName);\n\n if (!schema) {\n return;\n }\n\n const route = this.#routesApi.get(routeName);\n const defaultParams = route?.defaultParams;\n\n if (!defaultParams) {\n return;\n }\n\n const validation = schema[\"~standard\"].validate(defaultParams);\n\n if (validation instanceof Promise) {\n return;\n }\n\n if (\"issues\" in validation) {\n console.warn(\n `${ERROR_PREFIX} Route \"${routeName}\": defaultParams do not pass searchSchema`,\n validation.issues,\n );\n }\n }\n}\n","import { ERROR_PREFIX } from \"./constants\";\n\nimport type { SearchSchemaPluginOptions } from \"./types\";\n\nconst VALID_MODES = new Set([\"development\", \"production\"]);\n\nexport function validateOptions(options: SearchSchemaPluginOptions): void {\n if (options.mode !== undefined && !VALID_MODES.has(options.mode)) {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid mode: \"${options.mode}\". Must be \"development\" or \"production\".`,\n );\n }\n\n if (options.strict !== undefined && typeof options.strict !== \"boolean\") {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid strict option: expected boolean, got ${typeof options.strict}.`,\n );\n }\n\n if (options.onError !== undefined && typeof options.onError !== \"function\") {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid onError: expected function, got ${typeof options.onError}.`,\n );\n }\n}\n","import { getPluginApi, getRoutesApi } from \"@real-router/core/api\";\n\nimport { SearchSchemaPlugin } from \"./plugin\";\nimport { validateOptions } from \"./validation\";\n\nimport type { SearchSchemaPluginOptions } from \"./types\";\nimport type { PluginFactory, Plugin } from \"@real-router/core\";\n\nexport function searchSchemaPlugin(\n options: SearchSchemaPluginOptions = {},\n): PluginFactory {\n validateOptions(options);\n\n const frozenOptions: SearchSchemaPluginOptions = Object.freeze({\n ...options,\n });\n\n return (router): Plugin => {\n const pluginApi = getPluginApi(router);\n const routesApi = getRoutesApi(router);\n const plugin = new SearchSchemaPlugin(pluginApi, routesApi, frozenOptions);\n\n return plugin.getPlugin();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["#pluginApi","#routesApi","#mode","#strict","#onError","#removeForwardStateInterceptor","#removeChangesSubscription","#validateExistingDefaultParams","#validateState","#onTreeChanged","#validateSingleRouteDefaultParams","#getSchema","#walkTree"],"sources":["../../src/constants.ts","../../src/helpers.ts","../../src/plugin.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["// packages/search-schema-plugin/src/constants.ts\n\nexport const ERROR_PREFIX = \"[search-schema-plugin]\";\n","// packages/search-schema-plugin/src/helpers.ts\n\nimport type { StandardSchemaV1Issue } from \"./types\";\nimport type { Params } from \"@real-router/core\";\n\n/**\n * Extract top-level keys from validation issues.\n * Only processes issues with a non-empty path — issues without path\n * affect the whole object and can't be stripped by key.\n */\nexport function getInvalidKeys(\n issues: readonly StandardSchemaV1Issue[],\n): Set<string> {\n const keys = new Set<string>();\n\n for (const issue of issues) {\n if (issue.path && issue.path.length > 0) {\n const segment = issue.path[0];\n const key =\n typeof segment === \"object\" && \"key\" in segment ? segment.key : segment;\n\n keys.add(String(key));\n }\n }\n\n return keys;\n}\n\n/** Create a shallow copy of params without the specified keys. */\nexport function omitKeys(params: Params, keys: Set<string>): Params {\n const result: Params = {};\n\n for (const [key, value] of Object.entries(params)) {\n if (!keys.has(key)) {\n result[key] = value;\n }\n }\n\n return result;\n}\n","import { ERROR_PREFIX } from \"./constants\";\nimport { getInvalidKeys, omitKeys } from \"./helpers\";\n\nimport type {\n SearchSchemaPluginOptions,\n StandardSchemaV1,\n StandardSchemaV1Issue,\n} from \"./types\";\nimport type { Params, Plugin, TreeChangedEvent } from \"@real-router/core\";\nimport type { PluginApi, RoutesApi } from \"@real-router/core/api\";\n\nexport class SearchSchemaPlugin {\n readonly #pluginApi: PluginApi;\n readonly #routesApi: RoutesApi;\n readonly #mode: \"development\" | \"production\";\n readonly #strict: boolean;\n readonly #onError:\n | ((\n routeName: string,\n params: Params,\n issues: readonly StandardSchemaV1Issue[],\n ) => Params)\n | undefined;\n readonly #removeForwardStateInterceptor: () => void;\n readonly #removeChangesSubscription: () => void;\n\n constructor(\n pluginApi: PluginApi,\n routesApi: RoutesApi,\n options: SearchSchemaPluginOptions,\n ) {\n this.#pluginApi = pluginApi;\n this.#routesApi = routesApi;\n this.#mode = options.mode ?? \"development\";\n this.#strict = options.strict ?? false;\n this.#onError = options.onError;\n\n this.#validateExistingDefaultParams();\n\n this.#removeForwardStateInterceptor = this.#pluginApi.addInterceptor(\n \"forwardState\",\n (next, routeName, routeParams) => {\n const result = next(routeName, routeParams);\n\n return this.#validateState(result);\n },\n );\n\n // Dev-time defaultParams validation for runtime tree mutations. Replaces the\n // old `add` interceptor: TREE_CHANGED additionally covers `update` (changed\n // defaultParams) and `replace` (new route set) — the gap the interceptor\n // could not reach. Production mode skips the subscription entirely.\n this.#removeChangesSubscription =\n this.#mode === \"development\"\n ? this.#routesApi.subscribeChanges((event) => {\n this.#onTreeChanged(event);\n })\n : () => {};\n }\n\n getPlugin(): Plugin {\n return {\n teardown: () => {\n this.#removeForwardStateInterceptor();\n this.#removeChangesSubscription();\n },\n };\n }\n\n #onTreeChanged(event: TreeChangedEvent): void {\n switch (event.op) {\n case \"add\":\n case \"replace\": {\n // `added` is FLAT (full dotted names, descendants included).\n for (const route of event.added) {\n this.#validateSingleRouteDefaultParams(route.name);\n }\n\n break;\n }\n case \"update\": {\n // Only a defaultParams change can newly violate the schema.\n if (event.patch.defaultParams !== undefined) {\n this.#validateSingleRouteDefaultParams(event.name);\n }\n\n break;\n }\n // \"remove\" / \"clear\": the routes are gone — nothing to validate.\n }\n }\n\n #getSchema(routeName: string): StandardSchemaV1 | undefined {\n return this.#pluginApi.getRouteConfig(routeName)?.searchSchema as\n | StandardSchemaV1\n | undefined;\n }\n\n #validateState(result: { name: string; params: Params }): {\n name: string;\n params: Params;\n } {\n const schema = this.#getSchema(result.name);\n\n if (!schema) {\n return result;\n }\n\n const validation = schema[\"~standard\"].validate(result.params);\n\n if (validation instanceof Promise) {\n throw new TypeError(\n `${ERROR_PREFIX} Async schema validation is not supported. Route \"${result.name}\" returned a Promise from ~standard.validate().`,\n );\n }\n\n if (\"value\" in validation) {\n const params = this.#strict\n ? (validation.value as Params)\n : { ...result.params, ...(validation.value as Params) };\n\n return { ...result, params };\n }\n\n if (this.#onError) {\n return {\n ...result,\n params: this.#onError(result.name, result.params, validation.issues),\n };\n }\n\n if (this.#mode === \"development\") {\n console.error(\n `${ERROR_PREFIX} Route \"${result.name}\": invalid search params`,\n validation.issues,\n );\n }\n\n const invalidKeys = getInvalidKeys(validation.issues);\n const stripped = omitKeys(result.params, invalidKeys);\n const route = this.#routesApi.get(result.name);\n const defaults = route?.defaultParams;\n const restored = defaults ? { ...defaults, ...stripped } : stripped;\n\n return { ...result, params: restored };\n }\n\n #validateExistingDefaultParams(): void {\n if (this.#mode !== \"development\") {\n return;\n }\n\n const tree = this.#pluginApi.getTree() as unknown as\n | { fullName?: string; children?: ReadonlyMap<string, unknown> }\n | undefined;\n\n /* v8 ignore next -- @preserve: getTree() always returns a RouteTree, defensive check */\n if (!tree) {\n return;\n }\n\n this.#walkTree(tree);\n }\n\n #walkTree(node: {\n fullName?: string;\n children?: ReadonlyMap<string, unknown>;\n }): void {\n if (node.fullName) {\n this.#validateSingleRouteDefaultParams(node.fullName);\n }\n\n /* v8 ignore next 3 -- @preserve: children is always a Map in RouteTree */\n if (node.children instanceof Map) {\n for (const child of node.children.values()) {\n if (child && typeof child === \"object\") {\n this.#walkTree(\n child as {\n fullName?: string;\n children?: ReadonlyMap<string, unknown>;\n },\n );\n }\n }\n }\n }\n\n #validateSingleRouteDefaultParams(routeName: string): void {\n const schema = this.#getSchema(routeName);\n\n if (!schema) {\n return;\n }\n\n const route = this.#routesApi.get(routeName);\n const defaultParams = route?.defaultParams;\n\n if (!defaultParams) {\n return;\n }\n\n const validation = schema[\"~standard\"].validate(defaultParams);\n\n if (validation instanceof Promise) {\n return;\n }\n\n if (\"issues\" in validation) {\n console.warn(\n `${ERROR_PREFIX} Route \"${routeName}\": defaultParams do not pass searchSchema`,\n validation.issues,\n );\n }\n }\n}\n","import { ERROR_PREFIX } from \"./constants\";\n\nimport type { SearchSchemaPluginOptions } from \"./types\";\n\nconst VALID_MODES = new Set([\"development\", \"production\"]);\n\nexport function validateOptions(options: SearchSchemaPluginOptions): void {\n if (options.mode !== undefined && !VALID_MODES.has(options.mode)) {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid mode: \"${options.mode}\". Must be \"development\" or \"production\".`,\n );\n }\n\n if (options.strict !== undefined && typeof options.strict !== \"boolean\") {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid strict option: expected boolean, got ${typeof options.strict}.`,\n );\n }\n\n if (options.onError !== undefined && typeof options.onError !== \"function\") {\n throw new TypeError(\n `${ERROR_PREFIX} Invalid onError: expected function, got ${typeof options.onError}.`,\n );\n }\n}\n","import { getPluginApi, getRoutesApi } from \"@real-router/core/api\";\n\nimport { SearchSchemaPlugin } from \"./plugin\";\nimport { validateOptions } from \"./validation\";\n\nimport type { SearchSchemaPluginOptions } from \"./types\";\nimport type { PluginFactory, Plugin } from \"@real-router/core\";\n\nexport function searchSchemaPlugin(\n options: SearchSchemaPluginOptions = {},\n): PluginFactory {\n validateOptions(options);\n\n const frozenOptions: SearchSchemaPluginOptions = Object.freeze({\n ...options,\n });\n\n return (router): Plugin => {\n const pluginApi = getPluginApi(router);\n const routesApi = getRoutesApi(router);\n const plugin = new SearchSchemaPlugin(pluginApi, routesApi, frozenOptions);\n\n return plugin.getPlugin();\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@real-router/search-schema-plugin",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Runtime search parameter validation via Standard Schema for Real-Router",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"homepage": "https://github.com/greydragon888/real-router",
|
|
46
46
|
"sideEffects": false,
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@real-router/core": "^0.
|
|
48
|
+
"@real-router/core": "^0.59.0"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"test": "vitest",
|