@apex-stack/data 0.1.1 → 0.1.2
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 +35 -18
- package/dist/index.js +66 -21
- package/package.json +6 -3
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
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
db:
|
|
10
|
-
|
|
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
|
|
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(
|
|
36
|
+
declare function applyMigrations(handle: ApexDbHandle, dir: string): Promise<string[]>;
|
|
18
37
|
interface DefineResourceOptions {
|
|
19
|
-
db:
|
|
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
|
|
29
|
-
* each also an MCP tool.
|
|
30
|
-
*
|
|
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
|
|
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
|
|
10
|
-
|
|
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
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
+
await handle.exec(readFileSync(join(dir, file), "utf8"));
|
|
25
71
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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)
|
|
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))
|
|
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()
|
|
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()
|
|
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()
|
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apex-stack/data",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|
|
@@ -15,16 +15,19 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
+
"@libsql/client": "^0.17.4",
|
|
18
19
|
"better-sqlite3": "^11.0.0",
|
|
19
20
|
"drizzle-orm": "^0.38.0",
|
|
20
21
|
"zod": "^4.4.3",
|
|
21
|
-
"@apex-stack/core": "0.1.
|
|
22
|
+
"@apex-stack/core": "0.1.3"
|
|
22
23
|
},
|
|
23
24
|
"engines": {
|
|
24
25
|
"node": ">=20.19"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
|
-
"@
|
|
28
|
+
"@electric-sql/pglite": "^0.5.4",
|
|
29
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
30
|
+
"postgres": "^3.4.9"
|
|
28
31
|
},
|
|
29
32
|
"scripts": {
|
|
30
33
|
"build": "tsup",
|