@vertebr/admin 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.
Files changed (48) hide show
  1. package/dist/database/configStore.d.ts +10 -0
  2. package/dist/database/configStore.d.ts.map +1 -0
  3. package/dist/database/configStore.js +34 -0
  4. package/dist/database/configStore.js.map +1 -0
  5. package/dist/database/connector.d.ts +31 -0
  6. package/dist/database/connector.d.ts.map +1 -0
  7. package/dist/database/connector.js +187 -0
  8. package/dist/database/connector.js.map +1 -0
  9. package/dist/database/firestore.d.ts +71 -0
  10. package/dist/database/firestore.d.ts.map +1 -0
  11. package/dist/database/firestore.js +115 -0
  12. package/dist/database/firestore.js.map +1 -0
  13. package/dist/database/index.d.ts +8 -0
  14. package/dist/database/index.d.ts.map +1 -0
  15. package/dist/database/index.js +20 -0
  16. package/dist/database/index.js.map +1 -0
  17. package/dist/database/memory.d.ts +12 -0
  18. package/dist/database/memory.d.ts.map +1 -0
  19. package/dist/database/memory.js +116 -0
  20. package/dist/database/memory.js.map +1 -0
  21. package/dist/database/mongo.d.ts +41 -0
  22. package/dist/database/mongo.d.ts.map +1 -0
  23. package/dist/database/mongo.js +151 -0
  24. package/dist/database/mongo.js.map +1 -0
  25. package/dist/database/postgres.d.ts +46 -0
  26. package/dist/database/postgres.d.ts.map +1 -0
  27. package/dist/database/postgres.js +262 -0
  28. package/dist/database/postgres.js.map +1 -0
  29. package/dist/database/types.d.ts +70 -0
  30. package/dist/database/types.d.ts.map +1 -0
  31. package/dist/database/types.js +3 -0
  32. package/dist/database/types.js.map +1 -0
  33. package/dist/index.d.ts +210 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +650 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/schema.d.ts +18 -0
  38. package/dist/schema.d.ts.map +1 -0
  39. package/dist/schema.js +4 -0
  40. package/dist/schema.js.map +1 -0
  41. package/dist/store.d.ts +12 -0
  42. package/dist/store.d.ts.map +1 -0
  43. package/dist/store.js +36 -0
  44. package/dist/store.js.map +1 -0
  45. package/package.json +45 -0
  46. package/static/assets/index-CSsz_d7k.css +1 -0
  47. package/static/assets/index-D5rhtylJ.js +52 -0
  48. package/static/index.html +29 -0
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.memoryAdapter = memoryAdapter;
4
+ function matchesWhere(doc, clause) {
5
+ const value = doc[clause.field];
6
+ switch (clause.op) {
7
+ case "==":
8
+ return value === clause.value;
9
+ case "!=":
10
+ return value !== clause.value;
11
+ case ">":
12
+ return (typeof value === "number" &&
13
+ typeof clause.value === "number" &&
14
+ value > clause.value);
15
+ case "<":
16
+ return (typeof value === "number" &&
17
+ typeof clause.value === "number" &&
18
+ value < clause.value);
19
+ case ">=":
20
+ return (typeof value === "number" &&
21
+ typeof clause.value === "number" &&
22
+ value >= clause.value);
23
+ case "<=":
24
+ return (typeof value === "number" &&
25
+ typeof clause.value === "number" &&
26
+ value <= clause.value);
27
+ case "in":
28
+ return Array.isArray(clause.value) && clause.value.includes(value);
29
+ case "array-contains":
30
+ return Array.isArray(value) && value.includes(clause.value);
31
+ default:
32
+ return false;
33
+ }
34
+ }
35
+ function applyList(docs, options) {
36
+ let rows = [...docs];
37
+ for (const clause of options?.where ?? []) {
38
+ rows = rows.filter((doc) => matchesWhere(doc, clause));
39
+ }
40
+ if (options?.orderBy) {
41
+ const field = options.orderBy.field;
42
+ const dir = options.orderBy.direction === "desc" ? -1 : 1;
43
+ rows.sort((a, b) => {
44
+ const left = a[field];
45
+ const right = b[field];
46
+ if (left == null && right == null)
47
+ return 0;
48
+ if (left == null)
49
+ return -1 * dir;
50
+ if (right == null)
51
+ return 1 * dir;
52
+ if (left < right)
53
+ return -1 * dir;
54
+ if (left > right)
55
+ return 1 * dir;
56
+ return 0;
57
+ });
58
+ }
59
+ if (options?.limit != null) {
60
+ rows = rows.slice(0, options.limit);
61
+ }
62
+ return rows;
63
+ }
64
+ /**
65
+ * In-memory adapter for local demos and tests — no external DB required.
66
+ */
67
+ function memoryAdapter(options = {}) {
68
+ const root = options.store ?? new Map();
69
+ function collection(name) {
70
+ let table = root.get(name);
71
+ if (!table) {
72
+ table = new Map();
73
+ root.set(name, table);
74
+ }
75
+ return table;
76
+ }
77
+ function toDoc(id, value) {
78
+ return { ...value, id };
79
+ }
80
+ async function list(name, listOptions) {
81
+ const docs = [...collection(name).entries()].map(([id, value]) => toDoc(id, value));
82
+ return applyList(docs, listOptions);
83
+ }
84
+ return {
85
+ list,
86
+ async get(name, id) {
87
+ const value = collection(name).get(id);
88
+ return value ? toDoc(id, value) : null;
89
+ },
90
+ async set(name, id, value, setOptions) {
91
+ const table = collection(name);
92
+ const merge = setOptions?.merge ?? true;
93
+ const payload = { ...value };
94
+ delete payload.id;
95
+ const current = table.get(id) ?? {};
96
+ table.set(id, merge ? { ...current, ...payload } : { ...payload });
97
+ },
98
+ async create(name, value, id) {
99
+ const resolvedId = id ??
100
+ (typeof value.id === "string" ? value.id : undefined) ??
101
+ `row_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
102
+ const payload = { ...value };
103
+ delete payload.id;
104
+ collection(name).set(resolvedId, payload);
105
+ return toDoc(resolvedId, payload);
106
+ },
107
+ async remove(name, id) {
108
+ collection(name).delete(id);
109
+ },
110
+ async count(name, listOptions) {
111
+ const docs = await list(name, listOptions);
112
+ return docs.length;
113
+ },
114
+ };
115
+ }
116
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/database/memory.ts"],"names":[],"mappings":";;AAwFA,sCAoEC;AAnJD,SAAS,YAAY,CACnB,GAA4B,EAC5B,MAAqB;IAErB,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAChC,QAAQ,MAAM,CAAC,EAAE,EAAE,CAAC;QAClB,KAAK,IAAI;YACP,OAAO,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC;QAChC,KAAK,IAAI;YACP,OAAO,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC;QAChC,KAAK,GAAG;YACN,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;gBACzB,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;gBAChC,KAAK,GAAG,MAAM,CAAC,KAAK,CACrB,CAAC;QACJ,KAAK,GAAG;YACN,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;gBACzB,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;gBAChC,KAAK,GAAG,MAAM,CAAC,KAAK,CACrB,CAAC;QACJ,KAAK,IAAI;YACP,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;gBACzB,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;gBAChC,KAAK,IAAI,MAAM,CAAC,KAAK,CACtB,CAAC;QACJ,KAAK,IAAI;YACP,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;gBACzB,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;gBAChC,KAAK,IAAI,MAAM,CAAC,KAAK,CACtB,CAAC;QACJ,KAAK,IAAI;YACP,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACrE,KAAK,gBAAgB;YACnB,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9D;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAChB,IAAmB,EACnB,OAA6B;IAE7B,IAAI,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IACrB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,KAAK,IAAI,EAAE,EAAE,CAAC;QAC1C,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC;QACpC,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1D,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACjB,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;YACtB,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;YACvB,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI;gBAAE,OAAO,CAAC,CAAC;YAC5C,IAAI,IAAI,IAAI,IAAI;gBAAE,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC;YAClC,IAAI,KAAK,IAAI,IAAI;gBAAE,OAAO,CAAC,GAAG,GAAG,CAAC;YAClC,IAAI,IAAI,GAAG,KAAK;gBAAE,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC;YAClC,IAAI,IAAI,GAAG,KAAK;gBAAE,OAAO,CAAC,GAAG,GAAG,CAAC;YACjC,OAAO,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;IACL,CAAC;IACD,IAAI,OAAO,EAAE,KAAK,IAAI,IAAI,EAAE,CAAC;QAC3B,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAOD;;GAEG;AACH,SAAgB,aAAa,CAC3B,UAAgC,EAAE;IAElC,MAAM,IAAI,GAAgB,OAAO,CAAC,KAAK,IAAI,IAAI,GAAG,EAAE,CAAC;IAErD,SAAS,UAAU,CAAC,IAAY;QAC9B,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC;YAClB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACxB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,SAAS,KAAK,CACZ,EAAU,EACV,KAA8B;QAE9B,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,CAAC;IAC1B,CAAC;IAED,KAAK,UAAU,IAAI,CACjB,IAAY,EACZ,WAAiC;QAEjC,MAAM,IAAI,GAAG,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAC/D,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CACjB,CAAC;QACF,OAAO,SAAS,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACtC,CAAC;IAED,OAAO;QACL,IAAI;QAEJ,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE;YAChB,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACvC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACzC,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU;YACnC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAC/B,MAAM,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,IAAI,CAAC;YACxC,MAAM,OAAO,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;YAC7B,OAAO,OAAO,CAAC,EAAE,CAAC;YAClB,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;YACpC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;QACrE,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YAC1B,MAAM,UAAU,GACd,EAAE;gBACF,CAAC,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;gBACrD,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAC7E,MAAM,OAAO,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;YAC7B,OAAO,OAAO,CAAC,EAAE,CAAC;YAClB,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAC1C,OAAO,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACpC,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE;YACnB,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC9B,CAAC;QAED,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,WAAW;YAC3B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YAC3C,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { DatabaseAdapter } from "./types";
2
+ /**
3
+ * Minimal MongoDB surface — duck-typed so @vertebr/admin does not require
4
+ * the official driver as a dependency.
5
+ */
6
+ export type MongoCursorLike = {
7
+ sort: (sort: Record<string, 1 | -1>) => MongoCursorLike;
8
+ limit: (n: number) => MongoCursorLike;
9
+ toArray: () => Promise<Array<Record<string, unknown>>>;
10
+ };
11
+ export type MongoCollectionLike = {
12
+ find: (filter?: Record<string, unknown>) => MongoCursorLike;
13
+ findOne: (filter: Record<string, unknown>) => Promise<Record<string, unknown> | null>;
14
+ updateOne: (filter: Record<string, unknown>, update: Record<string, unknown>, options?: {
15
+ upsert?: boolean;
16
+ }) => Promise<unknown>;
17
+ insertOne: (doc: Record<string, unknown>) => Promise<{
18
+ insertedId?: unknown;
19
+ }>;
20
+ deleteOne: (filter: Record<string, unknown>) => Promise<unknown>;
21
+ countDocuments: (filter?: Record<string, unknown>) => Promise<number>;
22
+ };
23
+ export type MongoDbLike = {
24
+ collection: (name: string) => MongoCollectionLike;
25
+ };
26
+ export type MongoAdapterOptions = {
27
+ getDb: () => MongoDbLike | Promise<MongoDbLike>;
28
+ /** Field used as document id. @default "_id" mapped to `id` in rows. */
29
+ idField?: string;
30
+ };
31
+ /**
32
+ * Opt-in MongoDB adapter. The host app supplies `getDb()` — Vertebr never
33
+ * opens a Mongo connection string on its own.
34
+ *
35
+ * @example
36
+ * import { MongoClient } from "mongodb";
37
+ * const client = await MongoClient.connect(process.env.MONGO_URL!);
38
+ * const db = mongoAdapter({ getDb: () => client.db() });
39
+ */
40
+ export declare function mongoAdapter(options: MongoAdapterOptions): DatabaseAdapter;
41
+ //# sourceMappingURL=mongo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongo.d.ts","sourceRoot":"","sources":["../../src/database/mongo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAIhB,MAAM,SAAS,CAAC;AAEjB;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,eAAe,CAAC;IACxD,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,eAAe,CAAC;IACtC,OAAO,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,eAAe,CAAC;IAC5D,OAAO,EAAE,CACP,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IAC7C,SAAS,EAAE,CACT,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAC3B,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB,SAAS,EAAE,CACT,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACzB,OAAO,CAAC;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACvC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,cAAc,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACvE,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,mBAAmB,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAChD,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAiFF;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,eAAe,CAuF1E"}
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mongoAdapter = mongoAdapter;
4
+ function serializeValue(value) {
5
+ if (value == null)
6
+ return value;
7
+ if (value instanceof Date)
8
+ return value.toISOString();
9
+ if (Array.isArray(value))
10
+ return value.map(serializeValue);
11
+ if (typeof value === "object") {
12
+ // ObjectId / Decimal128 etc. — prefer string form when available
13
+ if ("toHexString" in value &&
14
+ typeof value.toHexString === "function") {
15
+ return value.toHexString();
16
+ }
17
+ const out = {};
18
+ for (const [key, entry] of Object.entries(value)) {
19
+ out[key] = serializeValue(entry);
20
+ }
21
+ return out;
22
+ }
23
+ return value;
24
+ }
25
+ function toDoc(raw, idField) {
26
+ const data = serializeValue(raw);
27
+ const id = String(data.id ?? data[idField] ?? "");
28
+ const { [idField]: _mongoId, ...rest } = data;
29
+ return { ...rest, id };
30
+ }
31
+ function mongoOp(op) {
32
+ switch (op) {
33
+ case "==":
34
+ return "$eq";
35
+ case "!=":
36
+ return "$ne";
37
+ case ">":
38
+ return "$gt";
39
+ case "<":
40
+ return "$lt";
41
+ case ">=":
42
+ return "$gte";
43
+ case "<=":
44
+ return "$lte";
45
+ case "in":
46
+ return "$in";
47
+ case "array-contains":
48
+ return "$elemMatch";
49
+ default:
50
+ throw new Error(`Unsupported where op: ${String(op)}`);
51
+ }
52
+ }
53
+ function buildFilter(options, idField) {
54
+ const filter = {};
55
+ for (const clause of options?.where ?? []) {
56
+ const field = clause.field === "id" ? idField : clause.field;
57
+ if (clause.op === "==") {
58
+ filter[field] = clause.value;
59
+ continue;
60
+ }
61
+ if (clause.op === "array-contains") {
62
+ filter[field] = clause.value;
63
+ continue;
64
+ }
65
+ const current = filter[field] ?? {};
66
+ current[mongoOp(clause.op)] = clause.value;
67
+ filter[field] = current;
68
+ }
69
+ return filter;
70
+ }
71
+ /**
72
+ * Opt-in MongoDB adapter. The host app supplies `getDb()` — Vertebr never
73
+ * opens a Mongo connection string on its own.
74
+ *
75
+ * @example
76
+ * import { MongoClient } from "mongodb";
77
+ * const client = await MongoClient.connect(process.env.MONGO_URL!);
78
+ * const db = mongoAdapter({ getDb: () => client.db() });
79
+ */
80
+ function mongoAdapter(options) {
81
+ const idField = options.idField ?? "_id";
82
+ async function db() {
83
+ return options.getDb();
84
+ }
85
+ return {
86
+ async list(collection, listOptions) {
87
+ const mongo = await db();
88
+ const filter = buildFilter(listOptions, idField);
89
+ let cursor = mongo.collection(collection).find(filter);
90
+ if (listOptions?.orderBy) {
91
+ const field = listOptions.orderBy.field === "id"
92
+ ? idField
93
+ : listOptions.orderBy.field;
94
+ const direction = listOptions.orderBy.direction?.toLowerCase() === "desc" ? -1 : 1;
95
+ cursor = cursor.sort({ [field]: direction });
96
+ }
97
+ const rows = listOptions?.limit != null
98
+ ? await cursor.limit(listOptions.limit).toArray()
99
+ : await cursor.toArray();
100
+ return rows.map((row) => toDoc(row, idField));
101
+ },
102
+ async get(collection, id) {
103
+ const mongo = await db();
104
+ const row = await mongo.collection(collection).findOne({ [idField]: id });
105
+ return row ? toDoc(row, idField) : null;
106
+ },
107
+ async set(collection, id, value, setOptions) {
108
+ const mongo = await db();
109
+ const merge = setOptions?.merge ?? true;
110
+ const payload = { ...value };
111
+ delete payload.id;
112
+ delete payload[idField];
113
+ if (merge) {
114
+ await mongo.collection(collection).updateOne({ [idField]: id }, { $set: { ...payload, [idField]: id } }, { upsert: true });
115
+ return;
116
+ }
117
+ // Full replace — drop previous fields by rewriting the document.
118
+ await mongo.collection(collection).deleteOne({ [idField]: id });
119
+ await mongo.collection(collection).insertOne({
120
+ ...payload,
121
+ [idField]: id,
122
+ });
123
+ },
124
+ async create(collection, value, id) {
125
+ const mongo = await db();
126
+ const resolvedId = id ??
127
+ (typeof value.id === "string" ? value.id : undefined) ??
128
+ (typeof crypto !== "undefined" && "randomUUID" in crypto
129
+ ? crypto.randomUUID()
130
+ : `row_${Date.now().toString(36)}`);
131
+ const payload = { ...value };
132
+ delete payload.id;
133
+ await mongo.collection(collection).insertOne({
134
+ ...payload,
135
+ [idField]: resolvedId,
136
+ });
137
+ return { id: resolvedId, ...payload };
138
+ },
139
+ async remove(collection, id) {
140
+ const mongo = await db();
141
+ await mongo.collection(collection).deleteOne({ [idField]: id });
142
+ },
143
+ async count(collection, listOptions) {
144
+ const mongo = await db();
145
+ return mongo
146
+ .collection(collection)
147
+ .countDocuments(buildFilter(listOptions, idField));
148
+ },
149
+ };
150
+ }
151
+ //# sourceMappingURL=mongo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongo.js","sourceRoot":"","sources":["../../src/database/mongo.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { DatabaseAdapter } from "./types";
2
+ /**
3
+ * Minimal Postgres client surface (`pg` Pool / Client, postgres.js, etc.).
4
+ * Duck-typed so @vertebr/admin does not require `pg` as a dependency.
5
+ */
6
+ export type PostgresQueryable = {
7
+ query: (text: string, values?: unknown[]) => Promise<{
8
+ rows: Array<Record<string, unknown>>;
9
+ }>;
10
+ };
11
+ export type PostgresAdapterMode = "flat" | "jsonb";
12
+ export type PostgresAdapterOptions = {
13
+ /**
14
+ * Return a queryable client/pool for the current call.
15
+ * Use ALS / env switching here so Vertebr stays DB-agnostic.
16
+ */
17
+ getClient: () => PostgresQueryable | Promise<PostgresQueryable>;
18
+ /**
19
+ * `flat` — row columns are document fields (default).
20
+ * `jsonb` — table has `id` + JSON `data` column (document store).
21
+ */
22
+ mode?: PostgresAdapterMode;
23
+ /** Primary key column. @default "id" */
24
+ idColumn?: string;
25
+ /** JSONB payload column when mode is `jsonb`. @default "data" */
26
+ dataColumn?: string;
27
+ /** Optional schema prefix, e.g. `public` → `"public"."products"`. */
28
+ schema?: string;
29
+ };
30
+ /**
31
+ * Opt-in Postgres adapter. The host app supplies `getClient()` — Vertebr
32
+ * never opens a database URL on its own.
33
+ *
34
+ * @example
35
+ * import pg from "pg";
36
+ * const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
37
+ * const db = postgresAdapter({ getClient: () => pool, mode: "jsonb" });
38
+ */
39
+ export declare function postgresAdapter(options: PostgresAdapterOptions): DatabaseAdapter;
40
+ /** Suggested DDL for jsonb-mode tables used with `postgresAdapter`. */
41
+ export declare function postgresJsonbTableDDL(table: string, options?: {
42
+ schema?: string;
43
+ idColumn?: string;
44
+ dataColumn?: string;
45
+ }): string;
46
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/database/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAIhB,MAAM,SAAS,CAAC;AAEjB;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,CACL,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,KACf,OAAO,CAAC;QAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;KAAE,CAAC,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEnD,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;OAGG;IACH,SAAS,EAAE,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAChE;;;OAGG;IACH,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AA8KF;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CA2JjB;AAED,uEAAuE;AACvE,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACpE,MAAM,CASR"}
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.postgresAdapter = postgresAdapter;
4
+ exports.postgresJsonbTableDDL = postgresJsonbTableDDL;
5
+ function assertIdent(name, label) {
6
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
7
+ throw new Error(`Invalid SQL ${label}: "${name}"`);
8
+ }
9
+ return name;
10
+ }
11
+ function quoteIdent(name) {
12
+ return `"${assertIdent(name, "identifier")}"`;
13
+ }
14
+ function quoteLiteral(value) {
15
+ return `'${value.replace(/'/g, "''")}'`;
16
+ }
17
+ function tableRef(collection, schema) {
18
+ const table = quoteIdent(collection);
19
+ if (!schema)
20
+ return table;
21
+ return `${quoteIdent(schema)}.${table}`;
22
+ }
23
+ function serializeValue(value) {
24
+ if (value == null)
25
+ return value;
26
+ if (value instanceof Date)
27
+ return value.toISOString();
28
+ if (Array.isArray(value))
29
+ return value.map(serializeValue);
30
+ if (typeof value === "object") {
31
+ const out = {};
32
+ for (const [key, entry] of Object.entries(value)) {
33
+ out[key] = serializeValue(entry);
34
+ }
35
+ return out;
36
+ }
37
+ return value;
38
+ }
39
+ function rowToDoc(row, idColumn, mode, dataColumn) {
40
+ if (mode === "jsonb") {
41
+ const raw = row[dataColumn];
42
+ const data = raw && typeof raw === "object" && !Array.isArray(raw)
43
+ ? serializeValue(raw)
44
+ : {};
45
+ const id = String(row[idColumn] ?? data.id ?? "");
46
+ return { id, ...data, [idColumn]: id };
47
+ }
48
+ const data = serializeValue(row);
49
+ const id = String(data[idColumn] ?? "");
50
+ return { ...data, id };
51
+ }
52
+ function sqlOp(op) {
53
+ switch (op) {
54
+ case "==":
55
+ return "=";
56
+ case "!=":
57
+ return "<>";
58
+ case ">":
59
+ case "<":
60
+ case ">=":
61
+ case "<=":
62
+ return op;
63
+ case "in":
64
+ return "IN";
65
+ case "array-contains":
66
+ return "@>";
67
+ default:
68
+ throw new Error(`Unsupported where op: ${String(op)}`);
69
+ }
70
+ }
71
+ function fieldExpr(field, mode, dataColumn) {
72
+ const safe = assertIdent(field, "field");
73
+ if (mode === "jsonb") {
74
+ return `${quoteIdent(dataColumn)}->>${quoteLiteral(safe)}`;
75
+ }
76
+ return quoteIdent(safe);
77
+ }
78
+ function buildWhere(options, mode, dataColumn) {
79
+ const values = [];
80
+ const parts = [];
81
+ let index = 1;
82
+ for (const clause of options?.where ?? []) {
83
+ const column = fieldExpr(clause.field, mode, dataColumn);
84
+ const op = sqlOp(clause.op);
85
+ if (clause.op === "in") {
86
+ if (!Array.isArray(clause.value)) {
87
+ throw new Error(`Where "in" requires an array for ${clause.field}`);
88
+ }
89
+ if (clause.value.length === 0) {
90
+ parts.push("FALSE");
91
+ continue;
92
+ }
93
+ const slots = clause.value.map((entry) => {
94
+ values.push(entry);
95
+ return `$${index++}`;
96
+ });
97
+ parts.push(`${column} IN (${slots.join(", ")})`);
98
+ continue;
99
+ }
100
+ if (clause.op === "array-contains") {
101
+ if (mode !== "jsonb") {
102
+ throw new Error(`array-contains is only supported in jsonb mode (field: ${clause.field})`);
103
+ }
104
+ const safe = assertIdent(clause.field, "field");
105
+ values.push(JSON.stringify([clause.value]));
106
+ parts.push(`${quoteIdent(dataColumn)}->${quoteLiteral(safe)} @> $${index++}::jsonb`);
107
+ continue;
108
+ }
109
+ values.push(clause.value);
110
+ parts.push(`${column} ${op} $${index++}`);
111
+ }
112
+ return {
113
+ sql: parts.length ? ` WHERE ${parts.join(" AND ")}` : "",
114
+ values,
115
+ };
116
+ }
117
+ function buildOrderLimit(options, mode, dataColumn) {
118
+ let sql = "";
119
+ if (options?.orderBy) {
120
+ const column = fieldExpr(options.orderBy.field, mode, dataColumn);
121
+ const direction = options.orderBy.direction?.toLowerCase() === "desc" ? "DESC" : "ASC";
122
+ sql += ` ORDER BY ${column} ${direction}`;
123
+ }
124
+ if (options?.limit != null) {
125
+ const limit = Number(options.limit);
126
+ if (!Number.isFinite(limit) || limit < 0) {
127
+ throw new Error(`Invalid limit: ${options.limit}`);
128
+ }
129
+ sql += ` LIMIT ${Math.floor(limit)}`;
130
+ }
131
+ return sql;
132
+ }
133
+ function cryptoRandomId() {
134
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
135
+ return crypto.randomUUID();
136
+ }
137
+ return `row_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
138
+ }
139
+ /**
140
+ * Opt-in Postgres adapter. The host app supplies `getClient()` — Vertebr
141
+ * never opens a database URL on its own.
142
+ *
143
+ * @example
144
+ * import pg from "pg";
145
+ * const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
146
+ * const db = postgresAdapter({ getClient: () => pool, mode: "jsonb" });
147
+ */
148
+ function postgresAdapter(options) {
149
+ const mode = options.mode ?? "flat";
150
+ const idColumn = options.idColumn ?? "id";
151
+ const dataColumn = options.dataColumn ?? "data";
152
+ assertIdent(idColumn, "idColumn");
153
+ assertIdent(dataColumn, "dataColumn");
154
+ async function client() {
155
+ return options.getClient();
156
+ }
157
+ function ref(collection) {
158
+ return tableRef(collection, options.schema);
159
+ }
160
+ async function set(collection, id, value, setOptions) {
161
+ const db = await client();
162
+ const merge = setOptions?.merge ?? true;
163
+ const payload = { ...value };
164
+ delete payload.id;
165
+ if (idColumn !== "id")
166
+ delete payload[idColumn];
167
+ if (mode === "jsonb") {
168
+ const json = JSON.stringify(serializeValue(payload));
169
+ if (merge) {
170
+ await db.query(`INSERT INTO ${ref(collection)} (${quoteIdent(idColumn)}, ${quoteIdent(dataColumn)})
171
+ VALUES ($1, $2::jsonb)
172
+ ON CONFLICT (${quoteIdent(idColumn)}) DO UPDATE SET
173
+ ${quoteIdent(dataColumn)} = COALESCE(${ref(collection)}.${quoteIdent(dataColumn)}, '{}'::jsonb) || EXCLUDED.${quoteIdent(dataColumn)}`, [id, json]);
174
+ }
175
+ else {
176
+ await db.query(`INSERT INTO ${ref(collection)} (${quoteIdent(idColumn)}, ${quoteIdent(dataColumn)})
177
+ VALUES ($1, $2::jsonb)
178
+ ON CONFLICT (${quoteIdent(idColumn)}) DO UPDATE SET
179
+ ${quoteIdent(dataColumn)} = EXCLUDED.${quoteIdent(dataColumn)}`, [id, json]);
180
+ }
181
+ return;
182
+ }
183
+ const existing = await db.query(`SELECT * FROM ${ref(collection)} WHERE ${quoteIdent(idColumn)} = $1 LIMIT 1`, [id]);
184
+ const current = (existing.rows[0] ?? {});
185
+ const serialized = serializeValue(payload);
186
+ const serializedObj = serialized && typeof serialized === "object" && !Array.isArray(serialized)
187
+ ? serialized
188
+ : {};
189
+ const next = merge
190
+ ? { ...current, ...serializedObj, [idColumn]: id }
191
+ : { ...serializedObj, [idColumn]: id };
192
+ // Keep the primary key; only drop a separate `id` field when PK is renamed.
193
+ if (idColumn !== "id") {
194
+ delete next.id;
195
+ }
196
+ const columns = Object.keys(next);
197
+ if (columns.length === 0) {
198
+ await db.query(`INSERT INTO ${ref(collection)} (${quoteIdent(idColumn)}) VALUES ($1)
199
+ ON CONFLICT (${quoteIdent(idColumn)}) DO NOTHING`, [id]);
200
+ return;
201
+ }
202
+ const insertCols = columns.map(quoteIdent).join(", ");
203
+ const insertVals = columns.map((_, i) => `$${i + 1}`).join(", ");
204
+ const updateCols = columns.filter((col) => col !== idColumn);
205
+ const updates = updateCols.length > 0
206
+ ? updateCols
207
+ .map((col) => `${quoteIdent(col)} = EXCLUDED.${quoteIdent(col)}`)
208
+ .join(", ")
209
+ : `${quoteIdent(idColumn)} = EXCLUDED.${quoteIdent(idColumn)}`;
210
+ const values = columns.map((col) => next[col]);
211
+ await db.query(`INSERT INTO ${ref(collection)} (${insertCols})
212
+ VALUES (${insertVals})
213
+ ON CONFLICT (${quoteIdent(idColumn)}) DO UPDATE SET ${updates}`, values);
214
+ }
215
+ async function get(collection, id) {
216
+ const db = await client();
217
+ const result = await db.query(`SELECT * FROM ${ref(collection)} WHERE ${quoteIdent(idColumn)} = $1 LIMIT 1`, [id]);
218
+ const row = result.rows[0];
219
+ return row ? rowToDoc(row, idColumn, mode, dataColumn) : null;
220
+ }
221
+ return {
222
+ async list(collection, listOptions) {
223
+ const db = await client();
224
+ const where = buildWhere(listOptions, mode, dataColumn);
225
+ const orderLimit = buildOrderLimit(listOptions, mode, dataColumn);
226
+ const result = await db.query(`SELECT * FROM ${ref(collection)}${where.sql}${orderLimit}`, where.values);
227
+ return result.rows.map((row) => rowToDoc(row, idColumn, mode, dataColumn));
228
+ },
229
+ get,
230
+ set,
231
+ async create(collection, value, id) {
232
+ const resolvedId = id ??
233
+ (typeof value.id === "string" ? value.id : undefined) ??
234
+ cryptoRandomId();
235
+ await set(collection, resolvedId, value, { merge: true });
236
+ const doc = await get(collection, resolvedId);
237
+ return doc ?? { id: resolvedId, ...value };
238
+ },
239
+ async remove(collection, id) {
240
+ const db = await client();
241
+ await db.query(`DELETE FROM ${ref(collection)} WHERE ${quoteIdent(idColumn)} = $1`, [id]);
242
+ },
243
+ async count(collection, listOptions) {
244
+ const db = await client();
245
+ const where = buildWhere(listOptions, mode, dataColumn);
246
+ const result = await db.query(`SELECT COUNT(*)::int AS count FROM ${ref(collection)}${where.sql}`, where.values);
247
+ return Number(result.rows[0]?.count ?? 0);
248
+ },
249
+ };
250
+ }
251
+ /** Suggested DDL for jsonb-mode tables used with `postgresAdapter`. */
252
+ function postgresJsonbTableDDL(table, options) {
253
+ const idColumn = options?.idColumn ?? "id";
254
+ const dataColumn = options?.dataColumn ?? "data";
255
+ const name = tableRef(table, options?.schema);
256
+ return `CREATE TABLE IF NOT EXISTS ${name} (
257
+ ${quoteIdent(idColumn)} TEXT PRIMARY KEY,
258
+ ${quoteIdent(dataColumn)} JSONB NOT NULL DEFAULT '{}'::jsonb,
259
+ updated_at TIMESTAMPTZ DEFAULT NOW()
260
+ );`;
261
+ }
262
+ //# sourceMappingURL=postgres.js.map