@apex-stack/data 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,23 +1,42 @@
1
1
  import { ApexResource } from '@apex-stack/core';
2
- import Database from 'better-sqlite3';
3
- import { drizzle } from 'drizzle-orm/better-sqlite3';
4
2
  import { ZodRawShape } from 'zod';
5
3
 
6
- type ApexDb = ReturnType<typeof drizzle>;
7
- /** Open a SQLite database and wrap it with Drizzle. */
8
- declare function createDb(path: string): {
9
- db: ApexDb;
10
- sqlite: Database.Database;
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;
11
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
+ * Postgres/PGlite drivers are loaded on demand — install only what you use.
30
+ */
31
+ declare function createDb(config: CreateDbConfig): Promise<ApexDbHandle>;
12
32
  /**
13
33
  * Apply pending `*.sql` migrations from `dir`, tracked in `_apex_migrations`.
14
- * Idempotent: each file runs once, in filename order, inside a transaction.
15
- * Returns the names of migrations applied this run.
34
+ * Idempotent; runs each file once in filename order. Works on any dialect.
16
35
  */
17
- declare function applyMigrations(sqlite: Database.Database, dir: string): string[];
36
+ declare function applyMigrations(handle: ApexDbHandle, dir: string): Promise<string[]>;
18
37
  interface DefineResourceOptions {
19
- db: ApexDb;
20
- /** A Drizzle table. */
38
+ db: any;
39
+ /** A Drizzle table (from `drizzle-orm/sqlite-core` or `…/pg-core`). */
21
40
  table: any;
22
41
  /** Zod raw shape validating the create payload. */
23
42
  insert: ZodRawShape;
@@ -25,12 +44,10 @@ interface DefineResourceOptions {
25
44
  pk?: string;
26
45
  }
27
46
  /**
28
- * Turn one table into a REST + MCP resource: `list`, `get`, and `create` routes,
29
- * each also an MCP tool. This is the moat your data is AI-callable by construction.
30
- * GET /api/<name> → list
31
- * GET /api/<name>/:id → get
32
- * POST /api/<name> → create
47
+ * Turn one table into a REST + MCP resource (list/get/create/update/delete),
48
+ * each also an MCP tool. Dialect-agnostic the same definition works whether
49
+ * `db` is backed by SQLite, Turso, Supabase, Neon, or embedded PGlite.
33
50
  */
34
51
  declare function defineResource(name: string, opts: DefineResourceOptions): ApexResource;
35
52
 
36
- export { type ApexDb, type DefineResourceOptions, applyMigrations, createDb, defineResource };
53
+ export { type ApexDbHandle, type CreateDbConfig, type DefineResourceOptions, type Dialect, applyMigrations, createDb, defineResource };
package/dist/index.js CHANGED
@@ -1,32 +1,77 @@
1
1
  // src/index.ts
2
- import { readdirSync, readFileSync } from "fs";
2
+ import { existsSync, readdirSync, readFileSync } from "fs";
3
3
  import { join } from "path";
4
4
  import { defineApexRoute } from "@apex-stack/core";
5
- import Database from "better-sqlite3";
6
- import { drizzle } from "drizzle-orm/better-sqlite3";
7
5
  import { eq } from "drizzle-orm";
8
6
  import { z } from "zod";
9
- function createDb(path) {
10
- const sqlite = new Database(path);
11
- sqlite.pragma("journal_mode = WAL");
12
- return { db: drizzle(sqlite), sqlite };
7
+ function libsqlUrl(pathOrUrl) {
8
+ return /^(file:|libsql:|https?:|:memory:)/.test(pathOrUrl) ? pathOrUrl : `file:${pathOrUrl}`;
13
9
  }
14
- function applyMigrations(sqlite, dir) {
15
- sqlite.exec(
10
+ async function createDb(config) {
11
+ const cfg = typeof config === "string" ? { driver: "libsql", url: config } : config;
12
+ if (cfg.driver === "sqlite" || cfg.driver === "libsql") {
13
+ const { createClient } = await import("@libsql/client");
14
+ const { drizzle: drizzle2 } = await import("drizzle-orm/libsql");
15
+ const client2 = createClient({ url: libsqlUrl(cfg.url) });
16
+ return {
17
+ db: drizzle2(client2),
18
+ dialect: "sqlite",
19
+ exec: async (sql) => {
20
+ await client2.executeMultiple(sql);
21
+ },
22
+ query: async (sql) => (await client2.execute(sql)).rows,
23
+ close: async () => {
24
+ client2.close();
25
+ }
26
+ };
27
+ }
28
+ if (cfg.driver === "postgres") {
29
+ const postgres = (await import("postgres")).default;
30
+ const { drizzle: drizzle2 } = await import("drizzle-orm/postgres-js");
31
+ const client2 = postgres(cfg.url);
32
+ return {
33
+ db: drizzle2(client2),
34
+ dialect: "postgres",
35
+ exec: async (sql) => {
36
+ await client2.unsafe(sql);
37
+ },
38
+ query: async (sql) => await client2.unsafe(sql),
39
+ close: async () => {
40
+ await client2.end();
41
+ }
42
+ };
43
+ }
44
+ const { PGlite } = await import("@electric-sql/pglite");
45
+ const { drizzle } = await import("drizzle-orm/pglite");
46
+ const client = new PGlite(cfg.dir ?? "memory://");
47
+ return {
48
+ db: drizzle(client),
49
+ dialect: "postgres",
50
+ exec: async (sql) => {
51
+ await client.exec(sql);
52
+ },
53
+ query: async (sql) => (await client.query(sql)).rows,
54
+ close: async () => {
55
+ await client.close();
56
+ }
57
+ };
58
+ }
59
+ async function applyMigrations(handle, dir) {
60
+ if (!existsSync(dir)) return [];
61
+ await handle.exec(
16
62
  "CREATE TABLE IF NOT EXISTS _apex_migrations (name TEXT PRIMARY KEY, applied_at TEXT NOT NULL)"
17
63
  );
18
64
  const applied = new Set(
19
- sqlite.prepare("SELECT name FROM _apex_migrations").all().map((r) => r.name)
65
+ (await handle.query("SELECT name FROM _apex_migrations")).map((r) => r.name)
20
66
  );
21
67
  const done = [];
22
68
  for (const file of readdirSync(dir).filter((f) => f.endsWith(".sql")).sort()) {
23
69
  if (applied.has(file)) continue;
24
- const sql = readFileSync(join(dir, file), "utf8");
70
+ await handle.exec(readFileSync(join(dir, file), "utf8"));
25
71
  const at = (/* @__PURE__ */ new Date()).toISOString();
26
- sqlite.transaction(() => {
27
- sqlite.exec(sql);
28
- sqlite.prepare("INSERT INTO _apex_migrations (name, applied_at) VALUES (?, ?)").run(file, at);
29
- })();
72
+ await handle.exec(
73
+ `INSERT INTO _apex_migrations (name, applied_at) VALUES ('${file.replace(/'/g, "''")}', '${at}')`
74
+ );
30
75
  done.push(file);
31
76
  }
32
77
  return done;
@@ -51,7 +96,7 @@ function defineResource(name, opts) {
51
96
  method: "GET",
52
97
  description: `List all ${name}`,
53
98
  mcp: true,
54
- handler: () => db.select().from(table).all()
99
+ handler: async () => await db.select().from(table)
55
100
  })
56
101
  },
57
102
  {
@@ -62,7 +107,7 @@ function defineResource(name, opts) {
62
107
  description: `Get a single ${name} by id`,
63
108
  input: { id: z.coerce.number() },
64
109
  mcp: true,
65
- handler: ({ input }) => db.select().from(table).where(eq(pkCol, input.id)).get() ?? null
110
+ handler: async ({ input }) => (await db.select().from(table).where(eq(pkCol, input.id)))[0] ?? null
66
111
  })
67
112
  },
68
113
  {
@@ -73,7 +118,7 @@ function defineResource(name, opts) {
73
118
  description: `Create a ${name}`,
74
119
  input: insert,
75
120
  mcp: true,
76
- handler: ({ input }) => db.insert(table).values(input).returning().get()
121
+ handler: async ({ input }) => (await db.insert(table).values(input).returning())[0]
77
122
  })
78
123
  },
79
124
  {
@@ -84,9 +129,9 @@ function defineResource(name, opts) {
84
129
  description: `Update a ${name} by id (partial)`,
85
130
  input: updateShape,
86
131
  mcp: true,
87
- handler: ({ input }) => {
132
+ handler: async ({ input }) => {
88
133
  const { id, ...fields } = input;
89
- return db.update(table).set(fields).where(eq(pkCol, id)).returning().get() ?? null;
134
+ return (await db.update(table).set(fields).where(eq(pkCol, id)).returning())[0] ?? null;
90
135
  }
91
136
  })
92
137
  },
@@ -98,7 +143,7 @@ function defineResource(name, opts) {
98
143
  description: `Delete a ${name} by id`,
99
144
  input: { id: z.coerce.number() },
100
145
  mcp: true,
101
- handler: ({ input }) => db.delete(table).where(eq(pkCol, input.id)).returning().get() ?? null
146
+ handler: async ({ input }) => (await db.delete(table).where(eq(pkCol, input.id)).returning())[0] ?? null
102
147
  })
103
148
  }
