@rebasepro/server-core 0.7.0 → 0.9.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 (300) hide show
  1. package/README.md +2 -2
  2. package/dist/api/ast-schema-editor.d.ts +21 -0
  3. package/dist/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
  4. package/dist/api/errors.d.ts +53 -0
  5. package/dist/api/index.d.ts +7 -0
  6. package/dist/api/logs-routes.d.ts +37 -0
  7. package/dist/api/openapi-generator.d.ts +16 -0
  8. package/dist/api/rest/api-generator.d.ts +68 -0
  9. package/{src/api/rest/index.ts → dist/api/rest/index.d.ts} +0 -1
  10. package/dist/api/rest/query-parser.d.ts +6 -0
  11. package/dist/api/schema-editor-routes.d.ts +3 -0
  12. package/dist/api/types.d.ts +56 -0
  13. package/dist/auth/adapter-middleware.d.ts +43 -0
  14. package/dist/auth/admin-roles-route.d.ts +18 -0
  15. package/dist/auth/admin-user-ops.d.ts +79 -0
  16. package/dist/auth/admin-users-route.d.ts +28 -0
  17. package/dist/auth/api-keys/api-key-middleware.d.ts +39 -0
  18. package/{src/auth/api-keys/api-key-permission-guard.ts → dist/auth/api-keys/api-key-permission-guard.d.ts} +2 -34
  19. package/dist/auth/api-keys/api-key-routes.d.ts +20 -0
  20. package/dist/auth/api-keys/api-key-store.d.ts +35 -0
  21. package/{src/auth/api-keys/api-key-types.ts → dist/auth/api-keys/api-key-types.d.ts} +0 -6
  22. package/{src/auth/api-keys/index.ts → dist/auth/api-keys/index.d.ts} +2 -22
  23. package/dist/auth/apple-oauth.d.ts +30 -0
  24. package/{src/auth/auth-hooks.ts → dist/auth/auth-hooks.d.ts} +15 -73
  25. package/dist/auth/bitbucket-oauth.d.ts +11 -0
  26. package/dist/auth/builtin-auth-adapter.d.ts +55 -0
  27. package/dist/auth/cookie-utils.d.ts +32 -0
  28. package/{src/auth/crypto-utils.ts → dist/auth/crypto-utils.d.ts} +1 -16
  29. package/dist/auth/custom-auth-adapter.d.ts +39 -0
  30. package/dist/auth/discord-oauth.d.ts +14 -0
  31. package/dist/auth/facebook-oauth.d.ts +14 -0
  32. package/dist/auth/github-oauth.d.ts +15 -0
  33. package/dist/auth/gitlab-oauth.d.ts +13 -0
  34. package/dist/auth/google-oauth.d.ts +47 -0
  35. package/{src/auth/index.ts → dist/auth/index.d.ts} +4 -22
  36. package/{src/auth/interfaces.ts → dist/auth/interfaces.d.ts} +20 -89
  37. package/dist/auth/jwt.d.ts +67 -0
  38. package/dist/auth/linkedin-oauth.d.ts +18 -0
  39. package/dist/auth/magic-link-routes.d.ts +32 -0
  40. package/dist/auth/mfa-crypto.d.ts +23 -0
  41. package/dist/auth/mfa-routes.d.ts +7 -0
  42. package/dist/auth/mfa.d.ts +49 -0
  43. package/dist/auth/microsoft-oauth.d.ts +16 -0
  44. package/dist/auth/middleware.d.ts +158 -0
  45. package/dist/auth/password.d.ts +22 -0
  46. package/dist/auth/rate-limiter.d.ts +50 -0
  47. package/dist/auth/reset-password-admin.d.ts +29 -0
  48. package/dist/auth/rls-scope.d.ts +31 -0
  49. package/dist/auth/routes.d.ts +66 -0
  50. package/dist/auth/session-routes.d.ts +29 -0
  51. package/dist/auth/slack-oauth.d.ts +12 -0
  52. package/dist/auth/spotify-oauth.d.ts +12 -0
  53. package/dist/auth/twitter-oauth.d.ts +18 -0
  54. package/dist/collections/BackendCollectionRegistry.d.ts +13 -0
  55. package/dist/collections/loader.d.ts +5 -0
  56. package/dist/cron/cron-loader.d.ts +18 -0
  57. package/dist/cron/cron-routes.d.ts +14 -0
  58. package/dist/cron/cron-scheduler.d.ts +106 -0
  59. package/dist/cron/cron-store.d.ts +32 -0
  60. package/dist/cron/define-cron.d.ts +28 -0
  61. package/{src/cron/index.ts → dist/cron/index.d.ts} +1 -0
  62. package/dist/db/interfaces.d.ts +18 -0
  63. package/dist/dynamic-import-Dvh-K5fl.js +22 -0
  64. package/dist/dynamic-import-Dvh-K5fl.js.map +1 -0
  65. package/dist/email/index.d.ts +6 -0
  66. package/dist/email/smtp-email-service.d.ts +30 -0
  67. package/dist/email/templates.d.ts +50 -0
  68. package/{src/email/types.ts → dist/email/types.d.ts} +40 -36
  69. package/dist/env.d.ts +102 -0
  70. package/dist/functions/define-function.d.ts +55 -0
  71. package/dist/functions/function-loader.d.ts +18 -0
  72. package/dist/functions/function-routes.d.ts +10 -0
  73. package/{src/functions/index.ts → dist/functions/index.d.ts} +2 -0
  74. package/dist/history/history-routes.d.ts +23 -0
  75. package/{src/history/index.ts → dist/history/index.d.ts} +0 -1
  76. package/dist/index.d.ts +35 -0
  77. package/dist/index.es.js +18184 -0
  78. package/dist/index.es.js.map +1 -0
  79. package/dist/index.umd.js +48629 -0
  80. package/dist/index.umd.js.map +1 -0
  81. package/dist/init/docs.d.ts +4 -0
  82. package/dist/init/health.d.ts +2 -0
  83. package/dist/init/middlewares.d.ts +10 -0
  84. package/dist/init/shutdown.d.ts +65 -0
  85. package/dist/init/storage.d.ts +5 -0
  86. package/dist/init.d.ts +344 -0
  87. package/dist/jwt-BwIn8xmk.js +4200 -0
  88. package/dist/jwt-BwIn8xmk.js.map +1 -0
  89. package/dist/logger-BYU66ENZ.js +94 -0
  90. package/dist/logger-BYU66ENZ.js.map +1 -0
  91. package/dist/ms-DnYXB-Wd.js +162 -0
  92. package/dist/ms-DnYXB-Wd.js.map +1 -0
  93. package/dist/openapi-generator-Z9oYWLf_.js +586 -0
  94. package/dist/openapi-generator-Z9oYWLf_.js.map +1 -0
  95. package/dist/schema-editor-routes-D3ef8zu1.js +219 -0
  96. package/dist/schema-editor-routes-D3ef8zu1.js.map +1 -0
  97. package/dist/serve-spa.d.ts +36 -0
  98. package/dist/services/driver-registry.d.ts +78 -0
  99. package/dist/services/routed-realtime-service.d.ts +43 -0
  100. package/dist/services/webhook-service.d.ts +29 -0
  101. package/dist/singleton.d.ts +51 -0
  102. package/dist/src-B4OLmNVa.js +437 -0
  103. package/dist/src-B4OLmNVa.js.map +1 -0
  104. package/dist/src-B5WkP1Rv.js +24778 -0
  105. package/dist/src-B5WkP1Rv.js.map +1 -0
  106. package/dist/storage/GCSStorageController.d.ts +43 -0
  107. package/dist/storage/LocalStorageController.d.ts +46 -0
  108. package/dist/storage/S3StorageController.d.ts +40 -0
  109. package/dist/storage/image-transform.d.ts +50 -0
  110. package/{src/storage/index.ts → dist/storage/index.d.ts} +6 -21
  111. package/dist/storage/routes.d.ts +70 -0
  112. package/dist/storage/storage-registry.d.ts +78 -0
  113. package/dist/storage/tus-handler.d.ts +53 -0
  114. package/{src/storage/types.ts → dist/storage/types.d.ts} +32 -38
  115. package/dist/types/index.d.ts +11 -0
  116. package/dist/utils/dev-port.d.ts +45 -0
  117. package/dist/utils/dynamic-import.d.ts +25 -0
  118. package/dist/utils/logger.d.ts +31 -0
  119. package/dist/utils/logging.d.ts +9 -0
  120. package/dist/utils/request-id.d.ts +4 -0
  121. package/dist/utils/request-logger.d.ts +19 -0
  122. package/{src/utils/sql.ts → dist/utils/sql.d.ts} +5 -16
  123. package/package.json +49 -22
  124. package/coverage/clover.xml +0 -3739
  125. package/coverage/coverage-final.json +0 -31
  126. package/coverage/lcov-report/base.css +0 -224
  127. package/coverage/lcov-report/block-navigation.js +0 -87
  128. package/coverage/lcov-report/favicon.png +0 -0
  129. package/coverage/lcov-report/index.html +0 -266
  130. package/coverage/lcov-report/prettify.css +0 -1
  131. package/coverage/lcov-report/prettify.js +0 -2
  132. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  133. package/coverage/lcov-report/sorter.js +0 -210
  134. package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +0 -952
  135. package/coverage/lcov-report/src/api/errors.ts.html +0 -472
  136. package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +0 -1069
  137. package/coverage/lcov-report/src/api/graphql/index.html +0 -116
  138. package/coverage/lcov-report/src/api/index.html +0 -176
  139. package/coverage/lcov-report/src/api/openapi-generator.ts.html +0 -565
  140. package/coverage/lcov-report/src/api/rest/api-generator.ts.html +0 -994
  141. package/coverage/lcov-report/src/api/rest/index.html +0 -131
  142. package/coverage/lcov-report/src/api/rest/query-parser.ts.html +0 -550
  143. package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +0 -202
  144. package/coverage/lcov-report/src/api/server.ts.html +0 -823
  145. package/coverage/lcov-report/src/auth/admin-routes.ts.html +0 -973
  146. package/coverage/lcov-report/src/auth/index.html +0 -176
  147. package/coverage/lcov-report/src/auth/jwt.ts.html +0 -574
  148. package/coverage/lcov-report/src/auth/middleware.ts.html +0 -745
  149. package/coverage/lcov-report/src/auth/password.ts.html +0 -310
  150. package/coverage/lcov-report/src/auth/services.ts.html +0 -2074
  151. package/coverage/lcov-report/src/collections/index.html +0 -116
  152. package/coverage/lcov-report/src/collections/loader.ts.html +0 -232
  153. package/coverage/lcov-report/src/db/auth-schema.ts.html +0 -523
  154. package/coverage/lcov-report/src/db/data-transformer.ts.html +0 -1753
  155. package/coverage/lcov-report/src/db/entityService.ts.html +0 -700
  156. package/coverage/lcov-report/src/db/index.html +0 -146
  157. package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +0 -4048
  158. package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +0 -883
  159. package/coverage/lcov-report/src/db/services/RelationService.ts.html +0 -3121
  160. package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +0 -442
  161. package/coverage/lcov-report/src/db/services/index.html +0 -176
  162. package/coverage/lcov-report/src/db/services/index.ts.html +0 -124
  163. package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +0 -1960
  164. package/coverage/lcov-report/src/index.html +0 -116
  165. package/coverage/lcov-report/src/services/driver-registry.ts.html +0 -631
  166. package/coverage/lcov-report/src/services/index.html +0 -131
  167. package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +0 -3025
  168. package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +0 -1189
  169. package/coverage/lcov-report/src/storage/S3StorageController.ts.html +0 -970
  170. package/coverage/lcov-report/src/storage/index.html +0 -161
  171. package/coverage/lcov-report/src/storage/storage-registry.ts.html +0 -646
  172. package/coverage/lcov-report/src/storage/types.ts.html +0 -451
  173. package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +0 -3082
  174. package/coverage/lcov-report/src/utils/index.html +0 -116
  175. package/coverage/lcov.info +0 -7179
  176. package/jest.config.cjs +0 -19
  177. package/src/api/ast-schema-editor.ts +0 -289
  178. package/src/api/collections_for_test/callbacks_test_collection.ts +0 -61
  179. package/src/api/errors.ts +0 -278
  180. package/src/api/graphql/graphql-schema-generator.ts +0 -383
  181. package/src/api/graphql/index.ts +0 -2
  182. package/src/api/index.ts +0 -11
  183. package/src/api/logs-routes.ts +0 -132
  184. package/src/api/openapi-generator.ts +0 -727
  185. package/src/api/rest/api-generator-count.test.ts +0 -126
  186. package/src/api/rest/api-generator.ts +0 -747
  187. package/src/api/rest/query-parser.ts +0 -300
  188. package/src/api/schema-editor-routes.ts +0 -41
  189. package/src/api/server.ts +0 -271
  190. package/src/api/types.ts +0 -104
  191. package/src/auth/adapter-middleware.ts +0 -114
  192. package/src/auth/admin-roles-route.ts +0 -36
  193. package/src/auth/admin-user-ops.ts +0 -236
  194. package/src/auth/admin-users-route.ts +0 -302
  195. package/src/auth/api-keys/api-key-middleware.ts +0 -133
  196. package/src/auth/api-keys/api-key-routes.ts +0 -193
  197. package/src/auth/api-keys/api-key-store.ts +0 -340
  198. package/src/auth/apple-oauth.ts +0 -122
  199. package/src/auth/bitbucket-oauth.ts +0 -84
  200. package/src/auth/builtin-auth-adapter.ts +0 -380
  201. package/src/auth/custom-auth-adapter.ts +0 -86
  202. package/src/auth/discord-oauth.ts +0 -85
  203. package/src/auth/facebook-oauth.ts +0 -74
  204. package/src/auth/github-oauth.ts +0 -112
  205. package/src/auth/gitlab-oauth.ts +0 -72
  206. package/src/auth/google-oauth.ts +0 -223
  207. package/src/auth/jwt.ts +0 -208
  208. package/src/auth/linkedin-oauth.ts +0 -83
  209. package/src/auth/magic-link-routes.ts +0 -167
  210. package/src/auth/mfa-crypto.ts +0 -91
  211. package/src/auth/mfa-routes.ts +0 -323
  212. package/src/auth/mfa.ts +0 -161
  213. package/src/auth/microsoft-oauth.ts +0 -90
  214. package/src/auth/middleware.ts +0 -369
  215. package/src/auth/password.ts +0 -77
  216. package/src/auth/rate-limiter.ts +0 -225
  217. package/src/auth/reset-password-admin.ts +0 -160
  218. package/src/auth/rls-scope.ts +0 -58
  219. package/src/auth/routes.ts +0 -802
  220. package/src/auth/session-routes.ts +0 -353
  221. package/src/auth/slack-oauth.ts +0 -73
  222. package/src/auth/spotify-oauth.ts +0 -69
  223. package/src/auth/twitter-oauth.ts +0 -128
  224. package/src/collections/BackendCollectionRegistry.ts +0 -20
  225. package/src/collections/loader.ts +0 -50
  226. package/src/cron/cron-loader.ts +0 -84
  227. package/src/cron/cron-routes.test.ts +0 -265
  228. package/src/cron/cron-routes.ts +0 -85
  229. package/src/cron/cron-scheduler.test.ts +0 -559
  230. package/src/cron/cron-scheduler.ts +0 -581
  231. package/src/cron/cron-store.ts +0 -166
  232. package/src/db/interfaces.ts +0 -60
  233. package/src/email/index.ts +0 -19
  234. package/src/email/smtp-email-service.ts +0 -123
  235. package/src/email/templates.ts +0 -470
  236. package/src/env.ts +0 -227
  237. package/src/functions/function-loader.ts +0 -116
  238. package/src/functions/function-routes.ts +0 -31
  239. package/src/history/history-routes.ts +0 -129
  240. package/src/index.ts +0 -70
  241. package/src/init/docs.ts +0 -47
  242. package/src/init/health.ts +0 -37
  243. package/src/init/middlewares.ts +0 -61
  244. package/src/init/shutdown.ts +0 -56
  245. package/src/init/storage.ts +0 -57
  246. package/src/init.ts +0 -1041
  247. package/src/serve-spa.ts +0 -89
  248. package/src/services/driver-registry.ts +0 -183
  249. package/src/services/routed-realtime-service.ts +0 -113
  250. package/src/services/webhook-service.ts +0 -155
  251. package/src/singleton.ts +0 -70
  252. package/src/storage/LocalStorageController.ts +0 -389
  253. package/src/storage/S3StorageController.ts +0 -300
  254. package/src/storage/image-transform.ts +0 -218
  255. package/src/storage/routes.ts +0 -368
  256. package/src/storage/storage-registry.ts +0 -188
  257. package/src/storage/tus-handler.ts +0 -315
  258. package/src/types/index.ts +0 -27
  259. package/src/utils/dev-port.ts +0 -196
  260. package/src/utils/logger.ts +0 -143
  261. package/src/utils/logging.ts +0 -40
  262. package/src/utils/request-id.ts +0 -40
  263. package/src/utils/request-logger.ts +0 -72
  264. package/test/api-generator.test.ts +0 -588
  265. package/test/api-key-permission-guard.test.ts +0 -144
  266. package/test/ast-schema-editor.test.ts +0 -89
  267. package/test/auth-middleware-hono.test.ts +0 -556
  268. package/test/auth-routes.test.ts +0 -1098
  269. package/test/backend-hooks-data.test.ts +0 -477
  270. package/test/custom-auth-adapter.test.ts +0 -189
  271. package/test/driver-registry.test.ts +0 -282
  272. package/test/email-templates.test.ts +0 -174
  273. package/test/env.test.ts +0 -128
  274. package/test/error-propagation.test.ts +0 -226
  275. package/test/errors-hono.test.ts +0 -133
  276. package/test/errors.test.ts +0 -155
  277. package/test/function-loader.test.ts +0 -127
  278. package/test/graphql-schema-generator.test.ts +0 -554
  279. package/test/jwt-security.test.ts +0 -182
  280. package/test/jwt.test.ts +0 -326
  281. package/test/mfa.test.ts +0 -197
  282. package/test/middleware.test.ts +0 -305
  283. package/test/multi-datasource-routing.test.ts +0 -113
  284. package/test/password.test.ts +0 -165
  285. package/test/query-parser.test.ts +0 -233
  286. package/test/rate-limiter.test.ts +0 -102
  287. package/test/reset-password-admin.test.ts +0 -113
  288. package/test/routed-realtime-service.test.ts +0 -86
  289. package/test/safe-compare.test.ts +0 -66
  290. package/test/singleton.test.ts +0 -59
  291. package/test/smtp-email-service.test.ts +0 -169
  292. package/test/storage-local.test.ts +0 -271
  293. package/test/storage-registry.test.ts +0 -282
  294. package/test/storage-routes.test.ts +0 -222
  295. package/test/storage-s3.test.ts +0 -304
  296. package/test/transform-auth-response.test.ts +0 -305
  297. package/test/webhook-service.test.ts +0 -260
  298. package/tsconfig.json +0 -54
  299. package/tsconfig.prod.json +0 -23
  300. package/vite.config.ts +0 -86
