mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-02 16:16:02 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c26d646bd | |||
| 4859b99954 |
+11
-2
@@ -1,3 +1,4 @@
|
|||||||
|
/** biome-ignore-all lint/suspicious/noConsole: . */
|
||||||
import { $ } from "bun";
|
import { $ } from "bun";
|
||||||
import * as tsup from "tsup";
|
import * as tsup from "tsup";
|
||||||
import pkg from "./package.json" with { type: "json" };
|
import pkg from "./package.json" with { type: "json" };
|
||||||
@@ -267,7 +268,7 @@ async function buildAdapters() {
|
|||||||
tsup.build(baseConfig("react-router")),
|
tsup.build(baseConfig("react-router")),
|
||||||
tsup.build(
|
tsup.build(
|
||||||
baseConfig("bun", {
|
baseConfig("bun", {
|
||||||
external: [/^bun\:.*/],
|
external: [/^bun:.*/],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
tsup.build(baseConfig("astro")),
|
tsup.build(baseConfig("astro")),
|
||||||
@@ -286,6 +287,14 @@ async function buildAdapters() {
|
|||||||
external: [/bknd/, "wrangler", "node:process"],
|
external: [/bknd/, "wrangler", "node:process"],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
tsup.build(
|
||||||
|
baseConfig("cloudflare/vite", {
|
||||||
|
target: "esnext",
|
||||||
|
entry: ["src/adapter/cloudflare/vite.ts"],
|
||||||
|
outDir: "dist/adapter/cloudflare",
|
||||||
|
metafile: false,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
|
||||||
tsup.build({
|
tsup.build({
|
||||||
...baseConfig("vite"),
|
...baseConfig("vite"),
|
||||||
@@ -322,7 +331,7 @@ async function buildAdapters() {
|
|||||||
entry: ["src/adapter/sqlite/bun.ts"],
|
entry: ["src/adapter/sqlite/bun.ts"],
|
||||||
outDir: "dist/adapter/sqlite",
|
outDir: "dist/adapter/sqlite",
|
||||||
metafile: false,
|
metafile: false,
|
||||||
external: [/^bun\:.*/],
|
external: [/^bun:.*/],
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,6 +214,11 @@
|
|||||||
"import": "./dist/adapter/cloudflare/proxy.js",
|
"import": "./dist/adapter/cloudflare/proxy.js",
|
||||||
"require": "./dist/adapter/cloudflare/proxy.js"
|
"require": "./dist/adapter/cloudflare/proxy.js"
|
||||||
},
|
},
|
||||||
|
"./adapter/cloudflare/vite": {
|
||||||
|
"types": "./dist/types/adapter/cloudflare/vite.d.ts",
|
||||||
|
"import": "./dist/adapter/cloudflare/vite.js",
|
||||||
|
"require": "./dist/adapter/cloudflare/vite.js"
|
||||||
|
},
|
||||||
"./adapter": {
|
"./adapter": {
|
||||||
"types": "./dist/types/adapter/index.d.ts",
|
"types": "./dist/types/adapter/index.d.ts",
|
||||||
"import": "./dist/adapter/index.js"
|
"import": "./dist/adapter/index.js"
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { inspect } from "node:util";
|
|
||||||
|
|
||||||
export type BindingTypeMap = {
|
export type BindingTypeMap = {
|
||||||
D1Database: D1Database;
|
D1Database: D1Database;
|
||||||
KVNamespace: KVNamespace;
|
KVNamespace: KVNamespace;
|
||||||
@@ -10,21 +8,40 @@ export type BindingTypeMap = {
|
|||||||
export type GetBindingType = keyof BindingTypeMap;
|
export type GetBindingType = keyof BindingTypeMap;
|
||||||
export type BindingMap<T extends GetBindingType> = { key: string; value: BindingTypeMap[T] };
|
export type BindingMap<T extends GetBindingType> = { key: string; value: BindingTypeMap[T] };
|
||||||
|
|
||||||
|
export const bindingMatchers = {
|
||||||
|
// D1Database instance doesn't stringify to [object D1Database] (yet)
|
||||||
|
D1Database: (value: object): value is D1Database => {
|
||||||
|
if (typeof value !== "object" || value === null) return false;
|
||||||
|
if (String(value) === "[object D1Database]") return true;
|
||||||
|
if (Object.keys(value).length !== 2) return false;
|
||||||
|
return ["alwaysPrimarySession", "fetcher"].every((key) => key in value);
|
||||||
|
},
|
||||||
|
KVNamespace: (value: object): value is KVNamespace =>
|
||||||
|
String(value) === "[object KvNamespace]" && Object.keys(value).length === 0,
|
||||||
|
DurableObjectNamespace: (value: object): value is DurableObjectNamespace =>
|
||||||
|
String(value) === "[object DurableObjectNamespace]" && Object.keys(value).length === 0,
|
||||||
|
R2Bucket: (value: object): value is R2Bucket =>
|
||||||
|
String(value) === "[object R2Bucket]" && Object.keys(value).length === 0,
|
||||||
|
};
|
||||||
|
|
||||||
export function getBindings<T extends GetBindingType>(env: any, type: T): BindingMap<T>[] {
|
export function getBindings<T extends GetBindingType>(env: any, type: T): BindingMap<T>[] {
|
||||||
const bindings: BindingMap<T>[] = [];
|
const bindings: BindingMap<T>[] = [];
|
||||||
|
if (!(type in bindingMatchers)) {
|
||||||
|
console.error(`No binding matcher for type ${type}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const matcher = bindingMatchers[type];
|
||||||
|
|
||||||
for (const key in env) {
|
for (const key in env) {
|
||||||
try {
|
const value = env[key];
|
||||||
if (
|
if (typeof value !== "object" || value === null) continue;
|
||||||
(env[key] as any).constructor.name === type ||
|
if (!matcher(value)) continue;
|
||||||
String(env[key]) === `[object ${type}]` ||
|
|
||||||
inspect(env[key]).includes(type)
|
bindings.push({
|
||||||
) {
|
key,
|
||||||
bindings.push({
|
value,
|
||||||
key,
|
} as any);
|
||||||
value: env[key] as BindingTypeMap[T],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
}
|
||||||
return bindings;
|
return bindings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { describe, afterAll, test, expect, beforeAll } from "vitest";
|
||||||
|
import { Miniflare } from "miniflare";
|
||||||
|
import { getBindings, type GetBindingType } from "./bindings";
|
||||||
|
|
||||||
|
let mf: Miniflare | undefined;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
mf = new Miniflare({
|
||||||
|
modules: true,
|
||||||
|
script: `import { DurableObject } from "cloudflare:workers";
|
||||||
|
export default { async fetch() { return new Response(null); } }
|
||||||
|
export class TestObject extends DurableObject {}
|
||||||
|
export class TestObject2 extends DurableObject {}`,
|
||||||
|
r2Buckets: ["BUCKET"],
|
||||||
|
d1Databases: ["DB"],
|
||||||
|
kvNamespaces: ["KV"],
|
||||||
|
durableObjects: {
|
||||||
|
DO_SQLITE: { className: "TestObject", useSQLite: true },
|
||||||
|
DO_KV: { className: "TestObject2" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
afterAll(() => {
|
||||||
|
mf?.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("bindings", () => {
|
||||||
|
test("extracts", async () => {
|
||||||
|
const env = {
|
||||||
|
...(await mf!.getBindings()),
|
||||||
|
some: "string",
|
||||||
|
another: 123,
|
||||||
|
array: [1, 2, 3],
|
||||||
|
};
|
||||||
|
|
||||||
|
const $getBindingKeys = (type: GetBindingType) => {
|
||||||
|
const bindings = getBindings(env, type);
|
||||||
|
const keys = bindings?.map((b) => b.key);
|
||||||
|
if (!keys || keys.length === 0) {
|
||||||
|
console.error(`No ${type} binding found`, { bindings });
|
||||||
|
throw new Error(`No ${type} binding found`);
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
};
|
||||||
|
|
||||||
|
expect($getBindingKeys("D1Database")).toEqual(["DB"]);
|
||||||
|
expect($getBindingKeys("KVNamespace")).toEqual(["KV"]);
|
||||||
|
expect($getBindingKeys("DurableObjectNamespace")).toEqual(["DO_SQLITE", "DO_KV"]);
|
||||||
|
expect($getBindingKeys("R2Bucket")).toEqual(["BUCKET"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -16,10 +16,14 @@ export {
|
|||||||
type GetBindingType,
|
type GetBindingType,
|
||||||
type BindingMap,
|
type BindingMap,
|
||||||
} from "./bindings";
|
} from "./bindings";
|
||||||
export { constants, makeConfig, type CloudflareContext } from "./config";
|
export {
|
||||||
|
constants,
|
||||||
|
makeConfig,
|
||||||
|
type CloudflareContext,
|
||||||
|
registerAsyncsExecutionContext,
|
||||||
|
} from "./config";
|
||||||
export { StorageR2Adapter, registerMedia } from "./storage/StorageR2Adapter";
|
export { StorageR2Adapter, registerMedia } from "./storage/StorageR2Adapter";
|
||||||
export { registries } from "bknd";
|
export { registries } from "bknd";
|
||||||
export { devFsVitePlugin, devFsWrite } from "./vite";
|
|
||||||
|
|
||||||
// for compatibility with old code
|
// for compatibility with old code
|
||||||
export function d1<DB extends D1Database | D1DatabaseSession = D1Database>(
|
export function d1<DB extends D1Database | D1DatabaseSession = D1Database>(
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export function registerMedia(
|
|||||||
env: Record<string, any>,
|
env: Record<string, any>,
|
||||||
registries: typeof $registries = $registries,
|
registries: typeof $registries = $registries,
|
||||||
) {
|
) {
|
||||||
|
if (registries.media.has("r2")) return;
|
||||||
const r2_bindings = getBindings(env, "R2Bucket");
|
const r2_bindings = getBindings(env, "R2Bucket");
|
||||||
|
|
||||||
registries.media.register(
|
registries.media.register(
|
||||||
@@ -172,7 +173,7 @@ export class StorageR2Adapter extends StorageAdapter {
|
|||||||
await this.bucket.delete(this.getKey(key));
|
await this.bucket.delete(this.getKey(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
getObjectUrl(key: string): string {
|
getObjectUrl(): string {
|
||||||
throw new Error("Method getObjectUrl not implemented.");
|
throw new Error("Method getObjectUrl not implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +184,7 @@ export class StorageR2Adapter extends StorageAdapter {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(secrets?: boolean) {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
type: this.getName(),
|
type: this.getName(),
|
||||||
config: {},
|
config: {},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// biome-ignore-all lint: .
|
||||||
import type { Plugin } from "vite";
|
import type { Plugin } from "vite";
|
||||||
import { writeFile as nodeWriteFile } from "node:fs/promises";
|
import { writeFile as nodeWriteFile } from "node:fs/promises";
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
@@ -48,7 +49,6 @@ export function devFsVitePlugin({
|
|||||||
// Skip our own debug output
|
// Skip our own debug output
|
||||||
if (output.includes("[dev-fs-plugin]") || output.includes("[dev-fs-polyfill]")) {
|
if (output.includes("[dev-fs-plugin]") || output.includes("[dev-fs-polyfill]")) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// biome-ignore lint/style/noArguments: <explanation>
|
|
||||||
return originalStdoutWrite.apply(process.stdout, arguments);
|
return originalStdoutWrite.apply(process.stdout, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +178,6 @@ export function devFsVitePlugin({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// biome-ignore lint:
|
|
||||||
return originalStdoutWrite.apply(process.stdout, arguments);
|
return originalStdoutWrite.apply(process.stdout, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -73,8 +73,9 @@ export const env = <
|
|||||||
onValid?: (valid: R) => void;
|
onValid?: (valid: R) => void;
|
||||||
},
|
},
|
||||||
): R extends undefined ? Fallback : R => {
|
): R extends undefined ? Fallback : R => {
|
||||||
|
const source = opts?.source ?? (typeof process !== "undefined" ? process?.env : {});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const source = opts?.source ?? process.env;
|
|
||||||
const c = envs[key];
|
const c = envs[key];
|
||||||
const g = source[c.key];
|
const g = source[c.key];
|
||||||
const v = c.validate(g) as any;
|
const v = c.validate(g) as any;
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ The [Cloudflare Vite Plugin](https://developers.cloudflare.com/workers/vite-plug
|
|||||||
To fix this, bknd exports a Vite plugin that provides filesystem access during development. You can use it by adding the following to your `vite.config.ts` file:
|
To fix this, bknd exports a Vite plugin that provides filesystem access during development. You can use it by adding the following to your `vite.config.ts` file:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { devFsVitePlugin } from "bknd/adapter/cloudflare";
|
import { devFsVitePlugin } from "bknd/adapter/cloudflare/vite";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [devFsVitePlugin()], // [!code highlight]
|
plugins: [devFsVitePlugin()], // [!code highlight]
|
||||||
@@ -174,7 +174,7 @@ export default defineConfig({
|
|||||||
Now to use this polyfill, you can use the `devFsWrite` function to write files to the filesystem.
|
Now to use this polyfill, you can use the `devFsWrite` function to write files to the filesystem.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { devFsWrite } from "bknd/adapter/cloudflare"; // [!code highlight]
|
import { devFsWrite } from "bknd/adapter/cloudflare/vite"; // [!code highlight]
|
||||||
import { syncTypes } from "bknd/plugins";
|
import { syncTypes } from "bknd/plugins";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -6,15 +6,15 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"deploy": "wrangler deploy",
|
"deploy": "wrangler deploy",
|
||||||
"dev": "wrangler dev",
|
"dev": "wrangler dev",
|
||||||
"bknd-typegen": "PROXY=1 npx bknd types",
|
"bknd-typegen": "PROXY=1 node_modules/.bin/bknd types",
|
||||||
"typegen": "wrangler types && npm run bknd-typegen",
|
"typegen": "wrangler types && npm run bknd-typegen",
|
||||||
"predev": "npm run typegen"
|
"postinstall": "npm run typegen"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bknd": "file:../../app"
|
"bknd": "file:../../app"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.3",
|
||||||
"wrangler": "^4.34.0"
|
"wrangler": "^4.46.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
"$schema": "node_modules/wrangler/config-schema.json",
|
"$schema": "node_modules/wrangler/config-schema.json",
|
||||||
"name": "bknd-cf-worker-example",
|
"name": "bknd-cf-worker-example",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"compatibility_date": "2025-08-03",
|
"compatibility_date": "2025-11-05",
|
||||||
"compatibility_flags": ["nodejs_compat"],
|
|
||||||
"workers_dev": true,
|
"workers_dev": true,
|
||||||
"minify": true,
|
"minify": true,
|
||||||
"assets": {
|
"assets": {
|
||||||
|
|||||||
Reference in New Issue
Block a user