@rebasepro/server-core 0.0.1-canary.09e5ec5

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/LICENSE +6 -0
  2. package/README.md +40 -0
  3. package/build-errors.txt +52 -0
  4. package/coverage/clover.xml +3739 -0
  5. package/coverage/coverage-final.json +31 -0
  6. package/coverage/lcov-report/base.css +224 -0
  7. package/coverage/lcov-report/block-navigation.js +87 -0
  8. package/coverage/lcov-report/favicon.png +0 -0
  9. package/coverage/lcov-report/index.html +266 -0
  10. package/coverage/lcov-report/prettify.css +1 -0
  11. package/coverage/lcov-report/prettify.js +2 -0
  12. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  13. package/coverage/lcov-report/sorter.js +210 -0
  14. package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +952 -0
  15. package/coverage/lcov-report/src/api/errors.ts.html +472 -0
  16. package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +1069 -0
  17. package/coverage/lcov-report/src/api/graphql/index.html +116 -0
  18. package/coverage/lcov-report/src/api/index.html +176 -0
  19. package/coverage/lcov-report/src/api/openapi-generator.ts.html +565 -0
  20. package/coverage/lcov-report/src/api/rest/api-generator.ts.html +994 -0
  21. package/coverage/lcov-report/src/api/rest/index.html +131 -0
  22. package/coverage/lcov-report/src/api/rest/query-parser.ts.html +550 -0
  23. package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +202 -0
  24. package/coverage/lcov-report/src/api/server.ts.html +823 -0
  25. package/coverage/lcov-report/src/auth/admin-routes.ts.html +973 -0
  26. package/coverage/lcov-report/src/auth/index.html +176 -0
  27. package/coverage/lcov-report/src/auth/jwt.ts.html +574 -0
  28. package/coverage/lcov-report/src/auth/middleware.ts.html +745 -0
  29. package/coverage/lcov-report/src/auth/password.ts.html +310 -0
  30. package/coverage/lcov-report/src/auth/services.ts.html +2074 -0
  31. package/coverage/lcov-report/src/collections/index.html +116 -0
  32. package/coverage/lcov-report/src/collections/loader.ts.html +232 -0
  33. package/coverage/lcov-report/src/db/auth-schema.ts.html +523 -0
  34. package/coverage/lcov-report/src/db/data-transformer.ts.html +1753 -0
  35. package/coverage/lcov-report/src/db/entityService.ts.html +700 -0
  36. package/coverage/lcov-report/src/db/index.html +146 -0
  37. package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +4048 -0
  38. package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +883 -0
  39. package/coverage/lcov-report/src/db/services/RelationService.ts.html +3121 -0
  40. package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +442 -0
  41. package/coverage/lcov-report/src/db/services/index.html +176 -0
  42. package/coverage/lcov-report/src/db/services/index.ts.html +124 -0
  43. package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +1960 -0
  44. package/coverage/lcov-report/src/index.html +116 -0
  45. package/coverage/lcov-report/src/services/driver-registry.ts.html +631 -0
  46. package/coverage/lcov-report/src/services/index.html +131 -0
  47. package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +3025 -0
  48. package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +1189 -0
  49. package/coverage/lcov-report/src/storage/S3StorageController.ts.html +970 -0
  50. package/coverage/lcov-report/src/storage/index.html +161 -0
  51. package/coverage/lcov-report/src/storage/storage-registry.ts.html +646 -0
  52. package/coverage/lcov-report/src/storage/types.ts.html +451 -0
  53. package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +3082 -0
  54. package/coverage/lcov-report/src/utils/index.html +116 -0
  55. package/coverage/lcov.info +7179 -0
  56. package/dist/common/src/collections/CollectionRegistry.d.ts +56 -0
  57. package/dist/common/src/collections/index.d.ts +1 -0
  58. package/dist/common/src/data/buildRebaseData.d.ts +14 -0
  59. package/dist/common/src/index.d.ts +3 -0
  60. package/dist/common/src/util/builders.d.ts +57 -0
  61. package/dist/common/src/util/callbacks.d.ts +6 -0
  62. package/dist/common/src/util/collections.d.ts +11 -0
  63. package/dist/common/src/util/common.d.ts +2 -0
  64. package/dist/common/src/util/conditions.d.ts +26 -0
  65. package/dist/common/src/util/entities.d.ts +58 -0
  66. package/dist/common/src/util/enums.d.ts +3 -0
  67. package/dist/common/src/util/index.d.ts +16 -0
  68. package/dist/common/src/util/navigation_from_path.d.ts +34 -0
  69. package/dist/common/src/util/navigation_utils.d.ts +20 -0
  70. package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
  71. package/dist/common/src/util/paths.d.ts +14 -0
  72. package/dist/common/src/util/permissions.d.ts +5 -0
  73. package/dist/common/src/util/references.d.ts +2 -0
  74. package/dist/common/src/util/relations.d.ts +22 -0
  75. package/dist/common/src/util/resolutions.d.ts +72 -0
  76. package/dist/common/src/util/storage.d.ts +24 -0
  77. package/dist/index-DXVBFp5V.js +37 -0
  78. package/dist/index-DXVBFp5V.js.map +1 -0
  79. package/dist/index.es.js +49934 -0
  80. package/dist/index.es.js.map +1 -0
  81. package/dist/index.umd.js +49968 -0
  82. package/dist/index.umd.js.map +1 -0
  83. package/dist/server-core/src/api/ast-schema-editor.d.ts +21 -0
  84. package/dist/server-core/src/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
  85. package/dist/server-core/src/api/errors.d.ts +35 -0
  86. package/dist/server-core/src/api/graphql/graphql-schema-generator.d.ts +35 -0
  87. package/dist/server-core/src/api/graphql/index.d.ts +1 -0
  88. package/dist/server-core/src/api/index.d.ts +9 -0
  89. package/dist/server-core/src/api/openapi-generator.d.ts +16 -0
  90. package/dist/server-core/src/api/rest/api-generator.d.ts +64 -0
  91. package/dist/server-core/src/api/rest/index.d.ts +1 -0
  92. package/dist/server-core/src/api/rest/query-parser.d.ts +9 -0
  93. package/dist/server-core/src/api/schema-editor-routes.d.ts +3 -0
  94. package/dist/server-core/src/api/server.d.ts +40 -0
  95. package/dist/server-core/src/api/types.d.ts +90 -0
  96. package/dist/server-core/src/auth/admin-routes.d.ts +16 -0
  97. package/dist/server-core/src/auth/apple-oauth.d.ts +30 -0
  98. package/dist/server-core/src/auth/bitbucket-oauth.d.ts +11 -0
  99. package/dist/server-core/src/auth/discord-oauth.d.ts +14 -0
  100. package/dist/server-core/src/auth/facebook-oauth.d.ts +14 -0
  101. package/dist/server-core/src/auth/github-oauth.d.ts +15 -0
  102. package/dist/server-core/src/auth/gitlab-oauth.d.ts +13 -0
  103. package/dist/server-core/src/auth/google-oauth.d.ts +14 -0
  104. package/dist/server-core/src/auth/index.d.ts +23 -0
  105. package/dist/server-core/src/auth/interfaces.d.ts +309 -0
  106. package/dist/server-core/src/auth/jwt.d.ts +43 -0
  107. package/dist/server-core/src/auth/linkedin-oauth.d.ts +18 -0
  108. package/dist/server-core/src/auth/microsoft-oauth.d.ts +16 -0
  109. package/dist/server-core/src/auth/middleware.d.ts +81 -0
  110. package/dist/server-core/src/auth/password.d.ts +22 -0
  111. package/dist/server-core/src/auth/rate-limiter.d.ts +31 -0
  112. package/dist/server-core/src/auth/routes.d.ts +27 -0
  113. package/dist/server-core/src/auth/slack-oauth.d.ts +12 -0
  114. package/dist/server-core/src/auth/spotify-oauth.d.ts +12 -0
  115. package/dist/server-core/src/auth/twitter-oauth.d.ts +18 -0
  116. package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
  117. package/dist/server-core/src/collections/BackendCollectionRegistry.d.ts +13 -0
  118. package/dist/server-core/src/collections/loader.d.ts +5 -0
  119. package/dist/server-core/src/cron/cron-loader.d.ts +17 -0
  120. package/dist/server-core/src/cron/cron-routes.d.ts +14 -0
  121. package/dist/server-core/src/cron/cron-scheduler.d.ts +61 -0
  122. package/dist/server-core/src/cron/cron-store.d.ts +32 -0
  123. package/dist/server-core/src/cron/index.d.ts +6 -0
  124. package/dist/server-core/src/db/interfaces.d.ts +18 -0
  125. package/dist/server-core/src/email/index.d.ts +6 -0
  126. package/dist/server-core/src/email/smtp-email-service.d.ts +25 -0
  127. package/dist/server-core/src/email/templates.d.ts +42 -0
  128. package/dist/server-core/src/email/types.d.ts +107 -0
  129. package/dist/server-core/src/functions/function-loader.d.ts +17 -0
  130. package/dist/server-core/src/functions/function-routes.d.ts +10 -0
  131. package/dist/server-core/src/functions/index.d.ts +3 -0
  132. package/dist/server-core/src/history/history-routes.d.ts +23 -0
  133. package/dist/server-core/src/history/index.d.ts +1 -0
  134. package/dist/server-core/src/index.d.ts +29 -0
  135. package/dist/server-core/src/init.d.ts +159 -0
  136. package/dist/server-core/src/serve-spa.d.ts +30 -0
  137. package/dist/server-core/src/services/driver-registry.d.ts +78 -0
  138. package/dist/server-core/src/singleton.d.ts +35 -0
  139. package/dist/server-core/src/storage/LocalStorageController.d.ts +46 -0
  140. package/dist/server-core/src/storage/S3StorageController.d.ts +36 -0
  141. package/dist/server-core/src/storage/index.d.ts +25 -0
  142. package/dist/server-core/src/storage/routes.d.ts +38 -0
  143. package/dist/server-core/src/storage/storage-registry.d.ts +78 -0
  144. package/dist/server-core/src/storage/types.d.ts +103 -0
  145. package/dist/server-core/src/types/index.d.ts +11 -0
  146. package/dist/server-core/src/utils/dev-port.d.ts +35 -0
  147. package/dist/server-core/src/utils/logger.d.ts +31 -0
  148. package/dist/server-core/src/utils/logging.d.ts +9 -0
  149. package/dist/server-core/src/utils/request-logger.d.ts +19 -0
  150. package/dist/server-core/src/utils/sql.d.ts +27 -0
  151. package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
  152. package/dist/types/src/controllers/auth.d.ts +119 -0
  153. package/dist/types/src/controllers/client.d.ts +170 -0
  154. package/dist/types/src/controllers/collection_registry.d.ts +45 -0
  155. package/dist/types/src/controllers/customization_controller.d.ts +60 -0
  156. package/dist/types/src/controllers/data.d.ts +168 -0
  157. package/dist/types/src/controllers/data_driver.d.ts +160 -0
  158. package/dist/types/src/controllers/database_admin.d.ts +11 -0
  159. package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
  160. package/dist/types/src/controllers/effective_role.d.ts +4 -0
  161. package/dist/types/src/controllers/email.d.ts +34 -0
  162. package/dist/types/src/controllers/index.d.ts +18 -0
  163. package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
  164. package/dist/types/src/controllers/navigation.d.ts +213 -0
  165. package/dist/types/src/controllers/registry.d.ts +54 -0
  166. package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
  167. package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
  168. package/dist/types/src/controllers/snackbar.d.ts +24 -0
  169. package/dist/types/src/controllers/storage.d.ts +171 -0
  170. package/dist/types/src/index.d.ts +4 -0
  171. package/dist/types/src/rebase_context.d.ts +105 -0
  172. package/dist/types/src/types/backend.d.ts +536 -0
  173. package/dist/types/src/types/builders.d.ts +15 -0
  174. package/dist/types/src/types/chips.d.ts +5 -0
  175. package/dist/types/src/types/collections.d.ts +856 -0
  176. package/dist/types/src/types/cron.d.ts +102 -0
  177. package/dist/types/src/types/data_source.d.ts +64 -0
  178. package/dist/types/src/types/entities.d.ts +145 -0
  179. package/dist/types/src/types/entity_actions.d.ts +98 -0
  180. package/dist/types/src/types/entity_callbacks.d.ts +173 -0
  181. package/dist/types/src/types/entity_link_builder.d.ts +7 -0
  182. package/dist/types/src/types/entity_overrides.d.ts +10 -0
  183. package/dist/types/src/types/entity_views.d.ts +61 -0
  184. package/dist/types/src/types/export_import.d.ts +21 -0
  185. package/dist/types/src/types/index.d.ts +23 -0
  186. package/dist/types/src/types/locales.d.ts +4 -0
  187. package/dist/types/src/types/modify_collections.d.ts +5 -0
  188. package/dist/types/src/types/plugins.d.ts +279 -0
  189. package/dist/types/src/types/properties.d.ts +1176 -0
  190. package/dist/types/src/types/property_config.d.ts +70 -0
  191. package/dist/types/src/types/relations.d.ts +336 -0
  192. package/dist/types/src/types/slots.d.ts +252 -0
  193. package/dist/types/src/types/translations.d.ts +870 -0
  194. package/dist/types/src/types/user_management_delegate.d.ts +121 -0
  195. package/dist/types/src/types/websockets.d.ts +78 -0
  196. package/dist/types/src/users/index.d.ts +2 -0
  197. package/dist/types/src/users/roles.d.ts +22 -0
  198. package/dist/types/src/users/user.d.ts +46 -0
  199. package/history_diff.log +385 -0
  200. package/jest.config.cjs +16 -0
  201. package/package.json +86 -0
  202. package/scratch.ts +9 -0
  203. package/src/api/ast-schema-editor.ts +289 -0
  204. package/src/api/collections_for_test/callbacks_test_collection.ts +60 -0
  205. package/src/api/errors.ts +179 -0
  206. package/src/api/graphql/graphql-schema-generator.ts +336 -0
  207. package/src/api/graphql/index.ts +2 -0
  208. package/src/api/index.ts +11 -0
  209. package/src/api/openapi-generator.ts +715 -0
  210. package/src/api/rest/api-generator.ts +472 -0
  211. package/src/api/rest/index.ts +2 -0
  212. package/src/api/rest/query-parser.ts +155 -0
  213. package/src/api/schema-editor-routes.ts +41 -0
  214. package/src/api/server.ts +248 -0
  215. package/src/api/types.ts +90 -0
  216. package/src/auth/admin-routes.ts +529 -0
  217. package/src/auth/apple-oauth.ts +130 -0
  218. package/src/auth/bitbucket-oauth.ts +82 -0
  219. package/src/auth/discord-oauth.ts +83 -0
  220. package/src/auth/facebook-oauth.ts +72 -0
  221. package/src/auth/github-oauth.ts +110 -0
  222. package/src/auth/gitlab-oauth.ts +70 -0
  223. package/src/auth/google-oauth.ts +48 -0
  224. package/src/auth/index.ts +34 -0
  225. package/src/auth/interfaces.ts +363 -0
  226. package/src/auth/jwt.ts +181 -0
  227. package/src/auth/linkedin-oauth.ts +81 -0
  228. package/src/auth/microsoft-oauth.ts +88 -0
  229. package/src/auth/middleware.ts +384 -0
  230. package/src/auth/password.ts +77 -0
  231. package/src/auth/rate-limiter.ts +129 -0
  232. package/src/auth/routes.ts +788 -0
  233. package/src/auth/slack-oauth.ts +71 -0
  234. package/src/auth/spotify-oauth.ts +67 -0
  235. package/src/auth/twitter-oauth.ts +120 -0
  236. package/src/bootstrappers/index.ts +1 -0
  237. package/src/collections/BackendCollectionRegistry.ts +20 -0
  238. package/src/collections/loader.ts +49 -0
  239. package/src/cron/cron-loader.ts +89 -0
  240. package/src/cron/cron-routes.test.ts +265 -0
  241. package/src/cron/cron-routes.ts +85 -0
  242. package/src/cron/cron-scheduler.test.ts +421 -0
  243. package/src/cron/cron-scheduler.ts +413 -0
  244. package/src/cron/cron-store.ts +163 -0
  245. package/src/cron/index.ts +6 -0
  246. package/src/db/interfaces.ts +60 -0
  247. package/src/email/index.ts +18 -0
  248. package/src/email/smtp-email-service.ts +91 -0
  249. package/src/email/templates.ts +388 -0
  250. package/src/email/types.ts +105 -0
  251. package/src/functions/function-loader.ts +119 -0
  252. package/src/functions/function-routes.ts +31 -0
  253. package/src/functions/index.ts +3 -0
  254. package/src/history/history-routes.ts +129 -0
  255. package/src/history/index.ts +2 -0
  256. package/src/index.ts +66 -0
  257. package/src/init.ts +727 -0
  258. package/src/serve-spa.ts +81 -0
  259. package/src/services/driver-registry.ts +182 -0
  260. package/src/singleton.test.ts +28 -0
  261. package/src/singleton.ts +70 -0
  262. package/src/storage/LocalStorageController.ts +365 -0
  263. package/src/storage/S3StorageController.ts +298 -0
  264. package/src/storage/index.ts +43 -0
  265. package/src/storage/routes.ts +264 -0
  266. package/src/storage/storage-registry.ts +187 -0
  267. package/src/storage/types.ts +134 -0
  268. package/src/types/index.ts +27 -0
  269. package/src/utils/dev-port.ts +176 -0
  270. package/src/utils/logger.ts +143 -0
  271. package/src/utils/logging.ts +38 -0
  272. package/src/utils/request-logger.ts +66 -0
  273. package/src/utils/sql.ts +38 -0
  274. package/test/admin-routes.test.ts +640 -0
  275. package/test/api-generator.test.ts +501 -0
  276. package/test/ast-schema-editor.test.ts +63 -0
  277. package/test/auth-middleware-hono.test.ts +556 -0
  278. package/test/auth-routes.test.ts +1047 -0
  279. package/test/driver-registry.test.ts +282 -0
  280. package/test/error-propagation.test.ts +226 -0
  281. package/test/errors-hono.test.ts +133 -0
  282. package/test/errors.test.ts +155 -0
  283. package/test/jwt-security.test.ts +182 -0
  284. package/test/jwt.test.ts +324 -0
  285. package/test/middleware.test.ts +300 -0
  286. package/test/password.test.ts +165 -0
  287. package/test/query-parser.test.ts +263 -0
  288. package/test/rate-limiter.test.ts +102 -0
  289. package/test/safe-compare.test.ts +66 -0
  290. package/test/singleton.test.ts +59 -0
  291. package/test/storage-local.test.ts +271 -0
  292. package/test/storage-registry.test.ts +282 -0
  293. package/test/storage-routes.test.ts +222 -0
  294. package/test/storage-s3.test.ts +304 -0
  295. package/test-ast.ts +28 -0
  296. package/test.ts +6 -0
  297. package/test_output.txt +1133 -0
  298. package/tsconfig.json +49 -0
  299. package/tsconfig.prod.json +20 -0
  300. package/vite.config.ts +80 -0