@@ -1,477 +0,0 @@
1
- /**
2
- * BackendHooks — Data (REST API) Integration Tests
3
- *
4
- * Verifies that DataHooks are correctly applied within RestApiGenerator
5
- * for all collection CRUD operations.
6
- */
7
-
8
- import { Hono } from "hono";
9
- import { RestApiGenerator } from "../src/api/rest/api-generator";
10
- import { errorHandler } from "../src/api/errors";
11
- import type { DataDriver } from "@rebasepro/types";
12
- import type { EntityCollection } from "@rebasepro/types";
13
- import type { DataHooks } from "@rebasepro/types";
14
-
15
- // ── Helpers ─────────────────────────────────────────────────────────────────
16
-
17
- function createMockDriver(): jest.Mocked<DataDriver> {
18
- return {
19
- key: "postgres",
20
- initialised: true,
21
- fetchCollection: jest.fn(),
22
- listenCollection: jest.fn(),
23
- fetchEntity: jest.fn(),
24
- listenEntity: jest.fn(),
25
- saveEntity: jest.fn(),
26
- deleteEntity: jest.fn(),
27
- checkUniqueField: jest.fn(),
28
- countEntities: jest.fn(),
29
- withAuth: jest.fn(),
30
- admin: {} as any
31
- } as unknown as jest.Mocked<DataDriver>;
32
- }
33
-
34
- const mockCollections: EntityCollection[] = [
35
- { slug: "products",
36
- name: "Products",
37
- singularName: "Product",
38
- properties: {} } as any,
39
- { slug: "orders",
40
- name: "Orders",
41
- singularName: "Order",
42
- properties: {} } as any
43
- ];
44
-
45
- function createApp(mockDriver: jest.Mocked<DataDriver>, hooks?: DataHooks) {
46
- const app = new Hono();
47
- app.onError(errorHandler);
48
- // RestApiGenerator.getScopedDriver reads from c.get("driver") which is
49
- // normally set by auth middleware. Replicate that here for tests.
50
- app.use("/api/*", async (c, next) => {
51
- c.set("driver", mockDriver);
52
- await next();
53
- });
54
- const generator = new RestApiGenerator(mockCollections, mockDriver, hooks);
55
- app.route("/api", generator.generateRoutes());
56
- return app;
57
- }
58
-
59
- // ═════════════════════════════════════════════════════════════════════════════
60
- // TESTS
61
- // ═════════════════════════════════════════════════════════════════════════════
62
-
63
- describe("DataHooks — REST API", () => {
64
- let mockDriver: jest.Mocked<DataDriver>;
65
-
66
- beforeEach(() => {
67
- mockDriver = createMockDriver();
68
- });
69
-
70
- // ── afterRead ─────────────────────────────────────────────────────
71
- describe("data.afterRead", () => {
72
- it("transforms entities in GET list response", async () => {
73
- const hooks: DataHooks = {
74
- afterRead(slug, entity) {
75
- // Mask price for non-premium entities
76
- if (slug === "products") {
77
- return { ...entity,
78
- price: "***" };
79
- }
80
- return entity;
81
- }
82
- };
83
- const app = createApp(mockDriver, hooks);
84
-
85
- mockDriver.fetchCollection.mockResolvedValue([
86
- { id: "p1",
87
- path: "products",
88
- values: { name: "Widget",
89
- price: 99 } } as any,
90
- { id: "p2",
91
- path: "products",
92
- values: { name: "Gadget",
93
- price: 199 } } as any
94
- ]);
95
- mockDriver.countEntities!.mockResolvedValue(2);
96
-
97
- const res = await app.request("/api/products");
98
- expect(res.status).toBe(200);
99
-
100
- const body = await res.json() as any;
101
- expect(body.data).toHaveLength(2);
102
- expect(body.data[0].price).toBe("***");
103
- expect(body.data[1].price).toBe("***");
104
- // Original field should still be there
105
- expect(body.data[0].name).toBe("Widget");
106
- });
107
-
108
- it("filters out entities by returning null", async () => {
109
- const hooks: DataHooks = {
110
- afterRead(slug, entity) {
111
- // Hide draft products
112
- if (entity.status === "draft") return null;
113
- return entity;
114
- }
115
- };
116
- const app = createApp(mockDriver, hooks);
117
-
118
- mockDriver.fetchCollection.mockResolvedValue([
119
- { id: "p1",
120
- path: "products",
121
- values: { name: "Published",
122
- status: "active" } } as any,
123
- { id: "p2",
124
- path: "products",
125
- values: { name: "Draft",
126
- status: "draft" } } as any,
127
- { id: "p3",
128
- path: "products",
129
- values: { name: "Also Published",
130
- status: "active" } } as any
131
- ]);
132
- mockDriver.countEntities!.mockResolvedValue(3);
133
-
134
- const res = await app.request("/api/products");
135
- const body = await res.json() as any;
136
- expect(body.data).toHaveLength(2);
137
- expect(body.data.map((d: any) => d.name)).toEqual(["Published", "Also Published"]);
138
- });
139
-
140
- it("transforms single entity GET response", async () => {
141
- const hooks: DataHooks = {
142
- afterRead(slug, entity) {
143
- return { ...entity,
144
- _readAt: "2024-01-01" };
145
- }
146
- };
147
- const app = createApp(mockDriver, hooks);
148
-
149
- mockDriver.fetchEntity.mockResolvedValue(
150
- { id: "p1",
151
- path: "products",
152
- values: { name: "Widget" } } as any
153
- );
154
-
155
- const res = await app.request("/api/products/p1");
156
- expect(res.status).toBe(200);
157
-
158
- const body = await res.json() as any;
159
- expect(body.name).toBe("Widget");
160
- expect(body._readAt).toBe("2024-01-01");
161
- });
162
-
163
- it("returns 404 when afterRead filters a single entity", async () => {
164
- const hooks: DataHooks = {
165
- afterRead(slug, entity) {
166
- if (entity.id === "hidden") return null;
167
- return entity;
168
- }
169
- };
170
- const app = createApp(mockDriver, hooks);
171
-
172
- mockDriver.fetchEntity.mockResolvedValue(
173
- { id: "hidden",
174
- path: "products",
175
- values: { name: "Secret" } } as any
176
- );
177
-
178
- const res = await app.request("/api/products/hidden");
179
- expect(res.status).toBe(404);
180
- });
181
-
182
- it("only affects targeted collection slug", async () => {
183
- const hooks: DataHooks = {
184
- afterRead(slug, entity) {
185
- if (slug === "products") {
186
- return { ...entity,
187
- hooked: true };
188
- }
189
- return entity;
190
- }
191
- };
192
- const app = createApp(mockDriver, hooks);
193
-
194
- // Products should be hooked
195
- mockDriver.fetchEntity.mockResolvedValueOnce(
196
- { id: "p1",
197
- path: "products",
198
- values: { name: "Widget" } } as any
199
- );
200
- const prodRes = await app.request("/api/products/p1");
201
- const prodBody = await prodRes.json() as any;
202
- expect(prodBody.hooked).toBe(true);
203
-
204
- // Orders should NOT be hooked
205
- mockDriver.fetchEntity.mockResolvedValueOnce(
206
- { id: "o1",
207
- path: "orders",
208
- values: { total: 42 } } as any
209
- );
210
- const orderRes = await app.request("/api/orders/o1");
211
- const orderBody = await orderRes.json() as any;
212
- expect(orderBody.hooked).toBeUndefined();
213
- });
214
- });
215
-
216
- // ── beforeSave ────────────────────────────────────────────────────
217
- describe("data.beforeSave", () => {
218
- it("transforms values before POST (create)", async () => {
219
- const hooks: DataHooks = {
220
- beforeSave(slug, values, entityId) {
221
- return { ...values,
222
- slug: values.name?.toString().toLowerCase().replace(/\s+/g, "-") };
223
- }
224
- };
225
- const app = createApp(mockDriver, hooks);
226
-
227
- mockDriver.saveEntity.mockResolvedValue(
228
- { id: "new-1",
229
- path: "products",
230
- values: { name: "Cool Widget",
231
- slug: "cool-widget" } } as any
232
- );
233
-
234
- const res = await app.request("/api/products", {
235
- method: "POST",
236
- headers: { "Content-Type": "application/json" },
237
- body: JSON.stringify({ name: "Cool Widget" })
238
- });
239
-
240
- expect(res.status).toBe(201);
241
- expect(mockDriver.saveEntity).toHaveBeenCalledWith(
242
- expect.objectContaining({
243
- values: expect.objectContaining({ slug: "cool-widget" })
244
- })
245
- );
246
- });
247
-
248
- it("transforms values before PUT (update)", async () => {
249
- const hooks: DataHooks = {
250
- beforeSave(slug, values, entityId) {
251
- // Add an updatedBy field
252
- return { ...values,
253
- updatedBy: "hook" };
254
- }
255
- };
256
- const app = createApp(mockDriver, hooks);
257
-
258
- mockDriver.fetchEntity.mockResolvedValue({ id: "p1",
259
- path: "products",
260
- values: {} } as any);
261
- mockDriver.saveEntity.mockResolvedValue(
262
- { id: "p1",
263
- path: "products",
264
- values: { name: "Updated",
265
- updatedBy: "hook" } } as any
266
- );
267
-
268
- const res = await app.request("/api/products/p1", {
269
- method: "PUT",
270
- headers: { "Content-Type": "application/json" },
271
- body: JSON.stringify({ name: "Updated" })
272
- });
273
-
274
- expect(res.status).toBe(200);
275
- expect(mockDriver.saveEntity).toHaveBeenCalledWith(
276
- expect.objectContaining({
277
- values: expect.objectContaining({ updatedBy: "hook" })
278
- })
279
- );
280
- });
281
-
282
- it("receives entityId=undefined on POST, actual id on PUT", async () => {
283
- const beforeSaveSpy = jest.fn((slug, values, entityId) => values);
284
- const hooks: DataHooks = { beforeSave: beforeSaveSpy };
285
- const app = createApp(mockDriver, hooks);
286
-
287
- // POST
288
- mockDriver.saveEntity.mockResolvedValueOnce(
289
- { id: "new-1",
290
- path: "products",
291
- values: { name: "A" } } as any
292
- );
293
- await app.request("/api/products", {
294
- method: "POST",
295
- headers: { "Content-Type": "application/json" },
296
- body: JSON.stringify({ name: "A" })
297
- });
298
- expect(beforeSaveSpy.mock.calls[0][2]).toBeUndefined(); // entityId
299
-
300
- // PUT
301
- mockDriver.fetchEntity.mockResolvedValueOnce({ id: "p1",
302
- path: "products",
303
- values: {} } as any);
304
- mockDriver.saveEntity.mockResolvedValueOnce(
305
- { id: "p1",
306
- path: "products",
307
- values: { name: "B" } } as any
308
- );
309
- await app.request("/api/products/p1", {
310
- method: "PUT",
311
- headers: { "Content-Type": "application/json" },
312
- body: JSON.stringify({ name: "B" })
313
- });
314
- expect(beforeSaveSpy.mock.calls[1][2]).toBe("p1"); // entityId
315
- });
316
-
317
- it("aborts save when beforeSave throws", async () => {
318
- const hooks: DataHooks = {
319
- beforeSave(slug, values) {
320
- if (!values.name) throw new Error("Name is required");
321
- return values;
322
- }
323
- };
324
- const app = createApp(mockDriver, hooks);
325
-
326
- const res = await app.request("/api/products", {
327
- method: "POST",
328
- headers: { "Content-Type": "application/json" },
329
- body: JSON.stringify({ price: 99 })
330
- });
331
-
332
- // Should get an error status
333
- expect(res.status).toBeGreaterThanOrEqual(400);
334
- expect(mockDriver.saveEntity).not.toHaveBeenCalled();
335
- });
336
- });
337
-
338
- // ── afterSave ─────────────────────────────────────────────────────
339
- describe("data.afterSave", () => {
340
- it("fires afterSave after POST", async () => {
341
- const afterSaveSpy = jest.fn();
342
- const hooks: DataHooks = { afterSave: afterSaveSpy };
343
- const app = createApp(mockDriver, hooks);
344
-
345
- mockDriver.saveEntity.mockResolvedValue(
346
- { id: "new-1",
347
- path: "products",
348
- values: { name: "Widget" } } as any
349
- );
350
-
351
- const res = await app.request("/api/products", {
352
- method: "POST",
353
- headers: { "Content-Type": "application/json" },
354
- body: JSON.stringify({ name: "Widget" })
355
- });
356
-
357
- expect(res.status).toBe(201);
358
- await new Promise(r => setTimeout(r, 50));
359
- expect(afterSaveSpy).toHaveBeenCalledTimes(1);
360
- expect(afterSaveSpy).toHaveBeenCalledWith(
361
- "products",
362
- expect.objectContaining({ id: "new-1",
363
- name: "Widget" }),
364
- expect.objectContaining({ method: "POST" })
365
- );
366
- });
367
-
368
- it("fires afterSave after PUT", async () => {
369
- const afterSaveSpy = jest.fn();
370
- const hooks: DataHooks = { afterSave: afterSaveSpy };
371
- const app = createApp(mockDriver, hooks);
372
-
373
- mockDriver.fetchEntity.mockResolvedValue({ id: "p1",
374
- path: "products",
375
- values: {} } as any);
376
- mockDriver.saveEntity.mockResolvedValue(
377
- { id: "p1",
378
- path: "products",
379
- values: { name: "Updated" } } as any
380
- );
381
-
382
- await app.request("/api/products/p1", {
383
- method: "PUT",
384
- headers: { "Content-Type": "application/json" },
385
- body: JSON.stringify({ name: "Updated" })
386
- });
387
-
388
- await new Promise(r => setTimeout(r, 50));
389
- expect(afterSaveSpy).toHaveBeenCalledWith(
390
- "products",
391
- expect.objectContaining({ id: "p1" }),
392
- expect.objectContaining({ method: "PUT" })
393
- );
394
- });
395
- });
396
-
397
- // ── beforeDelete / afterDelete ───────────────────────────────────
398
- describe("data.beforeDelete / afterDelete", () => {
399
- it("aborts deletion when beforeDelete throws", async () => {
400
- const hooks: DataHooks = {
401
- beforeDelete(slug, entityId) {
402
- if (entityId === "protected") {
403
- throw new Error("Cannot delete protected entity");
404
- }
405
- }
406
- };
407
- const app = createApp(mockDriver, hooks);
408
-
409
- mockDriver.fetchEntity.mockResolvedValue(
410
- { id: "protected",
411
- path: "products",
412
- values: {} } as any
413
- );
414
-
415
- const res = await app.request("/api/products/protected", { method: "DELETE" });
416
- expect(res.status).toBe(500);
417
- expect(mockDriver.deleteEntity).not.toHaveBeenCalled();
418
- });
419
-
420
- it("allows deletion when beforeDelete does not throw", async () => {
421
- const beforeDeleteSpy = jest.fn();
422
- const hooks: DataHooks = { beforeDelete: beforeDeleteSpy };
423
- const app = createApp(mockDriver, hooks);
424
-
425
- const existingEntity = { id: "p1",
426
- path: "products",
427
- values: {} } as any;
428
- mockDriver.fetchEntity.mockResolvedValue(existingEntity);
429
- mockDriver.deleteEntity.mockResolvedValue();
430
-
431
- const res = await app.request("/api/products/p1", { method: "DELETE" });
432
- expect(res.status).toBe(204);
433
- expect(beforeDeleteSpy).toHaveBeenCalledWith(
434
- "products", "p1", expect.objectContaining({ method: "DELETE" })
435
- );
436
- expect(mockDriver.deleteEntity).toHaveBeenCalled();
437
- });
438
-
439
- it("fires afterDelete after successful deletion", async () => {
440
- const afterDeleteSpy = jest.fn();
441
- const hooks: DataHooks = { afterDelete: afterDeleteSpy };
442
- const app = createApp(mockDriver, hooks);
443
-
444
- mockDriver.fetchEntity.mockResolvedValue({ id: "p1",
445
- path: "products",
446
- values: {} } as any);
447
- mockDriver.deleteEntity.mockResolvedValue();
448
-
449
- await app.request("/api/products/p1", { method: "DELETE" });
450
- await new Promise(r => setTimeout(r, 50));
451
-
452
- expect(afterDeleteSpy).toHaveBeenCalledWith(
453
- "products", "p1", expect.objectContaining({ method: "DELETE" })
454
- );
455
- });
456
- });
457
-
458
- // ── no hooks (passthrough) ──────────────────────────────────────
459
- describe("no hooks configured", () => {
460
- it("returns data unchanged when no hooks are provided", async () => {
461
- const app = createApp(mockDriver); // no hooks
462
- mockDriver.fetchCollection.mockResolvedValue([
463
- { id: "p1",
464
- path: "products",
465
- values: { name: "Widget" } } as any
466
- ]);
467
- mockDriver.countEntities!.mockResolvedValue(1);
468
-
469
- const res = await app.request("/api/products");
470
- expect(res.status).toBe(200);
471
-
472
- const body = await res.json() as any;
473
- expect(body.data).toHaveLength(1);
474
- expect(body.data[0].name).toBe("Widget");
475
- });
476
- });
477
- });
@@ -1,189 +0,0 @@
1
- import { createCustomAuthAdapter } from "../src/auth/custom-auth-adapter";
2
- import type { AuthenticatedUser, AuthResponsePayload, CustomAuthAdapterOptions } from "@rebasepro/types";
3
-
4
- const TEST_USER: AuthenticatedUser = {
5
- uid: "user-123",
6
- email: "test@example.com",
7
- displayName: "Test User",
8
- roles: ["editor"],
9
- isAdmin: false,
10
- rawToken: "tok_abc"
11
- };
12
-
13
- const ADMIN_USER: AuthenticatedUser = {
14
- uid: "admin-1",
15
- email: "admin@example.com",
16
- roles: ["admin"],
17
- isAdmin: true
18
- };
19
-
20
- describe("createCustomAuthAdapter", () => {
21
- it("sets the adapter id to 'custom'", () => {
22
- const adapter = createCustomAuthAdapter({
23
- verifyRequest: async () => null
24
- });
25
- expect(adapter.id).toBe("custom");
26
- });
27
-
28
- it("delegates verifyRequest to the provided function", async () => {
29
- const verifyRequest = jest.fn(async () => TEST_USER);
30
- const adapter = createCustomAuthAdapter({ verifyRequest });
31
- const req = new Request("http://localhost/api", {
32
- headers: { Authorization: "Bearer test-token" }
33
- });
34
- const result = await adapter.verifyRequest(req);
35
- expect(verifyRequest).toHaveBeenCalledWith(req);
36
- expect(result).toBe(TEST_USER);
37
- });
38
-
39
- it("returns null from verifyRequest when the user function returns null", async () => {
40
- const adapter = createCustomAuthAdapter({
41
- verifyRequest: async () => null
42
- });
43
- const result = await adapter.verifyRequest(new Request("http://localhost/"));
44
- expect(result).toBeNull();
45
- });
46
-
47
- // ── verifyToken (fallback via synthetic Request) ──────────────────
48
-
49
- it("synthesizes a Request when verifyToken is not provided", async () => {
50
- const verifyRequest = jest.fn(async (request: Request) => {
51
- const auth = request.headers.get("authorization");
52
- if (auth === "Bearer my-token") return TEST_USER;
53
- return null;
54
- });
55
- const adapter = createCustomAuthAdapter({ verifyRequest });
56
-
57
- expect(adapter.verifyToken).toBeDefined();
58
-
59
- const result = await adapter.verifyToken!("my-token");
60
- expect(result).toBe(TEST_USER);
61
-
62
- // Verify the synthetic request was created correctly
63
- expect(verifyRequest).toHaveBeenCalledTimes(1);
64
- const calledRequest = verifyRequest.mock.calls[0][0] as Request;
65
- expect(calledRequest.headers.get("authorization")).toBe("Bearer my-token");
66
- expect(calledRequest.url).toContain("_ws_auth");
67
- });
68
-
69
- it("returns null from fallback verifyToken for invalid token", async () => {
70
- const adapter = createCustomAuthAdapter({
71
- verifyRequest: async () => null
72
- });
73
- const result = await adapter.verifyToken!("bad-token");
74
- expect(result).toBeNull();
75
- });
76
-
77
- // ── verifyToken (direct passthrough) ──────────────────────────────
78
-
79
- it("uses the user-provided verifyToken when given", async () => {
80
- const customVerifyToken = jest.fn(async (token: string) => {
81
- if (token === "direct-token") return ADMIN_USER;
82
- return null;
83
- });
84
- const verifyRequest = jest.fn(async () => null);
85
- const adapter = createCustomAuthAdapter({
86
- verifyRequest,
87
- verifyToken: customVerifyToken
88
- });
89
-
90
- const result = await adapter.verifyToken!("direct-token");
91
- expect(result).toBe(ADMIN_USER);
92
-
93
- // verifyRequest should NOT be called — verifyToken takes precedence
94
- expect(verifyRequest).not.toHaveBeenCalled();
95
- });
96
-
97
- it("returns null from user-provided verifyToken for unknown token", async () => {
98
- const adapter = createCustomAuthAdapter({
99
- verifyRequest: async () => null,
100
- verifyToken: async () => null
101
- });
102
- const result = await adapter.verifyToken!("unknown");
103
- expect(result).toBeNull();
104
- });
105
-
106
- // ── Capabilities ──────────────────────────────────────────────────
107
-
108
- it("returns default capabilities when none are overridden", async () => {
109
- const adapter = createCustomAuthAdapter({
110
- verifyRequest: async () => null
111
- });
112
- const caps = await adapter.getCapabilities!();
113
- expect(caps).toEqual({
114
- hasBuiltInAuthRoutes: false,
115
- emailPasswordLogin: false,
116
- registration: false,
117
- passwordReset: false,
118
- sessionManagement: false,
119
- profileUpdate: false,
120
- emailVerification: false,
121
- magicLink: false,
122
- enabledProviders: []
123
- });
124
- });
125
-
126
- it("merges user-provided capabilities with defaults", async () => {
127
- const adapter = createCustomAuthAdapter({
128
- verifyRequest: async () => null,
129
- capabilities: { emailPasswordLogin: true,
130
- registration: true }
131
- });
132
- const caps = await adapter.getCapabilities!();
133
- expect(caps.emailPasswordLogin).toBe(true);
134
- expect(caps.registration).toBe(true);
135
- expect(caps.hasBuiltInAuthRoutes).toBe(false);
136
- });
137
-
138
- // ── Optional fields ───────────────────────────────────────────────
139
-
140
- it("passes through serviceKey", () => {
141
- const adapter = createCustomAuthAdapter({
142
- verifyRequest: async () => null,
143
- serviceKey: "sk_live_123"
144
- });
145
- expect(adapter.serviceKey).toBe("sk_live_123");
146
- });
147
-
148
- it("passes through userManagement when provided", () => {
149
- const userMgmt = {
150
- getUser: jest.fn(),
151
- listUsers: jest.fn(),
152
- createUser: jest.fn(),
153
- updateUser: jest.fn(),
154
- deleteUser: jest.fn()
155
- };
156
-
157
- const adapter = createCustomAuthAdapter({
158
- verifyRequest: async () => null,
159
- userManagement: userMgmt as unknown as CustomAuthAdapterOptions["userManagement"]
160
- });
161
-
162
- expect(adapter.userManagement).toBe(userMgmt);
163
- });
164
-
165
- it("omits userManagement when not provided", () => {
166
- const adapter = createCustomAuthAdapter({
167
- verifyRequest: async () => null
168
- });
169
- expect(adapter.userManagement).toBeUndefined();
170
- });
171
-
172
- // ── transformAuthResponse ────────────────────────────────────────────
173
-
174
- it("passes through transformAuthResponse when provided", () => {
175
- const transformFn = jest.fn(async (response: AuthResponsePayload) => response);
176
- const adapter = createCustomAuthAdapter({
177
- verifyRequest: async () => null,
178
- transformAuthResponse: transformFn
179
- });
180
- expect(adapter.transformAuthResponse).toBe(transformFn);
181
- });
182
-
183
- it("omits transformAuthResponse when not provided", () => {
184
- const adapter = createCustomAuthAdapter({
185
- verifyRequest: async () => null
186
- });
187
- expect(adapter.transformAuthResponse).toBeUndefined();
188
- });
189
- });