@apex-stack/data 0.1.4 → 0.1.5

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.
@@ -0,0 +1,54 @@
1
+ import { ApexResource } from '@apex-stack/core';
2
+ import { ZodRawShape } from 'zod';
3
+
4
+ type Dialect = 'sqlite' | 'postgres';
5
+ /** A driver-agnostic database handle. `db` is a Drizzle instance (async API). */
6
+ interface ApexDbHandle {
7
+ db: any;
8
+ dialect: Dialect;
9
+ /** Run raw SQL (one or more statements). */
10
+ exec(sql: string): Promise<void>;
11
+ /** Run a query and return rows. */
12
+ query(sql: string): Promise<Array<Record<string, unknown>>>;
13
+ close(): Promise<void>;
14
+ }
15
+ type CreateDbConfig = string | {
16
+ driver: 'sqlite' | 'libsql';
17
+ url: string;
18
+ } | {
19
+ driver: 'postgres';
20
+ url: string;
21
+ } | {
22
+ driver: 'pglite';
23
+ dir?: string;
24
+ };
25
+ /**
26
+ * Open a database and wrap it with Drizzle behind a driver-agnostic handle.
27
+ * All drivers use Drizzle's async query API, so `defineResource` works unchanged
28
+ * across SQLite (libSQL/Turso), Postgres (Supabase/Neon) and embedded PGlite.
29
+ * Every driver is an OPTIONAL peer dependency, loaded on demand — install only
30
+ * the one your database uses (`@libsql/client`, `postgres`, or `@electric-sql/pglite`).
31
+ */
32
+ declare function createDb(config: CreateDbConfig): Promise<ApexDbHandle>;
33
+ /**
34
+ * Apply pending `*.sql` migrations from `dir`, tracked in `_apex_migrations`.
35
+ * Idempotent; runs each file once in filename order. Works on any dialect.
36
+ */
37
+ declare function applyMigrations(handle: ApexDbHandle, dir: string): Promise<string[]>;
38
+ interface DefineResourceOptions {
39
+ db: any;
40
+ /** A Drizzle table (from `drizzle-orm/sqlite-core` or `…/pg-core`). */
41
+ table: any;
42
+ /** Zod raw shape validating the create payload. */
43
+ insert: ZodRawShape;
44
+ /** Primary-key column name. Defaults to `id`. */
45
+ pk?: string;
46
+ }
47
+ /**
48
+ * Turn one table into a REST + MCP resource (list/get/create/update/delete),
49
+ * each also an MCP tool. Dialect-agnostic — the same definition works whether
50
+ * `db` is backed by SQLite, Turso, Supabase, Neon, or embedded PGlite.
51
+ */
52
+ declare function defineResource(name: string, opts: DefineResourceOptions): ApexResource;
53
+
54
+ export { type ApexDbHandle, type CreateDbConfig, type DefineResourceOptions, type Dialect, applyMigrations, createDb, defineResource };
package/dist/index.js ADDED
@@ -0,0 +1,165 @@
1
+ // src/index.ts
2
+ import { existsSync, readFileSync, readdirSync } from "fs";
3
+ import { join } from "path";
4
+ import { defineApexRoute } from "@apex-stack/core";
5
+ import { eq } from "drizzle-orm";
6
+ import { z } from "zod";
7
+ function libsqlUrl(pathOrUrl) {
8
+ return /^(file:|libsql:|https?:|:memory:)/.test(pathOrUrl) ? pathOrUrl : `file:${pathOrUrl}`;
9
+ }
10
+ async function loadDriver(spec) {
11
+ try {
12
+ return await import(spec);
13
+ } catch {
14
+ throw new Error(
15
+ `@apex-stack/data: the "${spec}" driver isn't installed. Install only the driver your database needs \u2014 e.g. \`npm i ${spec}\`.`
16
+ );
17
+ }
18
+ }
19
+ async function createDb(config) {
20
+ const cfg = typeof config === "string" ? { driver: "libsql", url: config } : config;
21
+ if (cfg.driver === "sqlite" || cfg.driver === "libsql") {
22
+ const { createClient } = await loadDriver("@libsql/client");
23
+ const { drizzle: drizzle2 } = await import("drizzle-orm/libsql");
24
+ const client2 = createClient({ url: libsqlUrl(cfg.url) });
25
+ return {
26
+ db: drizzle2(client2),
27
+ dialect: "sqlite",
28
+ exec: async (sql) => {
29
+ await client2.executeMultiple(sql);
30
+ },
31
+ query: async (sql) => (await client2.execute(sql)).rows,
32
+ close: async () => {
33
+ client2.close();
34
+ }
35
+ };
36
+ }
37
+ if (cfg.driver === "postgres") {
38
+ const postgres = (await loadDriver("postgres")).default;
39
+ const { drizzle: drizzle2 } = await import("drizzle-orm/postgres-js");
40
+ const client2 = postgres(cfg.url);
41
+ return {
42
+ db: drizzle2(client2),
43
+ dialect: "postgres",
44
+ exec: async (sql) => {
45
+ await client2.unsafe(sql);
46
+ },
47
+ query: async (sql) => await client2.unsafe(sql),
48
+ close: async () => {
49
+ await client2.end();
50
+ }
51
+ };
52
+ }
53
+ const { PGlite } = await loadDriver("@electric-sql/pglite");
54
+ const { drizzle } = await import("drizzle-orm/pglite");
55
+ const client = new PGlite(cfg.dir ?? "memory://");
56
+ return {
57
+ db: drizzle(client),
58
+ dialect: "postgres",
59
+ exec: async (sql) => {
60
+ await client.exec(sql);
61
+ },
62
+ query: async (sql) => (await client.query(sql)).rows,
63
+ close: async () => {
64
+ await client.close();
65
+ }
66
+ };
67
+ }
68
+ async function applyMigrations(handle, dir) {
69
+ if (!existsSync(dir)) return [];
70
+ await handle.exec(
71
+ "CREATE TABLE IF NOT EXISTS _apex_migrations (name TEXT PRIMARY KEY, applied_at TEXT NOT NULL)"
72
+ );
73
+ const applied = new Set(
74
+ (await handle.query("SELECT name FROM _apex_migrations")).map((r) => r.name)
75
+ );
76
+ const done = [];
77
+ for (const file of readdirSync(dir).filter((f) => f.endsWith(".sql")).sort()) {
78
+ if (applied.has(file)) continue;
79
+ await handle.exec(readFileSync(join(dir, file), "utf8"));
80
+ const at = (/* @__PURE__ */ new Date()).toISOString();
81
+ await handle.exec(
82
+ `INSERT INTO _apex_migrations (name, applied_at) VALUES ('${file.replace(/'/g, "''")}', '${at}')`
83
+ );
84
+ done.push(file);
85
+ }
86
+ return done;
87
+ }
88
+ function defineResource(name, opts) {
89
+ const { db, table, insert, pk = "id" } = opts;
90
+ const pkCol = table[pk];
91
+ const updateShape = { id: z.coerce.number() };
92
+ for (const [key, schema] of Object.entries(
93
+ insert
94
+ )) {
95
+ updateShape[key] = schema.optional();
96
+ }
97
+ return {
98
+ __apexResource: true,
99
+ name,
100
+ routes: [
101
+ {
102
+ pathSuffix: "",
103
+ mcpName: `${name}_list`,
104
+ route: defineApexRoute({
105
+ method: "GET",
106
+ description: `List all ${name}`,
107
+ mcp: true,
108
+ handler: async () => await db.select().from(table)
109
+ })
110
+ },
111
+ {
112
+ pathSuffix: "/:id",
113
+ mcpName: `${name}_get`,
114
+ route: defineApexRoute({
115
+ method: "GET",
116
+ description: `Get a single ${name} by id`,
117
+ input: { id: z.coerce.number() },
118
+ mcp: true,
119
+ handler: async ({ input }) => (await db.select().from(table).where(eq(pkCol, input.id)))[0] ?? null
120
+ })
121
+ },
122
+ {
123
+ pathSuffix: "",
124
+ mcpName: `${name}_create`,
125
+ route: defineApexRoute({
126
+ method: "POST",
127
+ description: `Create a ${name}`,
128
+ input: insert,
129
+ mcp: true,
130
+ handler: async ({ input }) => (await db.insert(table).values(input).returning())[0]
131
+ })
132
+ },
133
+ {
134
+ pathSuffix: "/:id",
135
+ mcpName: `${name}_update`,
136
+ route: defineApexRoute({
137
+ method: "PATCH",
138
+ description: `Update a ${name} by id (partial)`,
139
+ input: updateShape,
140
+ mcp: true,
141
+ handler: async ({ input }) => {
142
+ const { id, ...fields } = input;
143
+ return (await db.update(table).set(fields).where(eq(pkCol, id)).returning())[0] ?? null;
144
+ }
145
+ })
146
+ },
147
+ {
148
+ pathSuffix: "/:id",
149
+ mcpName: `${name}_delete`,
150
+ route: defineApexRoute({
151
+ method: "DELETE",
152
+ description: `Delete a ${name} by id`,
153
+ input: { id: z.coerce.number() },
154
+ mcp: true,
155
+ handler: async ({ input }) => (await db.delete(table).where(eq(pkCol, input.id)).returning())[0] ?? null
156
+ })
157
+ }
158
+ ]
159
+ };
160
+ }
161
+ export {
162
+ applyMigrations,
163
+ createDb,
164
+ defineResource
165
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apex-stack/data",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Data layer for Apex JS — Drizzle-backed resources that are REST + MCP by default",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "dependencies": {
37
37
  "drizzle-orm": "^0.45.2",
38
38
  "zod": "^4.4.3",
39
- "@apex-stack/core": "0.1.19"
39
+ "@apex-stack/core": "0.2.0"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@electric-sql/pglite": "^0.5.0",