@@ -0,0 +1,222 @@
1
+ import { describe, it, expect, beforeEach, afterEach, jest as vi } from "@jest/globals";
2
+ /**
3
+ * Tests for storage routes — specifically the sub-router wildcard extraction
4
+ * that broke when Hono's c.req.param('*') stopped working in mounted sub-routers.
5
+ *
6
+ * These tests use Hono's built-in `app.fetch()` to simulate requests without
7
+ * needing a running HTTP server, which keeps them fast and deterministic.
8
+ */
9
+ import * as fs from "fs";
10
+ import * as path from "path";
11
+ import * as os from "os";
12
+ import { Hono } from "hono";
13
+ import { HonoEnv } from "../src/api/types";
14
+ import { errorHandler } from "../src/api/errors";
15
+ import { LocalStorageController } from "../src/storage/LocalStorageController";
16
+ import { createStorageRoutes, extractWildcardPath } from "../src/storage/routes";
17
+
18
+ // ──────────────────────────────────────────────────────────────────────
19
+ // Unit tests for extractWildcardPath
20
+ // ──────────────────────────────────────────────────────────────────────
21
+
22
+ describe("extractWildcardPath", () => {
23
+ it("should extract path after the route prefix", () => {
24
+ const result = extractWildcardPath({
25
+ req: {
26
+ path: "/api/storage/metadata/default/author_pictures/photo.jpg",
27
+ routePath: "/api/storage/metadata/*"
28
+ }
29
+ });
30
+ expect(result).toBe("default/author_pictures/photo.jpg");
31
+ });
32
+
33
+ it("should extract simple file path", () => {
34
+ const result = extractWildcardPath({
35
+ req: {
36
+ path: "/api/storage/file/testfile.jpg",
37
+ routePath: "/api/storage/file/*"
38
+ }
39
+ });
40
+ expect(result).toBe("testfile.jpg");
41
+ });
42
+
43
+ it("should return empty string for trailing-slash-only path", () => {
44
+ const result = extractWildcardPath({
45
+ req: {
46
+ path: "/api/storage/metadata/",
47
+ routePath: "/api/storage/metadata/*"
48
+ }
49
+ });
50
+ expect(result).toBe("");
51
+ });
52
+
53
+ it("should return empty string when path equals prefix (no trailing slash)", () => {
54
+ const result = extractWildcardPath({
55
+ req: {
56
+ path: "/api/storage/metadata",
57
+ routePath: "/api/storage/metadata/*"
58
+ }
59
+ });
60
+ expect(result).toBe("");
61
+ });
62
+
63
+ it("should handle deeply nested paths", () => {
64
+ const result = extractWildcardPath({
65
+ req: {
66
+ path: "/api/storage/file/bucket/a/b/c/d/file.png",
67
+ routePath: "/api/storage/file/*"
68
+ }
69
+ });
70
+ expect(result).toBe("bucket/a/b/c/d/file.png");
71
+ });
72
+
73
+ it("should handle paths with spaces and special chars", () => {
74
+ const result = extractWildcardPath({
75
+ req: {
76
+ path: "/api/storage/file/default/photos/my%20file%20(1).png",
77
+ routePath: "/api/storage/file/*"
78
+ }
79
+ });
80
+ expect(result).toBe("default/photos/my%20file%20(1).png");
81
+ });
82
+ });
83
+
84
+ // ──────────────────────────────────────────────────────────────────────
85
+ // Integration tests: storage routes mounted as Hono sub-router
86
+ // ──────────────────────────────────────────────────────────────────────
87
+
88
+ describe("Storage routes (sub-router integration)", () => {
89
+ let app: Hono<HonoEnv>;
90
+ let tempDir: string;
91
+ let controller: LocalStorageController;
92
+
93
+ beforeEach(async () => {
94
+ tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "rebase-routes-test-"));
95
+ controller = new LocalStorageController({ basePath: tempDir });
96
+
97
+ // Upload a test file so we have something to serve
98
+ const content = Buffer.from("Hello test file");
99
+ const file = new File([content], "test.txt", { type: "text/plain" });
100
+ await controller.putObject({ file,
101
+ key: "photos/test.txt" });
102
+
103
+ // Create the Hono app with storage routes mounted as a SUB-ROUTER
104
+ // (this is the exact pattern that caused the bug)
105
+ app = new Hono<HonoEnv>();
106
+ app.onError(errorHandler); // required to convert ApiError throws to proper HTTP responses
107
+ const storageRoutes = createStorageRoutes({
108
+ controller,
109
+ requireAuth: false // skip auth for tests
110
+ });
111
+ app.route("/api/storage", storageRoutes);
112
+ });
113
+
114
+ afterEach(async () => {
115
+ await fs.promises.rm(tempDir, { recursive: true,
116
+ force: true });
117
+ });
118
+
119
+ describe("GET /metadata/*", () => {
120
+ it("should return metadata for a valid file path", async () => {
121
+ const res = await app.fetch(
122
+ new Request("http://localhost/api/storage/metadata/default/photos/test.txt")
123
+ );
124
+
125
+ expect(res.status).toBe(200);
126
+ const body = await res.json() as { success: boolean; data: { contentType: string } };
127
+ expect(body.success).toBe(true);
128
+ expect(body.data).toBeDefined();
129
+ expect(body.data.contentType).toBe("text/plain");
130
+ });
131
+
132
+ it("should return metadata without explicit bucket prefix", async () => {
133
+ const res = await app.fetch(
134
+ new Request("http://localhost/api/storage/metadata/photos/test.txt")
135
+ );
136
+
137
+ expect(res.status).toBe(200);
138
+ const body = await res.json() as { success: boolean; data: { contentType: string } };
139
+ expect(body.success).toBe(true);
140
+ expect(body.data).toBeDefined();
141
+ });
142
+
143
+ it("should return 404 for empty path", async () => {
144
+ const res = await app.fetch(
145
+ new Request("http://localhost/api/storage/metadata/")
146
+ );
147
+ expect(res.status).toBe(404);
148
+ });
149
+
150
+ it("should return 404 for non-existent file", async () => {
151
+ const res = await app.fetch(
152
+ new Request("http://localhost/api/storage/metadata/default/nope/missing.txt")
153
+ );
154
+ expect(res.status).toBe(404);
155
+ });
156
+ });
157
+
158
+ describe("GET /file/*", () => {
159
+ it("should serve file content for a valid path", async () => {
160
+ const res = await app.fetch(
161
+ new Request("http://localhost/api/storage/file/default/photos/test.txt")
162
+ );
163
+
164
+ expect(res.status).toBe(200);
165
+ const body = await res.text();
166
+ expect(body).toBe("Hello test file");
167
+ });
168
+
169
+ it("should serve file without explicit bucket prefix", async () => {
170
+ const res = await app.fetch(
171
+ new Request("http://localhost/api/storage/file/photos/test.txt")
172
+ );
173
+
174
+ expect(res.status).toBe(200);
175
+ const body = await res.text();
176
+ expect(body).toBe("Hello test file");
177
+ });
178
+
179
+ it("should return 404 for empty path", async () => {
180
+ const res = await app.fetch(
181
+ new Request("http://localhost/api/storage/file/")
182
+ );
183
+ expect(res.status).toBe(404);
184
+ });
185
+
186
+ it("should return 404 for non-existent file", async () => {
187
+ const res = await app.fetch(
188
+ new Request("http://localhost/api/storage/file/default/nope/missing.txt")
189
+ );
190
+ expect(res.status).toBe(404);
191
+ });
192
+ });
193
+
194
+ describe("DELETE /file/*", () => {
195
+ it("should delete an existing file", async () => {
196
+ // Upload another file to delete
197
+ const file = new File([Buffer.from("delete me")], "deleteme.txt", { type: "text/plain" });
198
+ await controller.putObject({ file,
199
+ key: "photos/deleteme.txt" });
200
+
201
+ const res = await app.fetch(
202
+ new Request("http://localhost/api/storage/file/default/photos/deleteme.txt", { method: "DELETE" })
203
+ );
204
+
205
+ expect(res.status).toBe(200);
206
+ const body = await res.json() as { success: boolean };
207
+ expect(body.success).toBe(true);
208
+
209
+ // Verify the file is actually gone
210
+ const filePath = path.join(tempDir, "default", "photos", "deleteme.txt");
211
+ const exists = await fs.promises.access(filePath).then(() => true).catch(() => false);
212
+ expect(exists).toBe(false);
213
+ });
214
+
215
+ it("should handle empty path gracefully", async () => {
216
+ const res = await app.fetch(
217
+ new Request("http://localhost/api/storage/file/", { method: "DELETE" })
218
+ );
219
+ expect(res.status).toBe(200);
220
+ });
221
+ });
222
+ });
@@ -0,0 +1,304 @@
1
+ import { describe, it, expect, beforeEach, afterEach, jest as vi } from "@jest/globals";
2
+ import { S3StorageController } from "../src/storage/S3StorageController";
3
+
4
+ // Mock the AWS SDK before importing the controller
5
+ vi.mock("@aws-sdk/client-s3", () => ({
6
+ S3Client: vi.fn().mockImplementation(function() {
7
+ return { send: vi.fn() };
8
+ }),
9
+ PutObjectCommand: vi.fn(),
10
+ GetObjectCommand: vi.fn(),
11
+ DeleteObjectCommand: vi.fn(),
12
+ ListObjectsV2Command: vi.fn(),
13
+ HeadObjectCommand: vi.fn()
14
+ }));
15
+
16
+ vi.mock("@aws-sdk/s3-request-presigner", () => ({
17
+ getSignedUrl: vi.fn().mockResolvedValue("https://presigned-url.example.com")
18
+ }));
19
+
20
+ import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
21
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
22
+
23
+ describe("S3StorageController", () => {
24
+ let controller: S3StorageController;
25
+ let mockSend: vi.Mock;
26
+
27
+ const defaultConfig = {
28
+ bucket: "test-bucket",
29
+ region: "us-east-1",
30
+ accessKeyId: "test-access-key",
31
+ secretAccessKey: "test-secret-key"
32
+ };
33
+
34
+ beforeEach(() => {
35
+ vi.clearAllMocks();
36
+ mockSend = vi.fn();
37
+ vi.mocked(S3Client).mockImplementation(function() {
38
+ return { send: mockSend };
39
+ } as any);
40
+ controller = new S3StorageController(defaultConfig);
41
+ });
42
+
43
+ describe("constructor", () => {
44
+ it("should initialize S3 client with credentials", () => {
45
+ expect(S3Client).toHaveBeenCalledWith(expect.objectContaining({
46
+ region: "us-east-1",
47
+ credentials: {
48
+ accessKeyId: "test-access-key",
49
+ secretAccessKey: "test-secret-key"
50
+ }
51
+ }));
52
+ });
53
+
54
+ it("should initialize with endpoint for S3-compatible services", () => {
55
+ vi.clearAllMocks();
56
+ new S3StorageController({
57
+ ...defaultConfig,
58
+ endpoint: "https://minio.example.com",
59
+ forcePathStyle: true
60
+ });
61
+
62
+ expect(S3Client).toHaveBeenCalledWith(expect.objectContaining({
63
+ endpoint: "https://minio.example.com",
64
+ forcePathStyle: true
65
+ }));
66
+ });
67
+
68
+ it("should return 's3' as type", () => {
69
+ expect(controller.getType()).toBe("s3");
70
+ });
71
+ });
72
+
73
+ describe("putObject", () => {
74
+ it("should upload file using PutObjectCommand", async () => {
75
+ const content = Buffer.from("Test content");
76
+ const file = new File([content], "test.txt", { type: "text/plain" });
77
+
78
+ mockSend.mockResolvedValueOnce({});
79
+
80
+ const result = await controller.putObject({
81
+ file,
82
+ key: "uploads/test.txt"
83
+ });
84
+
85
+ expect(PutObjectCommand).toHaveBeenCalledWith(expect.objectContaining({
86
+ Bucket: "test-bucket",
87
+ Key: "uploads/test.txt",
88
+ ContentType: "text/plain"
89
+ }));
90
+ expect(mockSend).toHaveBeenCalled();
91
+ expect(result.key).toBe("uploads/test.txt");
92
+ });
93
+
94
+ it("should include metadata in upload", async () => {
95
+ const file = new File(["content"], "test.txt", { type: "text/plain" });
96
+
97
+ mockSend.mockResolvedValueOnce({});
98
+
99
+ await controller.putObject({
100
+ file,
101
+ key: "uploads/test.txt",
102
+ metadata: { customKey: "customValue" }
103
+ });
104
+
105
+ expect(PutObjectCommand).toHaveBeenCalledWith(expect.objectContaining({
106
+ Metadata: expect.objectContaining({
107
+ customKey: "customValue" // Keys are passed as-is, S3 handles casing
108
+ })
109
+ }));
110
+ });
111
+
112
+ it("should use custom bucket when specified", async () => {
113
+ const file = new File(["content"], "test.txt", { type: "text/plain" });
114
+
115
+ mockSend.mockResolvedValueOnce({});
116
+
117
+ await controller.putObject({
118
+ file,
119
+ key: "uploads/test.txt",
120
+ bucket: "custom-bucket"
121
+ });
122
+
123
+ expect(PutObjectCommand).toHaveBeenCalledWith(expect.objectContaining({
124
+ Bucket: "custom-bucket"
125
+ }));
126
+ });
127
+ });
128
+
129
+ describe("getSignedUrl", () => {
130
+ it("should generate presigned URL", async () => {
131
+ mockSend.mockResolvedValueOnce({
132
+ ContentType: "text/plain",
133
+ ContentLength: 100,
134
+ LastModified: new Date(),
135
+ Metadata: {}
136
+ });
137
+
138
+ const result = await controller.getSignedUrl("uploads/test.txt");
139
+
140
+ expect(getSignedUrl).toHaveBeenCalled();
141
+ expect(result.url).toBe("https://presigned-url.example.com");
142
+ });
143
+
144
+ it("should include metadata in download config", async () => {
145
+ const lastModified = new Date("2024-01-15T10:00:00Z");
146
+ mockSend.mockResolvedValueOnce({
147
+ ContentType: "image/png",
148
+ ContentLength: 5000,
149
+ LastModified: lastModified,
150
+ Metadata: { originalName: "photo.png" }
151
+ });
152
+
153
+ const result = await controller.getSignedUrl("images/photo.png");
154
+
155
+ expect(result.metadata).toBeDefined();
156
+ expect(result.metadata?.contentType).toBe("image/png");
157
+ });
158
+ });
159
+
160
+ describe("getObject", () => {
161
+ it("should return null for non-existent file", async () => {
162
+ const error = new Error("NoSuchKey");
163
+ (error as any).name = "NoSuchKey";
164
+ mockSend.mockRejectedValueOnce(error);
165
+
166
+ const result = await controller.getObject("nonexistent.txt");
167
+
168
+ expect(result).toBeNull();
169
+ });
170
+ });
171
+
172
+ describe("deleteObject", () => {
173
+ it("should delete file from S3", async () => {
174
+ mockSend.mockResolvedValueOnce({});
175
+
176
+ await controller.deleteObject("uploads/test.txt");
177
+
178
+ expect(DeleteObjectCommand).toHaveBeenCalledWith(expect.objectContaining({
179
+ Bucket: "test-bucket",
180
+ Key: "uploads/test.txt"
181
+ }));
182
+ });
183
+
184
+ it("should not throw for non-existent file", async () => {
185
+ mockSend.mockResolvedValueOnce({});
186
+
187
+ await expect(controller.deleteObject("nonexistent.txt")).resolves.not.toThrow();
188
+ });
189
+ });
190
+
191
+ describe("list", () => {
192
+ it("should list objects in S3 bucket", async () => {
193
+ mockSend.mockResolvedValueOnce({
194
+ Contents: [
195
+ { Key: "uploads/file1.txt",
196
+ Size: 100,
197
+ LastModified: new Date() },
198
+ { Key: "uploads/file2.txt",
199
+ Size: 200,
200
+ LastModified: new Date() }
201
+ ],
202
+ CommonPrefixes: [],
203
+ IsTruncated: false
204
+ });
205
+
206
+ const result = await controller.listObjects("uploads");
207
+
208
+ expect(ListObjectsV2Command).toHaveBeenCalledWith(expect.objectContaining({
209
+ Bucket: "test-bucket",
210
+ Prefix: "uploads" // Implementation doesn't add trailing slash
211
+ }));
212
+ expect(result.items).toHaveLength(2);
213
+ });
214
+
215
+ it("should handle pagination with maxResults", async () => {
216
+ mockSend.mockResolvedValueOnce({
217
+ Contents: [
218
+ { Key: "uploads/file1.txt",
219
+ Size: 100,
220
+ LastModified: new Date() }
221
+ ],
222
+ CommonPrefixes: [],
223
+ IsTruncated: true,
224
+ NextContinuationToken: "token123"
225
+ });
226
+
227
+ const result = await controller.listObjects("uploads", { maxResults: 1 });
228
+
229
+ expect(ListObjectsV2Command).toHaveBeenCalledWith(expect.objectContaining({
230
+ MaxKeys: 1
231
+ }));
232
+ expect(result.nextPageToken).toBe("token123");
233
+ });
234
+
235
+ it("should include common prefixes as subdirectories", async () => {
236
+ mockSend.mockResolvedValueOnce({
237
+ Contents: [],
238
+ CommonPrefixes: [
239
+ { Prefix: "uploads/images/" },
240
+ { Prefix: "uploads/documents/" }
241
+ ],
242
+ IsTruncated: false
243
+ });
244
+
245
+ const result = await controller.listObjects("uploads");
246
+
247
+ expect(result.prefixes).toHaveLength(2);
248
+ });
249
+
250
+ it("should use pageToken for continuation", async () => {
251
+ mockSend.mockResolvedValueOnce({
252
+ Contents: [],
253
+ CommonPrefixes: [],
254
+ IsTruncated: false
255
+ });
256
+
257
+ await controller.listObjects("uploads", { pageToken: "continue-token" });
258
+
259
+ expect(ListObjectsV2Command).toHaveBeenCalledWith(expect.objectContaining({
260
+ ContinuationToken: "continue-token"
261
+ }));
262
+ });
263
+ });
264
+
265
+ describe("validateFile", () => {
266
+ it("should accept valid file", () => {
267
+ const file = new File(["content"], "valid.txt", { type: "text/plain" });
268
+
269
+ expect(() => {
270
+ (controller as any).validateFile(file);
271
+ }).not.toThrow();
272
+ });
273
+
274
+ it("should reject file exceeding max size", () => {
275
+ const smallController = new S3StorageController({
276
+ ...defaultConfig,
277
+ maxFileSize: 10 // 10 bytes
278
+ });
279
+
280
+ const largeContent = Buffer.alloc(100);
281
+ const file = new File([largeContent], "large.txt", { type: "text/plain" });
282
+
283
+ expect(() => {
284
+ (smallController as any).validateFile(file);
285
+ }).toThrow(/exceeds/i);
286
+ });
287
+ });
288
+
289
+ describe("flattenMetadata", () => {
290
+ it("should flatten nested metadata to strings", () => {
291
+ const metadata = {
292
+ simple: "value",
293
+ number: 42,
294
+ nested: { key: "value" }
295
+ };
296
+
297
+ const flattened = (controller as any).flattenMetadata(metadata);
298
+
299
+ expect(flattened.simple).toBe("value");
300
+ expect(flattened.number).toBe("42");
301
+ expect(typeof flattened.nested).toBe("string");
302
+ });
303
+ });
304
+ });
package/test-ast.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { AstSchemaEditor } from "./src/api/ast-schema-editor";
2
+ import * as fs from "fs";
3
+
4
+ const collectionsDir = __dirname + "/collections-test";
5
+ fs.mkdirSync(collectionsDir, { recursive: true });
6
+ fs.writeFileSync(collectionsDir + "/users.ts", `
7
+ export default {
8
+ name: "Users",
9
+ slug: "users",
10
+ properties: {},
11
+ securityRules: [
12
+ { name: "test", operation: "read", mode: "permissive", roles: ["public"] }
13
+ ]
14
+ };
15
+ `);
16
+
17
+ async function main() {
18
+ const editor = new AstSchemaEditor(collectionsDir);
19
+ await editor.saveCollection("users", {
20
+ name: "Users",
21
+ slug: "users",
22
+ properties: {},
23
+ securityRules: []
24
+ });
25
+ console.log("AFTER SAVE:", fs.readFileSync(collectionsDir + "/users.ts", "utf8"));
26
+ }
27
+
28
+ main().catch(console.error);
package/test.ts ADDED
@@ -0,0 +1,6 @@
1
+ interface I {
2
+ verify: (payload: any) => void;
3
+ }
4
+ const obj: I = {
5
+ verify: (payload: { code: string }) => {}
6
+ };