bunderstack 0.25.1 → 1.0.0-beta.1
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/CHANGELOG.md +43 -0
- package/dist/auth-password.d.ts +27 -0
- package/dist/auth-password.d.ts.map +1 -0
- package/dist/auth-password.js +60 -0
- package/dist/auth-password.js.map +1 -0
- package/dist/auth.d.ts +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +1 -0
- package/dist/auth.js.map +1 -1
- package/dist/backend.d.ts +3 -0
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +3 -1
- package/dist/backend.js.map +1 -1
- package/dist/cli.d.ts +11 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +97 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +0 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -18
- package/dist/config.js.map +1 -1
- package/dist/crud-operations.d.ts.map +1 -1
- package/dist/crud-operations.js +19 -3
- package/dist/crud-operations.js.map +1 -1
- package/dist/dev/binaries.d.ts +22 -0
- package/dist/dev/binaries.d.ts.map +1 -0
- package/dist/dev/binaries.js +119 -0
- package/dist/dev/binaries.js.map +1 -0
- package/dist/dev/env.d.ts +11 -0
- package/dist/dev/env.d.ts.map +1 -0
- package/dist/dev/env.js +71 -0
- package/dist/dev/env.js.map +1 -0
- package/dist/dev/index.d.ts +45 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +287 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/processes.d.ts +37 -0
- package/dist/dev/processes.d.ts.map +1 -0
- package/dist/dev/processes.js +104 -0
- package/dist/dev/processes.js.map +1 -0
- package/dist/dev/push.d.ts +2 -0
- package/dist/dev/push.d.ts.map +1 -0
- package/dist/dev/push.js +25 -0
- package/dist/dev/push.js.map +1 -0
- package/dist/env-probe.d.ts.map +1 -1
- package/dist/env-probe.js +0 -1
- package/dist/env-probe.js.map +1 -1
- package/dist/env.d.ts +0 -3
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +0 -7
- package/dist/env.js.map +1 -1
- package/dist/handler.d.ts +2 -0
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +1 -2
- package/dist/handler.js.map +1 -1
- package/dist/hosted-contract.d.ts.map +1 -1
- package/dist/hosted-contract.js +7 -3
- package/dist/hosted-contract.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jobs/define.d.ts +5 -0
- package/dist/jobs/define.d.ts.map +1 -1
- package/dist/jobs/define.js.map +1 -1
- package/dist/jobs/index.d.ts +2 -4
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +2 -3
- package/dist/jobs/index.js.map +1 -1
- package/dist/jobs/queue.d.ts +1 -0
- package/dist/jobs/queue.d.ts.map +1 -1
- package/dist/jobs/queue.js +6 -3
- package/dist/jobs/queue.js.map +1 -1
- package/dist/jobs/slots.d.ts +5 -0
- package/dist/jobs/slots.d.ts.map +1 -1
- package/dist/jobs/slots.js +12 -0
- package/dist/jobs/slots.js.map +1 -1
- package/dist/jobs/worker.d.ts +6 -0
- package/dist/jobs/worker.d.ts.map +1 -1
- package/dist/jobs/worker.js +32 -1
- package/dist/jobs/worker.js.map +1 -1
- package/dist/platform.d.ts +25 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +24 -0
- package/dist/platform.js.map +1 -0
- package/dist/query/client.js +1 -1
- package/dist/query/client.js.map +1 -1
- package/dist/rate-limit.d.ts +2 -1
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +19 -22
- package/dist/rate-limit.js.map +1 -1
- package/dist/realtime/facade.d.ts +1 -1
- package/dist/realtime/facade.d.ts.map +1 -1
- package/dist/realtime/facade.js.map +1 -1
- package/dist/realtime/publisher.d.ts +0 -4
- package/dist/realtime/publisher.d.ts.map +1 -1
- package/dist/realtime/publisher.js +0 -9
- package/dist/realtime/publisher.js.map +1 -1
- package/dist/runtime.d.ts +5 -19
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +28 -102
- package/dist/runtime.js.map +1 -1
- package/dist/storage/registry.d.ts +2 -1
- package/dist/storage/registry.d.ts.map +1 -1
- package/dist/storage/registry.js +2 -2
- package/dist/storage/registry.js.map +1 -1
- package/dist/storage/s3.d.ts +11 -4
- package/dist/storage/s3.d.ts.map +1 -1
- package/dist/storage/s3.js +88 -34
- package/dist/storage/s3.js.map +1 -1
- package/dist/storage/sigv4.d.ts +24 -0
- package/dist/storage/sigv4.d.ts.map +1 -0
- package/dist/storage/sigv4.js +107 -0
- package/dist/storage/sigv4.js.map +1 -0
- package/dist/testing/fixture.d.ts.map +1 -1
- package/dist/testing/fixture.js +0 -3
- package/dist/testing/fixture.js.map +1 -1
- package/dist/testing/workers-fakes.d.ts +17 -0
- package/dist/testing/workers-fakes.d.ts.map +1 -0
- package/dist/testing/workers-fakes.js +98 -0
- package/dist/testing/workers-fakes.js.map +1 -0
- package/dist/typeid.d.ts +1 -0
- package/dist/typeid.d.ts.map +1 -1
- package/dist/typeid.js +34 -4
- package/dist/typeid.js.map +1 -1
- package/dist/vite.d.ts +30 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +37 -0
- package/dist/vite.js.map +1 -0
- package/dist/workers/app.d.ts +19 -0
- package/dist/workers/app.d.ts.map +1 -0
- package/dist/workers/app.js +56 -0
- package/dist/workers/app.js.map +1 -0
- package/dist/workers/index.d.ts +21 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +33 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/r2.d.ts +23 -0
- package/dist/workers/r2.d.ts.map +1 -0
- package/dist/workers/r2.js +77 -0
- package/dist/workers/r2.js.map +1 -0
- package/dist/workers/rate-limiter.d.ts +9 -0
- package/dist/workers/rate-limiter.d.ts.map +1 -0
- package/dist/workers/rate-limiter.js +22 -0
- package/dist/workers/rate-limiter.js.map +1 -0
- package/dist/workers/realtime-hub.d.ts +20 -0
- package/dist/workers/realtime-hub.d.ts.map +1 -0
- package/dist/workers/realtime-hub.js +117 -0
- package/dist/workers/realtime-hub.js.map +1 -0
- package/dist/workers/scheduler.d.ts +13 -0
- package/dist/workers/scheduler.d.ts.map +1 -0
- package/dist/workers/scheduler.js +71 -0
- package/dist/workers/scheduler.js.map +1 -0
- package/dist/workers/types.d.ts +55 -0
- package/dist/workers/types.d.ts.map +1 -0
- package/dist/workers/types.js +4 -0
- package/dist/workers/types.js.map +1 -0
- package/dist/workers/wrangler.d.ts +64 -0
- package/dist/workers/wrangler.d.ts.map +1 -0
- package/dist/workers/wrangler.js +115 -0
- package/dist/workers/wrangler.js.map +1 -0
- package/llms-full.txt +10 -1
- package/package.json +11 -3
- package/dist/jobs/runtime.d.ts +0 -17
- package/dist/jobs/runtime.d.ts.map +0 -1
- package/dist/jobs/runtime.js +0 -56
- package/dist/jobs/runtime.js.map +0 -1
package/dist/config.js
CHANGED
|
@@ -23,10 +23,6 @@ const RuntimeOptionsSchema = v.object({
|
|
|
23
23
|
v.object({
|
|
24
24
|
bufferSize: v.optional(v.number()),
|
|
25
25
|
resumeSeconds: v.optional(v.number()),
|
|
26
|
-
redis: v.optional(v.union([
|
|
27
|
-
v.string(),
|
|
28
|
-
v.object({ url: v.string(), token: v.optional(v.string()) }),
|
|
29
|
-
])),
|
|
30
26
|
}),
|
|
31
27
|
])),
|
|
32
28
|
openapi: v.optional(v.boolean()),
|
|
@@ -76,18 +72,4 @@ platformSource = process.env) {
|
|
|
76
72
|
export function resolveAuthConfig(auth, ctx) {
|
|
77
73
|
return typeof auth === 'function' ? auth(ctx) : auth;
|
|
78
74
|
}
|
|
79
|
-
export function resolveRealtimeRedisUrl(realtime, env, platformSource = process.env) {
|
|
80
|
-
const platformRedis = platformSource['REDIS_URL'];
|
|
81
|
-
if (platformRedis)
|
|
82
|
-
return platformRedis;
|
|
83
|
-
const envRedis = env?.REDIS_URL;
|
|
84
|
-
if (envRedis)
|
|
85
|
-
return envRedis;
|
|
86
|
-
const fromConfig = typeof realtime === 'object' && realtime.redis
|
|
87
|
-
? typeof realtime.redis === 'string'
|
|
88
|
-
? realtime.redis
|
|
89
|
-
: realtime.redis.url
|
|
90
|
-
: undefined;
|
|
91
|
-
return fromConfig ?? undefined;
|
|
92
|
-
}
|
|
93
75
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnyMiddleware, AnyRouter } from '@orpc/server'\n\n// src/config.ts\nimport { betterAuth } from 'better-auth'\nimport * as v from 'valibot'\n\nimport type {\n AuthSessionResolver,\n SessionUserConfig,\n SessionUserExtraOf,\n TableAccessInput,\n} from './access'\nimport type { BunderstackApiBuilder } from './api/builder'\nimport type { DatabaseAdapter } from './database/adapter'\nimport type { DbFor } from './db'\nimport type { AnyDb } from './dialect'\nimport type { IdempotencyConfig } from './idempotency'\nimport type { MessagingConfig } from './messaging'\nimport type { RateLimitConfig } from './rate-limit'\n\nimport {\n validateEnv,\n type BaseEnv,\n type EnvConfigInput,\n type ValidatedEnv,\n} from './env'\nimport {\n resolveBuckets,\n type ResolvedStorageBuckets,\n type StorageConfigInput,\n} from './storage/buckets'\n\nexport type BetterAuthConfig = Omit<\n NonNullable<Parameters<typeof betterAuth>[0]>,\n 'database'\n>\n\n/**\n * What an `auth` builder is handed. `db` is the app's own connection, typed\n * from `schema` alone — that is what keeps the builder cycle-free: better-auth\n * hooks living in another file get a db without importing the app whose type\n * they help produce.\n */\nexport type AuthConfigContext<\n TSchema extends Record<string, unknown>,\n TEnv extends EnvConfigInput | undefined = undefined,\n> = {\n db: DbFor<TSchema>\n env: ValidatedEnv<TEnv>\n}\n\nexport type AuthConfigInput<\n TSchema extends Record<string, unknown>,\n TEnv extends EnvConfigInput | undefined = undefined,\n TConfig extends BetterAuthConfig = BetterAuthConfig,\n> = TConfig | ((ctx: AuthConfigContext<TSchema, TEnv>) => TConfig)\n\n/**\n * Identity helper for defining an auth config in a separate file with full\n * type inference — no explicit generic annotations required.\n *\n * Follows the `defineConfig` / `defineComponent` ecosystem convention:\n * the function does nothing at runtime but gives TypeScript an inference\n * anchor through the `schema` parameter.\n *\n * @example\n * ```ts\n * // Static config (no db/env access needed)\n * export const authConfig = defineAuth({ ... })\n *\n * // Builder with schema-typed db\n * export const authConfig = defineAuth(schema, ({ db }) => ({\n * databaseHooks: {\n * user: { create: { after: async (user) => { await db.insert(...) } } }\n * }\n * }))\n * ```\n */\nexport function defineAuth<const TConfig extends BetterAuthConfig>(\n config: TConfig,\n): TConfig\nexport function defineAuth<\n TSchema extends Record<string, unknown>,\n TEnv extends EnvConfigInput | undefined,\n const TConfig extends BetterAuthConfig,\n>(\n context: { schema: TSchema; env: TEnv },\n builder: (ctx: AuthConfigContext<TSchema, TEnv>) => TConfig,\n): (ctx: AuthConfigContext<TSchema, TEnv>) => TConfig\nexport function defineAuth<\n TSchema extends Record<string, unknown>,\n const TConfig extends BetterAuthConfig,\n TEnv extends EnvConfigInput | undefined = undefined,\n>(\n schema: TSchema,\n builder: (ctx: AuthConfigContext<TSchema, TEnv>) => TConfig,\n): (ctx: AuthConfigContext<TSchema, TEnv>) => TConfig\nexport function defineAuth(\n schemaOrConfig: Record<string, unknown>,\n builder?: (ctx: any) => BetterAuthConfig,\n) {\n return builder ?? schemaOrConfig\n}\n\n/**\n * The builder as {@link ResolvedConfig} carries it: schema-agnostic, because\n * ResolvedConfig is not generic. {@link resolveAuthConfig} is the only caller.\n */\nexport type AuthConfigFactory = (ctx: {\n db: AnyDb\n env: BaseEnv\n}) => BetterAuthConfig\n\n// Only the union-shaped options need runtime validation: they are the ones a\n// JavaScript caller can plausibly get wrong in a way that fails confusingly\n// downstream. Everything else is either typed-only or read raw from `options`.\nconst RuntimeOptionsSchema = v.object({\n rateLimit: v.optional(\n v.union([\n v.boolean(),\n v.object({\n windowMs: v.optional(v.number()),\n max: v.optional(v.number()),\n }),\n ]),\n ),\n idempotency: v.optional(\n v.union([v.boolean(), v.object({ ttlMs: v.optional(v.number()) })]),\n ),\n realtime: v.optional(\n v.union([\n v.boolean(),\n v.object({\n bufferSize: v.optional(v.number()),\n resumeSeconds: v.optional(v.number()),\n redis: v.optional(\n v.union([\n v.string(),\n v.object({ url: v.string(), token: v.optional(v.string()) }),\n ]),\n ),\n }),\n ]),\n ),\n openapi: v.optional(v.boolean()),\n})\n\n/** Realtime declaration. Independent of schema, storage, and env. */\nexport type RealtimeConfigInput =\n | boolean\n | {\n bufferSize?: number\n resumeSeconds?: number\n redis?: string | { url: string; token?: string }\n }\n\nexport type BunderstackConfig<\n TSchema extends Record<string, unknown>,\n TAccess extends Record<string, TableAccessInput> | undefined =\n | Record<string, TableAccessInput>\n | undefined,\n TStorage extends StorageConfigInput | undefined =\n | StorageConfigInput\n | undefined,\n TEnv extends EnvConfigInput | undefined = EnvConfigInput | undefined,\n TCustomApiRouter extends AnyRouter | undefined = AnyRouter | undefined,\n TAuthConfig extends BetterAuthConfig = BetterAuthConfig,\n TSession extends SessionUserConfig<Record<string, unknown>> | undefined =\n undefined,\n> = {\n schema: TSchema\n access?: TAccess\n database: {\n adapter: DatabaseAdapter\n url?: string\n authToken?: string\n migrations?: string\n }\n /**\n * better-auth options, or a builder receiving `{ db, env }`. Use the builder\n * form when database hooks need to write: it hands out the app's own\n * connection, so the application never opens a second one just to satisfy a\n * config that is built before the app exists.\n */\n auth?: AuthConfigInput<NoInfer<TSchema>, NoInfer<TEnv>, TAuthConfig>\n /**\n * Reuse an application-owned session reader for the unified API while\n * keeping Bunderstack's auth handler available.\n */\n authResolver?: AuthSessionResolver<SessionUserExtraOf<TSession>>\n /** Explicitly project application fields from Better Auth into API users. */\n session?: TSession\n storage?: TStorage\n background?: { autoStart?: boolean }\n messaging?: MessagingConfig\n /**\n * The application's oRPC router. Pass the finished router object, or a\n * callback when the router needs the framework builder at configuration\n * time. Declare the router at module scope with `defineApi` and the object\n * form keeps router modules free of factory wrappers.\n */\n api?:\n | TCustomApiRouter\n | ((\n builder: BunderstackApiBuilder<\n TSchema,\n ValidatedEnv<TEnv>,\n MessagingConfig,\n SessionUserExtraOf<TSession>\n >,\n ) => TCustomApiRouter)\n /**\n * Middleware applied to every procedure in the graph: generated CRUD,\n * storage, realtime, health, and the application's own procedures. Declare\n * each one with `o.middleware(...)` from `defineApi`.\n *\n * It runs before authentication, so `context.user` is not available inside\n * it. Read an already-resolved caller with `context.peekSession()`.\n */\n middleware?: AnyMiddleware[]\n rateLimit?: boolean | RateLimitConfig\n idempotency?: boolean | IdempotencyConfig\n /** Generate and serve `/api/openapi.json`. Disabled by default. */\n openapi?: boolean\n realtime?: RealtimeConfigInput\n}\n\nexport type ResolvedConfig = {\n database: {\n adapter: DatabaseAdapter\n url: string\n authToken?: string\n migrations: string\n }\n auth: BetterAuthConfig | AuthConfigFactory\n storage: ResolvedStorageBuckets\n realtime?: RealtimeConfigInput\n}\n\nexport function resolveConfig<\n TSchema extends Record<string, unknown>,\n TAccess extends Record<string, TableAccessInput> | undefined = undefined,\n TStorage extends StorageConfigInput | undefined = undefined,\n TEnv extends EnvConfigInput | undefined = undefined,\n TCustomApiRouter extends AnyRouter | undefined = undefined,\n TAuthConfig extends BetterAuthConfig = BetterAuthConfig,\n TSession extends SessionUserConfig<Record<string, unknown>> | undefined =\n undefined,\n>(\n options: BunderstackConfig<\n TSchema,\n TAccess,\n TStorage,\n TEnv,\n TCustomApiRouter,\n TAuthConfig,\n TSession\n >,\n env?: BaseEnv,\n // Platform-injected overrides (Bunderhost & co.) beat code-level config so\n // apps with hardcoded local urls deploy unchanged.\n platformSource: Record<string, string | undefined> = process.env as Record<\n string,\n string | undefined\n >,\n): ResolvedConfig {\n const parsed = v.parse(RuntimeOptionsSchema, options)\n // Self-validate when the caller didn't pass a pre-validated env, so\n // resolveConfig stays usable standalone.\n const resolvedEnv = env ?? validateEnv(undefined)\n\n const adapter = options.database?.adapter\n if (!adapter) {\n throw new Error('[bunderstack] database.adapter is required')\n }\n\n const defaultUrl =\n adapter.dialect === 'sqlite' ? 'file:./data.db' : 'file:./data.pglite'\n\n return {\n database: {\n adapter,\n url:\n platformSource['BUNDERSTACK_DATABASE_URL'] ??\n options.database?.url ??\n resolvedEnv.DATABASE_URL ??\n defaultUrl,\n authToken:\n platformSource['BUNDERSTACK_DATABASE_AUTH_TOKEN'] ??\n options.database?.authToken ??\n resolvedEnv.DATABASE_AUTH_TOKEN,\n migrations: options.database?.migrations ?? './migrations',\n },\n auth: (() => {\n // The secret default has to survive the builder form too, and the builder\n // can only run once the db exists — so wrap it and default afterwards.\n const withSecret = (cfg: BetterAuthConfig): BetterAuthConfig => ({\n ...cfg,\n secret: cfg.secret ?? resolvedEnv.AUTH_SECRET,\n })\n const authInput = options.auth\n return typeof authInput === 'function'\n ? (((ctx) =>\n withSecret(authInput(ctx as never))) satisfies AuthConfigFactory)\n : withSecret(authInput ?? {})\n })(),\n storage: resolveBuckets(options.storage, platformSource),\n realtime: parsed.realtime,\n }\n}\n\n/** Collapse the object/builder union once the db is up. */\nexport function resolveAuthConfig(\n auth: ResolvedConfig['auth'],\n ctx: { db: AnyDb; env: BaseEnv },\n): BetterAuthConfig {\n return typeof auth === 'function' ? auth(ctx) : auth\n}\n\nexport function resolveRealtimeRedisUrl(\n realtime: ResolvedConfig['realtime'],\n env?: BaseEnv,\n platformSource: Record<string, string | undefined> = process.env as Record<\n string,\n string | undefined\n >,\n): string | undefined {\n const platformRedis = platformSource['REDIS_URL']\n if (platformRedis) return platformRedis\n\n const envRedis = env?.REDIS_URL\n if (envRedis) return envRedis\n\n const fromConfig =\n typeof realtime === 'object' && realtime.redis\n ? typeof realtime.redis === 'string'\n ? realtime.redis\n : realtime.redis.url\n : undefined\n\n return fromConfig ?? undefined\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnyMiddleware, AnyRouter } from '@orpc/server'\n\n// src/config.ts\nimport { betterAuth } from 'better-auth'\nimport * as v from 'valibot'\n\nimport type {\n AuthSessionResolver,\n SessionUserConfig,\n SessionUserExtraOf,\n TableAccessInput,\n} from './access'\nimport type { BunderstackApiBuilder } from './api/builder'\nimport type { DatabaseAdapter } from './database/adapter'\nimport type { DbFor } from './db'\nimport type { AnyDb } from './dialect'\nimport type { IdempotencyConfig } from './idempotency'\nimport type { MessagingConfig } from './messaging'\nimport type { RateLimitConfig } from './rate-limit'\n\nimport {\n validateEnv,\n type BaseEnv,\n type EnvConfigInput,\n type ValidatedEnv,\n} from './env'\nimport {\n resolveBuckets,\n type ResolvedStorageBuckets,\n type StorageConfigInput,\n} from './storage/buckets'\n\nexport type BetterAuthConfig = Omit<\n NonNullable<Parameters<typeof betterAuth>[0]>,\n 'database'\n>\n\n/**\n * What an `auth` builder is handed. `db` is the app's own connection, typed\n * from `schema` alone — that is what keeps the builder cycle-free: better-auth\n * hooks living in another file get a db without importing the app whose type\n * they help produce.\n */\nexport type AuthConfigContext<\n TSchema extends Record<string, unknown>,\n TEnv extends EnvConfigInput | undefined = undefined,\n> = {\n db: DbFor<TSchema>\n env: ValidatedEnv<TEnv>\n}\n\nexport type AuthConfigInput<\n TSchema extends Record<string, unknown>,\n TEnv extends EnvConfigInput | undefined = undefined,\n TConfig extends BetterAuthConfig = BetterAuthConfig,\n> = TConfig | ((ctx: AuthConfigContext<TSchema, TEnv>) => TConfig)\n\n/**\n * Identity helper for defining an auth config in a separate file with full\n * type inference — no explicit generic annotations required.\n *\n * Follows the `defineConfig` / `defineComponent` ecosystem convention:\n * the function does nothing at runtime but gives TypeScript an inference\n * anchor through the `schema` parameter.\n *\n * @example\n * ```ts\n * // Static config (no db/env access needed)\n * export const authConfig = defineAuth({ ... })\n *\n * // Builder with schema-typed db\n * export const authConfig = defineAuth(schema, ({ db }) => ({\n * databaseHooks: {\n * user: { create: { after: async (user) => { await db.insert(...) } } }\n * }\n * }))\n * ```\n */\nexport function defineAuth<const TConfig extends BetterAuthConfig>(\n config: TConfig,\n): TConfig\nexport function defineAuth<\n TSchema extends Record<string, unknown>,\n TEnv extends EnvConfigInput | undefined,\n const TConfig extends BetterAuthConfig,\n>(\n context: { schema: TSchema; env: TEnv },\n builder: (ctx: AuthConfigContext<TSchema, TEnv>) => TConfig,\n): (ctx: AuthConfigContext<TSchema, TEnv>) => TConfig\nexport function defineAuth<\n TSchema extends Record<string, unknown>,\n const TConfig extends BetterAuthConfig,\n TEnv extends EnvConfigInput | undefined = undefined,\n>(\n schema: TSchema,\n builder: (ctx: AuthConfigContext<TSchema, TEnv>) => TConfig,\n): (ctx: AuthConfigContext<TSchema, TEnv>) => TConfig\nexport function defineAuth(\n schemaOrConfig: Record<string, unknown>,\n builder?: (ctx: any) => BetterAuthConfig,\n) {\n return builder ?? schemaOrConfig\n}\n\n/**\n * The builder as {@link ResolvedConfig} carries it: schema-agnostic, because\n * ResolvedConfig is not generic. {@link resolveAuthConfig} is the only caller.\n */\nexport type AuthConfigFactory = (ctx: {\n db: AnyDb\n env: BaseEnv\n}) => BetterAuthConfig\n\n// Only the union-shaped options need runtime validation: they are the ones a\n// JavaScript caller can plausibly get wrong in a way that fails confusingly\n// downstream. Everything else is either typed-only or read raw from `options`.\nconst RuntimeOptionsSchema = v.object({\n rateLimit: v.optional(\n v.union([\n v.boolean(),\n v.object({\n windowMs: v.optional(v.number()),\n max: v.optional(v.number()),\n }),\n ]),\n ),\n idempotency: v.optional(\n v.union([v.boolean(), v.object({ ttlMs: v.optional(v.number()) })]),\n ),\n realtime: v.optional(\n v.union([\n v.boolean(),\n v.object({\n bufferSize: v.optional(v.number()),\n resumeSeconds: v.optional(v.number()),\n }),\n ]),\n ),\n openapi: v.optional(v.boolean()),\n})\n\n/** Realtime declaration. Independent of schema, storage, and env. */\nexport type RealtimeConfigInput =\n | boolean\n | {\n bufferSize?: number\n resumeSeconds?: number\n }\n\nexport type BunderstackConfig<\n TSchema extends Record<string, unknown>,\n TAccess extends Record<string, TableAccessInput> | undefined =\n | Record<string, TableAccessInput>\n | undefined,\n TStorage extends StorageConfigInput | undefined =\n | StorageConfigInput\n | undefined,\n TEnv extends EnvConfigInput | undefined = EnvConfigInput | undefined,\n TCustomApiRouter extends AnyRouter | undefined = AnyRouter | undefined,\n TAuthConfig extends BetterAuthConfig = BetterAuthConfig,\n TSession extends SessionUserConfig<Record<string, unknown>> | undefined =\n undefined,\n> = {\n schema: TSchema\n access?: TAccess\n database: {\n adapter: DatabaseAdapter\n url?: string\n authToken?: string\n migrations?: string\n }\n /**\n * better-auth options, or a builder receiving `{ db, env }`. Use the builder\n * form when database hooks need to write: it hands out the app's own\n * connection, so the application never opens a second one just to satisfy a\n * config that is built before the app exists.\n */\n auth?: AuthConfigInput<NoInfer<TSchema>, NoInfer<TEnv>, TAuthConfig>\n /**\n * Reuse an application-owned session reader for the unified API while\n * keeping Bunderstack's auth handler available.\n */\n authResolver?: AuthSessionResolver<SessionUserExtraOf<TSession>>\n /** Explicitly project application fields from Better Auth into API users. */\n session?: TSession\n storage?: TStorage\n messaging?: MessagingConfig\n /**\n * The application's oRPC router. Pass the finished router object, or a\n * callback when the router needs the framework builder at configuration\n * time. Declare the router at module scope with `defineApi` and the object\n * form keeps router modules free of factory wrappers.\n */\n api?:\n | TCustomApiRouter\n | ((\n builder: BunderstackApiBuilder<\n TSchema,\n ValidatedEnv<TEnv>,\n MessagingConfig,\n SessionUserExtraOf<TSession>\n >,\n ) => TCustomApiRouter)\n /**\n * Middleware applied to every procedure in the graph: generated CRUD,\n * storage, realtime, health, and the application's own procedures. Declare\n * each one with `o.middleware(...)` from `defineApi`.\n *\n * It runs before authentication, so `context.user` is not available inside\n * it. Read an already-resolved caller with `context.peekSession()`.\n */\n middleware?: AnyMiddleware[]\n rateLimit?: boolean | RateLimitConfig\n idempotency?: boolean | IdempotencyConfig\n /** Generate and serve `/api/openapi.json`. Disabled by default. */\n openapi?: boolean\n realtime?: RealtimeConfigInput\n}\n\nexport type ResolvedConfig = {\n database: {\n adapter: DatabaseAdapter\n url: string\n authToken?: string\n migrations: string\n }\n auth: BetterAuthConfig | AuthConfigFactory\n storage: ResolvedStorageBuckets\n realtime?: RealtimeConfigInput\n}\n\nexport function resolveConfig<\n TSchema extends Record<string, unknown>,\n TAccess extends Record<string, TableAccessInput> | undefined = undefined,\n TStorage extends StorageConfigInput | undefined = undefined,\n TEnv extends EnvConfigInput | undefined = undefined,\n TCustomApiRouter extends AnyRouter | undefined = undefined,\n TAuthConfig extends BetterAuthConfig = BetterAuthConfig,\n TSession extends SessionUserConfig<Record<string, unknown>> | undefined =\n undefined,\n>(\n options: BunderstackConfig<\n TSchema,\n TAccess,\n TStorage,\n TEnv,\n TCustomApiRouter,\n TAuthConfig,\n TSession\n >,\n env?: BaseEnv,\n // Platform-injected overrides (Bunderhost & co.) beat code-level config so\n // apps with hardcoded local urls deploy unchanged.\n platformSource: Record<string, string | undefined> = process.env as Record<\n string,\n string | undefined\n >,\n): ResolvedConfig {\n const parsed = v.parse(RuntimeOptionsSchema, options)\n // Self-validate when the caller didn't pass a pre-validated env, so\n // resolveConfig stays usable standalone.\n const resolvedEnv = env ?? validateEnv(undefined)\n\n const adapter = options.database?.adapter\n if (!adapter) {\n throw new Error('[bunderstack] database.adapter is required')\n }\n\n const defaultUrl =\n adapter.dialect === 'sqlite' ? 'file:./data.db' : 'file:./data.pglite'\n\n return {\n database: {\n adapter,\n url:\n platformSource['BUNDERSTACK_DATABASE_URL'] ??\n options.database?.url ??\n resolvedEnv.DATABASE_URL ??\n defaultUrl,\n authToken:\n platformSource['BUNDERSTACK_DATABASE_AUTH_TOKEN'] ??\n options.database?.authToken ??\n resolvedEnv.DATABASE_AUTH_TOKEN,\n migrations: options.database?.migrations ?? './migrations',\n },\n auth: (() => {\n // The secret default has to survive the builder form too, and the builder\n // can only run once the db exists — so wrap it and default afterwards.\n const withSecret = (cfg: BetterAuthConfig): BetterAuthConfig => ({\n ...cfg,\n secret: cfg.secret ?? resolvedEnv.AUTH_SECRET,\n })\n const authInput = options.auth\n return typeof authInput === 'function'\n ? (((ctx) =>\n withSecret(authInput(ctx as never))) satisfies AuthConfigFactory)\n : withSecret(authInput ?? {})\n })(),\n storage: resolveBuckets(options.storage, platformSource),\n realtime: parsed.realtime,\n }\n}\n\n/** Collapse the object/builder union once the db is up. */\nexport function resolveAuthConfig(\n auth: ResolvedConfig['auth'],\n ctx: { db: AnyDb; env: BaseEnv },\n): BetterAuthConfig {\n return typeof auth === 'function' ? auth(ctx) : auth\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud-operations.d.ts","sourceRoot":"","sources":["../src/crud-operations.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,EAML,KAAK,UAAU,EACf,KAAK,cAAc,EAGpB,MAAM,UAAU,CAAA;AACjB,OAAO,EACL,gBAAgB,EAIhB,KAAK,cAAc,EACpB,MAAM,UAAU,CAAA;AACjB,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAA;AACtB,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"crud-operations.d.ts","sourceRoot":"","sources":["../src/crud-operations.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,EAML,KAAK,UAAU,EACf,KAAK,cAAc,EAGpB,MAAM,UAAU,CAAA;AACjB,OAAO,EACL,gBAAgB,EAIhB,KAAK,cAAc,EACpB,MAAM,UAAU,CAAA;AACjB,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAA;AACtB,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAA;AAIrB,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE;QAAE,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CACjD;AAiBD,qBAAa,kBAAmB,SAAQ,gBAAgB;IAGpD,QAAQ,CAAC,UAAU,EAAE,cAAc;gBADnC,MAAM,EAAE,MAAM,EACL,UAAU,EAAE,cAAc,EACnC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,OAAO;CAepB;AAED,MAAM,MAAM,kBAAkB,CAC5B,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAC/D;IACF,MAAM,EAAE,OAAO,CAAA;IACf,EAAE,EAAE,KAAK,CAAA;IACT,MAAM,EAAE,cAAc,CAAA;IACtB,WAAW,CAAC,EAAE,OAAO,GAAG,iBAAiB,CAAA;IACzC,QAAQ,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACjE;IACE,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC,CAAA;AAyCL,wBAAgB,oBAAoB,CAClC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjE,IAAI,EAAE,kBAAkB,CAAC,OAAO,CAAC;oBAuClB,MAAM,UACT,eAAe,GAAG,SAAS,OAC9B,oBAAoB,GACxB,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;mBAqClC,MAAM,SACV,MAAM,GAAG,MAAM,OACjB,oBAAoB,GACxB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;sBAgCtB,MAAM,QACX,OAAO,WACJ,MAAM,GAAG,SAAS,kBACX,MAAM,GAAG,SAAS,OAC7B,oBAAoB,GACxB,OAAO,CAAC,YAAY,CAAC;sBAiGX,MAAM,SACV,MAAM,GAAG,MAAM,QAChB,OAAO,OACR,oBAAoB,GACxB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;sBAqEtB,MAAM,SACV,MAAM,GAAG,MAAM,OACjB,oBAAoB,GACxB,OAAO,CAAC,IAAI,CAAC;EAgCnB;AA2BD,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAA"}
|
package/dist/crud-operations.js
CHANGED
|
@@ -3,6 +3,7 @@ import { checkAccess, rowMatchesScope, sanitizeWriteBody, stampScope, tableEntry
|
|
|
3
3
|
import { BunderstackError, ErrorCode, ListQueryError, } from './errors.js';
|
|
4
4
|
import { lookupIdempotency, resolveIdempotencyConfig, storeIdempotency, } from './idempotency.js';
|
|
5
5
|
import { executeList, resolveListParams, } from './list-query.js';
|
|
6
|
+
import { consoleLogger } from './logging.js';
|
|
6
7
|
import { buildScopeWhere } from './scope.js';
|
|
7
8
|
function operationMetadata(request) {
|
|
8
9
|
const operationId = request.headers.get('x-bunderstack-operation-id')?.trim();
|
|
@@ -162,7 +163,7 @@ export function createCrudOperations(deps) {
|
|
|
162
163
|
throw error;
|
|
163
164
|
}
|
|
164
165
|
const created = rows[0];
|
|
165
|
-
|
|
166
|
+
await broadcast(realtime, table, 'create', created, ctx.request);
|
|
166
167
|
if (idempotency && trimmedKey) {
|
|
167
168
|
await storeIdempotency(db, tableName, trimmedKey, effectiveRawBody, 201, created, idempotency);
|
|
168
169
|
}
|
|
@@ -206,7 +207,7 @@ export function createCrudOperations(deps) {
|
|
|
206
207
|
throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found');
|
|
207
208
|
}
|
|
208
209
|
const updated = rows[0];
|
|
209
|
-
|
|
210
|
+
await broadcast(realtime, table, 'update', updated, ctx.request);
|
|
210
211
|
return updated;
|
|
211
212
|
},
|
|
212
213
|
async delete(tableName, rawId, ctx) {
|
|
@@ -226,8 +227,23 @@ export function createCrudOperations(deps) {
|
|
|
226
227
|
throw new CrudOperationError(denied.status === 401 ? 401 : 403, ErrorCode.FORBIDDEN, 'Forbidden');
|
|
227
228
|
}
|
|
228
229
|
await db.delete(table).where(eq(idCol, id));
|
|
229
|
-
|
|
230
|
+
await broadcast(realtime, table, 'delete', existingRow, ctx.request);
|
|
230
231
|
},
|
|
231
232
|
};
|
|
232
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Broadcast-on-write, best effort: the write has committed, so a failed
|
|
236
|
+
* publish only logs. Awaited, because a Worker cancels I/O that is still
|
|
237
|
+
* pending after the response, and the event would never reach the hub.
|
|
238
|
+
*/
|
|
239
|
+
async function broadcast(realtime, table, action, record, request) {
|
|
240
|
+
if (!realtime)
|
|
241
|
+
return;
|
|
242
|
+
try {
|
|
243
|
+
await realtime.publish(table, action, record, operationMetadata(request));
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
consoleLogger.error('[bunderstack] realtime publish failed:', error);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
233
249
|
//# sourceMappingURL=crud-operations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crud-operations.js","sourceRoot":"","sources":["../src/crud-operations.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n eq,\n getTableColumns,\n getTableName,\n isTable,\n type Table,\n} from 'drizzle-orm'\n\nimport type { AnyDb } from './dialect'\nimport type { RealtimeFacade } from './realtime/facade'\n\nimport {\n checkAccess,\n rowMatchesScope,\n sanitizeWriteBody,\n stampScope,\n tableEntryForName,\n type AccessUser,\n type ResolvedAccess,\n type ScopeMap,\n type ScopeResolver,\n} from './access'\nimport {\n BunderstackError,\n ErrorCode,\n ListQueryError,\n type BunderstackErrorCode,\n type ErrorCodeValue,\n} from './errors'\nimport {\n lookupIdempotency,\n resolveIdempotencyConfig,\n storeIdempotency,\n type IdempotencyConfig,\n} from './idempotency'\nimport {\n executeList,\n resolveListParams,\n type ListParamsInput,\n type ListResult,\n} from './list-query'\nimport { buildScopeWhere } from './scope'\n\nexport interface CrudExecutionContext {\n request: Request\n user: AccessUser | null\n session: { activeOrganizationId: string | null }\n}\n\nfunction operationMetadata(request: Request): { operationId?: string } {\n const operationId = request.headers.get('x-bunderstack-operation-id')?.trim()\n return operationId ? { operationId } : {}\n}\n\nfunction errorCodeForStatus(status: number): BunderstackErrorCode {\n if (status === 401) return 'UNAUTHORIZED'\n if (status === 403) return 'FORBIDDEN'\n if (status === 404) return 'NOT_FOUND'\n if (status === 409) return 'CONFLICT'\n if (status === 413) return 'PAYLOAD_TOO_LARGE'\n if (status === 429) return 'TOO_MANY_REQUESTS'\n return 'BAD_REQUEST'\n}\n\nexport class CrudOperationError extends BunderstackError {\n constructor(\n status: number,\n readonly legacyCode: ErrorCodeValue,\n message: string,\n details?: unknown,\n ) {\n const code = errorCodeForStatus(status)\n super(\n code,\n message,\n legacyCode === code\n ? details\n : {\n code: legacyCode,\n ...(details === undefined ? {} : { details }),\n },\n )\n this.name = 'CrudOperationError'\n }\n}\n\nexport type CrudOperationsDeps<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n> = {\n schema: TSchema\n db: AnyDb\n access: ResolvedAccess\n idempotency?: boolean | IdempotencyConfig\n realtime?: RealtimeFacade<TSchema>\n}\n\nexport type CreateResult =\n | { type: 'created'; status: 201; record: Record<string, unknown> }\n | {\n type: 'replay'\n status: number\n body: string\n record: Record<string, unknown>\n }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction isUniqueConstraintError(error: unknown): boolean {\n const seen = new Set<unknown>()\n let current = error\n\n while (isRecord(current) && !seen.has(current)) {\n seen.add(current)\n const code = current['code']\n if (\n code === '23505' ||\n code === 'SQLITE_CONSTRAINT_UNIQUE' ||\n code === 'SQLITE_CONSTRAINT_PRIMARYKEY'\n ) {\n return true\n }\n\n const message = current['message']\n if (\n typeof message === 'string' &&\n (/duplicate key value violates unique constraint/i.test(message) ||\n /unique constraint failed/i.test(message))\n ) {\n return true\n }\n\n current = current['cause']\n }\n\n return false\n}\n\nfunction coerceId(rawId: string | number): string | number {\n if (typeof rawId === 'number') return rawId\n return isNaN(Number(rawId)) ? rawId : Number(rawId)\n}\n\nexport function createCrudOperations<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n>(deps: CrudOperationsDeps<TSchema>) {\n const { schema, db, access, realtime } = deps\n const idempotency = resolveIdempotencyConfig(deps.idempotency)\n\n const scopeFor = (\n resolver: ScopeResolver | undefined,\n ctx: {\n user: AccessUser | null\n session: { activeOrganizationId: string | null }\n request: Request\n row?: Record<string, unknown>\n body?: Record<string, unknown>\n },\n ): ScopeMap | undefined => (resolver ? resolver(ctx) : undefined)\n\n function resolveTable(tableName: string) {\n const table = Object.values(schema).find(\n (t) => isTable(t) && getTableName(t) === tableName,\n ) as Table | undefined\n if (!table) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const tableAccess = tableEntryForName(access, tableName)\n if (!tableAccess || !tableAccess.enabled) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const idCol = getTableColumns(table)['id']\n if (!idCol) {\n throw new CrudOperationError(\n 400,\n ErrorCode.BAD_REQUEST,\n `Table ${tableName} has no id column`,\n )\n }\n return { table, tableAccess, idCol }\n }\n\n return {\n async list(\n tableName: string,\n params: ListParamsInput | undefined,\n ctx: CrudExecutionContext,\n ): Promise<ListResult<Record<string, unknown>>> {\n const { table, tableAccess, idCol } = resolveTable(tableName)\n\n const denied = await checkAccess(\n tableAccess.list,\n ctx,\n tableAccess.ownerColumn,\n )\n if (!denied.allowed) {\n throw new CrudOperationError(\n denied.status === 401 ? 401 : 403,\n ErrorCode.FORBIDDEN,\n 'Forbidden',\n )\n }\n\n try {\n const resolved = resolveListParams(params ?? {}, tableAccess)\n const scope = scopeFor(tableAccess.readScope, ctx)\n const scopeWhere = scope ? buildScopeWhere(table, scope) : undefined\n return await executeList(\n db,\n table,\n tableAccess,\n resolved,\n idCol,\n scopeWhere,\n )\n } catch (err) {\n if (err instanceof ListQueryError) {\n throw new CrudOperationError(400, err.code, err.message, err.details)\n }\n throw err\n }\n },\n\n async get(\n tableName: string,\n rawId: string | number,\n ctx: CrudExecutionContext,\n ): Promise<Record<string, unknown>> {\n const { table, tableAccess, idCol } = resolveTable(tableName)\n const id = coerceId(rawId)\n\n const rows = await db.select().from(table).where(eq(idCol, id))\n if (!rows[0]) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const row = rows[0] as Record<string, unknown>\n\n const denied = await checkAccess(\n tableAccess.get,\n { ...ctx, row },\n tableAccess.ownerColumn,\n )\n if (!denied.allowed) {\n throw new CrudOperationError(\n denied.status === 401 ? 401 : 403,\n ErrorCode.FORBIDDEN,\n 'Forbidden',\n )\n }\n\n const scope = scopeFor(tableAccess.readScope, ctx)\n if (scope && !rowMatchesScope(row, scope)) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n\n return row\n },\n\n async create(\n tableName: string,\n body: unknown,\n rawBody: string | undefined,\n idempotencyKey: string | undefined,\n ctx: CrudExecutionContext,\n ): Promise<CreateResult> {\n const { table, tableAccess } = resolveTable(tableName)\n\n const denied = await checkAccess(\n tableAccess.create,\n ctx,\n tableAccess.ownerColumn,\n )\n if (!denied.allowed) {\n throw new CrudOperationError(\n denied.status === 401 ? 401 : 403,\n ErrorCode.FORBIDDEN,\n 'Forbidden',\n )\n }\n\n if (!isRecord(body)) {\n throw new CrudOperationError(\n 400,\n ErrorCode.BAD_REQUEST,\n 'Invalid JSON body',\n )\n }\n\n const trimmedKey = idempotencyKey?.trim()\n const effectiveRawBody = rawBody ?? JSON.stringify(body)\n\n if (idempotency && trimmedKey) {\n const lookup = await lookupIdempotency(\n db,\n tableName,\n trimmedKey,\n effectiveRawBody,\n )\n if (lookup.type === 'conflict') {\n throw new CrudOperationError(\n 409,\n ErrorCode.IDEMPOTENCY_CONFLICT,\n 'Idempotency key reused with different body',\n )\n }\n if (lookup.type === 'replay') {\n return {\n type: 'replay',\n status: lookup.status,\n body: lookup.response,\n record: JSON.parse(lookup.response) as Record<string, unknown>,\n }\n }\n }\n\n const values = sanitizeWriteBody(\n body,\n tableAccess,\n 'create',\n ctx.user?.id ?? null,\n )\n\n const scope = scopeFor(tableAccess.writeScope, { ...ctx, body })\n const stamped = scope ? stampScope(values, scope) : values\n\n let rows: Record<string, unknown>[]\n try {\n rows = await db.insert(table).values(stamped).returning()\n } catch (error) {\n if (isUniqueConstraintError(error)) {\n throw new CrudOperationError(\n 409,\n ErrorCode.CONFLICT,\n 'Record already exists',\n )\n }\n throw error\n }\n const created = rows[0] as Record<string, unknown>\n void realtime?.publish(\n table as never,\n 'create',\n created as never,\n operationMetadata(ctx.request),\n )\n\n if (idempotency && trimmedKey) {\n await storeIdempotency(\n db,\n tableName,\n trimmedKey,\n effectiveRawBody,\n 201,\n created,\n idempotency,\n )\n }\n\n return {\n type: 'created',\n status: 201,\n record: created,\n }\n },\n\n async update(\n tableName: string,\n rawId: string | number,\n body: unknown,\n ctx: CrudExecutionContext,\n ): Promise<Record<string, unknown>> {\n const { table, tableAccess, idCol } = resolveTable(tableName)\n const id = coerceId(rawId)\n\n const existing = await db.select().from(table).where(eq(idCol, id))\n if (!existing[0]) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const existingRow = existing[0] as Record<string, unknown>\n\n const readScope = scopeFor(tableAccess.readScope, ctx)\n if (readScope && !rowMatchesScope(existingRow, readScope)) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n\n const denied = await checkAccess(\n tableAccess.update,\n { ...ctx, row: existingRow },\n tableAccess.ownerColumn,\n )\n if (!denied.allowed) {\n throw new CrudOperationError(\n denied.status === 401 ? 401 : 403,\n ErrorCode.FORBIDDEN,\n 'Forbidden',\n )\n }\n\n if (!isRecord(body)) {\n throw new CrudOperationError(\n 400,\n ErrorCode.BAD_REQUEST,\n 'Invalid JSON body',\n )\n }\n\n const values = sanitizeWriteBody(\n body,\n tableAccess,\n 'update',\n ctx.user?.id ?? null,\n )\n\n if (Object.keys(values).length === 0) {\n throw new CrudOperationError(\n 400,\n ErrorCode.BAD_REQUEST,\n 'No writable fields to update',\n )\n }\n\n const writeScope = scopeFor(tableAccess.writeScope, { ...ctx, body })\n const stamped = writeScope ? stampScope(values, writeScope) : values\n\n const rows = await db\n .update(table)\n .set(stamped)\n .where(eq(idCol, id))\n .returning()\n\n if (!rows[0]) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const updated = rows[0] as Record<string, unknown>\n void realtime?.publish(\n table as never,\n 'update',\n updated as never,\n operationMetadata(ctx.request),\n )\n return updated\n },\n\n async delete(\n tableName: string,\n rawId: string | number,\n ctx: CrudExecutionContext,\n ): Promise<void> {\n const { table, tableAccess, idCol } = resolveTable(tableName)\n const id = coerceId(rawId)\n\n const existing = await db.select().from(table).where(eq(idCol, id))\n if (!existing[0]) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const existingRow = existing[0] as Record<string, unknown>\n\n const readScope = scopeFor(tableAccess.readScope, ctx)\n if (readScope && !rowMatchesScope(existingRow, readScope)) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n\n const denied = await checkAccess(\n tableAccess.delete,\n { ...ctx, row: existingRow },\n tableAccess.ownerColumn,\n )\n if (!denied.allowed) {\n throw new CrudOperationError(\n denied.status === 401 ? 401 : 403,\n ErrorCode.FORBIDDEN,\n 'Forbidden',\n )\n }\n\n await db.delete(table).where(eq(idCol, id))\n void realtime?.publish(\n table as never,\n 'delete',\n existingRow as never,\n operationMetadata(ctx.request),\n )\n },\n }\n}\n\nexport type CrudOperations = ReturnType<typeof createCrudOperations>\n"]}
|
|
1
|
+
{"version":3,"file":"crud-operations.js","sourceRoot":"","sources":["../src/crud-operations.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n eq,\n getTableColumns,\n getTableName,\n isTable,\n type Table,\n} from 'drizzle-orm'\n\nimport type { AnyDb } from './dialect'\nimport type { RealtimeFacade } from './realtime/facade'\n\nimport {\n checkAccess,\n rowMatchesScope,\n sanitizeWriteBody,\n stampScope,\n tableEntryForName,\n type AccessUser,\n type ResolvedAccess,\n type ScopeMap,\n type ScopeResolver,\n} from './access'\nimport {\n BunderstackError,\n ErrorCode,\n ListQueryError,\n type BunderstackErrorCode,\n type ErrorCodeValue,\n} from './errors'\nimport {\n lookupIdempotency,\n resolveIdempotencyConfig,\n storeIdempotency,\n type IdempotencyConfig,\n} from './idempotency'\nimport {\n executeList,\n resolveListParams,\n type ListParamsInput,\n type ListResult,\n} from './list-query'\nimport { consoleLogger } from './logging'\nimport { buildScopeWhere } from './scope'\n\nexport interface CrudExecutionContext {\n request: Request\n user: AccessUser | null\n session: { activeOrganizationId: string | null }\n}\n\nfunction operationMetadata(request: Request): { operationId?: string } {\n const operationId = request.headers.get('x-bunderstack-operation-id')?.trim()\n return operationId ? { operationId } : {}\n}\n\nfunction errorCodeForStatus(status: number): BunderstackErrorCode {\n if (status === 401) return 'UNAUTHORIZED'\n if (status === 403) return 'FORBIDDEN'\n if (status === 404) return 'NOT_FOUND'\n if (status === 409) return 'CONFLICT'\n if (status === 413) return 'PAYLOAD_TOO_LARGE'\n if (status === 429) return 'TOO_MANY_REQUESTS'\n return 'BAD_REQUEST'\n}\n\nexport class CrudOperationError extends BunderstackError {\n constructor(\n status: number,\n readonly legacyCode: ErrorCodeValue,\n message: string,\n details?: unknown,\n ) {\n const code = errorCodeForStatus(status)\n super(\n code,\n message,\n legacyCode === code\n ? details\n : {\n code: legacyCode,\n ...(details === undefined ? {} : { details }),\n },\n )\n this.name = 'CrudOperationError'\n }\n}\n\nexport type CrudOperationsDeps<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n> = {\n schema: TSchema\n db: AnyDb\n access: ResolvedAccess\n idempotency?: boolean | IdempotencyConfig\n realtime?: RealtimeFacade<TSchema>\n}\n\nexport type CreateResult =\n | { type: 'created'; status: 201; record: Record<string, unknown> }\n | {\n type: 'replay'\n status: number\n body: string\n record: Record<string, unknown>\n }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction isUniqueConstraintError(error: unknown): boolean {\n const seen = new Set<unknown>()\n let current = error\n\n while (isRecord(current) && !seen.has(current)) {\n seen.add(current)\n const code = current['code']\n if (\n code === '23505' ||\n code === 'SQLITE_CONSTRAINT_UNIQUE' ||\n code === 'SQLITE_CONSTRAINT_PRIMARYKEY'\n ) {\n return true\n }\n\n const message = current['message']\n if (\n typeof message === 'string' &&\n (/duplicate key value violates unique constraint/i.test(message) ||\n /unique constraint failed/i.test(message))\n ) {\n return true\n }\n\n current = current['cause']\n }\n\n return false\n}\n\nfunction coerceId(rawId: string | number): string | number {\n if (typeof rawId === 'number') return rawId\n return isNaN(Number(rawId)) ? rawId : Number(rawId)\n}\n\nexport function createCrudOperations<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n>(deps: CrudOperationsDeps<TSchema>) {\n const { schema, db, access, realtime } = deps\n const idempotency = resolveIdempotencyConfig(deps.idempotency)\n\n const scopeFor = (\n resolver: ScopeResolver | undefined,\n ctx: {\n user: AccessUser | null\n session: { activeOrganizationId: string | null }\n request: Request\n row?: Record<string, unknown>\n body?: Record<string, unknown>\n },\n ): ScopeMap | undefined => (resolver ? resolver(ctx) : undefined)\n\n function resolveTable(tableName: string) {\n const table = Object.values(schema).find(\n (t) => isTable(t) && getTableName(t) === tableName,\n ) as Table | undefined\n if (!table) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const tableAccess = tableEntryForName(access, tableName)\n if (!tableAccess || !tableAccess.enabled) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const idCol = getTableColumns(table)['id']\n if (!idCol) {\n throw new CrudOperationError(\n 400,\n ErrorCode.BAD_REQUEST,\n `Table ${tableName} has no id column`,\n )\n }\n return { table, tableAccess, idCol }\n }\n\n return {\n async list(\n tableName: string,\n params: ListParamsInput | undefined,\n ctx: CrudExecutionContext,\n ): Promise<ListResult<Record<string, unknown>>> {\n const { table, tableAccess, idCol } = resolveTable(tableName)\n\n const denied = await checkAccess(\n tableAccess.list,\n ctx,\n tableAccess.ownerColumn,\n )\n if (!denied.allowed) {\n throw new CrudOperationError(\n denied.status === 401 ? 401 : 403,\n ErrorCode.FORBIDDEN,\n 'Forbidden',\n )\n }\n\n try {\n const resolved = resolveListParams(params ?? {}, tableAccess)\n const scope = scopeFor(tableAccess.readScope, ctx)\n const scopeWhere = scope ? buildScopeWhere(table, scope) : undefined\n return await executeList(\n db,\n table,\n tableAccess,\n resolved,\n idCol,\n scopeWhere,\n )\n } catch (err) {\n if (err instanceof ListQueryError) {\n throw new CrudOperationError(400, err.code, err.message, err.details)\n }\n throw err\n }\n },\n\n async get(\n tableName: string,\n rawId: string | number,\n ctx: CrudExecutionContext,\n ): Promise<Record<string, unknown>> {\n const { table, tableAccess, idCol } = resolveTable(tableName)\n const id = coerceId(rawId)\n\n const rows = await db.select().from(table).where(eq(idCol, id))\n if (!rows[0]) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const row = rows[0] as Record<string, unknown>\n\n const denied = await checkAccess(\n tableAccess.get,\n { ...ctx, row },\n tableAccess.ownerColumn,\n )\n if (!denied.allowed) {\n throw new CrudOperationError(\n denied.status === 401 ? 401 : 403,\n ErrorCode.FORBIDDEN,\n 'Forbidden',\n )\n }\n\n const scope = scopeFor(tableAccess.readScope, ctx)\n if (scope && !rowMatchesScope(row, scope)) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n\n return row\n },\n\n async create(\n tableName: string,\n body: unknown,\n rawBody: string | undefined,\n idempotencyKey: string | undefined,\n ctx: CrudExecutionContext,\n ): Promise<CreateResult> {\n const { table, tableAccess } = resolveTable(tableName)\n\n const denied = await checkAccess(\n tableAccess.create,\n ctx,\n tableAccess.ownerColumn,\n )\n if (!denied.allowed) {\n throw new CrudOperationError(\n denied.status === 401 ? 401 : 403,\n ErrorCode.FORBIDDEN,\n 'Forbidden',\n )\n }\n\n if (!isRecord(body)) {\n throw new CrudOperationError(\n 400,\n ErrorCode.BAD_REQUEST,\n 'Invalid JSON body',\n )\n }\n\n const trimmedKey = idempotencyKey?.trim()\n const effectiveRawBody = rawBody ?? JSON.stringify(body)\n\n if (idempotency && trimmedKey) {\n const lookup = await lookupIdempotency(\n db,\n tableName,\n trimmedKey,\n effectiveRawBody,\n )\n if (lookup.type === 'conflict') {\n throw new CrudOperationError(\n 409,\n ErrorCode.IDEMPOTENCY_CONFLICT,\n 'Idempotency key reused with different body',\n )\n }\n if (lookup.type === 'replay') {\n return {\n type: 'replay',\n status: lookup.status,\n body: lookup.response,\n record: JSON.parse(lookup.response) as Record<string, unknown>,\n }\n }\n }\n\n const values = sanitizeWriteBody(\n body,\n tableAccess,\n 'create',\n ctx.user?.id ?? null,\n )\n\n const scope = scopeFor(tableAccess.writeScope, { ...ctx, body })\n const stamped = scope ? stampScope(values, scope) : values\n\n let rows: Record<string, unknown>[]\n try {\n rows = await db.insert(table).values(stamped).returning()\n } catch (error) {\n if (isUniqueConstraintError(error)) {\n throw new CrudOperationError(\n 409,\n ErrorCode.CONFLICT,\n 'Record already exists',\n )\n }\n throw error\n }\n const created = rows[0] as Record<string, unknown>\n await broadcast(realtime, table, 'create', created, ctx.request)\n\n if (idempotency && trimmedKey) {\n await storeIdempotency(\n db,\n tableName,\n trimmedKey,\n effectiveRawBody,\n 201,\n created,\n idempotency,\n )\n }\n\n return {\n type: 'created',\n status: 201,\n record: created,\n }\n },\n\n async update(\n tableName: string,\n rawId: string | number,\n body: unknown,\n ctx: CrudExecutionContext,\n ): Promise<Record<string, unknown>> {\n const { table, tableAccess, idCol } = resolveTable(tableName)\n const id = coerceId(rawId)\n\n const existing = await db.select().from(table).where(eq(idCol, id))\n if (!existing[0]) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const existingRow = existing[0] as Record<string, unknown>\n\n const readScope = scopeFor(tableAccess.readScope, ctx)\n if (readScope && !rowMatchesScope(existingRow, readScope)) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n\n const denied = await checkAccess(\n tableAccess.update,\n { ...ctx, row: existingRow },\n tableAccess.ownerColumn,\n )\n if (!denied.allowed) {\n throw new CrudOperationError(\n denied.status === 401 ? 401 : 403,\n ErrorCode.FORBIDDEN,\n 'Forbidden',\n )\n }\n\n if (!isRecord(body)) {\n throw new CrudOperationError(\n 400,\n ErrorCode.BAD_REQUEST,\n 'Invalid JSON body',\n )\n }\n\n const values = sanitizeWriteBody(\n body,\n tableAccess,\n 'update',\n ctx.user?.id ?? null,\n )\n\n if (Object.keys(values).length === 0) {\n throw new CrudOperationError(\n 400,\n ErrorCode.BAD_REQUEST,\n 'No writable fields to update',\n )\n }\n\n const writeScope = scopeFor(tableAccess.writeScope, { ...ctx, body })\n const stamped = writeScope ? stampScope(values, writeScope) : values\n\n const rows = await db\n .update(table)\n .set(stamped)\n .where(eq(idCol, id))\n .returning()\n\n if (!rows[0]) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const updated = rows[0] as Record<string, unknown>\n await broadcast(realtime, table, 'update', updated, ctx.request)\n return updated\n },\n\n async delete(\n tableName: string,\n rawId: string | number,\n ctx: CrudExecutionContext,\n ): Promise<void> {\n const { table, tableAccess, idCol } = resolveTable(tableName)\n const id = coerceId(rawId)\n\n const existing = await db.select().from(table).where(eq(idCol, id))\n if (!existing[0]) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n const existingRow = existing[0] as Record<string, unknown>\n\n const readScope = scopeFor(tableAccess.readScope, ctx)\n if (readScope && !rowMatchesScope(existingRow, readScope)) {\n throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')\n }\n\n const denied = await checkAccess(\n tableAccess.delete,\n { ...ctx, row: existingRow },\n tableAccess.ownerColumn,\n )\n if (!denied.allowed) {\n throw new CrudOperationError(\n denied.status === 401 ? 401 : 403,\n ErrorCode.FORBIDDEN,\n 'Forbidden',\n )\n }\n\n await db.delete(table).where(eq(idCol, id))\n await broadcast(realtime, table, 'delete', existingRow, ctx.request)\n },\n }\n}\n\n/**\n * Broadcast-on-write, best effort: the write has committed, so a failed\n * publish only logs. Awaited, because a Worker cancels I/O that is still\n * pending after the response, and the event would never reach the hub.\n */\nasync function broadcast(\n realtime: RealtimeFacade<any> | undefined,\n table: unknown,\n action: 'create' | 'update' | 'delete',\n record: Record<string, unknown>,\n request: Request,\n): Promise<void> {\n if (!realtime) return\n try {\n await realtime.publish(\n table as never,\n action,\n record as never,\n operationMetadata(request),\n )\n } catch (error) {\n consoleLogger.error('[bunderstack] realtime publish failed:', error)\n }\n}\n\nexport type CrudOperations = ReturnType<typeof createCrudOperations>\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type BinaryName = 'celld' | 'sqld';
|
|
2
|
+
export type Pin = {
|
|
3
|
+
version: string;
|
|
4
|
+
format: 'gz' | 'tar.xz';
|
|
5
|
+
url: (target: string) => string;
|
|
6
|
+
/** sha256 of the downloaded asset, per release target. */
|
|
7
|
+
sha256: Partial<Record<string, string>>;
|
|
8
|
+
};
|
|
9
|
+
export type Pins = Partial<Record<BinaryName, Pin>>;
|
|
10
|
+
export type ResolveOptions = {
|
|
11
|
+
env?: Record<string, string | undefined>;
|
|
12
|
+
cacheDir?: string;
|
|
13
|
+
platform?: string;
|
|
14
|
+
arch?: string;
|
|
15
|
+
fetch?: (url: string) => Promise<Response>;
|
|
16
|
+
log?: (message: string) => void;
|
|
17
|
+
/** Replaces the pinned table; for tests. */
|
|
18
|
+
pins?: Pins;
|
|
19
|
+
};
|
|
20
|
+
export declare function binaryTarget(platform: string, arch: string): string | undefined;
|
|
21
|
+
export declare function resolveBinary(name: BinaryName, options?: ResolveOptions): Promise<string>;
|
|
22
|
+
//# sourceMappingURL=binaries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binaries.d.ts","sourceRoot":"","sources":["../../src/dev/binaries.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAA;AAEzC,MAAM,MAAM,GAAG,GAAG;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,IAAI,GAAG,QAAQ,CAAA;IACvB,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAA;IAC/B,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACxC,CAAA;AAED,MAAM,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAA;AAmCnD,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC1C,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,4CAA4C;IAC5C,IAAI,CAAC,EAAE,IAAI,CAAA;CACZ,CAAA;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,sBAM1D;AAyBD,wBAAsB,aAAa,CACjC,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAmEjB"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Pinned celld and sqld binaries for `bunderstack dev`. The first run
|
|
2
|
+
// downloads them to ~/.cache/bunderstack and checks their sha256; an env var
|
|
3
|
+
// selects a system binary instead.
|
|
4
|
+
import { access, chmod, constants, mkdir, mkdtemp, readdir, rename, rm, stat, } from 'node:fs/promises';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
const PINS = {
|
|
8
|
+
celld: {
|
|
9
|
+
version: 'v0.6.0',
|
|
10
|
+
format: 'gz',
|
|
11
|
+
url: (target) => `https://github.com/denoland/celld/releases/download/v0.6.0/celld-${target}.gz`,
|
|
12
|
+
sha256: {
|
|
13
|
+
'aarch64-apple-darwin': 'bf6f0c06c4f815eecddf61ae40340935a0dbf76be3d643bf75fdd92bfac425cd',
|
|
14
|
+
'aarch64-unknown-linux-gnu': '3d4945df3abcc6832b6e7fa978b9ee3c26e46a0b7791924843d42c0ed14eff96',
|
|
15
|
+
'x86_64-unknown-linux-gnu': '8f1e18072c234ab75459d4da104c13cebc9b29a3e4a4772086bf985829bea8aa',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
sqld: {
|
|
19
|
+
version: 'v0.24.32',
|
|
20
|
+
format: 'tar.xz',
|
|
21
|
+
url: (target) => `https://github.com/tursodatabase/libsql/releases/download/libsql-server-v0.24.32/libsql-server-${target}.tar.xz`,
|
|
22
|
+
sha256: {
|
|
23
|
+
'aarch64-apple-darwin': 'ced2a9d65a5d4b6bd72c67e98ad6c63139e2a139d91769f07fdd15be935381dd',
|
|
24
|
+
'x86_64-apple-darwin': '461480ea5a17781bab7dd5974aa804007434611baced37b6349c8060d0648e34',
|
|
25
|
+
'aarch64-unknown-linux-gnu': '37f9eee45b388a30192907ecf4565b93df945c079331657073b5b3caf8bb1cd0',
|
|
26
|
+
'x86_64-unknown-linux-gnu': '71720fc8648c19efef416efebd47145ef59b62e198770533530a858e1336879f',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
export function binaryTarget(platform, arch) {
|
|
31
|
+
const cpu = arch === 'arm64' ? 'aarch64' : arch === 'x64' ? 'x86_64' : '';
|
|
32
|
+
if (!cpu)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (platform === 'darwin')
|
|
35
|
+
return `${cpu}-apple-darwin`;
|
|
36
|
+
if (platform === 'linux')
|
|
37
|
+
return `${cpu}-unknown-linux-gnu`;
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
async function isExecutable(path) {
|
|
41
|
+
return access(path, constants.X_OK).then(() => true, () => false);
|
|
42
|
+
}
|
|
43
|
+
async function findFile(dir, name) {
|
|
44
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
45
|
+
const path = join(dir, entry.name);
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
const found = await findFile(path, name);
|
|
48
|
+
if (found)
|
|
49
|
+
return found;
|
|
50
|
+
}
|
|
51
|
+
else if (entry.name === name) {
|
|
52
|
+
return path;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
export async function resolveBinary(name, options = {}) {
|
|
58
|
+
const env = options.env ?? process.env;
|
|
59
|
+
const envName = `BUNDERSTACK_${name.toUpperCase()}_BIN`;
|
|
60
|
+
const override = env[envName];
|
|
61
|
+
if (override)
|
|
62
|
+
return override;
|
|
63
|
+
const pin = (options.pins ?? PINS)[name];
|
|
64
|
+
const target = binaryTarget(options.platform ?? process.platform, options.arch ?? process.arch);
|
|
65
|
+
const checksum = target ? pin?.sha256[target] : undefined;
|
|
66
|
+
if (!pin || !target || !checksum) {
|
|
67
|
+
throw new Error(`[bunderstack] no ${name} build for ${options.platform ?? process.platform} ${options.arch ?? process.arch}; install ${name} and set ${envName} to its path`);
|
|
68
|
+
}
|
|
69
|
+
const cacheDir = options.cacheDir ??
|
|
70
|
+
env.BUNDERSTACK_CACHE_DIR ??
|
|
71
|
+
join(homedir(), '.cache', 'bunderstack');
|
|
72
|
+
const home = join(cacheDir, `${name}-${pin.version}-${target}`);
|
|
73
|
+
const cached = await findFile(home, name).catch(() => undefined);
|
|
74
|
+
if (cached && (await isExecutable(cached)))
|
|
75
|
+
return cached;
|
|
76
|
+
const url = pin.url(target);
|
|
77
|
+
options.log?.(`downloading ${name} ${pin.version} from ${url}`);
|
|
78
|
+
const response = await (options.fetch ?? fetch)(url);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new Error(`[bunderstack] ${url} returned ${response.status}`);
|
|
81
|
+
}
|
|
82
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
83
|
+
const actual = new Bun.CryptoHasher('sha256').update(bytes).digest('hex');
|
|
84
|
+
if (actual !== checksum) {
|
|
85
|
+
throw new Error(`[bunderstack] ${name} checksum mismatch: expected ${checksum}, got ${actual}`);
|
|
86
|
+
}
|
|
87
|
+
await mkdir(cacheDir, { recursive: true });
|
|
88
|
+
const staging = await mkdtemp(join(cacheDir, `.${name}-`));
|
|
89
|
+
try {
|
|
90
|
+
if (pin.format === 'gz') {
|
|
91
|
+
await Bun.write(join(staging, name), Bun.gunzipSync(bytes));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const archive = join(staging, 'asset.tar.xz');
|
|
95
|
+
await Bun.write(archive, bytes);
|
|
96
|
+
const tar = Bun.spawnSync(['tar', '-xJf', archive], { cwd: staging });
|
|
97
|
+
if (tar.exitCode !== 0) {
|
|
98
|
+
throw new Error(`[bunderstack] tar failed: ${tar.stderr.toString()}`);
|
|
99
|
+
}
|
|
100
|
+
await rm(archive);
|
|
101
|
+
}
|
|
102
|
+
const binary = await findFile(staging, name);
|
|
103
|
+
if (!binary)
|
|
104
|
+
throw new Error(`[bunderstack] ${url} has no ${name} file`);
|
|
105
|
+
await chmod(binary, 0o755);
|
|
106
|
+
// Another run may have finished first; keep its copy.
|
|
107
|
+
if (!(await stat(home).catch(() => undefined))) {
|
|
108
|
+
await rename(staging, home);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
await rm(staging, { recursive: true, force: true });
|
|
113
|
+
}
|
|
114
|
+
const path = await findFile(home, name);
|
|
115
|
+
if (!path)
|
|
116
|
+
throw new Error(`[bunderstack] ${name} is missing from ${home}`);
|
|
117
|
+
return path;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=binaries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binaries.js","sourceRoot":"","sources":["../../src/dev/binaries.ts"],"names":[],"mappings":"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","sourcesContent":["// Pinned celld and sqld binaries for `bunderstack dev`. The first run\n// downloads them to ~/.cache/bunderstack and checks their sha256; an env var\n// selects a system binary instead.\nimport {\n access,\n chmod,\n constants,\n mkdir,\n mkdtemp,\n readdir,\n rename,\n rm,\n stat,\n} from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport type BinaryName = 'celld' | 'sqld'\n\nexport type Pin = {\n version: string\n format: 'gz' | 'tar.xz'\n url: (target: string) => string\n /** sha256 of the downloaded asset, per release target. */\n sha256: Partial<Record<string, string>>\n}\n\nexport type Pins = Partial<Record<BinaryName, Pin>>\n\nconst PINS: Record<BinaryName, Pin> = {\n celld: {\n version: 'v0.6.0',\n format: 'gz',\n url: (target) =>\n `https://github.com/denoland/celld/releases/download/v0.6.0/celld-${target}.gz`,\n sha256: {\n 'aarch64-apple-darwin':\n 'bf6f0c06c4f815eecddf61ae40340935a0dbf76be3d643bf75fdd92bfac425cd',\n 'aarch64-unknown-linux-gnu':\n '3d4945df3abcc6832b6e7fa978b9ee3c26e46a0b7791924843d42c0ed14eff96',\n 'x86_64-unknown-linux-gnu':\n '8f1e18072c234ab75459d4da104c13cebc9b29a3e4a4772086bf985829bea8aa',\n },\n },\n sqld: {\n version: 'v0.24.32',\n format: 'tar.xz',\n url: (target) =>\n `https://github.com/tursodatabase/libsql/releases/download/libsql-server-v0.24.32/libsql-server-${target}.tar.xz`,\n sha256: {\n 'aarch64-apple-darwin':\n 'ced2a9d65a5d4b6bd72c67e98ad6c63139e2a139d91769f07fdd15be935381dd',\n 'x86_64-apple-darwin':\n '461480ea5a17781bab7dd5974aa804007434611baced37b6349c8060d0648e34',\n 'aarch64-unknown-linux-gnu':\n '37f9eee45b388a30192907ecf4565b93df945c079331657073b5b3caf8bb1cd0',\n 'x86_64-unknown-linux-gnu':\n '71720fc8648c19efef416efebd47145ef59b62e198770533530a858e1336879f',\n },\n },\n}\n\nexport type ResolveOptions = {\n env?: Record<string, string | undefined>\n cacheDir?: string\n platform?: string\n arch?: string\n fetch?: (url: string) => Promise<Response>\n log?: (message: string) => void\n /** Replaces the pinned table; for tests. */\n pins?: Pins\n}\n\nexport function binaryTarget(platform: string, arch: string) {\n const cpu = arch === 'arm64' ? 'aarch64' : arch === 'x64' ? 'x86_64' : ''\n if (!cpu) return undefined\n if (platform === 'darwin') return `${cpu}-apple-darwin`\n if (platform === 'linux') return `${cpu}-unknown-linux-gnu`\n return undefined\n}\n\nasync function isExecutable(path: string) {\n return access(path, constants.X_OK).then(\n () => true,\n () => false,\n )\n}\n\nasync function findFile(\n dir: string,\n name: string,\n): Promise<string | undefined> {\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n const path = join(dir, entry.name)\n if (entry.isDirectory()) {\n const found = await findFile(path, name)\n if (found) return found\n } else if (entry.name === name) {\n return path\n }\n }\n return undefined\n}\n\nexport async function resolveBinary(\n name: BinaryName,\n options: ResolveOptions = {},\n): Promise<string> {\n const env = options.env ?? process.env\n const envName = `BUNDERSTACK_${name.toUpperCase()}_BIN`\n const override = env[envName]\n if (override) return override\n\n const pin = (options.pins ?? PINS)[name]\n const target = binaryTarget(\n options.platform ?? process.platform,\n options.arch ?? process.arch,\n )\n const checksum = target ? pin?.sha256[target] : undefined\n if (!pin || !target || !checksum) {\n throw new Error(\n `[bunderstack] no ${name} build for ${options.platform ?? process.platform} ${options.arch ?? process.arch}; install ${name} and set ${envName} to its path`,\n )\n }\n\n const cacheDir =\n options.cacheDir ??\n env.BUNDERSTACK_CACHE_DIR ??\n join(homedir(), '.cache', 'bunderstack')\n const home = join(cacheDir, `${name}-${pin.version}-${target}`)\n const cached = await findFile(home, name).catch(() => undefined)\n if (cached && (await isExecutable(cached))) return cached\n\n const url = pin.url(target)\n options.log?.(`downloading ${name} ${pin.version} from ${url}`)\n const response = await (options.fetch ?? fetch)(url)\n if (!response.ok) {\n throw new Error(`[bunderstack] ${url} returned ${response.status}`)\n }\n const bytes = new Uint8Array(await response.arrayBuffer())\n const actual = new Bun.CryptoHasher('sha256').update(bytes).digest('hex')\n if (actual !== checksum) {\n throw new Error(\n `[bunderstack] ${name} checksum mismatch: expected ${checksum}, got ${actual}`,\n )\n }\n\n await mkdir(cacheDir, { recursive: true })\n const staging = await mkdtemp(join(cacheDir, `.${name}-`))\n try {\n if (pin.format === 'gz') {\n await Bun.write(join(staging, name), Bun.gunzipSync(bytes))\n } else {\n const archive = join(staging, 'asset.tar.xz')\n await Bun.write(archive, bytes)\n const tar = Bun.spawnSync(['tar', '-xJf', archive], { cwd: staging })\n if (tar.exitCode !== 0) {\n throw new Error(`[bunderstack] tar failed: ${tar.stderr.toString()}`)\n }\n await rm(archive)\n }\n const binary = await findFile(staging, name)\n if (!binary) throw new Error(`[bunderstack] ${url} has no ${name} file`)\n await chmod(binary, 0o755)\n // Another run may have finished first; keep its copy.\n if (!(await stat(home).catch(() => undefined))) {\n await rename(staging, home)\n }\n } finally {\n await rm(staging, { recursive: true, force: true })\n }\n const path = await findFile(home, name)\n if (!path) throw new Error(`[bunderstack] ${name} is missing from ${home}`)\n return path\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function parseDotenv(text: string): Record<string, string>;
|
|
2
|
+
export declare function readUserEnv(directory: string): Promise<Record<string, string>>;
|
|
3
|
+
/** A random AUTH_SECRET kept in the dev state dir, so sessions survive restarts. */
|
|
4
|
+
export declare function devSecret(stateDir: string): Promise<string>;
|
|
5
|
+
export declare function devVars(input: {
|
|
6
|
+
userEnv: Record<string, string>;
|
|
7
|
+
databaseUrl: string;
|
|
8
|
+
appUrl: string;
|
|
9
|
+
authSecret: string;
|
|
10
|
+
}): string;
|
|
11
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/dev/env.ts"],"names":[],"mappings":"AAKA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAyBhE;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CASjC;AAED,oFAAoF;AACpF,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAWjE;AAOD,wBAAgB,OAAO,CAAC,KAAK,EAAE;IAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB,GAAG,MAAM,CAaT"}
|
package/dist/dev/env.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// The Worker env for `bunderstack dev`: the app's .env files plus the values
|
|
2
|
+
// the dev command owns, written as .dev.vars for celld.
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
export function parseDotenv(text) {
|
|
6
|
+
const vars = {};
|
|
7
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
8
|
+
const line = raw.trim();
|
|
9
|
+
if (!line || line.startsWith('#'))
|
|
10
|
+
continue;
|
|
11
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
|
12
|
+
if (!match)
|
|
13
|
+
continue;
|
|
14
|
+
const [, key, rest] = match;
|
|
15
|
+
let value;
|
|
16
|
+
if (rest.startsWith('"')) {
|
|
17
|
+
const end = rest.lastIndexOf('"');
|
|
18
|
+
value = rest
|
|
19
|
+
.slice(1, end > 0 ? end : undefined)
|
|
20
|
+
.replace(/\\(["\\n])/g, (_, c) => (c === 'n' ? '\n' : c));
|
|
21
|
+
}
|
|
22
|
+
else if (rest.startsWith("'")) {
|
|
23
|
+
const end = rest.lastIndexOf("'");
|
|
24
|
+
value = rest.slice(1, end > 0 ? end : undefined);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
value = rest.replace(/\s+#.*$/, '').trim();
|
|
28
|
+
}
|
|
29
|
+
vars[key] = value;
|
|
30
|
+
}
|
|
31
|
+
return vars;
|
|
32
|
+
}
|
|
33
|
+
export async function readUserEnv(directory) {
|
|
34
|
+
const vars = {};
|
|
35
|
+
for (const name of ['.env', '.env.local']) {
|
|
36
|
+
const text = await readFile(join(directory, name), 'utf8').catch(() => undefined);
|
|
37
|
+
if (text !== undefined)
|
|
38
|
+
Object.assign(vars, parseDotenv(text));
|
|
39
|
+
}
|
|
40
|
+
return vars;
|
|
41
|
+
}
|
|
42
|
+
/** A random AUTH_SECRET kept in the dev state dir, so sessions survive restarts. */
|
|
43
|
+
export async function devSecret(stateDir) {
|
|
44
|
+
const path = join(stateDir, 'auth-secret');
|
|
45
|
+
const existing = (await readFile(path, 'utf8').catch(() => '')).trim();
|
|
46
|
+
if (existing)
|
|
47
|
+
return existing;
|
|
48
|
+
await mkdir(stateDir, { recursive: true });
|
|
49
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
50
|
+
const secret = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
51
|
+
await writeFile(path, `${secret}\n`, { mode: 0o600 });
|
|
52
|
+
return secret;
|
|
53
|
+
}
|
|
54
|
+
function quote(value) {
|
|
55
|
+
if (/^[A-Za-z0-9_./:@+-]*$/.test(value))
|
|
56
|
+
return value;
|
|
57
|
+
return `"${value.replace(/["\\]/g, '\\$&').replace(/\n/g, '\\n')}"`;
|
|
58
|
+
}
|
|
59
|
+
export function devVars(input) {
|
|
60
|
+
const vars = {
|
|
61
|
+
...input.userEnv,
|
|
62
|
+
AUTH_SECRET: input.userEnv.AUTH_SECRET || input.authSecret,
|
|
63
|
+
// The dev command picks these; a value from .env would point elsewhere.
|
|
64
|
+
APP_URL: input.appUrl,
|
|
65
|
+
BUNDERSTACK_DATABASE_URL: input.databaseUrl,
|
|
66
|
+
};
|
|
67
|
+
return (Object.entries(vars)
|
|
68
|
+
.map(([key, value]) => `${key}=${quote(value)}`)
|
|
69
|
+
.join('\n') + '\n');
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/dev/env.ts"],"names":[],"mappings":"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","sourcesContent":["// The Worker env for `bunderstack dev`: the app's .env files plus the values\n// the dev command owns, written as .dev.vars for celld.\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nexport function parseDotenv(text: string): Record<string, string> {\n const vars: Record<string, string> = {}\n for (const raw of text.split(/\\r?\\n/)) {\n const line = raw.trim()\n if (!line || line.startsWith('#')) continue\n const match = /^(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*(.*)$/.exec(\n line,\n )\n if (!match) continue\n const [, key, rest] = match as unknown as [string, string, string]\n let value: string\n if (rest.startsWith('\"')) {\n const end = rest.lastIndexOf('\"')\n value = rest\n .slice(1, end > 0 ? end : undefined)\n .replace(/\\\\([\"\\\\n])/g, (_, c: string) => (c === 'n' ? '\\n' : c))\n } else if (rest.startsWith(\"'\")) {\n const end = rest.lastIndexOf(\"'\")\n value = rest.slice(1, end > 0 ? end : undefined)\n } else {\n value = rest.replace(/\\s+#.*$/, '').trim()\n }\n vars[key] = value\n }\n return vars\n}\n\nexport async function readUserEnv(\n directory: string,\n): Promise<Record<string, string>> {\n const vars: Record<string, string> = {}\n for (const name of ['.env', '.env.local']) {\n const text = await readFile(join(directory, name), 'utf8').catch(\n () => undefined,\n )\n if (text !== undefined) Object.assign(vars, parseDotenv(text))\n }\n return vars\n}\n\n/** A random AUTH_SECRET kept in the dev state dir, so sessions survive restarts. */\nexport async function devSecret(stateDir: string): Promise<string> {\n const path = join(stateDir, 'auth-secret')\n const existing = (await readFile(path, 'utf8').catch(() => '')).trim()\n if (existing) return existing\n await mkdir(stateDir, { recursive: true })\n const bytes = crypto.getRandomValues(new Uint8Array(32))\n const secret = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(\n '',\n )\n await writeFile(path, `${secret}\\n`, { mode: 0o600 })\n return secret\n}\n\nfunction quote(value: string) {\n if (/^[A-Za-z0-9_./:@+-]*$/.test(value)) return value\n return `\"${value.replace(/[\"\\\\]/g, '\\\\$&').replace(/\\n/g, '\\\\n')}\"`\n}\n\nexport function devVars(input: {\n userEnv: Record<string, string>\n databaseUrl: string\n appUrl: string\n authSecret: string\n}): string {\n const vars: Record<string, string> = {\n ...input.userEnv,\n AUTH_SECRET: input.userEnv.AUTH_SECRET || input.authSecret,\n // The dev command picks these; a value from .env would point elsewhere.\n APP_URL: input.appUrl,\n BUNDERSTACK_DATABASE_URL: input.databaseUrl,\n }\n return (\n Object.entries(vars)\n .map(([key, value]) => `${key}=${quote(value)}`)\n .join('\\n') + '\\n'\n )\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ProcessSpec } from './processes.js';
|
|
2
|
+
export type DevPlan = {
|
|
3
|
+
appUrl: string;
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
databaseUrl: string;
|
|
6
|
+
sqld?: ProcessSpec;
|
|
7
|
+
worker: ProcessSpec;
|
|
8
|
+
vite?: ProcessSpec;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* celld --logs mixes the Worker's console output with its own INFO lines.
|
|
12
|
+
* Keep the Worker output (without the timestamp), warnings, errors, and the
|
|
13
|
+
* start summary.
|
|
14
|
+
*/
|
|
15
|
+
export declare function celldLine(line: string): string | undefined;
|
|
16
|
+
export declare function planDev(input: {
|
|
17
|
+
directory: string;
|
|
18
|
+
stateDir: string;
|
|
19
|
+
userEnv: Record<string, string>;
|
|
20
|
+
hasVite: boolean;
|
|
21
|
+
ports: {
|
|
22
|
+
app: number;
|
|
23
|
+
api: number;
|
|
24
|
+
db: number;
|
|
25
|
+
};
|
|
26
|
+
binaries: {
|
|
27
|
+
celld: string;
|
|
28
|
+
sqld: string;
|
|
29
|
+
esbuild: string;
|
|
30
|
+
};
|
|
31
|
+
}): DevPlan;
|
|
32
|
+
/**
|
|
33
|
+
* The first port from `start` that is free on 127.0.0.1 and on ::1. The
|
|
34
|
+
* browser opens `localhost`, which can resolve to either; Vite on ::1 next to
|
|
35
|
+
* another server on 127.0.0.1 would answer only some requests.
|
|
36
|
+
*/
|
|
37
|
+
export declare function firstFreePort(start: number): Promise<number>;
|
|
38
|
+
export declare function runDev(options: {
|
|
39
|
+
directory: string;
|
|
40
|
+
port?: number;
|
|
41
|
+
}): Promise<number>;
|
|
42
|
+
export declare function runBuild(options: {
|
|
43
|
+
directory: string;
|
|
44
|
+
}): Promise<number>;
|
|
45
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dev/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAE5D,MAAM,MAAM,OAAO,GAAG;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,MAAM,EAAE,WAAW,CAAA;IACnB,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAID;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAc1D;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/C,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAC3D,GAAG,OAAO,CA4DV;AA+BD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlE;AAgCD,wBAAsB,MAAM,CAAC,OAAO,EAAE;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,OAAO,CAAC,MAAM,CAAC,CAwHlB;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE;IACtC,SAAS,EAAE,MAAM,CAAA;CAClB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBlB"}
|