@tanstack/start-static-server-functions
Advanced tools
@@ -1,2 +0,5 @@ | ||
| import { createMiddleware, startSerializer } from "@tanstack/start-client-core"; | ||
| import fs from "node:fs/promises"; | ||
| import path from "node:path"; | ||
| import { createMiddleware, getDefaultSerovalPlugins } from "@tanstack/start-client-core"; | ||
| import { fromJSON, toJSONAsync } from "seroval"; | ||
| async function sha1Hash(message) { | ||
@@ -22,22 +25,8 @@ const msgBuffer = new TextEncoder().encode(message); | ||
| const staticClientCache = typeof document !== "undefined" ? /* @__PURE__ */ new Map() : null; | ||
| const serverFnStaticCache = { | ||
| getItem: async (ctx) => { | ||
| if (typeof document === "undefined") { | ||
| const hash = jsonToFilenameSafeString(ctx.data); | ||
| const url = await getStaticCacheUrl({ functionId: ctx.functionId, hash }); | ||
| const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR; | ||
| const { promises: fs } = await import("node:fs"); | ||
| const path = await import("node:path"); | ||
| const filePath = path.join(publicUrl, url); | ||
| const [cachedResult, readError] = await fs.readFile(filePath, "utf-8").then((c) => [startSerializer.parse(c), null]).catch((e) => [null, e]); | ||
| if (readError && readError.code !== "ENOENT") { | ||
| throw readError; | ||
| } | ||
| return cachedResult; | ||
| } | ||
| return void 0; | ||
| }, | ||
| setItem: async ({ data, functionId, response }) => { | ||
| const { promises: fs } = await import("node:fs"); | ||
| const path = await import("node:path"); | ||
| async function addItemToCache({ | ||
| functionId, | ||
| data, | ||
| response | ||
| }) { | ||
| { | ||
| const hash = jsonToFilenameSafeString(data); | ||
@@ -48,12 +37,14 @@ const url = await getStaticCacheUrl({ functionId, hash }); | ||
| await fs.mkdir(path.dirname(filePath), { recursive: true }); | ||
| await fs.writeFile( | ||
| filePath, | ||
| startSerializer.stringify({ | ||
| result: response.result, | ||
| context: response.context.sendContext | ||
| }), | ||
| "utf-8" | ||
| const stringifiedResult = JSON.stringify( | ||
| await toJSONAsync( | ||
| { | ||
| result: response.result, | ||
| context: response.context.sendContext | ||
| }, | ||
| { plugins: getDefaultSerovalPlugins() } | ||
| ) | ||
| ); | ||
| await fs.writeFile(filePath, stringifiedResult, "utf-8"); | ||
| } | ||
| }; | ||
| } | ||
| const fetchItem = async ({ | ||
@@ -68,3 +59,3 @@ data, | ||
| method: "GET" | ||
| }).then((r) => r.text()).then((d) => startSerializer.parse(d)); | ||
| }).then((r) => r.json()).then((d) => fromJSON(d, { plugins: getDefaultSerovalPlugins() })); | ||
| return result; | ||
@@ -90,3 +81,3 @@ }; | ||
| if (process.env.NODE_ENV === "production") { | ||
| await serverFnStaticCache.setItem({ | ||
| await addItemToCache({ | ||
| functionId: ctx.functionId, | ||
@@ -93,0 +84,0 @@ response: { result: response.result, context: ctx }, |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"staticFunctionMiddleware.js","sources":["../../src/staticFunctionMiddleware.ts"],"sourcesContent":["import { createMiddleware, startSerializer } from '@tanstack/start-client-core'\n\ntype StaticCachedResult = {\n result: any\n context: any\n}\n\ntype ServerFnStaticCache = {\n getItem: (opts: {\n functionId: string\n data: any\n }) => StaticCachedResult | Promise<StaticCachedResult | undefined>\n setItem: (opts: {\n functionId: string\n data: any\n response: StaticCachedResult\n }) => Promise<void>\n}\n\n/**\n * This is a simple hash function for generating a hash from a string to make the filenames shorter.\n *\n * It is not cryptographically secure (as its using SHA-1) and should not be used for any security purposes.\n *\n * It is only used to generate a hash for the static cache filenames.\n *\n * @param message - The input string to hash.\n * @returns A promise that resolves to the SHA-1 hash of the input string in hexadecimal format.\n *\n * @example\n * ```typescript\n * const hash = await sha1Hash(\"hello\");\n * console.log(hash); // Outputs the SHA-1 hash of \"hello\" -> \"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d\"\n * ```\n */\nasync function sha1Hash(message: string): Promise<string> {\n // Encode the string as UTF-8\n const msgBuffer = new TextEncoder().encode(message)\n\n // Hash the message\n const hashBuffer = await crypto.subtle.digest('SHA-1', msgBuffer)\n\n // Convert the ArrayBuffer to a string\n const hashArray = Array.from(new Uint8Array(hashBuffer))\n const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')\n return hashHex\n}\n\nconst getStaticCacheUrl = async (opts: {\n functionId: string\n hash: string\n}) => {\n const filename = await sha1Hash(`${opts.functionId}__${opts.hash}`)\n return `/__tsr/staticServerFnCache/${filename}.json`\n}\n\nconst jsonToFilenameSafeString = (json: any) => {\n // Custom replacer to sort keys\n const sortedKeysReplacer = (key: string, value: any) =>\n value && typeof value === 'object' && !Array.isArray(value)\n ? Object.keys(value)\n .sort()\n .reduce((acc: any, curr: string) => {\n acc[curr] = value[curr]\n return acc\n }, {})\n : value\n\n // Convert JSON to string with sorted keys\n const jsonString = JSON.stringify(json ?? '', sortedKeysReplacer)\n\n // Replace characters invalid in filenames\n return jsonString\n .replace(/[/\\\\?%*:|\"<>]/g, '-') // Replace invalid characters with a dash\n .replace(/\\s+/g, '_') // Optionally replace whitespace with underscores\n}\n\nconst staticClientCache =\n typeof document !== 'undefined' ? new Map<string, any>() : null\n\nconst serverFnStaticCache: ServerFnStaticCache = {\n getItem: async (ctx) => {\n if (typeof document === 'undefined') {\n const hash = jsonToFilenameSafeString(ctx.data)\n const url = await getStaticCacheUrl({ functionId: ctx.functionId, hash })\n const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR!\n\n // Use fs instead of fetch to read from filesystem\n const { promises: fs } = await import('node:fs')\n const path = await import('node:path')\n const filePath = path.join(publicUrl, url)\n\n const [cachedResult, readError] = await fs\n .readFile(filePath, 'utf-8')\n .then((c) => [startSerializer.parse(c), null])\n .catch((e) => [null, e])\n\n if (readError && readError.code !== 'ENOENT') {\n throw readError\n }\n\n return cachedResult as StaticCachedResult\n }\n\n return undefined\n },\n setItem: async ({ data, functionId, response }) => {\n const { promises: fs } = await import('node:fs')\n const path = await import('node:path')\n\n const hash = jsonToFilenameSafeString(data)\n const url = await getStaticCacheUrl({ functionId, hash })\n const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR!\n const filePath = path.join(publicUrl, url)\n\n // Ensure the directory exists\n await fs.mkdir(path.dirname(filePath), { recursive: true })\n\n // Store the result with fs\n await fs.writeFile(\n filePath,\n startSerializer.stringify({\n result: response.result,\n context: response.context.sendContext,\n }),\n 'utf-8',\n )\n },\n}\n\nconst fetchItem = async ({\n data,\n functionId,\n}: {\n data: any\n functionId: string\n}) => {\n const hash = jsonToFilenameSafeString(data)\n const url = await getStaticCacheUrl({ functionId, hash })\n\n let result: any = staticClientCache?.get(url)\n\n result = await fetch(url, {\n method: 'GET',\n })\n .then((r) => r.text())\n .then((d) => startSerializer.parse(d))\n\n return result\n}\n\nexport const staticFunctionMiddleware = createMiddleware({ type: 'function' })\n .client(async (ctx) => {\n if (\n process.env.NODE_ENV === 'production' &&\n // do not run this during SSR on the server\n typeof document !== 'undefined'\n ) {\n const response = await fetchItem({\n functionId: ctx.functionId,\n data: ctx.data,\n })\n\n if (response) {\n return {\n result: response.result,\n context: { ...(ctx as any).context, ...response.context },\n } as any\n }\n }\n return ctx.next()\n })\n .server(async (ctx) => {\n const response = await ctx.next()\n\n if (process.env.NODE_ENV === 'production') {\n await serverFnStaticCache.setItem({\n functionId: ctx.functionId,\n response: { result: (response as any).result, context: ctx },\n data: ctx.data,\n })\n }\n\n return response\n })\n"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"staticFunctionMiddleware.js","sources":["../../src/staticFunctionMiddleware.ts"],"sourcesContent":["import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport {\n createMiddleware,\n getDefaultSerovalPlugins,\n} from '@tanstack/start-client-core'\nimport { fromJSON, toJSONAsync } from 'seroval'\n\ntype StaticCachedResult = {\n result: any\n context: any\n}\n\n/**\n * This is a simple hash function for generating a hash from a string to make the filenames shorter.\n *\n * It is not cryptographically secure (as its using SHA-1) and should not be used for any security purposes.\n *\n * It is only used to generate a hash for the static cache filenames.\n *\n * @param message - The input string to hash.\n * @returns A promise that resolves to the SHA-1 hash of the input string in hexadecimal format.\n *\n * @example\n * ```typescript\n * const hash = await sha1Hash(\"hello\");\n * console.log(hash); // Outputs the SHA-1 hash of \"hello\" -> \"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d\"\n * ```\n */\nasync function sha1Hash(message: string): Promise<string> {\n // Encode the string as UTF-8\n const msgBuffer = new TextEncoder().encode(message)\n\n // Hash the message\n const hashBuffer = await crypto.subtle.digest('SHA-1', msgBuffer)\n\n // Convert the ArrayBuffer to a string\n const hashArray = Array.from(new Uint8Array(hashBuffer))\n const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')\n return hashHex\n}\n\nconst getStaticCacheUrl = async (opts: {\n functionId: string\n hash: string\n}) => {\n const filename = await sha1Hash(`${opts.functionId}__${opts.hash}`)\n return `/__tsr/staticServerFnCache/${filename}.json`\n}\n\nconst jsonToFilenameSafeString = (json: any) => {\n // Custom replacer to sort keys\n const sortedKeysReplacer = (key: string, value: any) =>\n value && typeof value === 'object' && !Array.isArray(value)\n ? Object.keys(value)\n .sort()\n .reduce((acc: any, curr: string) => {\n acc[curr] = value[curr]\n return acc\n }, {})\n : value\n\n // Convert JSON to string with sorted keys\n const jsonString = JSON.stringify(json ?? '', sortedKeysReplacer)\n\n // Replace characters invalid in filenames\n return jsonString\n .replace(/[/\\\\?%*:|\"<>]/g, '-') // Replace invalid characters with a dash\n .replace(/\\s+/g, '_') // Optionally replace whitespace with underscores\n}\n\nconst staticClientCache =\n typeof document !== 'undefined' ? new Map<string, any>() : null\n\nasync function addItemToCache({\n functionId,\n data,\n response,\n}: {\n functionId: string\n data: any\n response: StaticCachedResult\n}): Promise<void> {\n {\n const hash = jsonToFilenameSafeString(data)\n const url = await getStaticCacheUrl({ functionId, hash })\n const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR!\n const filePath = path.join(publicUrl, url)\n\n // Ensure the directory exists\n await fs.mkdir(path.dirname(filePath), { recursive: true })\n\n // Store the result with fs\n const stringifiedResult = JSON.stringify(\n await toJSONAsync(\n {\n result: response.result,\n context: response.context.sendContext,\n },\n { plugins: getDefaultSerovalPlugins() },\n ),\n )\n await fs.writeFile(filePath, stringifiedResult, 'utf-8')\n }\n}\n\nconst fetchItem = async ({\n data,\n functionId,\n}: {\n data: any\n functionId: string\n}) => {\n const hash = jsonToFilenameSafeString(data)\n const url = await getStaticCacheUrl({ functionId, hash })\n\n let result: any = staticClientCache?.get(url)\n\n result = await fetch(url, {\n method: 'GET',\n })\n .then((r) => r.json())\n .then((d) => fromJSON(d, { plugins: getDefaultSerovalPlugins() }))\n\n return result\n}\n\nexport const staticFunctionMiddleware = createMiddleware({ type: 'function' })\n .client(async (ctx) => {\n if (\n process.env.NODE_ENV === 'production' &&\n // do not run this during SSR on the server\n typeof document !== 'undefined'\n ) {\n const response = await fetchItem({\n functionId: ctx.functionId,\n data: ctx.data,\n })\n\n if (response) {\n return {\n result: response.result,\n context: { ...(ctx as any).context, ...response.context },\n } as any\n }\n }\n return ctx.next()\n })\n .server(async (ctx) => {\n const response = await ctx.next()\n if (process.env.NODE_ENV === 'production') {\n await addItemToCache({\n functionId: ctx.functionId,\n response: { result: (response as any).result, context: ctx },\n data: ctx.data,\n })\n }\n\n return response\n })\n"],"names":[],"mappings":"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"} |
+3
-2
| { | ||
| "name": "@tanstack/start-static-server-functions", | ||
| "version": "1.132.0-alpha.2", | ||
| "version": "1.132.0-alpha.3", | ||
| "description": "Modern and scalable routing for React applications", | ||
@@ -46,5 +46,6 @@ "author": "Tanner Linsley", | ||
| "dependencies": { | ||
| "@tanstack/start-client-core": "1.132.0-alpha.2" | ||
| "seroval": "^1.3.2", | ||
| "@tanstack/start-client-core": "1.132.0-alpha.3" | ||
| }, | ||
| "scripts": {} | ||
| } |
@@ -1,2 +0,8 @@ | ||
| import { createMiddleware, startSerializer } from '@tanstack/start-client-core' | ||
| import fs from 'node:fs/promises' | ||
| import path from 'node:path' | ||
| import { | ||
| createMiddleware, | ||
| getDefaultSerovalPlugins, | ||
| } from '@tanstack/start-client-core' | ||
| import { fromJSON, toJSONAsync } from 'seroval' | ||
@@ -8,14 +14,2 @@ type StaticCachedResult = { | ||
| type ServerFnStaticCache = { | ||
| getItem: (opts: { | ||
| functionId: string | ||
| data: any | ||
| }) => StaticCachedResult | Promise<StaticCachedResult | undefined> | ||
| setItem: (opts: { | ||
| functionId: string | ||
| data: any | ||
| response: StaticCachedResult | ||
| }) => Promise<void> | ||
| } | ||
| /** | ||
@@ -82,32 +76,12 @@ * This is a simple hash function for generating a hash from a string to make the filenames shorter. | ||
| const serverFnStaticCache: ServerFnStaticCache = { | ||
| getItem: async (ctx) => { | ||
| if (typeof document === 'undefined') { | ||
| const hash = jsonToFilenameSafeString(ctx.data) | ||
| const url = await getStaticCacheUrl({ functionId: ctx.functionId, hash }) | ||
| const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR! | ||
| // Use fs instead of fetch to read from filesystem | ||
| const { promises: fs } = await import('node:fs') | ||
| const path = await import('node:path') | ||
| const filePath = path.join(publicUrl, url) | ||
| const [cachedResult, readError] = await fs | ||
| .readFile(filePath, 'utf-8') | ||
| .then((c) => [startSerializer.parse(c), null]) | ||
| .catch((e) => [null, e]) | ||
| if (readError && readError.code !== 'ENOENT') { | ||
| throw readError | ||
| } | ||
| return cachedResult as StaticCachedResult | ||
| } | ||
| return undefined | ||
| }, | ||
| setItem: async ({ data, functionId, response }) => { | ||
| const { promises: fs } = await import('node:fs') | ||
| const path = await import('node:path') | ||
| async function addItemToCache({ | ||
| functionId, | ||
| data, | ||
| response, | ||
| }: { | ||
| functionId: string | ||
| data: any | ||
| response: StaticCachedResult | ||
| }): Promise<void> { | ||
| { | ||
| const hash = jsonToFilenameSafeString(data) | ||
@@ -122,11 +96,13 @@ const url = await getStaticCacheUrl({ functionId, hash }) | ||
| // Store the result with fs | ||
| await fs.writeFile( | ||
| filePath, | ||
| startSerializer.stringify({ | ||
| result: response.result, | ||
| context: response.context.sendContext, | ||
| }), | ||
| 'utf-8', | ||
| const stringifiedResult = JSON.stringify( | ||
| await toJSONAsync( | ||
| { | ||
| result: response.result, | ||
| context: response.context.sendContext, | ||
| }, | ||
| { plugins: getDefaultSerovalPlugins() }, | ||
| ), | ||
| ) | ||
| }, | ||
| await fs.writeFile(filePath, stringifiedResult, 'utf-8') | ||
| } | ||
| } | ||
@@ -149,4 +125,4 @@ | ||
| }) | ||
| .then((r) => r.text()) | ||
| .then((d) => startSerializer.parse(d)) | ||
| .then((r) => r.json()) | ||
| .then((d) => fromJSON(d, { plugins: getDefaultSerovalPlugins() })) | ||
@@ -179,5 +155,4 @@ return result | ||
| const response = await ctx.next() | ||
| if (process.env.NODE_ENV === 'production') { | ||
| await serverFnStaticCache.setItem({ | ||
| await addItemToCache({ | ||
| functionId: ctx.functionId, | ||
@@ -184,0 +159,0 @@ response: { result: (response as any).result, context: ctx }, |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
17300
-14.04%2
100%238
-9.85%+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed