futonic 0.1.0 → 0.1.1-canary.55aeaaa

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.
Files changed (42) hide show
  1. package/README.md +211 -0
  2. package/dist/chunk-sasg8hs8.js +6 -0
  3. package/dist/client/index.js +0 -2
  4. package/dist/core/context.d.ts +2 -2
  5. package/dist/core/context.d.ts.map +1 -1
  6. package/dist/core/runtime.d.ts +10 -0
  7. package/dist/core/runtime.d.ts.map +1 -0
  8. package/dist/core/service.d.ts +23 -12
  9. package/dist/core/service.d.ts.map +1 -1
  10. package/dist/db/drizzle.d.ts +132 -0
  11. package/dist/db/drizzle.d.ts.map +1 -0
  12. package/dist/db/drizzle.js +110 -0
  13. package/dist/db/internal-adapter.d.ts +21 -2
  14. package/dist/db/internal-adapter.d.ts.map +1 -1
  15. package/dist/db/kysely-factory.d.ts +1 -1
  16. package/dist/db/kysely-factory.d.ts.map +1 -1
  17. package/dist/db/schema.d.ts +3 -13
  18. package/dist/db/schema.d.ts.map +1 -1
  19. package/dist/index.d.ts +5 -9
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +169 -157
  22. package/package.json +71 -62
  23. package/dist/chunk-5va59f7m.js +0 -20
  24. package/dist/chunk-6w1vqkpr.js +0 -87
  25. package/dist/chunk-j2yky04y.js +0 -130
  26. package/dist/chunk-nj3a3jzm.js +0 -26
  27. package/dist/chunk-q65vm76v.js +0 -180
  28. package/dist/cli/generators/drizzle.d.ts +0 -11
  29. package/dist/cli/generators/drizzle.d.ts.map +0 -1
  30. package/dist/cli/generators/kysely.d.ts +0 -17
  31. package/dist/cli/generators/kysely.d.ts.map +0 -1
  32. package/dist/cli/generators/prisma.d.ts +0 -10
  33. package/dist/cli/generators/prisma.d.ts.map +0 -1
  34. package/dist/cli/generators/types.d.ts +0 -24
  35. package/dist/cli/generators/types.d.ts.map +0 -1
  36. package/dist/cli/index.d.ts +0 -28
  37. package/dist/cli/index.d.ts.map +0 -1
  38. package/dist/cli/index.js +0 -101
  39. package/dist/core/host.d.ts +0 -14
  40. package/dist/core/host.d.ts.map +0 -1
  41. package/dist/router/endpoint.d.ts +0 -105
  42. package/dist/router/endpoint.d.ts.map +0 -1
package/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # Futonic 🛋️
2
+
3
+ **A framework for building services that embed into host applications instead of deploying alongside them.**
4
+
5
+ ## The problem
6
+
7
+ There are plenty of services available as easy-to-deploy containers — auth servers, payment processors, observability tools. But for most applications, deploying multiple containers (one for the main app plus one for each service) is a waste. An auth server, a payment webhook handler, and an observability stack don't each need their own process. They could share compute and a database without any meaningful noisy-neighbor issues — because let's be honest, most apps these days are just wrappers around heavier services anyway.
8
+
9
+ ## The idea
10
+
11
+ Futonic is heavily inspired by [better-auth](https://github.com/better-auth/better-auth)'s service embedding paradigm — the insight that many services don't need their own process, their own deployment, or even their own database. They just need a place to crash.
12
+
13
+ Futonic is a framework for building **embeddable services** that crash on a host application's futon. They share the host's compute. They share the host's database. They wake up when needed and stay out of the way when they're not.
14
+
15
+ No separate containers. No extra Dockerfiles. No internal networking. Just services that live inside the host app — and a great DX for building them.
16
+
17
+ ## Why build embeddable services
18
+
19
+ **Your users save money:**
20
+ Most apps aren't mega-scale. They're tools, SaaS products, and indie projects where every dollar of infrastructure counts. When your service embeds directly into the host, your users don't need to pay for another container sitting idle 99% of the time.
21
+
22
+ **Your users get a better dev experience:**
23
+ No more `docker-compose up` with 6 services just to work on a feature. No more debugging why the auth container can't talk to the payments container on a laptop. Embedded services run in-process. Switch branches, switch worktrees — everything just works.
24
+
25
+ **Simplicity sells:**
26
+ Fewer moving parts means fewer things that break at 2am. One deploy. One database. One set of logs. Developers can always decompose later if they outgrow it — but most apps never will. The easier your service is to adopt, the more people will use it.
27
+
28
+ ## How it works: the vertical slice
29
+
30
+ A futonic service is a **vertical slice** of your application. It defines everything from the client interface down to the database — API endpoints, input validation, business logic, and table schemas — as a single, self-contained unit.
31
+
32
+ ```mermaid
33
+ block-beta
34
+ columns 3
35
+ block:host["Host Application"]:3
36
+ columns 3
37
+ space:3
38
+ block:app["Your App Code"]:1
39
+ columns 1
40
+ appRoutes["Routes"]
41
+ appLogic["Logic"]
42
+ appTables["Tables"]
43
+ end
44
+ block:billing["@acme/billing"]:1
45
+ columns 1
46
+ billingAPI["Endpoints"]
47
+ billingLogic["Logic"]
48
+ billingTables["Tables"]
49
+ end
50
+ block:auth["@acme/auth"]:1
51
+ columns 1
52
+ authAPI["Endpoints"]
53
+ authLogic["Logic"]
54
+ authTables["Tables"]
55
+ end
56
+ space:3
57
+ end
58
+
59
+ style billing stroke:#22c55e,stroke-width:2px
60
+ style auth stroke:#22c55e,stroke-width:2px
61
+ style app stroke:#6366f1,stroke-width:2px
62
+ ```
63
+
64
+ Each green block is a futonic service — a vertical slice that owns its entire stack, published as an npm package. The host application mounts them alongside its own code. They all share one process, one database, one deployment.
65
+
66
+ This is what makes futonic different from microservices. A microservice is also a vertical slice, but it pays for that independence with its own container, its own deployment pipeline, and network hops between every call. A futonic service gets the same self-containment for free:
67
+
68
+ - **No deployment cost.** It runs inside your existing process. No extra containers, no extra infrastructure, no extra cloud bill.
69
+ - **No networking latency.** Service calls from backend code are direct function calls — same process, same database connection pool, zero serialization.
70
+ - **No protocol constraints.** Endpoints are built on web standard `Request`/`Response`, so a service can return JSON, full HTML pages, server-sent event streams, file downloads — anything `Response` supports.
71
+
72
+ ### What a service looks like
73
+
74
+ A service defines its database tables and API endpoints. The service author publishes this as an npm package:
75
+
76
+ ```typescript
77
+ // @acme/billing
78
+
79
+ import { createService } from "futonic";
80
+
81
+ export const billing = createService({
82
+ id: "billing",
83
+ version: "0.1.0",
84
+ dbSchema: {
85
+ tables: {
86
+ invoices: {
87
+ fields: {
88
+ id: { type: "string", primaryKey: true, required: true },
89
+ amount: { type: "number", required: true },
90
+ status: { type: "string", required: true },
91
+ },
92
+ },
93
+ },
94
+ },
95
+ endpoints: createBillingEndpoints, // the endpoints factory, defined below
96
+ });
97
+ ```
98
+
99
+ Endpoints are an `(use) => endpoints` factory. Futonic passes in the middleware that injects a `ServiceContext` — giving each endpoint scoped access to the service's database tables, config, and logger:
100
+
101
+ ```typescript
102
+ import { createEndpoint, type Middleware } from "better-call";
103
+
104
+ export function createBillingEndpoints(use: Middleware[]) {
105
+ return {
106
+ createInvoice: createEndpoint(
107
+ "/invoices",
108
+ { method: "POST", use, body: z.object({ amount: z.number(), status: z.string() }) },
109
+ async (ctx) => {
110
+ const svc = ctx.context.serviceCtx;
111
+ const invoice = await svc.db.invoices.create({
112
+ id: crypto.randomUUID(),
113
+ ...ctx.body,
114
+ });
115
+ svc.logger.info(`Invoice created: ${invoice.id}`);
116
+ return invoice;
117
+ },
118
+ ),
119
+ };
120
+ }
121
+ ```
122
+
123
+ The endpoint writes to `svc.db.invoices`, but the actual table in the database is `billing_invoices` — automatically prefixed with the service ID. A second service with its own `invoices` table would hit a completely separate table. Same database, no collisions.
124
+
125
+ ### How the host mounts it
126
+
127
+ The host developer installs the service and runs it — they never import futonic themselves. Futonic auto-detects the database driver — `pg`, `mysql2`, `better-sqlite3`, or `bun:sqlite`:
128
+
129
+ ```typescript
130
+ import { billing } from "@acme/billing";
131
+
132
+ const svc = billing({
133
+ database: pool, // Their existing pg.Pool, mysql2 pool, or sqlite instance
134
+ mount: "/api/billing",
135
+ baseURL: "http://localhost:3000",
136
+ });
137
+
138
+ await svc.init();
139
+ ```
140
+
141
+ Then a single catch-all route in their framework of choice — `svc.handler` is a web-standard `(Request) => Response` handler:
142
+
143
+ ```typescript
144
+ // Hono
145
+ app.all("/api/billing/*", (c) => svc.handler(c.req.raw));
146
+
147
+ // Next.js — app/api/billing/[...path]/route.ts
148
+ const handler = (req: Request) => svc.handler(req);
149
+ export { handler as GET, handler as POST, handler as PUT, handler as DELETE, handler as PATCH };
150
+ ```
151
+
152
+ That's it. The billing service handles requests at `/api/billing/*`, stores data in the host's database under prefixed tables, and the host never had to know futonic was involved — it just installed `@acme/billing` and ran it. On teardown, `await svc.shutdown()`.
153
+
154
+ ## More features
155
+
156
+ ### Type-safe RPC from the frontend
157
+
158
+ Host developers consume your service from their frontend with a fully typed client — autocomplete, return types, and error types included:
159
+
160
+ ```typescript
161
+ import { createClient } from "futonic/client";
162
+ import type { BillingApi } from "@acme/billing";
163
+
164
+ const billing = createClient<BillingApi>({
165
+ baseURL: "/api/billing",
166
+ });
167
+
168
+ const { data } = await billing.listInvoices();
169
+ const { data: invoice } = await billing.createInvoice({
170
+ body: { amount: 4200, status: "draft" },
171
+ });
172
+ ```
173
+
174
+ ### Skip networking on the backend
175
+
176
+ When the host's backend code needs to call the service, there's no HTTP round-trip. The service runs in the same process, shares the same database connection pool, and returns results directly:
177
+
178
+ ```typescript
179
+ const invoices = await svc.serviceContext!.db.invoices.findMany();
180
+ ```
181
+
182
+ ### Bring your own migrations
183
+
184
+ A service declares its tables in `dbSchema`, and exports that schema from its package. The host creates the matching prefixed tables (e.g. `billing_invoices`) with whatever migration tooling it already uses — futonic doesn't impose one.
185
+
186
+ ### Return any web standard response
187
+
188
+ Endpoints aren't limited to JSON. Serve full HTML pages for embedded UIs, server-sent event streams for real-time updates, file downloads, redirects — whatever `Response` supports. Build an entire admin dashboard that lives inside the host app.
189
+
190
+ ## Things you could build
191
+
192
+ - **Auth service** — User management, sessions, OAuth flows, and permissions. Like better-auth, but packaged as an embeddable service.
193
+ - **Payment service** — Webhook handlers, invoice management, subscription state. A reusable Stripe integration that anyone can drop into their app.
194
+ - **Observability service** — An API for ingesting traces, database tables for storing them, and an embedded UI for visualizing them. Jaeger without the deployment.
195
+ - **Feature flags** — A flag service with an admin UI, backed by the host's existing database.
196
+ - **CMS** — Content management endpoints with an embedded editor interface.
197
+ - **Notifications** — Email/push queue management with status tracking and retry logic.
198
+
199
+ ## Database support
200
+
201
+ Futonic auto-detects your database driver and works with:
202
+
203
+ - **PostgreSQL** via `pg`
204
+ - **MySQL** via `mysql2`
205
+ - **SQLite** via `better-sqlite3` or Bun's built-in `bun:sqlite`
206
+
207
+ Your service works with whatever database the host is already running — no extra configuration on your end.
208
+
209
+ ## License
210
+
211
+ MIT
@@ -0,0 +1,6 @@
1
+ // src/db/schema.ts
2
+ function prefixTableName(serviceId, tableName) {
3
+ return `${serviceId}_${tableName}`;
4
+ }
5
+
6
+ export { prefixTableName };
@@ -1,5 +1,3 @@
1
- import"../chunk-5va59f7m.js";
2
-
3
1
  // src/client/index.ts
4
2
  import { createClient } from "better-call/client";
5
3
  export {
@@ -6,7 +6,7 @@ export interface Logger {
6
6
  error(message: string, ...args: unknown[]): void;
7
7
  debug(message: string, ...args: unknown[]): void;
8
8
  }
9
- export interface HostInfo {
9
+ export interface MountInfo {
10
10
  baseURL: string;
11
11
  mountPath: string;
12
12
  }
@@ -17,7 +17,7 @@ export interface ServiceContext<TSchema extends ServiceDBSchema = ServiceDBSchem
17
17
  db: InternalAdapter<TSchema>;
18
18
  config: ResolvedConfig;
19
19
  logger: Logger;
20
- hostInfo: HostInfo;
20
+ mountInfo: MountInfo;
21
21
  }
22
22
  export declare function createLogger(serviceId: string): Logger;
23
23
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/core/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,MAAM;IACtB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACjD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,QAAQ;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAc,CAC9B,OAAO,SAAS,eAAe,GAAG,eAAe;IAEjD,EAAE,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAQtD"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/core/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,MAAM;IACtB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACjD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,SAAS;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAc,CAC9B,OAAO,SAAS,eAAe,GAAG,eAAe;IAEjD,EAAE,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAQtD"}
@@ -0,0 +1,10 @@
1
+ import { type Endpoint } from "better-call";
2
+ import type { ServiceDBSchema } from "../db/schema";
3
+ import type { EmbeddableService, RunnableService, ServiceConfig } from "./service";
4
+ /**
5
+ * Turns a service definition + mount config into a self-running unit: it opens
6
+ * its own database connection, builds its own router, and manages its own
7
+ * lifecycle. The host just calls init()/handler()/shutdown().
8
+ */
9
+ export declare function createServiceRuntime<TConfig, TSchema extends ServiceDBSchema, TEndpoints extends Record<string, Endpoint>>(definition: EmbeddableService<TConfig, TSchema, TEndpoints>, config: ServiceConfig<TConfig>): RunnableService<TSchema>;
10
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/core/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAgB,MAAM,aAAa,CAAC;AAI1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,OAAO,KAAK,EACX,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,MAAM,WAAW,CAAC;AAEnB;;;;GAIG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EACP,OAAO,SAAS,eAAe,EAC/B,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EAE3C,UAAU,EAAE,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,EAC3D,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,GAC5B,eAAe,CAAC,OAAO,CAAC,CA+C1B"}
@@ -1,25 +1,36 @@
1
+ import type { Endpoint, Middleware } from "better-call";
2
+ import type { DatabaseConnection } from "../db/kysely-factory";
1
3
  import type { ServiceDBSchema } from "../db/schema";
2
4
  import type { ServiceContext } from "./context";
3
5
  export interface ServiceConfig<TConfig = unknown> {
4
- mount: string;
5
- middleware?: unknown[];
6
+ /** HTTP mount path, e.g. "/api/billing". Becomes the better-call router basePath. */
7
+ /** Driver/dialect the service opens its own Kysely instance from. Required iff the service declares a dbSchema. */
8
+ database: DatabaseConnection;
9
+ /** Absolute base URL surfaced to endpoints via ctx.hostInfo.baseURL. */
10
+ /** Service-specific resolved config, surfaced as ctx.config. */
6
11
  config?: TConfig;
7
12
  }
8
- export interface EmbeddableService<TConfig = unknown, TSchema extends ServiceDBSchema = ServiceDBSchema, TEndpoints extends Record<string, unknown> = Record<string, unknown>> {
13
+ export interface EmbeddableService<TConfig = unknown, TSchema extends ServiceDBSchema = ServiceDBSchema, TEndpoints extends Record<string, Endpoint> = Record<string, Endpoint>> {
9
14
  id: string;
10
15
  version: string;
11
- dependencies: {
12
- database: boolean;
13
- };
16
+ /** Presence of a schema means the service needs a database. */
14
17
  dbSchema?: TSchema;
15
- endpoints: TEndpoints;
18
+ /** Factory: futonic passes the ServiceContext-injecting middleware; returns the endpoint map. */
19
+ endpoints: (use: Middleware[]) => TEndpoints;
16
20
  onInit?: (ctx: ServiceContext<TSchema>) => Promise<void>;
17
- onReady?: (ctx: ServiceContext<TSchema>) => Promise<void>;
18
21
  onShutdown?: () => Promise<void>;
19
22
  }
20
- export interface MountedService<TConfig = unknown, TSchema extends ServiceDBSchema = ServiceDBSchema, TEndpoints extends Record<string, unknown> = Record<string, unknown>> extends EmbeddableService<TConfig, TSchema, TEndpoints> {
21
- mountConfig: ServiceConfig<TConfig>;
22
- serviceContext?: ServiceContext<TSchema>;
23
+ /**
24
+ * The runnable object returned by the service factory. The host calls
25
+ * init()/handler()/shutdown() on it without ever touching futonic itself.
26
+ */
27
+ export interface RunnableService<TSchema extends ServiceDBSchema = ServiceDBSchema> {
28
+ id: string;
29
+ version: string;
30
+ createHandler(mountInfo: {
31
+ baseURL: string;
32
+ mountPath: string;
33
+ }): Promise<(request: Request) => Promise<Response>>;
23
34
  }
24
- export declare function createService<TConfig, TSchema extends ServiceDBSchema, TEndpoints extends Record<string, unknown>>(definition: EmbeddableService<TConfig, TSchema, TEndpoints>): (config: ServiceConfig<TConfig>) => MountedService<TConfig, TSchema, TEndpoints>;
35
+ export declare function createService<TConfig, TSchema extends ServiceDBSchema, TEndpoints extends Record<string, Endpoint>>(definition: EmbeddableService<TConfig, TSchema, TEndpoints>): (config: ServiceConfig<TConfig>) => RunnableService<TSchema>;
25
36
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/core/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,MAAM,WAAW,aAAa,CAAC,OAAO,GAAG,OAAO;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB,CACjC,OAAO,GAAG,OAAO,EACjB,OAAO,SAAS,eAAe,GAAG,eAAe,EACjD,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAEpE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE;QACb,QAAQ,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,UAAU,CAAC;IACtB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc,CAC9B,OAAO,GAAG,OAAO,EACjB,OAAO,SAAS,eAAe,GAAG,eAAe,EACjD,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACnE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC;IACxD,WAAW,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACpC,cAAc,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;CACzC;AAED,wBAAgB,aAAa,CAC5B,OAAO,EACP,OAAO,SAAS,eAAe,EAC/B,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAE1C,UAAU,EAAE,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,GACzD,CACF,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,KAC1B,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,CAKhD"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/core/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,MAAM,WAAW,aAAa,CAAC,OAAO,GAAG,OAAO;IAC/C,qFAAqF;IAErF,mHAAmH;IACnH,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,wEAAwE;IAExE,gEAAgE;IAChE,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB,CACjC,OAAO,GAAG,OAAO,EACjB,OAAO,SAAS,eAAe,GAAG,eAAe,EACjD,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;IAEtE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iGAAiG;IACjG,SAAS,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,UAAU,CAAC;IAC7C,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe,CAC/B,OAAO,SAAS,eAAe,GAAG,eAAe;IAEjD,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,SAAS,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CACxE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CACvC,CAAC;CACF;AAED,wBAAgB,aAAa,CAC5B,OAAO,EACP,OAAO,SAAS,eAAe,EAC/B,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EAE3C,UAAU,EAAE,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,GACzD,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,eAAe,CAAC,OAAO,CAAC,CAG9D"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Drizzle schema generator.
3
+ *
4
+ * Converts a service's dialect-agnostic `ServiceDBSchema` into a record of
5
+ * Drizzle table objects for a specific SQL dialect. Downstream services wrap
6
+ * `generateSchema` and re-export the result; hosts then feed those tables into
7
+ * their own Drizzle schema so `drizzle-kit` can produce migrations.
8
+ *
9
+ * The return type is fully inferred from the schema: when the schema is passed
10
+ * with literal types (a `const` binding or `satisfies ServiceDBSchema`), each
11
+ * generated table carries its real Drizzle column types, so services can export
12
+ * typed rows via `typeof tables.users.$inferSelect`.
13
+ *
14
+ * `drizzle-orm` is an optional peer dependency — importing this module (via the
15
+ * `futonic/drizzle` entry point) only works when the host has it installed.
16
+ */
17
+ import type { BuildColumns } from "drizzle-orm";
18
+ import { type MySqlTableWithColumns } from "drizzle-orm/mysql-core";
19
+ import { type PgTableWithColumns } from "drizzle-orm/pg-core";
20
+ import { type SQLiteTableWithColumns } from "drizzle-orm/sqlite-core";
21
+ import type { FieldDefinition, ServiceDBSchema } from "./schema";
22
+ /** Dialect identifier, aligned with the codebase's KyselyDatabaseType. */
23
+ export type DrizzleDialect = "postgres" | "mysql" | "sqlite";
24
+ /**
25
+ * Maps a dialect and a service field type to a dialect-specific column builder.
26
+ *
27
+ * Declared with `satisfies` (not an explicit annotation) so each factory's
28
+ * precise return type is preserved for `ColumnBuilderFor` to recover.
29
+ */
30
+ declare const TYPE_MAP: {
31
+ postgres: {
32
+ string: (n: string) => import("drizzle-orm/pg-core").PgTextBuilderInitial<string, [string, ...string[]]>;
33
+ number: (n: string) => import("drizzle-orm/pg-core").PgIntegerBuilderInitial<string>;
34
+ boolean: (n: string) => import("drizzle-orm/pg-core").PgBooleanBuilderInitial<string>;
35
+ date: (n: string) => import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>;
36
+ json: (n: string) => import("drizzle-orm/pg-core").PgJsonbBuilderInitial<string>;
37
+ binary: (n: string) => import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
38
+ name: string;
39
+ dataType: "custom";
40
+ columnType: "PgCustomColumn";
41
+ data: Buffer<ArrayBufferLike>;
42
+ driverParam: unknown;
43
+ enumValues: undefined;
44
+ }>;
45
+ };
46
+ mysql: {
47
+ string: (n: string) => import("drizzle-orm/mysql-core").MySqlTextBuilderInitial<string, [string, ...string[]]>;
48
+ number: (n: string) => import("drizzle-orm/mysql-core").MySqlIntBuilderInitial<string>;
49
+ boolean: (n: string) => import("drizzle-orm/mysql-core").MySqlBooleanBuilderInitial<string>;
50
+ date: (n: string) => import("drizzle-orm/mysql-core").MySqlDateTimeBuilderInitial<string>;
51
+ json: (n: string) => import("drizzle-orm/mysql-core").MySqlJsonBuilderInitial<string>;
52
+ binary: (n: string) => import("drizzle-orm/mysql-core").MySqlCustomColumnBuilder<{
53
+ name: string;
54
+ dataType: "custom";
55
+ columnType: "MySqlCustomColumn";
56
+ data: Buffer<ArrayBufferLike>;
57
+ driverParam: unknown;
58
+ enumValues: undefined;
59
+ }>;
60
+ };
61
+ sqlite: {
62
+ string: (n: string) => import("drizzle-orm/sqlite-core").SQLiteTextBuilderInitial<string, [string, ...string[]], number | undefined>;
63
+ number: (n: string) => import("drizzle-orm/sqlite-core").SQLiteIntegerBuilderInitial<string>;
64
+ boolean: (n: string) => import("drizzle-orm/sqlite-core").SQLiteBooleanBuilderInitial<string>;
65
+ date: (n: string) => import("drizzle-orm/sqlite-core").SQLiteTimestampBuilderInitial<string>;
66
+ json: (n: string) => import("drizzle-orm/sqlite-core").SQLiteTextJsonBuilderInitial<string>;
67
+ binary: (n: string) => import("drizzle-orm/sqlite-core").SQLiteBlobJsonBuilderInitial<string>;
68
+ };
69
+ };
70
+ /** The initial column builder the runtime creates for a dialect + field type. */
71
+ type ColumnBuilderFor<D extends DrizzleDialect, TType extends FieldDefinition["type"]> = ReturnType<(typeof TYPE_MAP)[D][TType]>;
72
+ /**
73
+ * The select/insert-affecting modifiers a field applies, expressed as the `_`
74
+ * config flags Drizzle reads (mirrors `NotNull`/`HasDefault`/`IsPrimaryKey`).
75
+ */
76
+ type ColumnModifiers<F extends FieldDefinition> = (F extends {
77
+ primaryKey: true;
78
+ } ? {
79
+ notNull: true;
80
+ isPrimaryKey: true;
81
+ } : {}) & (F extends {
82
+ required: true;
83
+ } ? {
84
+ notNull: true;
85
+ } : {}) & ("defaultValue" extends keyof F ? {
86
+ hasDefault: true;
87
+ } : {});
88
+ /** A field's column builder with its constraints applied. */
89
+ type ColumnBuilderForField<D extends DrizzleDialect, F extends FieldDefinition> = ColumnBuilderFor<D, F["type"]> & {
90
+ _: ColumnModifiers<F>;
91
+ };
92
+ /** The Drizzle column-builder map for a single table. */
93
+ type ColumnsMapForFields<D extends DrizzleDialect, TFields extends Record<string, FieldDefinition>> = {
94
+ [K in keyof TFields]: ColumnBuilderForField<D, TFields[K]>;
95
+ };
96
+ /** The fully-typed Drizzle table for a dialect, name, and field set. */
97
+ type DrizzleTableFor<D extends DrizzleDialect, TName extends string, TFields extends Record<string, FieldDefinition>> = D extends "postgres" ? PgTableWithColumns<{
98
+ name: TName;
99
+ schema: undefined;
100
+ columns: BuildColumns<TName, ColumnsMapForFields<"postgres", TFields>, "pg">;
101
+ dialect: "pg";
102
+ }> : D extends "mysql" ? MySqlTableWithColumns<{
103
+ name: TName;
104
+ schema: undefined;
105
+ columns: BuildColumns<TName, ColumnsMapForFields<"mysql", TFields>, "mysql">;
106
+ dialect: "mysql";
107
+ }> : D extends "sqlite" ? SQLiteTableWithColumns<{
108
+ name: TName;
109
+ schema: undefined;
110
+ columns: BuildColumns<TName, ColumnsMapForFields<"sqlite", TFields>, "sqlite">;
111
+ dialect: "sqlite";
112
+ }> : never;
113
+ /**
114
+ * The record `generateSchema` returns: logical table name -> fully-typed
115
+ * Drizzle table, with SQL names prefixed by the service id.
116
+ */
117
+ export type InferDrizzleSchema<TSchema extends ServiceDBSchema, D extends DrizzleDialect, TServiceId extends string> = {
118
+ [K in keyof TSchema["tables"] & string]: DrizzleTableFor<D, `${TServiceId}_${K}`, TSchema["tables"][K]["fields"]>;
119
+ };
120
+ /**
121
+ * Generates a record of fully-typed Drizzle tables from a service DB schema.
122
+ *
123
+ * @param schema The service's dialect-agnostic table definitions. Pass it as
124
+ * a `const` binding (or with `satisfies ServiceDBSchema`) to get precise
125
+ * per-column types on the result.
126
+ * @param dialect The target SQL dialect.
127
+ * @param serviceId Prefixes the emitted SQL table names (mirroring the runtime's
128
+ * table scoping); the returned record stays keyed by the logical table names.
129
+ */
130
+ export declare function generateSchema<const TSchema extends ServiceDBSchema, D extends DrizzleDialect, TServiceId extends string>(schema: TSchema, dialect: D, serviceId: TServiceId): InferDrizzleSchema<TSchema, D, TServiceId>;
131
+ export {};
132
+ //# sourceMappingURL=drizzle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drizzle.d.ts","sourceRoot":"","sources":["../../src/db/drizzle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ import {
2
+ prefixTableName
3
+ } from "../chunk-sasg8hs8.js";
4
+
5
+ // src/db/drizzle.ts
6
+ import {
7
+ boolean as mysqlBoolean,
8
+ customType as mysqlCustomType,
9
+ datetime as mysqlDatetime,
10
+ int as mysqlInt,
11
+ json as mysqlJson,
12
+ mysqlTable,
13
+ text as mysqlText
14
+ } from "drizzle-orm/mysql-core";
15
+ import {
16
+ boolean as pgBoolean,
17
+ customType as pgCustomType,
18
+ integer as pgInteger,
19
+ jsonb as pgJsonb,
20
+ pgTable,
21
+ text as pgText,
22
+ timestamp as pgTimestamp
23
+ } from "drizzle-orm/pg-core";
24
+ import {
25
+ blob as sqliteBlob,
26
+ integer as sqliteInteger,
27
+ sqliteTable,
28
+ text as sqliteText
29
+ } from "drizzle-orm/sqlite-core";
30
+ var pgBytea = pgCustomType({ dataType: () => "bytea" });
31
+ var mysqlBlob = mysqlCustomType({ dataType: () => "blob" });
32
+ var TABLE_CONSTRUCTORS = {
33
+ postgres: (name, columns) => pgTable(name, columns),
34
+ mysql: (name, columns) => mysqlTable(name, columns),
35
+ sqlite: (name, columns) => sqliteTable(name, columns)
36
+ };
37
+ var TYPE_MAP = {
38
+ postgres: {
39
+ string: (n) => pgText(n),
40
+ number: (n) => pgInteger(n),
41
+ boolean: (n) => pgBoolean(n),
42
+ date: (n) => pgTimestamp(n),
43
+ json: (n) => pgJsonb(n),
44
+ binary: (n) => pgBytea(n)
45
+ },
46
+ mysql: {
47
+ string: (n) => mysqlText(n),
48
+ number: (n) => mysqlInt(n),
49
+ boolean: (n) => mysqlBoolean(n),
50
+ date: (n) => mysqlDatetime(n),
51
+ json: (n) => mysqlJson(n),
52
+ binary: (n) => mysqlBlob(n)
53
+ },
54
+ sqlite: {
55
+ string: (n) => sqliteText(n),
56
+ number: (n) => sqliteInteger(n),
57
+ boolean: (n) => sqliteInteger(n, { mode: "boolean" }),
58
+ date: (n) => sqliteInteger(n, { mode: "timestamp" }),
59
+ json: (n) => sqliteText(n, { mode: "json" }),
60
+ binary: (n) => sqliteBlob(n)
61
+ }
62
+ };
63
+ function toDrizzleAction(onDelete) {
64
+ return onDelete === "set-null" ? "set null" : onDelete;
65
+ }
66
+ function buildColumn(dialect, field, columnName, tables) {
67
+ const factory = TYPE_MAP[dialect][field.type];
68
+ if (!factory) {
69
+ throw new Error(`Unsupported field type "${field.type}" for dialect "${dialect}"`);
70
+ }
71
+ let column = factory(columnName);
72
+ if (field.primaryKey)
73
+ column = column.primaryKey();
74
+ if (field.required)
75
+ column = column.notNull();
76
+ if (field.unique)
77
+ column = column.unique();
78
+ if (field.defaultValue !== undefined)
79
+ column = column.default(field.defaultValue);
80
+ if (field.references) {
81
+ const { model, field: refField, onDelete } = field.references;
82
+ column = column.references(() => {
83
+ const target = tables[model];
84
+ if (!target) {
85
+ throw new Error(`Field "${columnName}" references unknown table "${model}"`);
86
+ }
87
+ return target[refField];
88
+ }, onDelete ? { onDelete: toDrizzleAction(onDelete) } : undefined);
89
+ }
90
+ return column;
91
+ }
92
+ function generateSchema(schema, dialect, serviceId) {
93
+ const constructTable = TABLE_CONSTRUCTORS[dialect];
94
+ if (!constructTable) {
95
+ throw new Error(`Unsupported dialect "${dialect}"`);
96
+ }
97
+ const tables = {};
98
+ for (const [tableName, tableDef] of Object.entries(schema.tables)) {
99
+ const columns = {};
100
+ for (const [fieldName, field] of Object.entries(tableDef.fields)) {
101
+ columns[fieldName] = buildColumn(dialect, field, fieldName, tables);
102
+ }
103
+ const sqlName = prefixTableName(serviceId, tableName);
104
+ tables[tableName] = constructTable(sqlName, columns);
105
+ }
106
+ return tables;
107
+ }
108
+ export {
109
+ generateSchema
110
+ };
@@ -12,10 +12,26 @@ export interface Where {
12
12
  field: string;
13
13
  value: unknown;
14
14
  operator?: "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "not_in";
15
- connector?: "AND" | "OR";
16
15
  }
16
+ export type FilterOp = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "not_in" | "contains" | "startsWith" | "endsWith" | "isNull" | "isNotNull";
17
+ export type FilterNode = {
18
+ type: "and" | "or";
19
+ nodes: FilterNode[];
20
+ } | {
21
+ type: "not";
22
+ node: FilterNode;
23
+ } | {
24
+ type: "cond";
25
+ field: string;
26
+ op: FilterOp;
27
+ value?: unknown;
28
+ };
17
29
  export interface FindManyOptions {
18
30
  where?: Where[];
31
+ /** Boolean expression tree (supports and/or/not + contains/like); combined with `where`. */
32
+ filter?: FilterNode;
33
+ /** Column projection; when omitted selects all columns. */
34
+ select?: string[];
19
35
  limit?: number;
20
36
  offset?: number;
21
37
  sortBy?: {
@@ -27,7 +43,10 @@ export interface TableAdapter {
27
43
  create(data: Record<string, unknown>): Promise<Record<string, unknown>>;
28
44
  findOne(where: Where[]): Promise<Record<string, unknown> | null>;
29
45
  findMany(options?: FindManyOptions): Promise<Record<string, unknown>[]>;
30
- count(where?: Where[]): Promise<number>;
46
+ count(where?: Where[] | {
47
+ where?: Where[];
48
+ filter?: FilterNode;
49
+ }): Promise<number>;
31
50
  update(where: Where[], data: Record<string, unknown>): Promise<Record<string, unknown>>;
32
51
  updateMany(where: Where[], data: Record<string, unknown>): Promise<number>;
33
52
  delete(where: Where[]): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"internal-adapter.d.ts","sourceRoot":"","sources":["../../src/db/internal-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAEjE,MAAM,WAAW,KAAK;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,QAAQ,CAAC;IACvE,SAAS,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,CAAC;CACtD;AAED,MAAM,WAAW,YAAY;IAC5B,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACxE,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,CACL,KAAK,EAAE,KAAK,EAAE,EACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpC,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3E,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5C;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,CAAC,OAAO,SAAS,eAAe,GAAG,eAAe,IAC5E;KACE,CAAC,IAAI,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,YAAY;CAC5C,CAAC;AAEH,wBAAgB,qBAAqB,CAAC,OAAO,SAAS,eAAe,EACpE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACvC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,OAAO,GACd,eAAe,CAAC,OAAO,CAAC,CAe1B"}
1
+ {"version":3,"file":"internal-adapter.d.ts","sourceRoot":"","sources":["../../src/db/internal-adapter.ts"],"names":[],"mappings":"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"}
@@ -8,7 +8,7 @@
8
8
  import { Kysely } from "kysely";
9
9
  export type KyselyDatabaseType = "sqlite" | "mysql" | "postgres";
10
10
  /**
11
- * The host passes one of these to `createHost({ database: ... })`.
11
+ * The host passes one of these as the service's `database` config.
12
12
  *
13
13
  * Accepted forms:
14
14
  * - A Kysely `Dialect` instance (most explicit)
@@ -1 +1 @@
1
- {"version":3,"file":"kysely-factory.d.ts","sourceRoot":"","sources":["../../src/db/kysely-factory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACN,MAAM,EAIN,MAAM,QAAQ,CAAC;AAEhB,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAEjE;;;;;;;;;GASG;AAEH,MAAM,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAErC;;;;GAIG;AACH,wBAAgB,kBAAkB,CACjC,EAAE,EAAE,kBAAkB,GACpB,kBAAkB,GAAG,IAAI,CA6B3B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,UAAU,EAAE,kBAAkB,GAC5B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAkDjC"}
1
+ {"version":3,"file":"kysely-factory.d.ts","sourceRoot":"","sources":["../../src/db/kysely-factory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,MAAM,EAAgD,MAAM,QAAQ,CAAC;AAE9E,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAEjE;;;;;;;;;GASG;AAEH,MAAM,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAErC;;;;GAIG;AACH,wBAAgB,kBAAkB,CACjC,EAAE,EAAE,kBAAkB,GACpB,kBAAkB,GAAG,IAAI,CA6B3B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,UAAU,EAAE,kBAAkB,GAC5B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAkDjC"}