@saas-maker/foundry-db 0.1.0

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,61 @@
1
+ import * as drizzle_orm from 'drizzle-orm';
2
+ import * as drizzle_orm_sqlite_core from 'drizzle-orm/sqlite-core';
3
+
4
+ type LibSQLClient = {
5
+ execute: (query: string | {
6
+ sql: string;
7
+ args?: unknown[];
8
+ }) => Promise<unknown>;
9
+ batch?: (queries: unknown[]) => Promise<unknown[]>;
10
+ };
11
+ interface FoundryDbConfig {
12
+ project?: string;
13
+ d1?: D1Database;
14
+ url?: string;
15
+ authToken?: string;
16
+ }
17
+ /**
18
+ * Get a DB client. Auto-detects D1 vs Turso based on what's provided.
19
+ *
20
+ * In Cloudflare Workers: pass env.DB as `d1`
21
+ * In Node.js/Vite: reads DATABASE_URL + TURSO_AUTH_TOKEN from process.env
22
+ *
23
+ * Usage:
24
+ * // CF Worker
25
+ * const db = getDbClient({ d1: env.DB, project: 'linkchat' });
26
+ *
27
+ * // Node/Vite / Next.js
28
+ * const db = getDbClient({ project: 'significanthobbies' });
29
+ * // requires DATABASE_URL (and optionally TURSO_AUTH_TOKEN) in env
30
+ */
31
+ declare function getDbClient(config?: FoundryDbConfig): LibSQLClient;
32
+ /** Reset the cached libSQL client (useful in tests). */
33
+ declare function resetDbClient(): void;
34
+
35
+ /**
36
+ * Standard columns every Foundry table should have.
37
+ * Usage: const myTable = sqliteTable('my_table', { ...foundryColumns(), ...myColumns })
38
+ */
39
+ declare function foundryColumns(): {
40
+ id: drizzle_orm.HasRuntimeDefault<drizzle_orm.HasDefault<drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"id", [string, ...string[]], number | undefined>>>>>;
41
+ createdAt: drizzle_orm.HasDefault<drizzle_orm.NotNull<drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"created_at", [string, ...string[]], number | undefined>>>;
42
+ updatedAt: drizzle_orm.HasDefault<drizzle_orm.NotNull<drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"updated_at", [string, ...string[]], number | undefined>>>;
43
+ deletedAt: drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"deleted_at", [string, ...string[]], number | undefined>;
44
+ version: drizzle_orm.HasDefault<drizzle_orm.NotNull<drizzle_orm_sqlite_core.SQLiteIntegerBuilderInitial<"version">>>;
45
+ };
46
+ /**
47
+ * Soft-delete helper — sets deletedAt instead of removing the row.
48
+ */
49
+ declare function softDelete(): {
50
+ deletedAt: drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"deleted_at", [string, ...string[]], number | undefined>;
51
+ };
52
+
53
+ /**
54
+ * Convenience wrapper for manually tracing DB operations.
55
+ * Use when getDbClient() auto-tracing isn't sufficient.
56
+ *
57
+ * const results = await dbTrace('users:findByEmail', () => db.select(...), 'linkchat');
58
+ */
59
+ declare function dbTrace<T>(operation: string, fn: () => Promise<T>, project?: string): Promise<T>;
60
+
61
+ export { type FoundryDbConfig, type LibSQLClient, dbTrace, foundryColumns, getDbClient, resetDbClient, softDelete };
@@ -0,0 +1,61 @@
1
+ import * as drizzle_orm from 'drizzle-orm';
2
+ import * as drizzle_orm_sqlite_core from 'drizzle-orm/sqlite-core';
3
+
4
+ type LibSQLClient = {
5
+ execute: (query: string | {
6
+ sql: string;
7
+ args?: unknown[];
8
+ }) => Promise<unknown>;
9
+ batch?: (queries: unknown[]) => Promise<unknown[]>;
10
+ };
11
+ interface FoundryDbConfig {
12
+ project?: string;
13
+ d1?: D1Database;
14
+ url?: string;
15
+ authToken?: string;
16
+ }
17
+ /**
18
+ * Get a DB client. Auto-detects D1 vs Turso based on what's provided.
19
+ *
20
+ * In Cloudflare Workers: pass env.DB as `d1`
21
+ * In Node.js/Vite: reads DATABASE_URL + TURSO_AUTH_TOKEN from process.env
22
+ *
23
+ * Usage:
24
+ * // CF Worker
25
+ * const db = getDbClient({ d1: env.DB, project: 'linkchat' });
26
+ *
27
+ * // Node/Vite / Next.js
28
+ * const db = getDbClient({ project: 'significanthobbies' });
29
+ * // requires DATABASE_URL (and optionally TURSO_AUTH_TOKEN) in env
30
+ */
31
+ declare function getDbClient(config?: FoundryDbConfig): LibSQLClient;
32
+ /** Reset the cached libSQL client (useful in tests). */
33
+ declare function resetDbClient(): void;
34
+
35
+ /**
36
+ * Standard columns every Foundry table should have.
37
+ * Usage: const myTable = sqliteTable('my_table', { ...foundryColumns(), ...myColumns })
38
+ */
39
+ declare function foundryColumns(): {
40
+ id: drizzle_orm.HasRuntimeDefault<drizzle_orm.HasDefault<drizzle_orm.IsPrimaryKey<drizzle_orm.NotNull<drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"id", [string, ...string[]], number | undefined>>>>>;
41
+ createdAt: drizzle_orm.HasDefault<drizzle_orm.NotNull<drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"created_at", [string, ...string[]], number | undefined>>>;
42
+ updatedAt: drizzle_orm.HasDefault<drizzle_orm.NotNull<drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"updated_at", [string, ...string[]], number | undefined>>>;
43
+ deletedAt: drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"deleted_at", [string, ...string[]], number | undefined>;
44
+ version: drizzle_orm.HasDefault<drizzle_orm.NotNull<drizzle_orm_sqlite_core.SQLiteIntegerBuilderInitial<"version">>>;
45
+ };
46
+ /**
47
+ * Soft-delete helper — sets deletedAt instead of removing the row.
48
+ */
49
+ declare function softDelete(): {
50
+ deletedAt: drizzle_orm_sqlite_core.SQLiteTextBuilderInitial<"deleted_at", [string, ...string[]], number | undefined>;
51
+ };
52
+
53
+ /**
54
+ * Convenience wrapper for manually tracing DB operations.
55
+ * Use when getDbClient() auto-tracing isn't sufficient.
56
+ *
57
+ * const results = await dbTrace('users:findByEmail', () => db.select(...), 'linkchat');
58
+ */
59
+ declare function dbTrace<T>(operation: string, fn: () => Promise<T>, project?: string): Promise<T>;
60
+
61
+ export { type FoundryDbConfig, type LibSQLClient, dbTrace, foundryColumns, getDbClient, resetDbClient, softDelete };
package/dist/index.js ADDED
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ dbTrace: () => dbTrace,
24
+ foundryColumns: () => foundryColumns,
25
+ getDbClient: () => getDbClient,
26
+ resetDbClient: () => resetDbClient,
27
+ softDelete: () => softDelete
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/client.ts
32
+ var import_ops = require("@saas-maker/ops");
33
+ var _cachedClient = null;
34
+ function getDbClient(config = {}) {
35
+ if (config.d1) {
36
+ return wrapWithTrace(adaptD1(config.d1), config.project);
37
+ }
38
+ if (_cachedClient) return _cachedClient;
39
+ const url = config.url ?? (typeof process !== "undefined" ? process.env["DATABASE_URL"] : void 0);
40
+ const authToken = config.authToken ?? (typeof process !== "undefined" ? process.env["TURSO_AUTH_TOKEN"] : void 0);
41
+ if (!url) {
42
+ throw import_ops.FoundryErrors.db(
43
+ "No database URL provided. Set DATABASE_URL or pass d1 binding."
44
+ );
45
+ }
46
+ const client = createLibSQLClient(url, authToken);
47
+ _cachedClient = wrapWithTrace(client, config.project);
48
+ return _cachedClient;
49
+ }
50
+ function resetDbClient() {
51
+ _cachedClient = null;
52
+ }
53
+ function adaptD1(d1) {
54
+ return {
55
+ execute: async (query) => {
56
+ const sql2 = typeof query === "string" ? query : query.sql;
57
+ const args = typeof query === "string" ? [] : query.args ?? [];
58
+ const stmt = d1.prepare(sql2);
59
+ return args.length > 0 ? stmt.bind(...args).all() : stmt.all();
60
+ }
61
+ };
62
+ }
63
+ function createLibSQLClient(url, authToken) {
64
+ try {
65
+ const { createClient } = require("@libsql/client");
66
+ return createClient({ url, authToken });
67
+ } catch {
68
+ throw import_ops.FoundryErrors.db(
69
+ "@libsql/client not installed. Run: pnpm add @libsql/client"
70
+ );
71
+ }
72
+ }
73
+ function wrapWithTrace(client, project) {
74
+ return {
75
+ execute: async (query) => {
76
+ const sql2 = typeof query === "string" ? query : query.sql;
77
+ const opName = `db:${extractTableName(sql2)}`;
78
+ return (0, import_ops.trace)(opName, () => client.execute(query), { project });
79
+ },
80
+ batch: client.batch ? async (queries) => (0, import_ops.trace)("db:batch", () => client.batch(queries), { project }) : void 0
81
+ };
82
+ }
83
+ function extractTableName(sql2) {
84
+ const match = sql2.match(/(?:FROM|INTO|UPDATE|DELETE\s+FROM)\s+["'`]?(\w+)/i);
85
+ return match?.[1] ?? "query";
86
+ }
87
+
88
+ // src/columns.ts
89
+ var import_drizzle_orm = require("drizzle-orm");
90
+ var import_sqlite_core = require("drizzle-orm/sqlite-core");
91
+ function foundryColumns() {
92
+ return {
93
+ id: (0, import_sqlite_core.text)("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
94
+ createdAt: (0, import_sqlite_core.text)("created_at").notNull().default(import_drizzle_orm.sql`(datetime('now'))`),
95
+ updatedAt: (0, import_sqlite_core.text)("updated_at").notNull().default(import_drizzle_orm.sql`(datetime('now'))`),
96
+ deletedAt: (0, import_sqlite_core.text)("deleted_at"),
97
+ version: (0, import_sqlite_core.integer)("version").notNull().default(1)
98
+ // optimistic locking
99
+ };
100
+ }
101
+ function softDelete() {
102
+ return { deletedAt: (0, import_sqlite_core.text)("deleted_at") };
103
+ }
104
+
105
+ // src/trace.ts
106
+ var import_ops2 = require("@saas-maker/ops");
107
+ function dbTrace(operation, fn, project) {
108
+ return (0, import_ops2.trace)(`db:${operation}`, fn, { project });
109
+ }
110
+ // Annotate the CommonJS export names for ESM import in node:
111
+ 0 && (module.exports = {
112
+ dbTrace,
113
+ foundryColumns,
114
+ getDbClient,
115
+ resetDbClient,
116
+ softDelete
117
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,93 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/client.ts
9
+ import { FoundryErrors, trace } from "@saas-maker/ops";
10
+ var _cachedClient = null;
11
+ function getDbClient(config = {}) {
12
+ if (config.d1) {
13
+ return wrapWithTrace(adaptD1(config.d1), config.project);
14
+ }
15
+ if (_cachedClient) return _cachedClient;
16
+ const url = config.url ?? (typeof process !== "undefined" ? process.env["DATABASE_URL"] : void 0);
17
+ const authToken = config.authToken ?? (typeof process !== "undefined" ? process.env["TURSO_AUTH_TOKEN"] : void 0);
18
+ if (!url) {
19
+ throw FoundryErrors.db(
20
+ "No database URL provided. Set DATABASE_URL or pass d1 binding."
21
+ );
22
+ }
23
+ const client = createLibSQLClient(url, authToken);
24
+ _cachedClient = wrapWithTrace(client, config.project);
25
+ return _cachedClient;
26
+ }
27
+ function resetDbClient() {
28
+ _cachedClient = null;
29
+ }
30
+ function adaptD1(d1) {
31
+ return {
32
+ execute: async (query) => {
33
+ const sql2 = typeof query === "string" ? query : query.sql;
34
+ const args = typeof query === "string" ? [] : query.args ?? [];
35
+ const stmt = d1.prepare(sql2);
36
+ return args.length > 0 ? stmt.bind(...args).all() : stmt.all();
37
+ }
38
+ };
39
+ }
40
+ function createLibSQLClient(url, authToken) {
41
+ try {
42
+ const { createClient } = __require("@libsql/client");
43
+ return createClient({ url, authToken });
44
+ } catch {
45
+ throw FoundryErrors.db(
46
+ "@libsql/client not installed. Run: pnpm add @libsql/client"
47
+ );
48
+ }
49
+ }
50
+ function wrapWithTrace(client, project) {
51
+ return {
52
+ execute: async (query) => {
53
+ const sql2 = typeof query === "string" ? query : query.sql;
54
+ const opName = `db:${extractTableName(sql2)}`;
55
+ return trace(opName, () => client.execute(query), { project });
56
+ },
57
+ batch: client.batch ? async (queries) => trace("db:batch", () => client.batch(queries), { project }) : void 0
58
+ };
59
+ }
60
+ function extractTableName(sql2) {
61
+ const match = sql2.match(/(?:FROM|INTO|UPDATE|DELETE\s+FROM)\s+["'`]?(\w+)/i);
62
+ return match?.[1] ?? "query";
63
+ }
64
+
65
+ // src/columns.ts
66
+ import { sql } from "drizzle-orm";
67
+ import { text, integer } from "drizzle-orm/sqlite-core";
68
+ function foundryColumns() {
69
+ return {
70
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
71
+ createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
72
+ updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
73
+ deletedAt: text("deleted_at"),
74
+ version: integer("version").notNull().default(1)
75
+ // optimistic locking
76
+ };
77
+ }
78
+ function softDelete() {
79
+ return { deletedAt: text("deleted_at") };
80
+ }
81
+
82
+ // src/trace.ts
83
+ import { trace as trace2 } from "@saas-maker/ops";
84
+ function dbTrace(operation, fn, project) {
85
+ return trace2(`db:${operation}`, fn, { project });
86
+ }
87
+ export {
88
+ dbTrace,
89
+ foundryColumns,
90
+ getDbClient,
91
+ resetDbClient,
92
+ softDelete
93
+ };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@saas-maker/foundry-db",
3
+ "version": "0.1.0",
4
+ "description": "Foundry DB layer — environment-aware D1/Turso factory with golden columns and auto-tracing",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.js" }
10
+ },
11
+ "files": ["dist"],
12
+ "scripts": {
13
+ "build": "tsup src/index.ts --format esm,cjs --dts",
14
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch"
15
+ },
16
+ "publishConfig": { "access": "public" },
17
+ "dependencies": {
18
+ "@saas-maker/ops": "^0.1.0"
19
+ },
20
+ "peerDependencies": {
21
+ "drizzle-orm": ">=0.30.0",
22
+ "@libsql/client": ">=0.6.0"
23
+ },
24
+ "peerDependenciesMeta": {
25
+ "@libsql/client": { "optional": true }
26
+ },
27
+ "devDependencies": {
28
+ "tsup": "^8.0.0",
29
+ "typescript": "^5.9.0",
30
+ "drizzle-orm": "^0.45.0",
31
+ "@libsql/client": "^0.14.0",
32
+ "@types/node": "^22.0.0",
33
+ "@cloudflare/workers-types": "^4.0.0"
34
+ }
35
+ }