@rebasepro/server-core 0.5.0 → 0.6.1
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.
- package/dist/{server-core/src/api → api}/errors.d.ts +5 -2
- package/dist/{server-core/src/api → api}/rest/api-generator.d.ts +8 -2
- package/dist/{server-core/src/api → api}/types.d.ts +2 -0
- package/dist/auth/admin-user-ops.d.ts +79 -0
- package/dist/{server-core/src/auth → auth}/api-keys/index.d.ts +2 -2
- package/dist/{server-core/src/auth → auth}/auth-hooks.d.ts +37 -9
- package/dist/{server-core/src/auth → auth}/builtin-auth-adapter.d.ts +4 -4
- package/dist/{server-core/src/auth → auth}/index.d.ts +5 -2
- package/dist/auth/mfa-routes.d.ts +6 -0
- package/dist/auth/reset-password-admin.d.ts +29 -0
- package/dist/auth/session-routes.d.ts +25 -0
- package/dist/backend-CIxN4FVm.js +15 -0
- package/dist/backend-CIxN4FVm.js.map +1 -0
- package/dist/chunk-Dze3rakg.js +42 -0
- package/dist/{server-core/src/cron → cron}/cron-scheduler.d.ts +1 -1
- package/dist/dist-CZKP-Xz4.js +832 -0
- package/dist/dist-CZKP-Xz4.js.map +1 -0
- package/dist/env.d.ts +102 -0
- package/dist/from-VbwD7xRf.js +3849 -0
- package/dist/from-VbwD7xRf.js.map +1 -0
- package/dist/{server-core/src/index.d.ts → index.d.ts} +1 -0
- package/dist/index.es.js +60872 -50462
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +73156 -51298
- package/dist/index.umd.js.map +1 -1
- package/dist/init/docs.d.ts +4 -0
- package/dist/init/health.d.ts +2 -0
- package/dist/init/middlewares.d.ts +10 -0
- package/dist/init/shutdown.d.ts +11 -0
- package/dist/init/storage.d.ts +5 -0
- package/dist/{server-core/src/init.d.ts → init.d.ts} +4 -4
- package/dist/jwt-DHcQRGC3.js +4168 -0
- package/dist/jwt-DHcQRGC3.js.map +1 -0
- package/dist/logger-BYU66ENZ.js +94 -0
- package/dist/logger-BYU66ENZ.js.map +1 -0
- package/dist/ms-BeBSuOXN.js +125 -0
- package/dist/ms-BeBSuOXN.js.map +1 -0
- package/dist/multipart-parser-CedBDOeC.js +299 -0
- package/dist/multipart-parser-CedBDOeC.js.map +1 -0
- package/dist/src-COaj0G3P.js +1182 -0
- package/dist/src-COaj0G3P.js.map +1 -0
- package/dist/{server-core/src/storage → storage}/image-transform.d.ts +3 -1
- package/dist/utils/request-id.d.ts +4 -0
- package/package.json +29 -29
- package/src/api/collections_for_test/callbacks_test_collection.ts +7 -6
- package/src/api/errors.ts +75 -19
- package/src/api/graphql/graphql-schema-generator.ts +15 -10
- package/src/api/logs-routes.ts +5 -2
- package/src/api/rest/api-generator-count.test.ts +21 -10
- package/src/api/rest/api-generator.ts +102 -26
- package/src/api/rest/query-parser.ts +18 -12
- package/src/api/server.ts +12 -1
- package/src/api/types.ts +2 -0
- package/src/auth/adapter-middleware.ts +17 -10
- package/src/auth/admin-user-ops.ts +236 -0
- package/src/auth/api-keys/api-key-middleware.ts +15 -9
- package/src/auth/api-keys/api-key-permission-guard.ts +1 -1
- package/src/auth/api-keys/api-key-routes.ts +3 -3
- package/src/auth/api-keys/api-key-store.ts +14 -8
- package/src/auth/api-keys/index.ts +2 -2
- package/src/auth/apple-oauth.ts +4 -3
- package/src/auth/auth-hooks.ts +50 -10
- package/src/auth/bitbucket-oauth.ts +5 -4
- package/src/auth/builtin-auth-adapter.ts +60 -39
- package/src/auth/custom-auth-adapter.ts +4 -5
- package/src/auth/discord-oauth.ts +5 -4
- package/src/auth/facebook-oauth.ts +5 -4
- package/src/auth/github-oauth.ts +6 -5
- package/src/auth/gitlab-oauth.ts +5 -4
- package/src/auth/google-oauth.ts +2 -1
- package/src/auth/index.ts +6 -2
- package/src/auth/jwt.ts +5 -4
- package/src/auth/linkedin-oauth.ts +4 -3
- package/src/auth/mfa-routes.ts +299 -0
- package/src/auth/mfa.ts +2 -1
- package/src/auth/microsoft-oauth.ts +5 -4
- package/src/auth/middleware.ts +5 -4
- package/src/auth/rate-limiter.ts +1 -1
- package/src/auth/reset-password-admin.ts +144 -0
- package/src/auth/rls-scope.ts +1 -1
- package/src/auth/routes.ts +52 -596
- package/src/auth/session-routes.ts +341 -0
- package/src/auth/slack-oauth.ts +6 -5
- package/src/auth/spotify-oauth.ts +5 -4
- package/src/auth/twitter-oauth.ts +4 -3
- package/src/collections/loader.ts +5 -4
- package/src/cron/cron-scheduler.test.ts +23 -11
- package/src/cron/cron-scheduler.ts +14 -8
- package/src/cron/cron-store.ts +1 -1
- package/src/email/smtp-email-service.ts +3 -2
- package/src/env.ts +11 -10
- package/src/index.ts +1 -0
- package/src/init/docs.ts +47 -0
- package/src/init/health.ts +37 -0
- package/src/init/middlewares.ts +61 -0
- package/src/init/shutdown.ts +56 -0
- package/src/init/storage.ts +57 -0
- package/src/init.ts +85 -279
- package/src/serve-spa.ts +12 -5
- package/src/services/driver-registry.ts +4 -3
- package/src/storage/S3StorageController.ts +1 -2
- package/src/storage/image-transform.ts +25 -10
- package/src/storage/routes.ts +34 -11
- package/src/storage/storage-registry.ts +4 -3
- package/src/storage/tus-handler.ts +12 -12
- package/src/utils/request-id.ts +40 -0
- package/src/utils/request-logger.ts +6 -0
- package/test/api-generator.test.ts +90 -2
- package/test/api-key-permission-guard.test.ts +24 -12
- package/test/auth-routes.test.ts +5 -3
- package/test/backend-hooks-data.test.ts +99 -30
- package/test/custom-auth-adapter.test.ts +16 -15
- package/test/email-templates.test.ts +10 -5
- package/test/env.test.ts +10 -10
- package/test/function-loader.test.ts +7 -4
- package/test/graphql-schema-generator.test.ts +554 -0
- package/test/jwt-security.test.ts +1 -1
- package/test/query-parser.test.ts +0 -1
- package/test/reset-password-admin.test.ts +113 -0
- package/test/singleton.test.ts +5 -5
- package/test/smtp-email-service.test.ts +21 -21
- package/test/webhook-service.test.ts +22 -11
- package/tsconfig.json +2 -0
- package/tsconfig.prod.json +3 -0
- package/vite.config.ts +10 -5
- package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
- package/dist/common/src/collections/default-collections.d.ts +0 -9
- package/dist/common/src/collections/index.d.ts +0 -2
- package/dist/common/src/data/buildRebaseData.d.ts +0 -14
- package/dist/common/src/data/query_builder.d.ts +0 -55
- package/dist/common/src/index.d.ts +0 -4
- package/dist/common/src/util/builders.d.ts +0 -57
- package/dist/common/src/util/callbacks.d.ts +0 -6
- package/dist/common/src/util/collections.d.ts +0 -11
- package/dist/common/src/util/common.d.ts +0 -2
- package/dist/common/src/util/conditions.d.ts +0 -26
- package/dist/common/src/util/entities.d.ts +0 -58
- package/dist/common/src/util/enums.d.ts +0 -3
- package/dist/common/src/util/index.d.ts +0 -16
- package/dist/common/src/util/navigation_from_path.d.ts +0 -34
- package/dist/common/src/util/navigation_utils.d.ts +0 -20
- package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
- package/dist/common/src/util/paths.d.ts +0 -14
- package/dist/common/src/util/permissions.d.ts +0 -14
- package/dist/common/src/util/references.d.ts +0 -2
- package/dist/common/src/util/relations.d.ts +0 -22
- package/dist/common/src/util/resolutions.d.ts +0 -72
- package/dist/common/src/util/storage.d.ts +0 -24
- package/dist/index-Cr1D21av.js +0 -49
- package/dist/index-Cr1D21av.js.map +0 -1
- package/dist/server-core/src/auth/admin-routes.d.ts +0 -27
- package/dist/server-core/src/env.d.ts +0 -137
- package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
- package/dist/types/src/controllers/auth.d.ts +0 -104
- package/dist/types/src/controllers/client.d.ts +0 -168
- package/dist/types/src/controllers/collection_registry.d.ts +0 -46
- package/dist/types/src/controllers/customization_controller.d.ts +0 -60
- package/dist/types/src/controllers/data.d.ts +0 -207
- package/dist/types/src/controllers/data_driver.d.ts +0 -218
- package/dist/types/src/controllers/database_admin.d.ts +0 -11
- package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
- package/dist/types/src/controllers/effective_role.d.ts +0 -4
- package/dist/types/src/controllers/email.d.ts +0 -36
- package/dist/types/src/controllers/index.d.ts +0 -18
- package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
- package/dist/types/src/controllers/navigation.d.ts +0 -225
- package/dist/types/src/controllers/registry.d.ts +0 -63
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
- package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
- package/dist/types/src/controllers/snackbar.d.ts +0 -24
- package/dist/types/src/controllers/storage.d.ts +0 -171
- package/dist/types/src/index.d.ts +0 -4
- package/dist/types/src/rebase_context.d.ts +0 -122
- package/dist/types/src/types/auth_adapter.d.ts +0 -301
- package/dist/types/src/types/backend.d.ts +0 -571
- package/dist/types/src/types/backend_hooks.d.ts +0 -172
- package/dist/types/src/types/builders.d.ts +0 -15
- package/dist/types/src/types/chips.d.ts +0 -5
- package/dist/types/src/types/collections.d.ts +0 -961
- package/dist/types/src/types/component_ref.d.ts +0 -47
- package/dist/types/src/types/cron.d.ts +0 -102
- package/dist/types/src/types/data_source.d.ts +0 -64
- package/dist/types/src/types/database_adapter.d.ts +0 -94
- package/dist/types/src/types/entities.d.ts +0 -145
- package/dist/types/src/types/entity_actions.d.ts +0 -104
- package/dist/types/src/types/entity_callbacks.d.ts +0 -173
- package/dist/types/src/types/entity_link_builder.d.ts +0 -7
- package/dist/types/src/types/entity_overrides.d.ts +0 -10
- package/dist/types/src/types/entity_views.d.ts +0 -87
- package/dist/types/src/types/export_import.d.ts +0 -21
- package/dist/types/src/types/formex.d.ts +0 -40
- package/dist/types/src/types/index.d.ts +0 -28
- package/dist/types/src/types/locales.d.ts +0 -4
- package/dist/types/src/types/modify_collections.d.ts +0 -5
- package/dist/types/src/types/plugins.d.ts +0 -282
- package/dist/types/src/types/properties.d.ts +0 -1173
- package/dist/types/src/types/property_config.d.ts +0 -74
- package/dist/types/src/types/relations.d.ts +0 -336
- package/dist/types/src/types/slots.d.ts +0 -262
- package/dist/types/src/types/translations.d.ts +0 -900
- package/dist/types/src/types/user_management_delegate.d.ts +0 -86
- package/dist/types/src/types/websockets.d.ts +0 -78
- package/dist/types/src/users/index.d.ts +0 -1
- package/dist/types/src/users/user.d.ts +0 -50
- package/src/auth/admin-routes.ts +0 -534
- package/test/admin-routes.test.ts +0 -544
- package/test/backend-hooks-admin.test.ts +0 -388
- /package/dist/{server-core/src/api → api}/ast-schema-editor.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/collections_for_test/callbacks_test_collection.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/graphql/graphql-schema-generator.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/graphql/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/logs-routes.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/openapi-generator.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/rest/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/rest/query-parser.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/schema-editor-routes.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/server.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/adapter-middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-permission-guard.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-routes.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-store.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-types.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/apple-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/bitbucket-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/crypto-utils.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/custom-auth-adapter.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/discord-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/facebook-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/github-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/gitlab-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/google-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/interfaces.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/jwt.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/linkedin-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/mfa.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/microsoft-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/password.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/rate-limiter.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/rls-scope.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/routes.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/slack-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/spotify-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/twitter-oauth.d.ts +0 -0
- /package/dist/{server-core/src/collections → collections}/BackendCollectionRegistry.d.ts +0 -0
- /package/dist/{server-core/src/collections → collections}/loader.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-loader.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-routes.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-store.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/index.d.ts +0 -0
- /package/dist/{server-core/src/db → db}/interfaces.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/index.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/smtp-email-service.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/templates.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/types.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/function-loader.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/function-routes.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/index.d.ts +0 -0
- /package/dist/{server-core/src/history → history}/history-routes.d.ts +0 -0
- /package/dist/{server-core/src/history → history}/index.d.ts +0 -0
- /package/dist/{server-core/src/serve-spa.d.ts → serve-spa.d.ts} +0 -0
- /package/dist/{server-core/src/services → services}/driver-registry.d.ts +0 -0
- /package/dist/{server-core/src/services → services}/webhook-service.d.ts +0 -0
- /package/dist/{server-core/src/singleton.d.ts → singleton.d.ts} +0 -0
- /package/dist/{server-core/src/storage → storage}/LocalStorageController.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/S3StorageController.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/index.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/routes.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/storage-registry.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/tus-handler.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/types.d.ts +0 -0
- /package/dist/{server-core/src/types → types}/index.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/dev-port.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/logger.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/logging.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/request-logger.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/sql.d.ts +0 -0
|
@@ -184,8 +184,9 @@ export class GraphQLSchemaGenerator {
|
|
|
184
184
|
id: { type: new GraphQLNonNull(GraphQLString) }
|
|
185
185
|
},
|
|
186
186
|
resolve: async (_, args, context: unknown) => {
|
|
187
|
-
const ctx = context as { driver
|
|
188
|
-
const ds = ctx
|
|
187
|
+
const ctx = context as { driver?: DataDriver } | undefined;
|
|
188
|
+
const ds = ctx?.driver;
|
|
189
|
+
if (!ds) throw new Error("Scoped driver not available");
|
|
189
190
|
const entity = await ds.fetchEntity({
|
|
190
191
|
path: collection.slug,
|
|
191
192
|
entityId: args.id,
|
|
@@ -207,8 +208,9 @@ defaultValue: 0 },
|
|
|
207
208
|
orderBy: { type: GraphQLString }
|
|
208
209
|
},
|
|
209
210
|
resolve: async (_, args, context: unknown) => {
|
|
210
|
-
const ctx = context as { driver
|
|
211
|
-
const ds = ctx
|
|
211
|
+
const ctx = context as { driver?: DataDriver } | undefined;
|
|
212
|
+
const ds = ctx?.driver;
|
|
213
|
+
if (!ds) throw new Error("Scoped driver not available");
|
|
212
214
|
let filter: FetchCollectionProps["filter"] | undefined;
|
|
213
215
|
if (args.where) {
|
|
214
216
|
try {
|
|
@@ -259,8 +261,9 @@ defaultValue: 0 },
|
|
|
259
261
|
input: { type: new GraphQLNonNull(inputType) }
|
|
260
262
|
},
|
|
261
263
|
resolve: async (_, args, context: unknown) => {
|
|
262
|
-
const ctx = context as { driver
|
|
263
|
-
const ds = ctx
|
|
264
|
+
const ctx = context as { driver?: DataDriver } | undefined;
|
|
265
|
+
const ds = ctx?.driver;
|
|
266
|
+
if (!ds) throw new Error("Scoped driver not available");
|
|
264
267
|
const path = collection.slug;
|
|
265
268
|
const entity = await ds.saveEntity({
|
|
266
269
|
path,
|
|
@@ -280,8 +283,9 @@ defaultValue: 0 },
|
|
|
280
283
|
input: { type: new GraphQLNonNull(inputType) }
|
|
281
284
|
},
|
|
282
285
|
resolve: async (_, args, context: unknown) => {
|
|
283
|
-
const ctx = context as { driver
|
|
284
|
-
const ds = ctx
|
|
286
|
+
const ctx = context as { driver?: DataDriver } | undefined;
|
|
287
|
+
const ds = ctx?.driver;
|
|
288
|
+
if (!ds) throw new Error("Scoped driver not available");
|
|
285
289
|
const entity = await ds.saveEntity({
|
|
286
290
|
path: collection.slug,
|
|
287
291
|
entityId: args.id,
|
|
@@ -301,8 +305,9 @@ defaultValue: 0 },
|
|
|
301
305
|
},
|
|
302
306
|
resolve: async (_, args, context: unknown) => {
|
|
303
307
|
try {
|
|
304
|
-
const ctx = context as { driver
|
|
305
|
-
const ds = ctx
|
|
308
|
+
const ctx = context as { driver?: DataDriver } | undefined;
|
|
309
|
+
const ds = ctx?.driver;
|
|
310
|
+
if (!ds) throw new Error("Scoped driver not available");
|
|
306
311
|
const existingEntity = await ds.fetchEntity({
|
|
307
312
|
path: collection.slug,
|
|
308
313
|
entityId: args.id,
|
package/src/api/logs-routes.ts
CHANGED
|
@@ -22,7 +22,8 @@ class LogRingBuffer {
|
|
|
22
22
|
|
|
23
23
|
push(entry: Omit<LogEntry, "id">): void {
|
|
24
24
|
const id = `log_${++this.idCounter}`;
|
|
25
|
-
this.buffer.push({ ...entry,
|
|
25
|
+
this.buffer.push({ ...entry,
|
|
26
|
+
id });
|
|
26
27
|
if (this.buffer.length > this.maxSize) {
|
|
27
28
|
this.buffer.shift();
|
|
28
29
|
}
|
|
@@ -95,11 +96,13 @@ export function logMiddleware(): MiddlewareHandler<HonoEnv> {
|
|
|
95
96
|
const start = Date.now();
|
|
96
97
|
await next();
|
|
97
98
|
const duration = Date.now() - start;
|
|
99
|
+
const reqId = c.get("requestId");
|
|
98
100
|
addLog("info", "api", `${c.req.method} ${c.req.path} ${c.res.status} ${duration}ms`, {
|
|
99
101
|
method: c.req.method,
|
|
100
102
|
path: c.req.path,
|
|
101
103
|
status: c.res.status,
|
|
102
|
-
duration
|
|
104
|
+
duration,
|
|
105
|
+
...(reqId && { requestId: reqId })
|
|
103
106
|
});
|
|
104
107
|
};
|
|
105
108
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jest } from "@jest/globals";
|
|
2
|
+
import { Hono } from "hono";
|
|
2
3
|
import { RestApiGenerator } from "./api-generator";
|
|
3
4
|
import type { DataDriver, Entity, EntityCollection, FetchCollectionProps } from "@rebasepro/types";
|
|
4
5
|
|
|
@@ -27,6 +28,21 @@ function createTestCollection(slug: string): EntityCollection {
|
|
|
27
28
|
} as unknown as EntityCollection;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Wraps generated routes in a parent Hono app that injects the driver
|
|
33
|
+
* into context — replicating what auth middleware does in production.
|
|
34
|
+
*/
|
|
35
|
+
function createApp(collections: EntityCollection[], driver: DataDriver): Hono {
|
|
36
|
+
const parent = new Hono();
|
|
37
|
+
parent.use("/*", async (c, next) => {
|
|
38
|
+
c.set("driver", driver);
|
|
39
|
+
await next();
|
|
40
|
+
});
|
|
41
|
+
const generator = new RestApiGenerator(collections, driver);
|
|
42
|
+
parent.route("/", generator.generateRoutes());
|
|
43
|
+
return parent;
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
describe("RestApiGenerator - Count Endpoint", () => {
|
|
31
47
|
let driver: DataDriver;
|
|
32
48
|
let collection: EntityCollection;
|
|
@@ -39,8 +55,7 @@ describe("RestApiGenerator - Count Endpoint", () => {
|
|
|
39
55
|
});
|
|
40
56
|
|
|
41
57
|
it("GET /products/count should return a count object", async () => {
|
|
42
|
-
const
|
|
43
|
-
const app = generator.generateRoutes();
|
|
58
|
+
const app = createApp([collection], driver);
|
|
44
59
|
|
|
45
60
|
const res = await app.request("/products/count");
|
|
46
61
|
expect(res.status).toBe(200);
|
|
@@ -50,8 +65,7 @@ describe("RestApiGenerator - Count Endpoint", () => {
|
|
|
50
65
|
});
|
|
51
66
|
|
|
52
67
|
it("should pass filters to countEntities driver", async () => {
|
|
53
|
-
const
|
|
54
|
-
const app = generator.generateRoutes();
|
|
68
|
+
const app = createApp([collection], driver);
|
|
55
69
|
|
|
56
70
|
const res = await app.request("/products/count?status=eq.active");
|
|
57
71
|
expect(res.status).toBe(200);
|
|
@@ -67,8 +81,7 @@ describe("RestApiGenerator - Count Endpoint", () => {
|
|
|
67
81
|
});
|
|
68
82
|
|
|
69
83
|
it("should pass searchString to countEntities driver", async () => {
|
|
70
|
-
const
|
|
71
|
-
const app = generator.generateRoutes();
|
|
84
|
+
const app = createApp([collection], driver);
|
|
72
85
|
|
|
73
86
|
const res = await app.request("/products/count?searchString=widget");
|
|
74
87
|
expect(res.status).toBe(200);
|
|
@@ -83,8 +96,7 @@ describe("RestApiGenerator - Count Endpoint", () => {
|
|
|
83
96
|
|
|
84
97
|
it("should return 0 when countEntities is not available on driver", async () => {
|
|
85
98
|
const driverWithoutCount = createMockDriver({ countEntities: undefined });
|
|
86
|
-
const
|
|
87
|
-
const app = generator.generateRoutes();
|
|
99
|
+
const app = createApp([collection], driverWithoutCount);
|
|
88
100
|
|
|
89
101
|
const res = await app.request("/products/count");
|
|
90
102
|
expect(res.status).toBe(200);
|
|
@@ -100,8 +112,7 @@ describe("RestApiGenerator - Count Endpoint", () => {
|
|
|
100
112
|
countEntities: jest.fn<NonNullable<DataDriver["countEntities"]>>().mockResolvedValue(99),
|
|
101
113
|
fetchEntity: fetchEntity as unknown as DataDriver["fetchEntity"]
|
|
102
114
|
});
|
|
103
|
-
const
|
|
104
|
-
const app = generator.generateRoutes();
|
|
115
|
+
const app = createApp([collection], driverCustom);
|
|
105
116
|
|
|
106
117
|
const res = await app.request("/products/count");
|
|
107
118
|
expect(res.status).toBe(200);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { DataDriver, Entity, EntityCollection, FetchCollectionProps, DataHooks, BackendHookContext, RestFetchService } from "@rebasepro/types";
|
|
2
|
+
import { AuthAdapter, DataDriver, Entity, EntityCollection, FetchCollectionProps, DataHooks, BackendHookContext, RestFetchService } from "@rebasepro/types";
|
|
3
3
|
import { QueryOptions, HonoEnv } from "../types";
|
|
4
4
|
import { ApiError, isRebaseApiError } from "../errors";
|
|
5
5
|
import { parseQueryOptions } from "./query-parser";
|
|
6
6
|
import { httpMethodToOperation, isOperationAllowed } from "../../auth/api-keys/api-key-permission-guard";
|
|
7
7
|
import type { ApiKeyMasked } from "../../auth/api-keys/api-key-types";
|
|
8
|
+
import { logger } from "../../utils/logger";
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -16,11 +17,18 @@ export class RestApiGenerator {
|
|
|
16
17
|
private router: Hono<HonoEnv>;
|
|
17
18
|
private driver: DataDriver;
|
|
18
19
|
private dataHooks?: DataHooks;
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
private authAdapter?: AuthAdapter;
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
collections: EntityCollection[],
|
|
24
|
+
driver: DataDriver,
|
|
25
|
+
dataHooks?: DataHooks,
|
|
26
|
+
authAdapter?: AuthAdapter
|
|
27
|
+
) {
|
|
21
28
|
this.collections = collections;
|
|
22
29
|
this.driver = driver;
|
|
23
30
|
this.dataHooks = dataHooks;
|
|
31
|
+
this.authAdapter = authAdapter;
|
|
24
32
|
this.router = new Hono<HonoEnv>();
|
|
25
33
|
}
|
|
26
34
|
|
|
@@ -28,7 +36,8 @@ export class RestApiGenerator {
|
|
|
28
36
|
private buildHookContext(c: { get: (key: string) => unknown }, method: BackendHookContext["method"]): BackendHookContext {
|
|
29
37
|
const user = c.get("user") as { userId: string; roles?: string[] } | undefined;
|
|
30
38
|
return {
|
|
31
|
-
requestUser: user ? { userId: user.userId,
|
|
39
|
+
requestUser: user ? { userId: user.userId,
|
|
40
|
+
roles: user.roles ?? [] } : undefined,
|
|
32
41
|
method
|
|
33
42
|
};
|
|
34
43
|
}
|
|
@@ -56,7 +65,7 @@ export class RestApiGenerator {
|
|
|
56
65
|
*/
|
|
57
66
|
private enforceApiKeyPermission(
|
|
58
67
|
c: { get: (key: string) => unknown; req: { method: string } },
|
|
59
|
-
collectionSlug: string
|
|
68
|
+
collectionSlug: string
|
|
60
69
|
): void {
|
|
61
70
|
const apiKey = c.get("apiKey") as ApiKeyMasked | undefined;
|
|
62
71
|
if (!apiKey) return; // Not an API key request — skip
|
|
@@ -65,11 +74,21 @@ export class RestApiGenerator {
|
|
|
65
74
|
if (!isOperationAllowed(apiKey.permissions, collectionSlug, operation)) {
|
|
66
75
|
throw ApiError.forbidden(
|
|
67
76
|
`API key does not have "${operation}" permission for collection "${collectionSlug}"`,
|
|
68
|
-
"API_KEY_FORBIDDEN"
|
|
77
|
+
"API_KEY_FORBIDDEN"
|
|
69
78
|
);
|
|
70
79
|
}
|
|
71
80
|
}
|
|
72
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Get the request-scoped driver. Throws if none is set — never falls
|
|
84
|
+
* back to the unscoped `this.driver` to avoid bypassing RLS/auth.
|
|
85
|
+
*/
|
|
86
|
+
private getScopedDriver(c: { get: (key: string) => unknown }): DataDriver {
|
|
87
|
+
const driver = c.get("driver") as DataDriver | undefined;
|
|
88
|
+
if (!driver) throw ApiError.internal("Scoped driver not available");
|
|
89
|
+
return driver;
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
/**
|
|
74
93
|
* Get the typed RestFetchService from a driver if it exposes one (for include support).
|
|
75
94
|
*/
|
|
@@ -90,7 +109,7 @@ export class RestApiGenerator {
|
|
|
90
109
|
const queryDict = c.req.queries();
|
|
91
110
|
const queryOptions = parseQueryOptions(queryDict);
|
|
92
111
|
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : undefined;
|
|
93
|
-
const driver =
|
|
112
|
+
const driver = this.getScopedDriver(c);
|
|
94
113
|
|
|
95
114
|
const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
|
|
96
115
|
return c.json({ count: total });
|
|
@@ -103,7 +122,7 @@ export class RestApiGenerator {
|
|
|
103
122
|
const queryOptions = parseQueryOptions(queryDict);
|
|
104
123
|
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : undefined;
|
|
105
124
|
|
|
106
|
-
const driver =
|
|
125
|
+
const driver = this.getScopedDriver(c);
|
|
107
126
|
const fetchService = this.getFetchService(driver);
|
|
108
127
|
const hookCtx = this.buildHookContext(c, "GET");
|
|
109
128
|
|
|
@@ -163,7 +182,7 @@ export class RestApiGenerator {
|
|
|
163
182
|
const id = c.req.param("id");
|
|
164
183
|
const queryDict = c.req.queries();
|
|
165
184
|
const queryOptions = parseQueryOptions(queryDict);
|
|
166
|
-
const driver =
|
|
185
|
+
const driver = this.getScopedDriver(c);
|
|
167
186
|
const fetchService = this.getFetchService(driver);
|
|
168
187
|
const hookCtx = this.buildHookContext(c, "GET");
|
|
169
188
|
|
|
@@ -207,7 +226,7 @@ export class RestApiGenerator {
|
|
|
207
226
|
this.router.post(basePath, async (c) => {
|
|
208
227
|
try {
|
|
209
228
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
210
|
-
const driver =
|
|
229
|
+
const driver = this.getScopedDriver(c);
|
|
211
230
|
const path = collection.slug;
|
|
212
231
|
const hookCtx = this.buildHookContext(c, "POST");
|
|
213
232
|
|
|
@@ -217,6 +236,46 @@ export class RestApiGenerator {
|
|
|
217
236
|
body = await this.dataHooks.beforeSave(path, body, undefined, hookCtx);
|
|
218
237
|
}
|
|
219
238
|
|
|
239
|
+
const isAuth = collection.auth;
|
|
240
|
+
const isAuthCollection = isAuth === true || (isAuth && typeof isAuth === "object" && isAuth.enabled === true);
|
|
241
|
+
|
|
242
|
+
if (isAuthCollection && this.authAdapter?.prepareUserCreation) {
|
|
243
|
+
const collectionAuthConfig = typeof isAuth === "object" ? isAuth : undefined;
|
|
244
|
+
const prepared = await this.authAdapter.prepareUserCreation(body, collectionAuthConfig);
|
|
245
|
+
|
|
246
|
+
const entity = await driver.saveEntity({
|
|
247
|
+
path,
|
|
248
|
+
values: prepared.values,
|
|
249
|
+
collection: resolvedCollection,
|
|
250
|
+
status: "new"
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const result = prepared.hookHandledEmail
|
|
254
|
+
? { temporaryPassword: prepared.clearPassword,
|
|
255
|
+
invitationSent: prepared.invitationSent }
|
|
256
|
+
: this.authAdapter.finalizeUserCreation
|
|
257
|
+
? await this.authAdapter.finalizeUserCreation(
|
|
258
|
+
{ id: entity.id as string,
|
|
259
|
+
values: entity.values as Record<string, unknown> },
|
|
260
|
+
prepared.clearPassword
|
|
261
|
+
)
|
|
262
|
+
: { invitationSent: false };
|
|
263
|
+
|
|
264
|
+
const response = this.formatResponse(entity) as Record<string, unknown>;
|
|
265
|
+
|
|
266
|
+
if (this.dataHooks?.afterSave) {
|
|
267
|
+
Promise.resolve(this.dataHooks.afterSave(path, response, hookCtx)).catch(err => {
|
|
268
|
+
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return c.json({
|
|
273
|
+
...response,
|
|
274
|
+
invitationSent: result.invitationSent,
|
|
275
|
+
...(result.temporaryPassword ? { temporaryPassword: result.temporaryPassword } : {})
|
|
276
|
+
}, 201);
|
|
277
|
+
}
|
|
278
|
+
|
|
220
279
|
const entity = await driver.saveEntity({
|
|
221
280
|
path,
|
|
222
281
|
values: body,
|
|
@@ -228,14 +287,23 @@ export class RestApiGenerator {
|
|
|
228
287
|
|
|
229
288
|
if (this.dataHooks?.afterSave) {
|
|
230
289
|
Promise.resolve(this.dataHooks.afterSave(path, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
231
|
-
|
|
290
|
+
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
232
291
|
});
|
|
233
292
|
}
|
|
234
293
|
|
|
235
294
|
return c.json(response, 201);
|
|
236
295
|
} catch (error) {
|
|
237
296
|
if (isRebaseApiError(error) && !error.code) {
|
|
238
|
-
|
|
297
|
+
// Only classify as BAD_REQUEST if it's an operational error
|
|
298
|
+
// (e.g. validation, DB constraints). Runtime bugs like TypeError,
|
|
299
|
+
// RangeError etc. should remain as 500 INTERNAL_ERROR.
|
|
300
|
+
const isRuntimeBug = error instanceof TypeError
|
|
301
|
+
|| error instanceof RangeError
|
|
302
|
+
|| error instanceof SyntaxError
|
|
303
|
+
|| error instanceof ReferenceError;
|
|
304
|
+
if (!isRuntimeBug) {
|
|
305
|
+
error.code = "BAD_REQUEST";
|
|
306
|
+
}
|
|
239
307
|
}
|
|
240
308
|
throw error;
|
|
241
309
|
}
|
|
@@ -246,7 +314,7 @@ export class RestApiGenerator {
|
|
|
246
314
|
try {
|
|
247
315
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
248
316
|
const id = c.req.param("id");
|
|
249
|
-
const driver =
|
|
317
|
+
const driver = this.getScopedDriver(c);
|
|
250
318
|
const hookCtx = this.buildHookContext(c, "PUT");
|
|
251
319
|
|
|
252
320
|
const existingEntity = await driver.fetchEntity({
|
|
@@ -277,14 +345,22 @@ export class RestApiGenerator {
|
|
|
277
345
|
|
|
278
346
|
if (this.dataHooks?.afterSave) {
|
|
279
347
|
Promise.resolve(this.dataHooks.afterSave(collection.slug, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
280
|
-
|
|
348
|
+
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
281
349
|
});
|
|
282
350
|
}
|
|
283
351
|
|
|
284
352
|
return c.json(response);
|
|
285
353
|
} catch (error) {
|
|
286
354
|
if (isRebaseApiError(error) && !error.code) {
|
|
287
|
-
|
|
355
|
+
// Only classify as BAD_REQUEST if it's an operational error.
|
|
356
|
+
// Runtime bugs (TypeError, RangeError, etc.) stay as 500.
|
|
357
|
+
const isRuntimeBug = error instanceof TypeError
|
|
358
|
+
|| error instanceof RangeError
|
|
359
|
+
|| error instanceof SyntaxError
|
|
360
|
+
|| error instanceof ReferenceError;
|
|
361
|
+
if (!isRuntimeBug) {
|
|
362
|
+
error.code = "BAD_REQUEST";
|
|
363
|
+
}
|
|
288
364
|
}
|
|
289
365
|
throw error;
|
|
290
366
|
}
|
|
@@ -294,7 +370,7 @@ export class RestApiGenerator {
|
|
|
294
370
|
this.router.delete(`${basePath}/:id`, async (c) => {
|
|
295
371
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
296
372
|
const id = c.req.param("id");
|
|
297
|
-
const driver =
|
|
373
|
+
const driver = this.getScopedDriver(c);
|
|
298
374
|
const hookCtx = this.buildHookContext(c, "DELETE");
|
|
299
375
|
|
|
300
376
|
const existingEntity = await driver.fetchEntity({
|
|
@@ -318,7 +394,7 @@ export class RestApiGenerator {
|
|
|
318
394
|
|
|
319
395
|
if (this.dataHooks?.afterDelete) {
|
|
320
396
|
Promise.resolve(this.dataHooks.afterDelete(collection.slug, String(id), hookCtx)).catch(err => {
|
|
321
|
-
|
|
397
|
+
logger.error("[BackendHooks] data.afterDelete error", { error: err instanceof Error ? err.message : err });
|
|
322
398
|
});
|
|
323
399
|
}
|
|
324
400
|
|
|
@@ -384,7 +460,7 @@ entityId };
|
|
|
384
460
|
const parsed = parseSubPath(rawPath);
|
|
385
461
|
if (!parsed) return next();
|
|
386
462
|
|
|
387
|
-
const driver =
|
|
463
|
+
const driver = this.getScopedDriver(c);
|
|
388
464
|
|
|
389
465
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
390
466
|
|
|
@@ -395,13 +471,13 @@ entityId };
|
|
|
395
471
|
const queryDict = c.req.queries();
|
|
396
472
|
const queryOptions = parseQueryOptions(queryDict);
|
|
397
473
|
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : undefined;
|
|
398
|
-
|
|
474
|
+
|
|
399
475
|
const total = driver.countEntities ? await driver.countEntities({
|
|
400
476
|
path: parsed.collectionPath,
|
|
401
477
|
filter: queryOptions.where as FetchCollectionProps["filter"],
|
|
402
478
|
searchString
|
|
403
479
|
}) : 0;
|
|
404
|
-
|
|
480
|
+
|
|
405
481
|
return c.json({ count: total });
|
|
406
482
|
} else if (parsed.entityId) {
|
|
407
483
|
// GET /parent/:parentId/child/:id — single entity
|
|
@@ -459,7 +535,7 @@ entityId };
|
|
|
459
535
|
const parsed = parseSubPath(rawPath);
|
|
460
536
|
if (!parsed || parsed.entityId) return next();
|
|
461
537
|
|
|
462
|
-
const driver =
|
|
538
|
+
const driver = this.getScopedDriver(c);
|
|
463
539
|
const hookCtx = this.buildHookContext(c, "POST");
|
|
464
540
|
|
|
465
541
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
@@ -479,7 +555,7 @@ entityId };
|
|
|
479
555
|
|
|
480
556
|
if (this.dataHooks?.afterSave) {
|
|
481
557
|
Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
482
|
-
|
|
558
|
+
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
483
559
|
});
|
|
484
560
|
}
|
|
485
561
|
|
|
@@ -494,7 +570,7 @@ entityId };
|
|
|
494
570
|
const parsed = parseSubPath(rawPath);
|
|
495
571
|
if (!parsed || !parsed.entityId) return next();
|
|
496
572
|
|
|
497
|
-
const driver =
|
|
573
|
+
const driver = this.getScopedDriver(c);
|
|
498
574
|
const hookCtx = this.buildHookContext(c, "PUT");
|
|
499
575
|
|
|
500
576
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
@@ -516,7 +592,7 @@ entityId };
|
|
|
516
592
|
|
|
517
593
|
if (this.dataHooks?.afterSave) {
|
|
518
594
|
Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
519
|
-
|
|
595
|
+
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
520
596
|
});
|
|
521
597
|
}
|
|
522
598
|
|
|
@@ -531,7 +607,7 @@ entityId };
|
|
|
531
607
|
const parsed = parseSubPath(rawPath);
|
|
532
608
|
if (!parsed || !parsed.entityId) return next();
|
|
533
609
|
|
|
534
|
-
const driver =
|
|
610
|
+
const driver = this.getScopedDriver(c);
|
|
535
611
|
const hookCtx = this.buildHookContext(c, "DELETE");
|
|
536
612
|
|
|
537
613
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
@@ -551,7 +627,7 @@ entityId };
|
|
|
551
627
|
|
|
552
628
|
if (this.dataHooks?.afterDelete) {
|
|
553
629
|
Promise.resolve(this.dataHooks.afterDelete(parsed.collectionPath, parsed.entityId, hookCtx)).catch(err => {
|
|
554
|
-
|
|
630
|
+
logger.error("[BackendHooks] data.afterDelete error", { error: err instanceof Error ? err.message : err });
|
|
555
631
|
});
|
|
556
632
|
}
|
|
557
633
|
|
|
@@ -31,17 +31,21 @@ export function parseLogicalString(str: string): FilterCondition | LogicalCondit
|
|
|
31
31
|
str = str.trim();
|
|
32
32
|
if (str.startsWith("or(") && str.endsWith(")")) {
|
|
33
33
|
const inner = str.slice(3, -1);
|
|
34
|
-
return { type: "or",
|
|
34
|
+
return { type: "or",
|
|
35
|
+
conditions: parseLogicalList(inner) };
|
|
35
36
|
}
|
|
36
37
|
if (str.startsWith("and(") && str.endsWith(")")) {
|
|
37
38
|
const inner = str.slice(4, -1);
|
|
38
|
-
return { type: "and",
|
|
39
|
+
return { type: "and",
|
|
40
|
+
conditions: parseLogicalList(inner) };
|
|
39
41
|
}
|
|
40
|
-
|
|
42
|
+
|
|
41
43
|
// It's a leaf condition: field.op.val
|
|
42
44
|
const firstDot = str.indexOf(".");
|
|
43
45
|
if (firstDot === -1) {
|
|
44
|
-
return { column: str,
|
|
46
|
+
return { column: str,
|
|
47
|
+
operator: "==",
|
|
48
|
+
value: true };
|
|
45
49
|
}
|
|
46
50
|
const field = str.substring(0, firstDot);
|
|
47
51
|
const rest = str.substring(firstDot + 1);
|
|
@@ -55,7 +59,7 @@ export function parseLogicalString(str: string): FilterCondition | LogicalCondit
|
|
|
55
59
|
op = "eq";
|
|
56
60
|
valStr = rest;
|
|
57
61
|
}
|
|
58
|
-
|
|
62
|
+
|
|
59
63
|
const rebaseOp = (mapOperator(op) || "==") as FilterCondition["operator"];
|
|
60
64
|
let parsedVal: unknown = valStr;
|
|
61
65
|
if (valStr === "true") parsedVal = true;
|
|
@@ -73,20 +77,22 @@ export function parseLogicalString(str: string): FilterCondition | LogicalCondit
|
|
|
73
77
|
return trimmed;
|
|
74
78
|
});
|
|
75
79
|
}
|
|
76
|
-
|
|
77
|
-
return { column: field,
|
|
80
|
+
|
|
81
|
+
return { column: field,
|
|
82
|
+
operator: rebaseOp,
|
|
83
|
+
value: parsedVal };
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
function parseLogicalList(str: string): (FilterCondition | LogicalCondition)[] {
|
|
81
87
|
const list: (FilterCondition | LogicalCondition)[] = [];
|
|
82
88
|
let depth = 0;
|
|
83
89
|
let current = "";
|
|
84
|
-
|
|
90
|
+
|
|
85
91
|
for (let i = 0; i < str.length; i++) {
|
|
86
92
|
const char = str[i];
|
|
87
93
|
if (char === "(") depth++;
|
|
88
94
|
if (char === ")") depth--;
|
|
89
|
-
|
|
95
|
+
|
|
90
96
|
if (char === "," && depth === 0) {
|
|
91
97
|
list.push(parseLogicalString(current));
|
|
92
98
|
current = "";
|
|
@@ -109,10 +115,10 @@ export function parseQueryOptions(query: Record<string, unknown>): QueryOptions
|
|
|
109
115
|
// Pagination
|
|
110
116
|
const limitVal = getLastValue(query.limit);
|
|
111
117
|
if (limitVal) options.limit = parseInt(String(limitVal));
|
|
112
|
-
|
|
118
|
+
|
|
113
119
|
const offsetVal = getLastValue(query.offset);
|
|
114
120
|
if (offsetVal) options.offset = parseInt(String(offsetVal));
|
|
115
|
-
|
|
121
|
+
|
|
116
122
|
const pageVal = getLastValue(query.page);
|
|
117
123
|
if (pageVal) {
|
|
118
124
|
const page = parseInt(String(pageVal));
|
|
@@ -275,7 +281,7 @@ export function parseQueryOptions(query: Record<string, unknown>): QueryOptions
|
|
|
275
281
|
const vectorSearch: VectorSearchParams = {
|
|
276
282
|
property: String(vectorSearchVal),
|
|
277
283
|
vector: queryVector,
|
|
278
|
-
distance: distanceParam
|
|
284
|
+
distance: distanceParam
|
|
279
285
|
};
|
|
280
286
|
|
|
281
287
|
const thresholdVal = getLastValue(query.vector_threshold);
|
package/src/api/server.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { createSchemaEditorRoutes } from "./schema-editor-routes";
|
|
|
12
12
|
import { createAuthMiddleware, requireAuth, requireAdmin } from "../auth/middleware";
|
|
13
13
|
import { errorHandler } from "./errors";
|
|
14
14
|
import { generateOpenApiSpec } from "./openapi-generator";
|
|
15
|
+
import { requestId } from "../utils/request-id";
|
|
16
|
+
import { logger } from "../utils/logger";
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Simplified API server that leverages existing Rebase infrastructure
|
|
@@ -69,6 +71,9 @@ export class RebaseApiServer {
|
|
|
69
71
|
* Setup Hono middleware
|
|
70
72
|
*/
|
|
71
73
|
private setupMiddleware(): void {
|
|
74
|
+
// Request ID — must be first so all downstream middleware can access it
|
|
75
|
+
this.router.use("/*", requestId());
|
|
76
|
+
|
|
72
77
|
// Security headers — use same-origin-allow-popups for COOP so that
|
|
73
78
|
// OAuth popup flows (Google, etc.) can postMessage back to the opener.
|
|
74
79
|
this.router.use("/*", secureHeaders({
|
|
@@ -86,6 +91,12 @@ export class RebaseApiServer {
|
|
|
86
91
|
: (origin ?? "*"),
|
|
87
92
|
credentials: this.config.cors.credentials ?? false
|
|
88
93
|
}));
|
|
94
|
+
} else if (process.env.NODE_ENV !== "production") {
|
|
95
|
+
logger.warn(
|
|
96
|
+
"⚠️ [RebaseApiServer] No CORS configuration provided. " +
|
|
97
|
+
"The API will accept requests from any origin. " +
|
|
98
|
+
"Pass a `cors` option or configure CORS on your Hono app."
|
|
99
|
+
);
|
|
89
100
|
}
|
|
90
101
|
|
|
91
102
|
// Auth middleware
|
|
@@ -179,7 +190,7 @@ export class RebaseApiServer {
|
|
|
179
190
|
// Schema Editor endpoints
|
|
180
191
|
if (this.config.collectionsDir) {
|
|
181
192
|
if (process.env.NODE_ENV === "production") {
|
|
182
|
-
|
|
193
|
+
logger.warn("[RebaseApiServer] Schema Editor is disabled in production environments for security.");
|
|
183
194
|
} else {
|
|
184
195
|
// Auth middlewares applied to schema-editor via the router prefix
|
|
185
196
|
// MUST be declared before .route() so they execute first
|
package/src/api/types.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type HonoEnv = {
|
|
|
14
14
|
driver?: DataDriver;
|
|
15
15
|
/** Set when the request is authenticated via a Service API Key. */
|
|
16
16
|
apiKey?: ApiKeyMasked;
|
|
17
|
+
/** Unique request correlation ID (generated or propagated from X-Request-ID header). */
|
|
18
|
+
requestId?: string;
|
|
17
19
|
}
|
|
18
20
|
};
|
|
19
21
|
|