@vobase/core 0.10.0 → 0.12.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 (188) hide show
  1. package/package.json +7 -9
  2. package/src/__tests__/drizzle-introspection.test.ts +77 -0
  3. package/src/__tests__/e2e.test.ts +225 -0
  4. package/src/__tests__/permissions.test.ts +157 -0
  5. package/src/__tests__/rpc-types.test.ts +92 -0
  6. package/src/app.test.ts +99 -0
  7. package/src/app.ts +178 -0
  8. package/src/audit.test.ts +126 -0
  9. package/src/auth.test.ts +74 -0
  10. package/src/contracts/auth.ts +37 -0
  11. package/{dist/contracts/module.d.ts → src/contracts/module.ts} +6 -6
  12. package/src/contracts/notify.ts +47 -0
  13. package/src/contracts/permissions.ts +10 -0
  14. package/src/contracts/storage.ts +61 -0
  15. package/src/ctx.test.ts +162 -0
  16. package/src/ctx.ts +64 -0
  17. package/src/db/client.test.ts +75 -0
  18. package/src/db/client.ts +15 -0
  19. package/src/db/helpers.test.ts +147 -0
  20. package/src/db/helpers.ts +51 -0
  21. package/src/db/index.ts +8 -0
  22. package/{dist/index.d.ts → src/index.ts} +103 -6
  23. package/src/infra/circuit-breaker.test.ts +74 -0
  24. package/src/infra/circuit-breaker.ts +57 -0
  25. package/src/infra/errors.test.ts +175 -0
  26. package/src/infra/errors.ts +64 -0
  27. package/src/infra/http-client.test.ts +482 -0
  28. package/src/infra/http-client.ts +221 -0
  29. package/src/infra/index.ts +35 -0
  30. package/src/infra/job.test.ts +85 -0
  31. package/src/infra/job.ts +94 -0
  32. package/src/infra/logger.test.ts +65 -0
  33. package/src/infra/logger.ts +18 -0
  34. package/src/infra/queue.test.ts +46 -0
  35. package/src/infra/queue.ts +147 -0
  36. package/src/infra/throw-proxy.test.ts +34 -0
  37. package/src/infra/throw-proxy.ts +17 -0
  38. package/src/infra/webhooks-schema.ts +17 -0
  39. package/src/infra/webhooks.test.ts +364 -0
  40. package/src/infra/webhooks.ts +146 -0
  41. package/src/mcp/auth.test.ts +129 -0
  42. package/src/mcp/crud.test.ts +128 -0
  43. package/src/mcp/crud.ts +171 -0
  44. package/{dist/mcp/index.d.ts → src/mcp/index.ts} +0 -1
  45. package/src/mcp/server.test.ts +153 -0
  46. package/src/mcp/server.ts +178 -0
  47. package/src/middleware/audit.test.ts +169 -0
  48. package/src/module-registry.ts +18 -0
  49. package/src/module.test.ts +168 -0
  50. package/src/module.ts +111 -0
  51. package/src/modules/audit/index.ts +18 -0
  52. package/src/modules/audit/middleware.ts +33 -0
  53. package/src/modules/audit/schema.ts +35 -0
  54. package/src/modules/audit/track-changes.ts +70 -0
  55. package/src/modules/auth/audit-hooks.ts +74 -0
  56. package/src/modules/auth/index.ts +101 -0
  57. package/src/modules/auth/middleware.ts +51 -0
  58. package/src/modules/auth/permissions.ts +46 -0
  59. package/src/modules/auth/schema.ts +184 -0
  60. package/src/modules/credentials/encrypt.ts +95 -0
  61. package/src/modules/credentials/index.ts +15 -0
  62. package/src/modules/credentials/schema.ts +10 -0
  63. package/src/modules/notify/index.ts +90 -0
  64. package/src/modules/notify/notify.test.ts +145 -0
  65. package/src/modules/notify/providers/resend.ts +47 -0
  66. package/src/modules/notify/providers/smtp.ts +117 -0
  67. package/src/modules/notify/providers/waba.ts +82 -0
  68. package/src/modules/notify/schema.ts +27 -0
  69. package/src/modules/notify/service.ts +93 -0
  70. package/src/modules/sequences/index.ts +15 -0
  71. package/src/modules/sequences/next-sequence.ts +48 -0
  72. package/src/modules/sequences/schema.ts +12 -0
  73. package/src/modules/storage/index.ts +44 -0
  74. package/src/modules/storage/providers/local.ts +124 -0
  75. package/src/modules/storage/providers/s3.ts +83 -0
  76. package/src/modules/storage/routes.ts +76 -0
  77. package/src/modules/storage/schema.ts +26 -0
  78. package/src/modules/storage/service.ts +202 -0
  79. package/src/modules/storage/storage.test.ts +225 -0
  80. package/src/schemas.test.ts +44 -0
  81. package/src/schemas.ts +63 -0
  82. package/src/sequence.test.ts +56 -0
  83. package/dist/app.d.ts +0 -37
  84. package/dist/app.d.ts.map +0 -1
  85. package/dist/contracts/auth.d.ts +0 -35
  86. package/dist/contracts/auth.d.ts.map +0 -1
  87. package/dist/contracts/module.d.ts.map +0 -1
  88. package/dist/contracts/notify.d.ts +0 -46
  89. package/dist/contracts/notify.d.ts.map +0 -1
  90. package/dist/contracts/permissions.d.ts +0 -10
  91. package/dist/contracts/permissions.d.ts.map +0 -1
  92. package/dist/contracts/storage.d.ts +0 -54
  93. package/dist/contracts/storage.d.ts.map +0 -1
  94. package/dist/ctx.d.ts +0 -40
  95. package/dist/ctx.d.ts.map +0 -1
  96. package/dist/db/client.d.ts +0 -4
  97. package/dist/db/client.d.ts.map +0 -1
  98. package/dist/db/helpers.d.ts +0 -26
  99. package/dist/db/helpers.d.ts.map +0 -1
  100. package/dist/db/index.d.ts +0 -3
  101. package/dist/db/index.d.ts.map +0 -1
  102. package/dist/index.d.ts.map +0 -1
  103. package/dist/index.js +0 -98611
  104. package/dist/infra/circuit-breaker.d.ts +0 -17
  105. package/dist/infra/circuit-breaker.d.ts.map +0 -1
  106. package/dist/infra/errors.d.ts +0 -26
  107. package/dist/infra/errors.d.ts.map +0 -1
  108. package/dist/infra/http-client.d.ts +0 -31
  109. package/dist/infra/http-client.d.ts.map +0 -1
  110. package/dist/infra/index.d.ts +0 -11
  111. package/dist/infra/index.d.ts.map +0 -1
  112. package/dist/infra/job.d.ts +0 -14
  113. package/dist/infra/job.d.ts.map +0 -1
  114. package/dist/infra/logger.d.ts +0 -7
  115. package/dist/infra/logger.d.ts.map +0 -1
  116. package/dist/infra/queue.d.ts +0 -18
  117. package/dist/infra/queue.d.ts.map +0 -1
  118. package/dist/infra/throw-proxy.d.ts +0 -7
  119. package/dist/infra/throw-proxy.d.ts.map +0 -1
  120. package/dist/infra/webhooks-schema.d.ts +0 -60
  121. package/dist/infra/webhooks-schema.d.ts.map +0 -1
  122. package/dist/infra/webhooks.d.ts +0 -46
  123. package/dist/infra/webhooks.d.ts.map +0 -1
  124. package/dist/mcp/crud.d.ts +0 -12
  125. package/dist/mcp/crud.d.ts.map +0 -1
  126. package/dist/mcp/index.d.ts.map +0 -1
  127. package/dist/mcp/server.d.ts +0 -16
  128. package/dist/mcp/server.d.ts.map +0 -1
  129. package/dist/module-registry.d.ts +0 -3
  130. package/dist/module-registry.d.ts.map +0 -1
  131. package/dist/module.d.ts +0 -29
  132. package/dist/module.d.ts.map +0 -1
  133. package/dist/modules/audit/index.d.ts +0 -5
  134. package/dist/modules/audit/index.d.ts.map +0 -1
  135. package/dist/modules/audit/middleware.d.ts +0 -3
  136. package/dist/modules/audit/middleware.d.ts.map +0 -1
  137. package/dist/modules/audit/schema.d.ts +0 -247
  138. package/dist/modules/audit/schema.d.ts.map +0 -1
  139. package/dist/modules/audit/track-changes.d.ts +0 -3
  140. package/dist/modules/audit/track-changes.d.ts.map +0 -1
  141. package/dist/modules/auth/audit-hooks.d.ts +0 -6
  142. package/dist/modules/auth/audit-hooks.d.ts.map +0 -1
  143. package/dist/modules/auth/index.d.ts +0 -25
  144. package/dist/modules/auth/index.d.ts.map +0 -1
  145. package/dist/modules/auth/middleware.d.ts +0 -15
  146. package/dist/modules/auth/middleware.d.ts.map +0 -1
  147. package/dist/modules/auth/permissions.d.ts +0 -5
  148. package/dist/modules/auth/permissions.d.ts.map +0 -1
  149. package/dist/modules/auth/schema.d.ts +0 -2519
  150. package/dist/modules/auth/schema.d.ts.map +0 -1
  151. package/dist/modules/credentials/encrypt.d.ts +0 -12
  152. package/dist/modules/credentials/encrypt.d.ts.map +0 -1
  153. package/dist/modules/credentials/index.d.ts +0 -4
  154. package/dist/modules/credentials/index.d.ts.map +0 -1
  155. package/dist/modules/credentials/schema.d.ts +0 -56
  156. package/dist/modules/credentials/schema.d.ts.map +0 -1
  157. package/dist/modules/notify/index.d.ts +0 -36
  158. package/dist/modules/notify/index.d.ts.map +0 -1
  159. package/dist/modules/notify/providers/resend.d.ts +0 -7
  160. package/dist/modules/notify/providers/resend.d.ts.map +0 -1
  161. package/dist/modules/notify/providers/smtp.d.ts +0 -18
  162. package/dist/modules/notify/providers/smtp.d.ts.map +0 -1
  163. package/dist/modules/notify/providers/waba.d.ts +0 -12
  164. package/dist/modules/notify/providers/waba.d.ts.map +0 -1
  165. package/dist/modules/notify/schema.d.ts +0 -337
  166. package/dist/modules/notify/schema.d.ts.map +0 -1
  167. package/dist/modules/notify/service.d.ts +0 -22
  168. package/dist/modules/notify/service.d.ts.map +0 -1
  169. package/dist/modules/sequences/index.d.ts +0 -4
  170. package/dist/modules/sequences/index.d.ts.map +0 -1
  171. package/dist/modules/sequences/next-sequence.d.ts +0 -8
  172. package/dist/modules/sequences/next-sequence.d.ts.map +0 -1
  173. package/dist/modules/sequences/schema.d.ts +0 -72
  174. package/dist/modules/sequences/schema.d.ts.map +0 -1
  175. package/dist/modules/storage/index.d.ts +0 -24
  176. package/dist/modules/storage/index.d.ts.map +0 -1
  177. package/dist/modules/storage/providers/local.d.ts +0 -3
  178. package/dist/modules/storage/providers/local.d.ts.map +0 -1
  179. package/dist/modules/storage/providers/s3.d.ts +0 -3
  180. package/dist/modules/storage/providers/s3.d.ts.map +0 -1
  181. package/dist/modules/storage/routes.d.ts +0 -4
  182. package/dist/modules/storage/routes.d.ts.map +0 -1
  183. package/dist/modules/storage/schema.d.ts +0 -273
  184. package/dist/modules/storage/schema.d.ts.map +0 -1
  185. package/dist/modules/storage/service.d.ts +0 -35
  186. package/dist/modules/storage/service.d.ts.map +0 -1
  187. package/dist/schemas.d.ts +0 -19
  188. package/dist/schemas.d.ts.map +0 -1
