@riffyh/server 1.0.1 → 1.0.2

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/index.d.mts CHANGED
@@ -16,6 +16,9 @@ declare const server: Elysia<"", {
16
16
  } & {
17
17
  typebox: {};
18
18
  error: {};
19
+ } & {
20
+ typebox: {};
21
+ error: {};
19
22
  }, {
20
23
  schema: {};
21
24
  standaloneSchema: {};
@@ -36,6 +39,13 @@ declare const server: Elysia<"", {
36
39
  macroFn: {};
37
40
  parser: {};
38
41
  response: {};
42
+ } & {
43
+ schema: {};
44
+ standaloneSchema: {};
45
+ macro: {};
46
+ macroFn: {};
47
+ parser: {};
48
+ response: {};
39
49
  }, {
40
50
  get: {
41
51
  body: unknown;
@@ -90,7 +100,7 @@ declare const server: Elysia<"", {
90
100
  params: {};
91
101
  query: {
92
102
  id: string;
93
- dataSource: string;
103
+ dataSource: never;
94
104
  };
95
105
  headers: unknown;
96
106
  response: {
@@ -103,11 +113,11 @@ declare const server: Elysia<"", {
103
113
  type: _$_riffyh_commons0.TagType;
104
114
  }[];
105
115
  key: string;
106
- id: string;
107
116
  title: {
108
117
  display: string;
109
118
  original: string | null;
110
119
  };
120
+ id: string;
111
121
  cover: {
112
122
  src: string;
113
123
  width: number;
@@ -147,11 +157,11 @@ declare const server: Elysia<"", {
147
157
  200: {
148
158
  galleries: {
149
159
  key: string;
150
- id: string;
151
160
  title: {
152
161
  display: string;
153
162
  original: string | null;
154
163
  };
164
+ id: string;
155
165
  cover: {
156
166
  src: string;
157
167
  width: number;
@@ -173,15 +183,64 @@ declare const server: Elysia<"", {
173
183
  };
174
184
  };
175
185
  };
186
+ } & {
187
+ collection: {
188
+ export: {
189
+ post: {
190
+ body: string;
191
+ params: {};
192
+ query: unknown;
193
+ headers: unknown;
194
+ response: {
195
+ 200: string;
196
+ 422: {
197
+ type: "validation";
198
+ on: string;
199
+ summary?: string;
200
+ message?: string;
201
+ found?: unknown;
202
+ property?: string;
203
+ expected?: string;
204
+ };
205
+ };
206
+ };
207
+ };
208
+ };
209
+ } & {
210
+ collection: {
211
+ import: {
212
+ get: {
213
+ body: unknown;
214
+ params: {};
215
+ query: {
216
+ key: string;
217
+ };
218
+ headers: unknown;
219
+ response: {
220
+ 200: string;
221
+ 422: {
222
+ type: "validation";
223
+ on: string;
224
+ summary?: string;
225
+ message?: string;
226
+ found?: unknown;
227
+ property?: string;
228
+ expected?: string;
229
+ };
230
+ };
231
+ };
232
+ };
233
+ };
176
234
  } & {
177
235
  image: {
178
236
  get: {
179
237
  body: unknown;
180
238
  params: {};
181
239
  query: {
240
+ type: "page" | "cover";
182
241
  url: string;
183
242
  dataSource: never;
184
- format: "webp" | "jpg";
243
+ format: "avif" | "webp" | "jpg";
185
244
  };
186
245
  headers: unknown;
187
246
  response: {
@@ -221,6 +280,12 @@ declare const server: Elysia<"", {
221
280
  schema: {};
222
281
  standaloneSchema: {};
223
282
  response: {};
283
+ } & {
284
+ derive: {};
285
+ resolve: {};
286
+ schema: {};
287
+ standaloneSchema: {};
288
+ response: {};
224
289
  }>;
225
290
  type Server = typeof server;
226
291
  //#endregion
package/dist/index.mjs CHANGED
@@ -2,11 +2,47 @@
2
2
  import { Elysia, t } from "elysia";
3
3
  import { swagger } from "@elysiajs/swagger";
4
4
  import { cors } from "@elysiajs/cors";
5
+ import { toon } from "@toon-tools/elysia";
5
6
  import { defineCacheInstance } from "@rayriffy/filesystem";
6
7
  import sharp from "sharp";
7
8
  import { galleryModel, listingResultModel } from "@riffyh/commons";
8
9
  import debug from "debug";
9
10
  import path from "node:path";
11
+ import { randomBytes, secretbox } from "tweetnacl";
12
+ import { decodeBase64, decodeUTF8, encodeBase64, encodeUTF8 } from "tweetnacl-util";
13
+ //#region src/bytebin.ts
14
+ const upload = async (content, key) => {
15
+ const nonce = randomBytes(secretbox.nonceLength);
16
+ const keyUint8Array = decodeBase64(key);
17
+ const box = secretbox(decodeUTF8(content), nonce, keyUint8Array);
18
+ const fullMessage = new Uint8Array(nonce.length + box.length);
19
+ fullMessage.set(nonce);
20
+ fullMessage.set(box, nonce.length);
21
+ const base64FullMessage = encodeBase64(fullMessage);
22
+ const response = await fetch("https://api.pastes.dev/post", {
23
+ method: "POST",
24
+ headers: {
25
+ "Content-Type": "text/plain",
26
+ "User-Agent": "RiffyH (github.com/rayriffy/rayriffy-h)"
27
+ },
28
+ body: base64FullMessage
29
+ });
30
+ if (!response.ok) throw new Error("failed to upload content");
31
+ return (await response.json()).key;
32
+ };
33
+ const download = async (code, key) => {
34
+ const response = await fetch(`https://api.pastes.dev/${code}`);
35
+ if (!response.ok) throw new Error(`failed to download content with code ${code}`);
36
+ const encryptedContent = await response.text();
37
+ const keyUint8Array = decodeBase64(key);
38
+ const messageWithNonceAsUint8Array = decodeBase64(encryptedContent);
39
+ const nonce = messageWithNonceAsUint8Array.slice(0, secretbox.nonceLength);
40
+ const message = messageWithNonceAsUint8Array.slice(secretbox.nonceLength, encryptedContent.length);
41
+ const decrypted = secretbox.open(message, nonce, keyUint8Array);
42
+ if (!decrypted) throw new Error("could not decrypt message");
43
+ return encodeUTF8(decrypted);
44
+ };
45
+ //#endregion
10
46
  //#region src/index.ts
11
47
  const log = debug("riffyh:server");
12
48
  const cache = defineCacheInstance();
@@ -15,9 +51,16 @@ const configFile = process.env.RIFFYH_CONFIG_PATH || "./riffyh.config.ts";
15
51
  const configPath = path.resolve(configFile);
16
52
  log(`resolving config file at ${configPath}...`);
17
53
  const config = await import(configPath).then((o) => o.default);
54
+ try {
55
+ if (typeof config.secretboxKey !== "string" || decodeBase64(config.secretboxKey).length !== secretbox.keyLength) throw new Error("key length mismatch");
56
+ } catch (_) {
57
+ const generatedKey = encodeBase64(randomBytes(secretbox.keyLength));
58
+ console.error(`unable to parse secret key. please either generate key via https://tweetnacl.js.org/#/secretbox or copy following key to configuration: ${generatedKey}`);
59
+ process.exit(1);
60
+ }
18
61
  log(`loaded configuration with ${config.dataSources.length} data sources`);
19
62
  const dataSourceKeys = t.Union(config.dataSources.map((o) => t.Literal(o.key)));
20
- const server = new Elysia().use(swagger({ exclude: ["/_image"] })).use(cors()).get("/", ({ redirect }) => redirect("/swagger")).get("/health", () => "healthy").get("/dataSources", () => config.dataSources.map((o) => ({
63
+ const server = new Elysia().use(swagger({ exclude: ["/_image"] })).use(toon()).use(cors()).get("/", ({ redirect }) => redirect("/swagger")).get("/health", () => "healthy").get("/dataSources", () => config.dataSources.map((o) => ({
21
64
  key: o.key,
22
65
  name: o.name,
23
66
  iconUrl: o.iconUrl
@@ -32,7 +75,7 @@ const server = new Elysia().use(swagger({ exclude: ["/_image"] })).use(cors()).g
32
75
  }, {
33
76
  query: t.Object({
34
77
  id: t.String(),
35
- dataSource: t.String()
78
+ dataSource: dataSourceKeys
36
79
  }),
37
80
  response: galleryModel
38
81
  }).get("/listing", async ({ query }) => {
@@ -49,24 +92,41 @@ const server = new Elysia().use(swagger({ exclude: ["/_image"] })).use(cors()).g
49
92
  dataSource: dataSourceKeys
50
93
  }),
51
94
  response: listingResultModel
52
- }).get("/image", async ({ query }) => {
95
+ }).post("/collection/export", ({ body }) => upload(body, config.secretboxKey), { body: t.String() }).get("/collection/import", async ({ query }) => download(query.key, config.secretboxKey), { query: t.Object({ key: t.String() }) }).get("/image", async ({ query }) => {
53
96
  const cacheKeys = [
54
97
  query.dataSource,
55
98
  query.url,
56
- query.format
99
+ query.format,
100
+ query.type
57
101
  ];
58
102
  const cachedImage = await cache.read(cacheKeys);
59
- if (cachedImage !== null) return new Response(Buffer.from(cachedImage.data), { headers: { "Content-Type": `image/${query.format}` } });
103
+ if (cachedImage !== null) return new Response(Buffer.from(cachedImage.data), { headers: {
104
+ "Content-Type": `image/${query.format}`,
105
+ "Cache-Control": "public, max-age=86400000"
106
+ } });
60
107
  const dataSource = config.dataSources.find((o) => o.key === query.dataSource);
61
108
  if (dataSource === void 0) throw new Error(`data source ${query.dataSource} not found`);
62
- const resizedImage = await sharp(await dataSource.getImage({ url: query.url })).resize({ width: 1280 }).toFormat(query.format, { quality: 72 }).toBuffer();
109
+ const resizedImage = await sharp(await dataSource.getImage({ url: query.url })).resize({ width: query.type === "cover" ? 640 : 1280 }).toFormat(query.format, { quality: 72 }).toBuffer();
63
110
  await cache.write(cacheKeys, resizedImage, 864e5);
64
- return new Response(resizedImage, { headers: { "Content-Type": `image/${query.format}` } });
111
+ return new Response(resizedImage, { headers: {
112
+ "Content-Type": `image/${query.format}`,
113
+ "Cache-Control": "public, max-age=86400",
114
+ "CDN-Cache-Control": "public, max-age=2592000",
115
+ "Cloudflare-CDN-Cache-Control": "public, max-age=2592000"
116
+ } });
65
117
  }, { query: t.Object({
66
118
  url: t.String(),
67
- format: t.Union([t.Literal("webp"), t.Literal("jpg")]),
119
+ format: t.Union([
120
+ t.Literal("avif"),
121
+ t.Literal("webp"),
122
+ t.Literal("jpg")
123
+ ]),
124
+ type: t.Union([t.Literal("cover"), t.Literal("page")]),
68
125
  dataSource: dataSourceKeys
69
- }) }).listen(3e3);
126
+ }) }).listen({
127
+ hostname: config.hostname ?? "0.0.0.0",
128
+ port: config.port ?? 3e3
129
+ });
70
130
  console.log(`🦊 Elysia is running at http://${server.server?.hostname}:${server.server?.port}`);
71
131
  //#endregion
72
132
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env bun\nimport { t, Elysia } from \"elysia\";\nimport { swagger } from \"@elysiajs/swagger\";\nimport { cors } from \"@elysiajs/cors\";\nimport { defineCacheInstance } from \"@rayriffy/filesystem\";\nimport sharp from \"sharp\";\n\nimport { galleryModel, listingResultModel, type Config } from \"@riffyh/commons\";\nimport debug from \"debug\";\nimport path from \"node:path\";\n\nconst log = debug(\"riffyh:server\");\nconst cache = defineCacheInstance();\n\nlog(\"warming up server...\");\n\nconst configFile = process.env.RIFFYH_CONFIG_PATH || \"./riffyh.config.ts\";\nconst configPath = path.resolve(configFile);\nlog(`resolving config file at ${configPath}...`);\nconst config: Config = await import(configPath).then((o) => o.default);\n\nlog(`loaded configuration with ${config.dataSources.length} data sources`);\n\nconst dataSourceKeys = t.Union(config.dataSources.map((o) => t.Literal(o.key)));\n\nconst server = new Elysia()\n .use(\n swagger({\n exclude: [\"/_image\"],\n }),\n )\n .use(cors())\n .get(\"/\", ({ redirect }) => redirect(\"/swagger\"))\n .get(\"/health\", () => \"healthy\")\n .get(\n \"/dataSources\",\n () =>\n config.dataSources.map((o) => ({\n key: o.key,\n name: o.name,\n iconUrl: o.iconUrl,\n })),\n {\n response: t.Array(\n t.Object({\n key: t.String(),\n name: t.String(),\n iconUrl: t.String(),\n }),\n ),\n },\n )\n .get(\n \"/gallery\",\n async ({ query }) => {\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n return dataSource.getGallery({\n id: query.id,\n });\n },\n {\n query: t.Object({\n id: t.String(),\n dataSource: t.String(),\n }),\n response: galleryModel,\n },\n )\n .get(\n \"/listing\",\n async ({ query }) => {\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n return dataSource.getListing({\n searchQuery: query.query || null,\n page: query.page,\n });\n },\n {\n query: t.Object({\n query: t.Optional(t.String()),\n page: t.Number(),\n dataSource: dataSourceKeys,\n }),\n response: listingResultModel,\n },\n )\n .get(\n \"/image\",\n async ({ query }) => {\n const cacheKeys = [query.dataSource, query.url, query.format];\n\n const cachedImage = await cache.read<Buffer>(cacheKeys);\n if (cachedImage !== null)\n return new Response(Buffer.from(cachedImage.data), {\n headers: {\n \"Content-Type\": `image/${query.format}`,\n },\n });\n\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n const fetchedImage = await dataSource.getImage({\n url: query.url,\n });\n const resizedImage = await sharp(fetchedImage)\n .resize({\n width: 1280,\n })\n .toFormat(query.format, {\n quality: 72,\n })\n .toBuffer();\n await cache.write(\n cacheKeys,\n resizedImage,\n 86_400_000, // 1 month\n );\n\n return new Response(resizedImage, {\n headers: {\n \"Content-Type\": `image/${query.format}`,\n },\n });\n },\n {\n query: t.Object({\n url: t.String(),\n format: t.Union([t.Literal(\"webp\"), t.Literal(\"jpg\")]),\n dataSource: dataSourceKeys,\n }),\n },\n )\n .listen(3000);\n\nexport type Server = typeof server;\n\nconsole.log(`🦊 Elysia is running at http://${server.server?.hostname}:${server.server?.port}`);\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/bytebin.ts","../src/index.ts"],"sourcesContent":["import { secretbox, randomBytes } from \"tweetnacl\";\nimport { decodeUTF8, encodeUTF8, encodeBase64, decodeBase64 } from \"tweetnacl-util\";\n\nexport const upload = async (content: string, key: string): Promise<string> => {\n const nonce = randomBytes(secretbox.nonceLength);\n const keyUint8Array = decodeBase64(key);\n const messageUint8 = decodeUTF8(content);\n const box = secretbox(messageUint8, nonce, keyUint8Array);\n\n const fullMessage = new Uint8Array(nonce.length + box.length);\n fullMessage.set(nonce);\n fullMessage.set(box, nonce.length);\n\n const base64FullMessage = encodeBase64(fullMessage);\n const response = await fetch(\"https://api.pastes.dev/post\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"text/plain\",\n \"User-Agent\": \"RiffyH (github.com/rayriffy/rayriffy-h)\",\n },\n body: base64FullMessage,\n });\n\n if (!response.ok) throw new Error(\"failed to upload content\");\n\n const data = response.json() as Promise<{ key: string }>;\n return (await data).key;\n};\n\nexport const download = async (code: string, key: string): Promise<string> => {\n const response = await fetch(`https://api.pastes.dev/${code}`);\n\n if (!response.ok) throw new Error(`failed to download content with code ${code}`);\n\n const encryptedContent = await response.text();\n\n const keyUint8Array = decodeBase64(key);\n const messageWithNonceAsUint8Array = decodeBase64(encryptedContent);\n const nonce = messageWithNonceAsUint8Array.slice(0, secretbox.nonceLength);\n const message = messageWithNonceAsUint8Array.slice(\n secretbox.nonceLength,\n encryptedContent.length,\n );\n\n const decrypted = secretbox.open(message, nonce, keyUint8Array);\n\n if (!decrypted) throw new Error(\"could not decrypt message\");\n\n return encodeUTF8(decrypted);\n};\n","#!/usr/bin/env bun\n\nimport { t, Elysia } from \"elysia\";\nimport { swagger } from \"@elysiajs/swagger\";\nimport { cors } from \"@elysiajs/cors\";\nimport { toon } from \"@toon-tools/elysia\";\n\nimport { defineCacheInstance } from \"@rayriffy/filesystem\";\nimport sharp from \"sharp\";\nimport { galleryModel, listingResultModel, type Config } from \"@riffyh/commons\";\nimport debug from \"debug\";\nimport path from \"node:path\";\nimport { download, upload } from \"./bytebin\";\nimport { secretbox, randomBytes } from \"tweetnacl\";\nimport { encodeBase64, decodeBase64 } from \"tweetnacl-util\";\n\nconst log = debug(\"riffyh:server\");\nconst cache = defineCacheInstance();\n\nlog(\"warming up server...\");\n\nconst configFile = process.env.RIFFYH_CONFIG_PATH || \"./riffyh.config.ts\";\nconst configPath = path.resolve(configFile);\nlog(`resolving config file at ${configPath}...`);\nconst config: Config = await import(configPath).then((o) => o.default);\n\ntry {\n if (\n typeof config.secretboxKey !== \"string\" ||\n decodeBase64(config.secretboxKey).length !== secretbox.keyLength\n )\n throw new Error(\"key length mismatch\");\n // oxlint-disable-next-line no-unused-vars\n} catch (_) {\n const generatedKey = encodeBase64(randomBytes(secretbox.keyLength));\n console.error(\n `unable to parse secret key. please either generate key via https://tweetnacl.js.org/#/secretbox or copy following key to configuration: ${generatedKey}`,\n );\n process.exit(1);\n}\n\nlog(`loaded configuration with ${config.dataSources.length} data sources`);\n\nconst dataSourceKeys = t.Union(config.dataSources.map((o) => t.Literal(o.key)));\n\nconst server = new Elysia()\n .use(\n swagger({\n exclude: [\"/_image\"],\n }),\n )\n .use(toon())\n .use(cors())\n .get(\"/\", ({ redirect }) => redirect(\"/swagger\"))\n .get(\"/health\", () => \"healthy\")\n .get(\n \"/dataSources\",\n () =>\n config.dataSources.map((o) => ({\n key: o.key,\n name: o.name,\n iconUrl: o.iconUrl,\n })),\n {\n response: t.Array(\n t.Object({\n key: t.String(),\n name: t.String(),\n iconUrl: t.String(),\n }),\n ),\n },\n )\n .get(\n \"/gallery\",\n async ({ query }) => {\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n return dataSource.getGallery({\n id: query.id,\n });\n },\n {\n query: t.Object({\n id: t.String(),\n dataSource: dataSourceKeys,\n }),\n response: galleryModel,\n },\n )\n .get(\n \"/listing\",\n async ({ query }) => {\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n return dataSource.getListing({\n searchQuery: query.query || null,\n page: query.page,\n });\n },\n {\n query: t.Object({\n query: t.Optional(t.String()),\n page: t.Number(),\n dataSource: dataSourceKeys,\n }),\n response: listingResultModel,\n },\n )\n .post(\"/collection/export\", ({ body }) => upload(body, config.secretboxKey), {\n body: t.String(),\n })\n .get(\"/collection/import\", async ({ query }) => download(query.key, config.secretboxKey), {\n query: t.Object({\n key: t.String(),\n }),\n })\n .get(\n \"/image\",\n async ({ query }) => {\n const cacheKeys = [query.dataSource, query.url, query.format, query.type];\n\n const cachedImage = await cache.read<Buffer>(cacheKeys);\n if (cachedImage !== null)\n return new Response(Buffer.from(cachedImage.data), {\n headers: {\n \"Content-Type\": `image/${query.format}`,\n \"Cache-Control\": \"public, max-age=86400000\",\n },\n });\n\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n const fetchedImage = await dataSource.getImage({\n url: query.url,\n });\n const resizedImage = await sharp(fetchedImage)\n .resize({\n width: query.type === \"cover\" ? 640 : 1280,\n })\n .toFormat(query.format, {\n quality: 72,\n })\n .toBuffer();\n await cache.write(\n cacheKeys,\n resizedImage,\n 86_400_000, // 1 month\n );\n\n return new Response(resizedImage, {\n headers: {\n \"Content-Type\": `image/${query.format}`,\n \"Cache-Control\": \"public, max-age=86400\",\n \"CDN-Cache-Control\": \"public, max-age=2592000\",\n \"Cloudflare-CDN-Cache-Control\": \"public, max-age=2592000\",\n },\n });\n },\n {\n query: t.Object({\n url: t.String(),\n format: t.Union([t.Literal(\"avif\"), t.Literal(\"webp\"), t.Literal(\"jpg\")]),\n type: t.Union([t.Literal(\"cover\"), t.Literal(\"page\")]),\n dataSource: dataSourceKeys,\n }),\n },\n )\n .listen({\n hostname: config.hostname ?? \"0.0.0.0\",\n port: config.port ?? 3000,\n });\n\nexport type Server = typeof server;\n\nconsole.log(`🦊 Elysia is running at http://${server.server?.hostname}:${server.server?.port}`);\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@riffyh/server",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rayriffy/rayriffy-h.git"
7
7
  },
8
- "bin": "dist/index.mjs",
9
8
  "files": [
10
9
  "dist/"
11
10
  ],
@@ -21,19 +20,23 @@
21
20
  "@elysiajs/swagger": "1.3.1",
22
21
  "@rayriffy/filesystem": "1.0.1",
23
22
  "@sinclair/typebox": "0.34.49",
23
+ "@toon-tools/elysia": "1.1.0",
24
24
  "debug": "4.4.3",
25
25
  "elysia": "1.4.28",
26
- "sharp": "^0.34.5",
27
- "@riffyh/commons": "2.1.0"
26
+ "sharp": "0.34.5",
27
+ "tweetnacl": "1.0.3",
28
+ "tweetnacl-util": "0.15.1",
29
+ "@riffyh/commons": "2.1.1"
28
30
  },
29
31
  "devDependencies": {
30
32
  "@types/debug": "4.1.13",
31
33
  "tsdown": "0.21.7"
32
34
  },
33
35
  "scripts": {
34
- "test": "echo \"Error: no test specified\" && exit 1",
35
- "dev": "bun run --watch src/index.ts",
36
- "build": "tsdown",
37
- "start": "bun run dist/index.mjs"
36
+ "dev": "tsdown --watch",
37
+ "build": "tsdown"
38
+ },
39
+ "bin": {
40
+ "server": "dist/index.mjs"
38
41
  }
39
42
  }