104
149
  ]
package/package.json CHANGED
@@ -1,9 +1,28 @@
1
1
  {
2
2
  "name": "@apex-stack/data",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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",
7
+ "author": "Andre Corugda",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/andrecorugda/apexjs.git",
11
+ "directory": "packages/data"
12
+ },
13
+ "homepage": "https://apexjs.site",
14
+ "bugs": {
15
+ "url": "https://github.com/andrecorugda/apexjs/issues"
16
+ },
17
+ "keywords": [
18
+ "alpine",
19
+ "alpinejs",
20
+ "drizzle",
21
+ "orm",
22
+ "mcp",
23
+ "apex",
24
+ "apexjs"
25
+ ],
7
26
  "exports": {
8
27
  ".": {
9
28
  "types": "./dist/index.d.ts",
@@ -15,16 +34,19 @@
15
34
  "dist"
16
35
  ],
17
36
  "dependencies": {
37
+ "@libsql/client": "^0.17.4",
18
38
  "better-sqlite3": "^11.0.0",
19
39
  "drizzle-orm": "^0.38.0",
20
40
  "zod": "^4.4.3",
21
- "@apex-stack/core": "0.1.1"
41
+ "@apex-stack/core": "0.1.6"
22
42
  },
23
43
  "engines": {
24
44
  "node": ">=20.19"
25
45
  },
26
46
  "devDependencies": {
27
- "@types/better-sqlite3": "^7.6.13"
47
+ "@electric-sql/pglite": "^0.5.4",
48
+ "@types/better-sqlite3": "^7.6.13",
49
+ "postgres": "^3.4.9"
28
50
  },
29
51
  "scripts": {
30
52
  "build": "tsup",