@@ -0,0 +1,162 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { Hono } from 'hono';
3
+
4
+ import {
5
+ contextMiddleware,
6
+ getCtx,
7
+ type VobaseCtx,
8
+ type VobaseUser,
9
+ } from './ctx';
10
+ import type { VobaseDb } from './db';
11
+ import type { HttpClient } from './infra/http-client';
12
+ import type { Scheduler } from './infra/queue';
13
+ import type { NotifyService } from './modules/notify/service';
14
+ import type { StorageService } from './modules/storage/service';
15
+
16
+ const db = {} as VobaseDb;
17
+ const scheduler: Scheduler = {
18
+ async add() {},
19
+ };
20
+ const storage: StorageService = {
21
+ bucket() {
22
+ throw new Error('not implemented');
23
+ },
24
+ };
25
+ const notify: NotifyService = {
26
+ email: { send: async () => ({ success: true }) },
27
+ whatsapp: { send: async () => ({ success: true }) },
28
+ };
29
+ const mockResponse = { ok: true, status: 200, headers: new Headers(), data: null, raw: new Response() };
30
+ const http: HttpClient = {
31
+ fetch: async () => mockResponse,
32
+ get: async () => mockResponse,
33
+ post: async () => mockResponse,
34
+ put: async () => mockResponse,
35
+ delete: async () => mockResponse,
36
+ } as HttpClient;
37
+
38
+ function expectType<T>(_value: T): void {}
39
+
40
+ describe('ctx helpers', () => {
41
+ it('getCtx(c) returns all properties when set', async () => {
42
+ const user: VobaseUser = {
43
+ id: 'user_123',
44
+ email: 'user@example.com',
45
+ name: 'Test User',
46
+ role: 'admin',
47
+ };
48
+
49
+ const app = new Hono();
50
+ app.use('*', async (c, next) => {
51
+ c.set('db', db);
52
+ c.set('scheduler', scheduler);
53
+ c.set('storage', storage);
54
+ c.set('notify', notify);
55
+ c.set('http', http);
56
+ c.set('user', user);
57
+ await next();
58
+ });
59
+ app.get('/', (c) => {
60
+ const ctx = getCtx(c);
61
+ return c.json({
62
+ hasDb: ctx.db === db,
63
+ hasScheduler: ctx.scheduler === scheduler,
64
+ hasStorage: ctx.storage === storage,
65
+ hasNotify: ctx.notify === notify,
66
+ hasHttp: ctx.http === http,
67
+ user: ctx.user,
68
+ });
69
+ });
70
+
71
+ const response = await app.request('http://localhost/');
72
+ const body = (await response.json()) as {
73
+ hasDb: boolean;
74
+ hasScheduler: boolean;
75
+ hasStorage: boolean;
76
+ hasNotify: boolean;
77
+ hasHttp: boolean;
78
+ user: VobaseUser | null;
79
+ };
80
+
81
+ expect(response.status).toBe(200);
82
+ expect(body.hasDb).toBe(true);
83
+ expect(body.hasScheduler).toBe(true);
84
+ expect(body.hasStorage).toBe(true);
85
+ expect(body.hasNotify).toBe(true);
86
+ expect(body.hasHttp).toBe(true);
87
+ expect(body.user).toEqual(user);
88
+ });
89
+
90
+ it('returns null user when session middleware did not set user', async () => {
91
+ const app = new Hono();
92
+ app.use('*', contextMiddleware({ db, scheduler, storage, notify, http }));
93
+ app.get('/', (c) => c.json({ user: getCtx(c).user }));
94
+
95
+ const response = await app.request('http://localhost/');
96
+ const body = (await response.json()) as { user: VobaseUser | null };
97
+
98
+ expect(response.status).toBe(200);
99
+ expect(body.user).toBeNull();
100
+ });
101
+
102
+ it('contextMiddleware sets db, scheduler, storage, and http', async () => {
103
+ const app = new Hono();
104
+ app.use('*', contextMiddleware({ db, scheduler, storage, notify, http }));
105
+ app.get('/', (c) => {
106
+ return c.json({
107
+ hasDb: c.get('db') === db,
108
+ hasScheduler: c.get('scheduler') === scheduler,
109
+ hasStorage: c.get('storage') === storage,
110
+ hasHttp: c.get('http') === http,
111
+ });
112
+ });
113
+
114
+ const response = await app.request('http://localhost/');
115
+ const body = (await response.json()) as {
116
+ hasDb: boolean;
117
+ hasScheduler: boolean;
118
+ hasStorage: boolean;
119
+ hasHttp: boolean;
120
+ };
121
+
122
+ expect(response.status).toBe(200);
123
+ expect(body.hasDb).toBe(true);
124
+ expect(body.hasScheduler).toBe(true);
125
+ expect(body.hasStorage).toBe(true);
126
+ expect(body.hasHttp).toBe(true);
127
+ });
128
+
129
+ it('getCtx(c) includes http client', async () => {
130
+ const app = new Hono();
131
+ app.use('*', contextMiddleware({ db, scheduler, storage, notify, http }));
132
+ app.get('/', (c) => {
133
+ const ctx = getCtx(c);
134
+ return c.json({ hasHttp: ctx.http === http });
135
+ });
136
+
137
+ const response = await app.request('http://localhost/');
138
+ const body = (await response.json()) as { hasHttp: boolean };
139
+
140
+ expect(response.status).toBe(200);
141
+ expect(body.hasHttp).toBe(true);
142
+ });
143
+
144
+ it('exposes correctly typed properties on VobaseCtx', async () => {
145
+ const app = new Hono();
146
+ app.use('*', contextMiddleware({ db, scheduler, storage, notify, http }));
147
+ app.get('/', (c) => {
148
+ const ctx = getCtx(c);
149
+ expectType<VobaseCtx>(ctx);
150
+ expectType<VobaseDb>(ctx.db);
151
+ expectType<VobaseUser | null>(ctx.user);
152
+ expectType<Scheduler>(ctx.scheduler);
153
+ expectType<StorageService>(ctx.storage);
154
+ expectType<NotifyService>(ctx.notify);
155
+ expectType<HttpClient>(ctx.http);
156
+ return c.text('ok');
157
+ });
158
+
159
+ const response = await app.request('http://localhost/');
160
+ expect(response.status).toBe(200);
161
+ });
162
+ });
package/src/ctx.ts ADDED
@@ -0,0 +1,64 @@
1
+ import type { Context } from 'hono';
2
+ import { createMiddleware } from 'hono/factory';
3
+
4
+ import type { VobaseDb } from './db';
5
+ import type { HttpClient } from './infra/http-client';
6
+ import type { Scheduler } from './infra/queue';
7
+ import type { NotifyService } from './modules/notify/service';
8
+ import type { StorageService } from './modules/storage/service';
9
+
10
+ export interface VobaseUser {
11
+ id: string;
12
+ email: string;
13
+ name: string;
14
+ role: string;
15
+ /** Set when the user has an active organization (better-auth organization plugin) */
16
+ activeOrganizationId?: string;
17
+ }
18
+
19
+ export interface VobaseCtx {
20
+ db: VobaseDb;
21
+ user: VobaseUser | null;
22
+ scheduler: Scheduler;
23
+ storage: StorageService;
24
+ notify: NotifyService;
25
+ http: HttpClient;
26
+ }
27
+
28
+ declare module 'hono' {
29
+ interface ContextVariableMap {
30
+ db: VobaseDb;
31
+ scheduler: Scheduler;
32
+ storage: StorageService;
33
+ notify: NotifyService;
34
+ http: HttpClient;
35
+ }
36
+ }
37
+
38
+ export function contextMiddleware(deps: {
39
+ db: VobaseDb;
40
+ scheduler: Scheduler;
41
+ storage: StorageService;
42
+ notify: NotifyService;
43
+ http: HttpClient;
44
+ }) {
45
+ return createMiddleware(async (c, next) => {
46
+ c.set('db', deps.db);
47
+ c.set('scheduler', deps.scheduler);
48
+ c.set('storage', deps.storage);
49
+ c.set('notify', deps.notify);
50
+ c.set('http', deps.http);
51
+ await next();
52
+ });
53
+ }
54
+
55
+ export function getCtx(c: Context): VobaseCtx {
56
+ return {
57
+ db: c.get('db'),
58
+ user: c.get('user') ?? null,
59
+ scheduler: c.get('scheduler'),
60
+ storage: c.get('storage'),
61
+ notify: c.get('notify'),
62
+ http: c.get('http'),
63
+ };
64
+ }
@@ -0,0 +1,75 @@
1
+ import { rmSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { resolve } from 'node:path';
4
+ import type { Database } from 'bun:sqlite';
5
+ import { afterEach, describe, expect, it } from 'bun:test';
6
+
7
+ import { createDatabase } from './client';
8
+
9
+ type DbWithClient = ReturnType<typeof createDatabase> & { $client: Database };
10
+
11
+ const tempDbPaths = new Set<string>();
12
+
13
+ function getPragmaValue(db: DbWithClient, pragma: string): string {
14
+ const result = db.$client.query(`PRAGMA ${pragma}`).get() as Record<
15
+ string,
16
+ unknown
17
+ >;
18
+ return String(Object.values(result)[0]);
19
+ }
20
+
21
+ function closeDatabase(db: DbWithClient): void {
22
+ db.$client.close();
23
+ }
24
+
25
+ afterEach(() => {
26
+ for (const dbPath of tempDbPaths) {
27
+ rmSync(dbPath, { force: true });
28
+ }
29
+ tempDbPaths.clear();
30
+ });
31
+
32
+ describe('createDatabase', () => {
33
+ it('creates an in-memory drizzle database client', () => {
34
+ const db = createDatabase(':memory:') as DbWithClient;
35
+
36
+ expect(db).toBeDefined();
37
+ expect(db.$client).toBeDefined();
38
+
39
+ closeDatabase(db);
40
+ });
41
+
42
+ it('sets PRAGMA busy_timeout to 5000', () => {
43
+ const db = createDatabase(':memory:') as DbWithClient;
44
+
45
+ expect(getPragmaValue(db, 'busy_timeout')).toBe('5000');
46
+
47
+ closeDatabase(db);
48
+ });
49
+
50
+ it('sets PRAGMA synchronous to NORMAL (1)', () => {
51
+ const db = createDatabase(':memory:') as DbWithClient;
52
+
53
+ expect(getPragmaValue(db, 'synchronous')).toBe('1');
54
+
55
+ closeDatabase(db);
56
+ });
57
+
58
+ it('sets PRAGMA foreign_keys to ON (1)', () => {
59
+ const db = createDatabase(':memory:') as DbWithClient;
60
+
61
+ expect(getPragmaValue(db, 'foreign_keys')).toBe('1');
62
+
63
+ closeDatabase(db);
64
+ });
65
+
66
+ it('sets PRAGMA journal_mode to WAL for file databases', () => {
67
+ const dbPath = resolve(tmpdir(), `vobase-client-${Date.now()}.db`);
68
+ tempDbPaths.add(dbPath);
69
+ const db = createDatabase(dbPath) as DbWithClient;
70
+
71
+ expect(getPragmaValue(db, 'journal_mode')).toBe('wal');
72
+
73
+ closeDatabase(db);
74
+ });
75
+ });
@@ -0,0 +1,15 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { drizzle } from 'drizzle-orm/bun-sqlite';
3
+
4
+ export type VobaseDb = ReturnType<typeof drizzle>;
5
+
6
+ export function createDatabase(dbPath: string): VobaseDb {
7
+ const sqlite = new Database(dbPath);
8
+
9
+ sqlite.run('PRAGMA journal_mode=WAL');
10
+ sqlite.run('PRAGMA busy_timeout=5000');
11
+ sqlite.run('PRAGMA synchronous=NORMAL');
12
+ sqlite.run('PRAGMA foreign_keys=ON');
13
+
14
+ return drizzle({ client: sqlite });
15
+ }
@@ -0,0 +1,147 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import {
4
+ createNanoid,
5
+ DEFAULT_COLUMNS,
6
+ NANOID_ALPHABET,
7
+ NANOID_LENGTH,
8
+ nanoidPrimaryKey,
9
+ } from './helpers';
10
+
11
+ // Drizzle marks `.config` as protected; bracket access bypasses this for test inspection
12
+ function getConfig(col: unknown): Record<string, unknown> {
13
+ return (col as Record<string, Record<string, unknown>>).config;
14
+ }
15
+
16
+ describe('nanoid helpers', () => {
17
+ describe('NANOID_LENGTH constants', () => {
18
+ it('should have SHORT, DEFAULT, and LONG lengths defined', () => {
19
+ expect(NANOID_LENGTH.SHORT).toBe(8);
20
+ expect(NANOID_LENGTH.DEFAULT).toBe(12);
21
+ expect(NANOID_LENGTH.LONG).toBe(16);
22
+ });
23
+ });
24
+
25
+ describe('NANOID_ALPHABET', () => {
26
+ it('should only contain lowercase alphanumeric characters', () => {
27
+ expect(NANOID_ALPHABET).toBe('0123456789abcdefghijklmnopqrstuvwxyz');
28
+ });
29
+ });
30
+
31
+ describe('createNanoid()', () => {
32
+ it('should generate IDs of the correct length', () => {
33
+ const generateShort = createNanoid(NANOID_LENGTH.SHORT);
34
+ const generateDefault = createNanoid(NANOID_LENGTH.DEFAULT);
35
+ const generateLong = createNanoid(NANOID_LENGTH.LONG);
36
+
37
+ expect(generateShort().length).toBe(8);
38
+ expect(generateDefault().length).toBe(12);
39
+ expect(generateLong().length).toBe(16);
40
+ });
41
+
42
+ it('should generate IDs using only the alphabet', () => {
43
+ const generate = createNanoid(NANOID_LENGTH.DEFAULT);
44
+ const id = generate();
45
+
46
+ for (const char of id) {
47
+ expect(NANOID_ALPHABET.includes(char)).toBe(true);
48
+ }
49
+ });
50
+
51
+ it('should cache generators and reuse them', () => {
52
+ const gen1 = createNanoid(NANOID_LENGTH.DEFAULT);
53
+ const gen2 = createNanoid(NANOID_LENGTH.DEFAULT);
54
+
55
+ // Should be the same function instance (cached)
56
+ expect(gen1).toBe(gen2);
57
+ });
58
+
59
+ it('should generate unique IDs', () => {
60
+ const generate = createNanoid(NANOID_LENGTH.DEFAULT);
61
+ const ids = new Set();
62
+
63
+ for (let i = 0; i < 1000; i++) {
64
+ ids.add(generate());
65
+ }
66
+
67
+ expect(ids.size).toBe(1000);
68
+ });
69
+
70
+ it('should use default length when not specified', () => {
71
+ const generate = createNanoid();
72
+ expect(generate().length).toBe(NANOID_LENGTH.DEFAULT);
73
+ });
74
+ });
75
+
76
+ describe('nanoidPrimaryKey()', () => {
77
+ it('should create a text column named "id"', () => {
78
+ const column = nanoidPrimaryKey();
79
+ expect(getConfig(column).name).toBe('id');
80
+ });
81
+
82
+ it('should be a primary key', () => {
83
+ const column = nanoidPrimaryKey();
84
+ expect(getConfig(column).primaryKey).toBe(true);
85
+ });
86
+
87
+ it('should have a default function', () => {
88
+ const column = nanoidPrimaryKey();
89
+ expect(typeof column.$default).toBe('function');
90
+ });
91
+
92
+ it('should generate valid nanoid', () => {
93
+ // createNanoid() returns a generator function
94
+ const generator = createNanoid();
95
+ const id = generator();
96
+
97
+ expect(id).toBeDefined();
98
+ expect(typeof id).toBe('string');
99
+ expect(id.length).toBe(NANOID_LENGTH.DEFAULT);
100
+ });
101
+
102
+ it('should support custom lengths', () => {
103
+ const columnShort = nanoidPrimaryKey(NANOID_LENGTH.SHORT);
104
+ const columnLong = nanoidPrimaryKey(NANOID_LENGTH.LONG);
105
+
106
+ expect(getConfig(columnShort).name).toBe('id');
107
+ expect(getConfig(columnLong).name).toBe('id');
108
+ });
109
+ });
110
+
111
+ describe('DEFAULT_COLUMNS', () => {
112
+ it('should have createdAt and updatedAt columns', () => {
113
+ expect(DEFAULT_COLUMNS.createdAt).toBeDefined();
114
+ expect(DEFAULT_COLUMNS.updatedAt).toBeDefined();
115
+ });
116
+
117
+ it('createdAt should be an integer column with timestamp_ms mode', () => {
118
+ const col = DEFAULT_COLUMNS.createdAt;
119
+ expect(getConfig(col).name).toBe('created_at');
120
+ expect(getConfig(col).mode).toBe('timestamp_ms');
121
+ expect(getConfig(col).hasDefault).toBe(true);
122
+ expect(getConfig(col).notNull).toBe(true);
123
+ });
124
+
125
+ it('updatedAt should be an integer column with timestamp_ms mode', () => {
126
+ const col = DEFAULT_COLUMNS.updatedAt;
127
+ expect(getConfig(col).name).toBe('updated_at');
128
+ expect(getConfig(col).mode).toBe('timestamp_ms');
129
+ expect(getConfig(col).hasDefault).toBe(true);
130
+ expect(getConfig(col).notNull).toBe(true);
131
+ });
132
+
133
+ it('timestamps should have default functions', () => {
134
+ expect(typeof DEFAULT_COLUMNS.createdAt.$default).toBe('function');
135
+ expect(typeof DEFAULT_COLUMNS.updatedAt.$default).toBe('function');
136
+ });
137
+
138
+ it('updatedAt should have onUpdate function', () => {
139
+ expect(typeof DEFAULT_COLUMNS.updatedAt.$onUpdate).toBe('function');
140
+ });
141
+
142
+ it('timestamp columns should use integer data type', () => {
143
+ expect(getConfig(DEFAULT_COLUMNS.createdAt).dataType).toBe('object date');
144
+ expect(getConfig(DEFAULT_COLUMNS.updatedAt).dataType).toBe('object date');
145
+ });
146
+ });
147
+ });
@@ -0,0 +1,51 @@
1
+ import { integer, text } from 'drizzle-orm/sqlite-core';
2
+ import { customAlphabet } from 'nanoid';
3
+
4
+ export const NANOID_LENGTH = { SHORT: 8, DEFAULT: 12, LONG: 16 } as const;
5
+ export const NANOID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
6
+
7
+ // Cache generators to avoid recreating
8
+ const nanoidGenerators = new Map<number, () => string>();
9
+
10
+ /**
11
+ * Create or retrieve a cached nanoid generator with the specified length.
12
+ * Uses a custom alphabet of lowercase alphanumeric characters.
13
+ */
14
+ export function createNanoid(
15
+ length: number = NANOID_LENGTH.DEFAULT,
16
+ ): () => string {
17
+ if (!nanoidGenerators.has(length)) {
18
+ nanoidGenerators.set(length, customAlphabet(NANOID_ALPHABET, length));
19
+ }
20
+ const generator = nanoidGenerators.get(length);
21
+
22
+ if (!generator) {
23
+ throw new Error(`No nanoid generator for length ${length}`);
24
+ }
25
+
26
+ return generator;
27
+ }
28
+
29
+ /**
30
+ * Create a nanoid-based primary key column for SQLite.
31
+ * Generates a 12-character ID by default (customizable).
32
+ */
33
+ export const nanoidPrimaryKey = (length: number = NANOID_LENGTH.DEFAULT) =>
34
+ text('id')
35
+ .primaryKey()
36
+ .$defaultFn(() => createNanoid(length)());
37
+
38
+ /**
39
+ * Default timestamp columns for SQLite using timestamp_ms mode.
40
+ * createdAt: set on insert and never updated
41
+ * updatedAt: set on insert and updated on every row modification
42
+ */
43
+ export const DEFAULT_COLUMNS = {
44
+ createdAt: integer('created_at', { mode: 'timestamp_ms' })
45
+ .notNull()
46
+ .$defaultFn(() => new Date()),
47
+ updatedAt: integer('updated_at', { mode: 'timestamp_ms' })
48
+ .notNull()
49
+ .$defaultFn(() => new Date())
50
+ .$onUpdate(() => new Date()),
51
+ } as const;
@@ -0,0 +1,8 @@
1
+ export { createDatabase, type VobaseDb } from './client';
2
+ export {
3
+ createNanoid,
4
+ DEFAULT_COLUMNS,
5
+ NANOID_ALPHABET,
6
+ NANOID_LENGTH,
7
+ nanoidPrimaryKey,
8
+ } from './helpers';
@@ -1,47 +1,144 @@
1
+ // Engine
1
2
  export { type CreateAppConfig, createApp } from './app';
3
+
4
+ // Auth Module
2
5
  export { createAuthModule, type AuthModuleConfig, type AuthModule } from './modules/auth';
3
6
  export { sessionMiddleware, optionalSessionMiddleware } from './modules/auth/middleware';
7
+
8
+ // Auth Schema (tables managed by better-auth)
9
+ export {
10
+ authUser,
11
+ authSession,
12
+ authAccount,
13
+ authVerification,
14
+ authApikey,
15
+ authOrganization,
16
+ authMember,
17
+ authInvitation,
18
+ } from './modules/auth/schema';
19
+
20
+ // RBAC
4
21
  export { requireRole, requirePermission, requireOrg } from './modules/auth/permissions';
5
22
  export type { Permission, OrganizationContext } from './contracts/permissions';
23
+
24
+ // Contracts
6
25
  export type { AuthAdapter, AuthSession, AuthUser } from './contracts/auth';
7
- export type { StorageProvider, UploadOptions, PresignOptions, ListOptions, StorageListResult, StorageObjectInfo, LocalProviderConfig, S3ProviderConfig, StorageProviderConfig, } from './contracts/storage';
8
- export type { EmailProvider, EmailMessage, EmailAttachment, EmailResult, WhatsAppProvider, WhatsAppMessage, WhatsAppResult, } from './contracts/notify';
26
+ export type {
27
+ StorageProvider,
28
+ UploadOptions,
29
+ PresignOptions,
30
+ ListOptions,
31
+ StorageListResult,
32
+ StorageObjectInfo,
33
+ LocalProviderConfig,
34
+ S3ProviderConfig,
35
+ StorageProviderConfig,
36
+ } from './contracts/storage';
37
+ export type {
38
+ EmailProvider,
39
+ EmailMessage,
40
+ EmailAttachment,
41
+ EmailResult,
42
+ WhatsAppProvider,
43
+ WhatsAppMessage,
44
+ WhatsAppResult,
45
+ } from './contracts/notify';
9
46
  export type { ModuleInitContext } from './contracts/module';
47
+
48
+ // Context
10
49
  export type { VobaseCtx, VobaseUser } from './ctx';
11
50
  export { contextMiddleware, getCtx } from './ctx';
51
+
52
+ // Circuit Breaker
12
53
  export { CircuitBreaker, type CircuitBreakerOptions } from './infra/circuit-breaker';
13
- export { createHttpClient, type HttpClient, type HttpClientOptions, type HttpResponse, type RequestOptions, } from './infra/http-client';
54
+
55
+ // HTTP Client
56
+ export {
57
+ createHttpClient,
58
+ type HttpClient,
59
+ type HttpClientOptions,
60
+ type HttpResponse,
61
+ type RequestOptions,
62
+ } from './infra/http-client';
63
+
64
+ // DB
14
65
  export { createDatabase, type VobaseDb } from './db';
15
- export { createNanoid, DEFAULT_COLUMNS, NANOID_ALPHABET, NANOID_LENGTH, nanoidPrimaryKey, } from './db/helpers';
16
- export { conflict, dbBusy, ERROR_CODES, type ErrorCode, errorHandler, forbidden, notFound, unauthorized, VobaseError, validation, } from './infra/errors';
66
+ export {
67
+ createNanoid,
68
+ DEFAULT_COLUMNS,
69
+ NANOID_ALPHABET,
70
+ NANOID_LENGTH,
71
+ nanoidPrimaryKey,
72
+ } from './db/helpers';
73
+
74
+ // Errors
75
+ export {
76
+ conflict,
77
+ dbBusy,
78
+ ERROR_CODES,
79
+ type ErrorCode,
80
+ errorHandler,
81
+ forbidden,
82
+ notFound,
83
+ unauthorized,
84
+ VobaseError,
85
+ validation,
86
+ } from './infra/errors';
87
+
88
+ // Jobs
17
89
  export type { JobDefinition, JobHandler, WorkerOptions } from './infra/job';
18
90
  export { createWorker, defineJob } from './infra/job';
91
+
92
+ // Logger
19
93
  export { logger } from './infra/logger';
94
+
95
+ // Auth Audit Hooks (re-exported from auth module)
20
96
  export { createAuthAuditHooks } from './modules/auth/audit-hooks';
97
+
98
+ // Module
21
99
  export type { DefineModuleConfig, VobaseModule } from './module';
22
100
  export { defineModule } from './module';
101
+
102
+ // Module Registry
23
103
  export { registerModules } from './module-registry';
104
+
105
+ // Queue
24
106
  export { createScheduler, type JobOptions, type Scheduler } from './infra/queue';
107
+
108
+ // Built-in Modules: Audit
25
109
  export { createAuditModule, auditLog, recordAudits } from './modules/audit';
26
110
  export { trackChanges } from './modules/audit/track-changes';
27
111
  export { requestAuditMiddleware } from './modules/audit/middleware';
112
+
113
+ // Built-in Modules: Sequences
28
114
  export { createSequencesModule, sequences } from './modules/sequences';
29
115
  export { nextSequence, type SequenceOptions } from './modules/sequences/next-sequence';
116
+
117
+ // Built-in Modules: Credentials
30
118
  export { createCredentialsModule, credentialsTable } from './modules/credentials';
31
119
  export { encrypt, decrypt, getCredential, setCredential, deleteCredential } from './modules/credentials/encrypt';
120
+
121
+ // Schemas
32
122
  export { getActiveSchemas, type SchemaConfig } from './schemas';
123
+
124
+ // Throw Proxy
33
125
  export { createThrowProxy } from './infra/throw-proxy';
126
+
127
+ // Built-in Modules: Storage
34
128
  export { createStorageModule, type StorageModuleConfig } from './modules/storage';
35
129
  export { createLocalProvider } from './modules/storage/providers/local';
36
130
  export { createS3Provider } from './modules/storage/providers/s3';
37
131
  export { createStorageRoutes } from './modules/storage/routes';
38
132
  export { storageObjects } from './modules/storage/schema';
39
133
  export type { StorageService, BucketConfig, BucketHandle, StorageObject, BucketListOptions } from './modules/storage/service';
134
+
135
+ // Built-in Modules: Notify
40
136
  export { createNotifyModule, type NotifyModuleConfig } from './modules/notify';
41
137
  export { notifyLog } from './modules/notify/schema';
42
138
  export { createResendProvider, type ResendConfig } from './modules/notify/providers/resend';
43
139
  export { createSmtpProvider, type SmtpConfig } from './modules/notify/providers/smtp';
44
140
  export { createWabaProvider, type WabaConfig } from './modules/notify/providers/waba';
45
141
  export type { NotifyService, EmailChannel, WhatsAppChannel } from './modules/notify/service';
142
+
143
+ // Webhooks
46
144
  export { type WebhookConfig, verifyHmacSignature, createWebhookRoutes, webhookDedup } from './infra/webhooks';
47
- //# sourceMappingURL=index.d.ts.map