@orbit-work/block-sdk 0.0.0-stage → 0.2.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 CHANGED
@@ -1,3 +1,81 @@
1
- # Temporary Holding Version
1
+ # JSON block SDK
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@orbit-work/block-sdk` is an independent, publishable npm package with an ESM
4
+ entry and TypeScript declarations. It has no runtime dependencies or knowledge,
5
+ identity, UI or React behavior.
6
+
7
+ ```ts
8
+ import { getBlockData } from "@orbit-work/block-sdk"
9
+
10
+ const data = await getBlockData<unknown>("my-config", {
11
+ env: "pre", // uat | pre | prod
12
+ signal: controller.signal,
13
+ timeoutMs: 3000,
14
+ })
15
+ ```
16
+
17
+ The key is a standalone path identifier, unrelated to app IDs. Requests read
18
+ `https://s.fzcdn1.com/cdn/h5-data/<configKey>/<env>/data.json` with anonymous GET,
19
+ `credentials: "omit"`, `cache: "no-store"`, and no redirects. The default timeout
20
+ is three seconds and covers the response body. There is no persistent cache or
21
+ offline fallback. `BlockDataError.code` distinguishes `invalid-options`, `network`,
22
+ `http` (with `status`), `timeout`, `cancelled`, and `json` failures.
23
+
24
+ The generic is a TypeScript return type, not runtime validation. Each consumer
25
+ validates its own JSON fields and handles failures.
26
+
27
+ The browser and Node.js 20+ use their native Fetch API. Each host may configure an
28
+ isolated client for all of its blocks:
29
+
30
+ ```ts
31
+ import { createBlockClient, BLOCK_DEV_PROXY_PATH } from "@orbit-work/block-sdk"
32
+
33
+ export const blocks = createBlockClient({
34
+ baseUrl: import.meta.env.DEV ? BLOCK_DEV_PROXY_PATH : undefined,
35
+ })
36
+ const features = await blocks.getBlockData("orbit-features-preview", { env: "prod" })
37
+ const other = await blocks.getBlockData("another-block", { env: "uat" })
38
+ ```
39
+
40
+ `baseUrl` accepts a root-relative path or an absolute HTTPS base, without
41
+ credentials, queries, fragments or traversal segments. Its default is the public
42
+ CDN base. Client configuration does not mutate `getBlockData` or other clients;
43
+ environment selection remains explicit on every read. The core has no Vite,
44
+ Node-only, UI or authentication imports.
45
+
46
+ ## Local Vite development
47
+
48
+ Add the optional Vite 8 plugin to the application's Vite config:
49
+
50
+ ```ts
51
+ import { blockDataDevProxy } from "@orbit-work/block-sdk/vite"
52
+
53
+ export default defineConfig({ plugins: [blockDataDevProxy()] })
54
+ ```
55
+
56
+ The development server serves `GET /__orbit_blocks__/<key>/<uat|pre|prod>/data.json`
57
+ and fetches the real public CDN on the server. The browser reads its own origin,
58
+ so localhost needs no additional CDN CORS registration. All legal keys use this
59
+ same transport. The plugin validates the entire path and method, accepts no target
60
+ URL, forwards no incoming headers or credentials, follows no redirects and
61
+ forwards no upstream cookies. Responses use `Cache-Control: no-store`.
62
+
63
+ The upstream timeout defaults to three seconds, includes body streaming and can be
64
+ set with `blockDataDevProxy({ timeoutMs: 5000 })`. A caller disconnect cancels the
65
+ upstream read. Network/timeout failures return bounded JSON with 502/504; upstream
66
+ HTTP failures retain their status without exposing their response body. Consumers
67
+ still own schema validation and recovery policy.
68
+
69
+ The plugin installs only development middleware, with no production build or
70
+ preview endpoint. Production clients with no `baseUrl` continue using the CDN.
71
+ Use localhost HTTP for the existing local login registration; custom hosts still
72
+ need trusted HTTPS and their own identity callback registration for login.
73
+
74
+ Build and test with `pnpm --filter @orbit-work/block-sdk test`.
75
+ `pnpm --filter @orbit-work/block-sdk pack` builds an installable tarball; it does
76
+ not publish to the registry.
77
+
78
+ Both the core and `/vite` entries are included in the tarball. Tests exercise an
79
+ isolated npm consumer as well as real Vite middleware for multiple keys and
80
+ environments, anonymity, invalid routes, failure recovery, body timeout,
81
+ disconnection and production preview isolation.
@@ -0,0 +1,11 @@
1
+ // src/paths.ts
2
+ var BLOCK_CDN_BASE_URL = "https://s.fzcdn1.com/cdn/h5-data";
3
+ var BLOCK_DEV_PROXY_PATH = "/__orbit_blocks__";
4
+ var BLOCK_KEY = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
5
+
6
+ export {
7
+ BLOCK_CDN_BASE_URL,
8
+ BLOCK_DEV_PROXY_PATH,
9
+ BLOCK_KEY
10
+ };
11
+ //# sourceMappingURL=chunk-MCUJICYI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/paths.ts"],"sourcesContent":["export const BLOCK_CDN_BASE_URL = \"https://s.fzcdn1.com/cdn/h5-data\"\nexport const BLOCK_DEV_PROXY_PATH = \"/__orbit_blocks__\"\n\nexport const BLOCK_KEY = /^[A-Za-z0-9][A-Za-z0-9_-]*$/\n"],"mappings":";AAAO,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAE7B,IAAM,YAAY;","names":[]}
@@ -0,0 +1,26 @@
1
+ declare const BLOCK_DEV_PROXY_PATH = "/__orbit_blocks__";
2
+
3
+ type BlockEnvironment = "uat" | "pre" | "prod";
4
+ type GetBlockDataOptions = {
5
+ env: BlockEnvironment;
6
+ signal?: AbortSignal;
7
+ timeoutMs?: number;
8
+ };
9
+ type BlockClientOptions = {
10
+ baseUrl?: string;
11
+ };
12
+ type BlockClient = {
13
+ getBlockData: typeof getBlockData;
14
+ };
15
+ type BlockErrorCode = "invalid-options" | "network" | "http" | "timeout" | "cancelled" | "json";
16
+ declare class BlockDataError extends Error {
17
+ readonly code: BlockErrorCode;
18
+ readonly status?: number;
19
+ constructor(code: BlockErrorCode, status?: number);
20
+ }
21
+ /** Dora's config key identifies a block, independently of any application ID. */
22
+ declare function getBlockData<T = unknown>(configKey: string, options: GetBlockDataOptions): Promise<T>;
23
+ /** Configure one isolated client for all of a host's blocks, without global or framework state. */
24
+ declare function createBlockClient(options?: BlockClientOptions): BlockClient;
25
+
26
+ export { BLOCK_DEV_PROXY_PATH, type BlockClient, type BlockClientOptions, BlockDataError, type BlockEnvironment, type BlockErrorCode, type GetBlockDataOptions, createBlockClient, getBlockData };
package/dist/index.js ADDED
@@ -0,0 +1,91 @@
1
+ import {
2
+ BLOCK_CDN_BASE_URL,
3
+ BLOCK_DEV_PROXY_PATH,
4
+ BLOCK_KEY
5
+ } from "./chunk-MCUJICYI.js";
6
+
7
+ // src/index.ts
8
+ var BlockDataError = class extends Error {
9
+ code;
10
+ status;
11
+ constructor(code, status) {
12
+ super(`Block data request failed: ${code}`);
13
+ this.name = "BlockDataError";
14
+ this.code = code;
15
+ this.status = status;
16
+ }
17
+ };
18
+ async function getBlockData(configKey, options) {
19
+ return readBlock(BLOCK_CDN_BASE_URL, configKey, options);
20
+ }
21
+ function createBlockClient(options = {}) {
22
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
23
+ return {
24
+ getBlockData: (key, options2) => readBlock(baseUrl, key, options2)
25
+ };
26
+ }
27
+ function normalizeBaseUrl(value) {
28
+ if (value === void 0) return BLOCK_CDN_BASE_URL;
29
+ if (typeof value !== "string" || !value || /[\\\s?#%]/.test(value)) throw new BlockDataError("invalid-options");
30
+ const path = value.startsWith("/") ? value : value.replace(/^https:\/\/[^/]+/, "");
31
+ if (path.split("/").some((part) => part === "." || part === "..")) throw new BlockDataError("invalid-options");
32
+ if (/^\/(?!\/)[A-Za-z0-9_/-]*$/.test(value)) return value.replace(/\/+$/, "");
33
+ try {
34
+ const url = new URL(value);
35
+ if (url.protocol !== "https:" || url.username || url.password) throw new Error();
36
+ return url.href.replace(/\/+$/, "");
37
+ } catch {
38
+ throw new BlockDataError("invalid-options");
39
+ }
40
+ }
41
+ async function readBlock(baseUrl, configKey, options) {
42
+ const timeoutMs = options.timeoutMs ?? 3e3;
43
+ if (typeof configKey !== "string" || !BLOCK_KEY.test(configKey) || !["uat", "pre", "prod"].includes(options.env) || !Number.isFinite(timeoutMs) || timeoutMs <= 0)
44
+ throw new BlockDataError("invalid-options");
45
+ const controller = new AbortController();
46
+ let timedOut = false;
47
+ const cancel = () => controller.abort();
48
+ if (options.signal?.aborted) throw new BlockDataError("cancelled");
49
+ options.signal?.addEventListener("abort", cancel, { once: true });
50
+ const timer = setTimeout(() => {
51
+ timedOut = true;
52
+ controller.abort();
53
+ }, timeoutMs);
54
+ try {
55
+ let response;
56
+ try {
57
+ response = await fetch(`${baseUrl}/${configKey}/${options.env}/data.json`, {
58
+ method: "GET",
59
+ credentials: "omit",
60
+ cache: "no-store",
61
+ redirect: "error",
62
+ referrerPolicy: "no-referrer",
63
+ signal: controller.signal
64
+ });
65
+ } catch {
66
+ throw new BlockDataError("network");
67
+ }
68
+ if (!response.ok) throw new BlockDataError("http", response.status);
69
+ try {
70
+ const data = await response.json();
71
+ if (controller.signal.aborted) throw new BlockDataError("cancelled");
72
+ return data;
73
+ } catch {
74
+ throw new BlockDataError("json");
75
+ }
76
+ } catch (error) {
77
+ if (options.signal?.aborted) throw new BlockDataError("cancelled");
78
+ if (timedOut) throw new BlockDataError("timeout");
79
+ throw error instanceof BlockDataError ? error : new BlockDataError("network");
80
+ } finally {
81
+ clearTimeout(timer);
82
+ options.signal?.removeEventListener("abort", cancel);
83
+ }
84
+ }
85
+ export {
86
+ BLOCK_DEV_PROXY_PATH,
87
+ BlockDataError,
88
+ createBlockClient,
89
+ getBlockData
90
+ };
91
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { BLOCK_CDN_BASE_URL, BLOCK_KEY } from \"./paths\"\n\nexport { BLOCK_DEV_PROXY_PATH } from \"./paths\"\n\nexport type BlockEnvironment = \"uat\" | \"pre\" | \"prod\"\n\nexport type GetBlockDataOptions = { env: BlockEnvironment; signal?: AbortSignal; timeoutMs?: number }\n\nexport type BlockClientOptions = { baseUrl?: string }\nexport type BlockClient = { getBlockData: typeof getBlockData }\n\nexport type BlockErrorCode = \"invalid-options\" | \"network\" | \"http\" | \"timeout\" | \"cancelled\" | \"json\"\n\nexport class BlockDataError extends Error {\n readonly code: BlockErrorCode\n readonly status?: number\n constructor(code: BlockErrorCode, status?: number) {\n super(`Block data request failed: ${code}`)\n this.name = \"BlockDataError\"\n this.code = code\n this.status = status\n }\n}\n\n/** Dora's config key identifies a block, independently of any application ID. */\nexport async function getBlockData<T = unknown>(configKey: string, options: GetBlockDataOptions): Promise<T> {\n return readBlock<T>(BLOCK_CDN_BASE_URL, configKey, options)\n}\n\n/** Configure one isolated client for all of a host's blocks, without global or framework state. */\nexport function createBlockClient(options: BlockClientOptions = {}): BlockClient {\n const baseUrl = normalizeBaseUrl(options.baseUrl)\n return {\n getBlockData: <T = unknown>(key: string, options: GetBlockDataOptions) => readBlock<T>(baseUrl, key, options),\n }\n}\n\nfunction normalizeBaseUrl(value: string | undefined): string {\n if (value === undefined) return BLOCK_CDN_BASE_URL\n if (typeof value !== \"string\" || !value || /[\\\\\\s?#%]/.test(value)) throw new BlockDataError(\"invalid-options\")\n const path = value.startsWith(\"/\") ? value : value.replace(/^https:\\/\\/[^/]+/, \"\")\n if (path.split(\"/\").some((part) => part === \".\" || part === \"..\")) throw new BlockDataError(\"invalid-options\")\n if (/^\\/(?!\\/)[A-Za-z0-9_/-]*$/.test(value)) return value.replace(/\\/+$/, \"\")\n try {\n const url = new URL(value)\n if (url.protocol !== \"https:\" || url.username || url.password) throw new Error()\n return url.href.replace(/\\/+$/, \"\")\n } catch {\n throw new BlockDataError(\"invalid-options\")\n }\n}\n\nasync function readBlock<T>(baseUrl: string, configKey: string, options: GetBlockDataOptions): Promise<T> {\n const timeoutMs = options.timeoutMs ?? 3000\n if (\n typeof configKey !== \"string\" ||\n !BLOCK_KEY.test(configKey) ||\n ![\"uat\", \"pre\", \"prod\"].includes(options.env) ||\n !Number.isFinite(timeoutMs) ||\n timeoutMs <= 0\n )\n throw new BlockDataError(\"invalid-options\")\n\n const controller = new AbortController()\n let timedOut = false\n const cancel = () => controller.abort()\n if (options.signal?.aborted) throw new BlockDataError(\"cancelled\")\n options.signal?.addEventListener(\"abort\", cancel, { once: true })\n const timer = setTimeout(() => {\n timedOut = true\n controller.abort()\n }, timeoutMs)\n try {\n let response: Response\n try {\n response = await fetch(`${baseUrl}/${configKey}/${options.env}/data.json`, {\n method: \"GET\",\n credentials: \"omit\",\n cache: \"no-store\",\n redirect: \"error\",\n referrerPolicy: \"no-referrer\",\n signal: controller.signal,\n })\n } catch {\n throw new BlockDataError(\"network\")\n }\n if (!response.ok) throw new BlockDataError(\"http\", response.status)\n try {\n const data = (await response.json()) as T\n if (controller.signal.aborted) throw new BlockDataError(\"cancelled\")\n return data\n } catch {\n throw new BlockDataError(\"json\")\n }\n } catch (error) {\n if (options.signal?.aborted) throw new BlockDataError(\"cancelled\")\n if (timedOut) throw new BlockDataError(\"timeout\")\n throw error instanceof BlockDataError ? error : new BlockDataError(\"network\")\n } finally {\n clearTimeout(timer)\n options.signal?.removeEventListener(\"abort\", cancel)\n }\n}\n"],"mappings":"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","names":["options"]}
package/dist/vite.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ type BlockDataDevProxyOptions = {
4
+ timeoutMs?: number;
5
+ };
6
+ /** Anonymous development transport only. Production build and preview install no middleware. */
7
+ declare function blockDataDevProxy(options?: BlockDataDevProxyOptions): Plugin;
8
+
9
+ export { type BlockDataDevProxyOptions, blockDataDevProxy };
package/dist/vite.js ADDED
@@ -0,0 +1,97 @@
1
+ import {
2
+ BLOCK_CDN_BASE_URL,
3
+ BLOCK_DEV_PROXY_PATH
4
+ } from "./chunk-MCUJICYI.js";
5
+
6
+ // src/vite.ts
7
+ import { once } from "events";
8
+ function blockDataDevProxy(options = {}) {
9
+ const timeoutMs = options.timeoutMs ?? 3e3;
10
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("Invalid block proxy timeout");
11
+ return {
12
+ name: "orbit-block-data-dev-proxy",
13
+ apply: "serve",
14
+ configureServer(server) {
15
+ server.middlewares.use((request, response, next) => {
16
+ const path = request.url ?? "";
17
+ const pathname = path.split("?")[0];
18
+ if (pathname !== BLOCK_DEV_PROXY_PATH && !pathname.startsWith(`${BLOCK_DEV_PROXY_PATH}/`)) return next();
19
+ response.setHeader("Cache-Control", "no-store");
20
+ const reply = (status, error) => {
21
+ response.statusCode = status;
22
+ response.setHeader("Content-Type", "application/json; charset=utf-8");
23
+ response.end(JSON.stringify({ error }));
24
+ };
25
+ if (request.method !== "GET") {
26
+ response.setHeader("Allow", "GET");
27
+ reply(405, "method-not-allowed");
28
+ return;
29
+ }
30
+ const match = /^\/__orbit_blocks__\/([A-Za-z0-9][A-Za-z0-9_-]*)\/(uat|pre|prod)\/data\.json$/.exec(path);
31
+ if (!match) {
32
+ reply(400, "invalid-block-path");
33
+ return;
34
+ }
35
+ const controller = new AbortController();
36
+ let timedOut = false;
37
+ const cancel = () => controller.abort();
38
+ request.once("aborted", cancel);
39
+ response.once("close", cancel);
40
+ const timer = setTimeout(() => {
41
+ timedOut = true;
42
+ cancel();
43
+ }, timeoutMs);
44
+ timer.unref();
45
+ void (async () => {
46
+ try {
47
+ const upstream = await fetch(`${BLOCK_CDN_BASE_URL}/${match[1]}/${match[2]}/data.json`, {
48
+ method: "GET",
49
+ credentials: "omit",
50
+ cache: "no-store",
51
+ redirect: "error",
52
+ referrerPolicy: "no-referrer",
53
+ signal: controller.signal
54
+ });
55
+ if (controller.signal.aborted) throw new Error("cancelled");
56
+ if (!upstream.ok) {
57
+ await upstream.body?.cancel();
58
+ reply(upstream.status, "block-http");
59
+ return;
60
+ }
61
+ response.statusCode = upstream.status;
62
+ response.setHeader("Content-Type", "application/json; charset=utf-8");
63
+ await forwardBody(upstream, response, controller.signal);
64
+ response.end();
65
+ } catch {
66
+ if (response.destroyed) return;
67
+ if (response.headersSent) response.destroy();
68
+ else reply(timedOut ? 504 : 502, timedOut ? "block-timeout" : "block-network");
69
+ } finally {
70
+ clearTimeout(timer);
71
+ request.off("aborted", cancel);
72
+ response.off("close", cancel);
73
+ }
74
+ })();
75
+ });
76
+ }
77
+ };
78
+ }
79
+ async function forwardBody(upstream, response, signal) {
80
+ const reader = upstream.body?.getReader();
81
+ if (!reader) return;
82
+ try {
83
+ while (true) {
84
+ const { done, value } = await reader.read();
85
+ if (signal.aborted) throw new Error("cancelled");
86
+ if (done) return;
87
+ if (!response.write(value)) await once(response, "drain", { signal });
88
+ }
89
+ } finally {
90
+ await reader.cancel().catch(() => void 0);
91
+ reader.releaseLock();
92
+ }
93
+ }
94
+ export {
95
+ blockDataDevProxy
96
+ };
97
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite.ts"],"sourcesContent":["import { once } from \"node:events\"\nimport type { ServerResponse } from \"node:http\"\nimport type { Plugin } from \"vite\"\nimport { BLOCK_CDN_BASE_URL, BLOCK_DEV_PROXY_PATH } from \"./paths\"\n\nexport type BlockDataDevProxyOptions = { timeoutMs?: number }\n\n/** Anonymous development transport only. Production build and preview install no middleware. */\nexport function blockDataDevProxy(options: BlockDataDevProxyOptions = {}): Plugin {\n const timeoutMs = options.timeoutMs ?? 3000\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error(\"Invalid block proxy timeout\")\n return {\n name: \"orbit-block-data-dev-proxy\",\n apply: \"serve\",\n configureServer(server) {\n server.middlewares.use((request, response, next) => {\n const path = request.url ?? \"\"\n const pathname = path.split(\"?\")[0]\n if (pathname !== BLOCK_DEV_PROXY_PATH && !pathname.startsWith(`${BLOCK_DEV_PROXY_PATH}/`)) return next()\n response.setHeader(\"Cache-Control\", \"no-store\")\n const reply = (status: number, error: string) => {\n response.statusCode = status\n response.setHeader(\"Content-Type\", \"application/json; charset=utf-8\")\n response.end(JSON.stringify({ error }))\n }\n if (request.method !== \"GET\") {\n response.setHeader(\"Allow\", \"GET\")\n reply(405, \"method-not-allowed\")\n return\n }\n const match = /^\\/__orbit_blocks__\\/([A-Za-z0-9][A-Za-z0-9_-]*)\\/(uat|pre|prod)\\/data\\.json$/.exec(path)\n if (!match) {\n reply(400, \"invalid-block-path\")\n return\n }\n const controller = new AbortController()\n let timedOut = false\n const cancel = () => controller.abort()\n request.once(\"aborted\", cancel)\n response.once(\"close\", cancel)\n const timer = setTimeout(() => {\n timedOut = true\n cancel()\n }, timeoutMs)\n timer.unref()\n void (async () => {\n try {\n // Construct the destination here; never forward request headers, a target URL, cookies or redirects.\n const upstream = await fetch(`${BLOCK_CDN_BASE_URL}/${match[1]}/${match[2]}/data.json`, {\n method: \"GET\",\n credentials: \"omit\",\n cache: \"no-store\",\n redirect: \"error\",\n referrerPolicy: \"no-referrer\",\n signal: controller.signal,\n })\n if (controller.signal.aborted) throw new Error(\"cancelled\")\n if (!upstream.ok) {\n await upstream.body?.cancel()\n reply(upstream.status, \"block-http\")\n return\n }\n response.statusCode = upstream.status\n response.setHeader(\"Content-Type\", \"application/json; charset=utf-8\")\n await forwardBody(upstream, response, controller.signal)\n response.end()\n } catch {\n if (response.destroyed) return\n if (response.headersSent) response.destroy()\n else reply(timedOut ? 504 : 502, timedOut ? \"block-timeout\" : \"block-network\")\n } finally {\n clearTimeout(timer)\n request.off(\"aborted\", cancel)\n response.off(\"close\", cancel)\n }\n })()\n })\n },\n }\n}\n\nasync function forwardBody(upstream: Response, response: ServerResponse, signal: AbortSignal) {\n const reader = upstream.body?.getReader()\n if (!reader) return\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (signal.aborted) throw new Error(\"cancelled\")\n if (done) return\n if (!response.write(value)) await once(response, \"drain\", { signal })\n }\n } finally {\n await reader.cancel().catch(() => undefined)\n reader.releaseLock()\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,45 @@
1
1
  {
2
2
  "name": "@orbit-work/block-sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Anonymous JSON block client for CDN-published Dora data.",
5
+ "type": "module",
6
+ "publishConfig": {
7
+ "access": "public",
8
+ "registry": "https://registry.npmjs.org/"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./vite": {
18
+ "types": "./dist/vite.d.ts",
19
+ "import": "./dist/vite.js"
20
+ }
21
+ },
22
+ "files": ["dist"],
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "prepack": "npm run build",
27
+ "typecheck": "tsc --noEmit",
28
+ "test": "pnpm run build && node --test \"src/**/*.test.mjs\""
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.10.2",
32
+ "tsup": "^8.5.1",
33
+ "typescript": "^5.6.3",
34
+ "vite": "^8.3.0"
35
+ },
36
+ "peerDependencies": {
37
+ "vite": "^8.3.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "vite": { "optional": true }
41
+ },
42
+ "engines": {
43
+ "node": ">=20.0.0"
44
+ }
45
+ }