@rebasepro/server-core 0.0.1-canary.000dc36
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/LICENSE +6 -0
- package/README.md +40 -0
- package/build-errors.txt +52 -0
- package/coverage/clover.xml +3739 -0
- package/coverage/coverage-final.json +31 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +266 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +952 -0
- package/coverage/lcov-report/src/api/errors.ts.html +472 -0
- package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +1069 -0
- package/coverage/lcov-report/src/api/graphql/index.html +116 -0
- package/coverage/lcov-report/src/api/index.html +176 -0
- package/coverage/lcov-report/src/api/openapi-generator.ts.html +565 -0
- package/coverage/lcov-report/src/api/rest/api-generator.ts.html +994 -0
- package/coverage/lcov-report/src/api/rest/index.html +131 -0
- package/coverage/lcov-report/src/api/rest/query-parser.ts.html +550 -0
- package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +202 -0
- package/coverage/lcov-report/src/api/server.ts.html +823 -0
- package/coverage/lcov-report/src/auth/admin-routes.ts.html +973 -0
- package/coverage/lcov-report/src/auth/index.html +176 -0
- package/coverage/lcov-report/src/auth/jwt.ts.html +574 -0
- package/coverage/lcov-report/src/auth/middleware.ts.html +745 -0
- package/coverage/lcov-report/src/auth/password.ts.html +310 -0
- package/coverage/lcov-report/src/auth/services.ts.html +2074 -0
- package/coverage/lcov-report/src/collections/index.html +116 -0
- package/coverage/lcov-report/src/collections/loader.ts.html +232 -0
- package/coverage/lcov-report/src/db/auth-schema.ts.html +523 -0
- package/coverage/lcov-report/src/db/data-transformer.ts.html +1753 -0
- package/coverage/lcov-report/src/db/entityService.ts.html +700 -0
- package/coverage/lcov-report/src/db/index.html +146 -0
- package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +4048 -0
- package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +883 -0
- package/coverage/lcov-report/src/db/services/RelationService.ts.html +3121 -0
- package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +442 -0
- package/coverage/lcov-report/src/db/services/index.html +176 -0
- package/coverage/lcov-report/src/db/services/index.ts.html +124 -0
- package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +1960 -0
- package/coverage/lcov-report/src/index.html +116 -0
- package/coverage/lcov-report/src/services/driver-registry.ts.html +631 -0
- package/coverage/lcov-report/src/services/index.html +131 -0
- package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +3025 -0
- package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +1189 -0
- package/coverage/lcov-report/src/storage/S3StorageController.ts.html +970 -0
- package/coverage/lcov-report/src/storage/index.html +161 -0
- package/coverage/lcov-report/src/storage/storage-registry.ts.html +646 -0
- package/coverage/lcov-report/src/storage/types.ts.html +451 -0
- package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +3082 -0
- package/coverage/lcov-report/src/utils/index.html +116 -0
- package/coverage/lcov.info +7179 -0
- package/dist/common/src/collections/CollectionRegistry.d.ts +56 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/data/buildRebaseData.d.ts +14 -0
- package/dist/common/src/index.d.ts +3 -0
- package/dist/common/src/util/builders.d.ts +57 -0
- package/dist/common/src/util/callbacks.d.ts +6 -0
- package/dist/common/src/util/collections.d.ts +11 -0
- package/dist/common/src/util/common.d.ts +2 -0
- package/dist/common/src/util/conditions.d.ts +26 -0
- package/dist/common/src/util/entities.d.ts +58 -0
- package/dist/common/src/util/enums.d.ts +3 -0
- package/dist/common/src/util/index.d.ts +16 -0
- package/dist/common/src/util/navigation_from_path.d.ts +34 -0
- package/dist/common/src/util/navigation_utils.d.ts +20 -0
- package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
- package/dist/common/src/util/paths.d.ts +14 -0
- package/dist/common/src/util/permissions.d.ts +5 -0
- package/dist/common/src/util/references.d.ts +2 -0
- package/dist/common/src/util/relations.d.ts +22 -0
- package/dist/common/src/util/resolutions.d.ts +72 -0
- package/dist/common/src/util/storage.d.ts +24 -0
- package/dist/index-DXVBFp5V.js +37 -0
- package/dist/index-DXVBFp5V.js.map +1 -0
- package/dist/index.es.js +49249 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +49283 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/server-core/src/api/ast-schema-editor.d.ts +21 -0
- package/dist/server-core/src/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
- package/dist/server-core/src/api/errors.d.ts +35 -0
- package/dist/server-core/src/api/graphql/graphql-schema-generator.d.ts +35 -0
- package/dist/server-core/src/api/graphql/index.d.ts +1 -0
- package/dist/server-core/src/api/index.d.ts +9 -0
- package/dist/server-core/src/api/openapi-generator.d.ts +16 -0
- package/dist/server-core/src/api/rest/api-generator.d.ts +76 -0
- package/dist/server-core/src/api/rest/index.d.ts +1 -0
- package/dist/server-core/src/api/rest/query-parser.d.ts +9 -0
- package/dist/server-core/src/api/schema-editor-routes.d.ts +3 -0
- package/dist/server-core/src/api/server.d.ts +40 -0
- package/dist/server-core/src/api/types.d.ts +90 -0
- package/dist/server-core/src/auth/admin-routes.d.ts +21 -0
- package/dist/server-core/src/auth/apple-oauth.d.ts +30 -0
- package/dist/server-core/src/auth/bitbucket-oauth.d.ts +11 -0
- package/dist/server-core/src/auth/discord-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/facebook-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/github-oauth.d.ts +15 -0
- package/dist/server-core/src/auth/gitlab-oauth.d.ts +13 -0
- package/dist/server-core/src/auth/google-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/index.d.ts +23 -0
- package/dist/server-core/src/auth/interfaces.d.ts +309 -0
- package/dist/server-core/src/auth/jwt.d.ts +43 -0
- package/dist/server-core/src/auth/linkedin-oauth.d.ts +18 -0
- package/dist/server-core/src/auth/microsoft-oauth.d.ts +16 -0
- package/dist/server-core/src/auth/middleware.d.ts +81 -0
- package/dist/server-core/src/auth/password.d.ts +22 -0
- package/dist/server-core/src/auth/rate-limiter.d.ts +31 -0
- package/dist/server-core/src/auth/routes.d.ts +27 -0
- package/dist/server-core/src/auth/slack-oauth.d.ts +12 -0
- package/dist/server-core/src/auth/spotify-oauth.d.ts +12 -0
- package/dist/server-core/src/auth/twitter-oauth.d.ts +18 -0
- package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
- package/dist/server-core/src/collections/BackendCollectionRegistry.d.ts +13 -0
- package/dist/server-core/src/collections/loader.d.ts +5 -0
- package/dist/server-core/src/cron/cron-loader.d.ts +17 -0
- package/dist/server-core/src/cron/cron-routes.d.ts +14 -0
- package/dist/server-core/src/cron/cron-scheduler.d.ts +106 -0
- package/dist/server-core/src/cron/cron-store.d.ts +32 -0
- package/dist/server-core/src/cron/index.d.ts +6 -0
- package/dist/server-core/src/db/interfaces.d.ts +18 -0
- package/dist/server-core/src/email/index.d.ts +6 -0
- package/dist/server-core/src/email/smtp-email-service.d.ts +25 -0
- package/dist/server-core/src/email/templates.d.ts +42 -0
- package/dist/server-core/src/email/types.d.ts +107 -0
- package/dist/server-core/src/functions/function-loader.d.ts +17 -0
- package/dist/server-core/src/functions/function-routes.d.ts +10 -0
- package/dist/server-core/src/functions/index.d.ts +3 -0
- package/dist/server-core/src/history/history-routes.d.ts +23 -0
- package/dist/server-core/src/history/index.d.ts +1 -0
- package/dist/server-core/src/index.d.ts +29 -0
- package/dist/server-core/src/init.d.ts +168 -0
- package/dist/server-core/src/serve-spa.d.ts +30 -0
- package/dist/server-core/src/services/driver-registry.d.ts +78 -0
- package/dist/server-core/src/singleton.d.ts +35 -0
- package/dist/server-core/src/storage/LocalStorageController.d.ts +46 -0
- package/dist/server-core/src/storage/S3StorageController.d.ts +36 -0
- package/dist/server-core/src/storage/index.d.ts +25 -0
- package/dist/server-core/src/storage/routes.d.ts +38 -0
- package/dist/server-core/src/storage/storage-registry.d.ts +78 -0
- package/dist/server-core/src/storage/types.d.ts +103 -0
- package/dist/server-core/src/types/index.d.ts +11 -0
- package/dist/server-core/src/utils/dev-port.d.ts +35 -0
- package/dist/server-core/src/utils/logger.d.ts +31 -0
- package/dist/server-core/src/utils/logging.d.ts +9 -0
- package/dist/server-core/src/utils/request-logger.d.ts +19 -0
- package/dist/server-core/src/utils/sql.d.ts +27 -0
- package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
- package/dist/types/src/controllers/auth.d.ts +119 -0
- package/dist/types/src/controllers/client.d.ts +170 -0
- package/dist/types/src/controllers/collection_registry.d.ts +46 -0
- package/dist/types/src/controllers/customization_controller.d.ts +60 -0
- package/dist/types/src/controllers/data.d.ts +168 -0
- package/dist/types/src/controllers/data_driver.d.ts +195 -0
- package/dist/types/src/controllers/database_admin.d.ts +11 -0
- package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
- package/dist/types/src/controllers/effective_role.d.ts +4 -0
- package/dist/types/src/controllers/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +18 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
- package/dist/types/src/controllers/navigation.d.ts +213 -0
- package/dist/types/src/controllers/registry.d.ts +54 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
- package/dist/types/src/controllers/snackbar.d.ts +24 -0
- package/dist/types/src/controllers/storage.d.ts +171 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/rebase_context.d.ts +105 -0
- package/dist/types/src/types/backend.d.ts +536 -0
- package/dist/types/src/types/backend_hooks.d.ts +187 -0
- package/dist/types/src/types/builders.d.ts +15 -0
- package/dist/types/src/types/chips.d.ts +5 -0
- package/dist/types/src/types/collections.d.ts +857 -0
- package/dist/types/src/types/cron.d.ts +102 -0
- package/dist/types/src/types/data_source.d.ts +64 -0
- package/dist/types/src/types/entities.d.ts +145 -0
- package/dist/types/src/types/entity_actions.d.ts +98 -0
- package/dist/types/src/types/entity_callbacks.d.ts +173 -0
- package/dist/types/src/types/entity_link_builder.d.ts +7 -0
- package/dist/types/src/types/entity_overrides.d.ts +10 -0
- package/dist/types/src/types/entity_views.d.ts +59 -0
- package/dist/types/src/types/export_import.d.ts +21 -0
- package/dist/types/src/types/formex.d.ts +40 -0
- package/dist/types/src/types/index.d.ts +25 -0
- package/dist/types/src/types/locales.d.ts +4 -0
- package/dist/types/src/types/modify_collections.d.ts +5 -0
- package/dist/types/src/types/plugins.d.ts +282 -0
- package/dist/types/src/types/properties.d.ts +1148 -0
- package/dist/types/src/types/property_config.d.ts +70 -0
- package/dist/types/src/types/relations.d.ts +336 -0
- package/dist/types/src/types/slots.d.ts +262 -0
- package/dist/types/src/types/translations.d.ts +874 -0
- package/dist/types/src/types/user_management_delegate.d.ts +121 -0
- package/dist/types/src/types/websockets.d.ts +78 -0
- package/dist/types/src/users/index.d.ts +2 -0
- package/dist/types/src/users/roles.d.ts +22 -0
- package/dist/types/src/users/user.d.ts +46 -0
- package/history_diff.log +385 -0
- package/jest.config.cjs +16 -0
- package/package.json +86 -0
- package/scratch.ts +9 -0
- package/src/api/ast-schema-editor.ts +289 -0
- package/src/api/collections_for_test/callbacks_test_collection.ts +60 -0
- package/src/api/errors.ts +179 -0
- package/src/api/graphql/graphql-schema-generator.ts +336 -0
- package/src/api/graphql/index.ts +2 -0
- package/src/api/index.ts +11 -0
- package/src/api/openapi-generator.ts +715 -0
- package/src/api/rest/api-generator-count.test.ts +113 -0
- package/src/api/rest/api-generator.ts +573 -0
- package/src/api/rest/index.ts +2 -0
- package/src/api/rest/query-parser.ts +155 -0
- package/src/api/schema-editor-routes.ts +41 -0
- package/src/api/server.ts +249 -0
- package/src/api/types.ts +90 -0
- package/src/auth/admin-routes.ts +605 -0
- package/src/auth/apple-oauth.ts +120 -0
- package/src/auth/bitbucket-oauth.ts +82 -0
- package/src/auth/discord-oauth.ts +83 -0
- package/src/auth/facebook-oauth.ts +72 -0
- package/src/auth/github-oauth.ts +110 -0
- package/src/auth/gitlab-oauth.ts +70 -0
- package/src/auth/google-oauth.ts +48 -0
- package/src/auth/index.ts +34 -0
- package/src/auth/interfaces.ts +363 -0
- package/src/auth/jwt.ts +181 -0
- package/src/auth/linkedin-oauth.ts +81 -0
- package/src/auth/microsoft-oauth.ts +88 -0
- package/src/auth/middleware.ts +384 -0
- package/src/auth/password.ts +77 -0
- package/src/auth/rate-limiter.ts +133 -0
- package/src/auth/routes.ts +788 -0
- package/src/auth/slack-oauth.ts +71 -0
- package/src/auth/spotify-oauth.ts +67 -0
- package/src/auth/twitter-oauth.ts +120 -0
- package/src/bootstrappers/index.ts +1 -0
- package/src/collections/BackendCollectionRegistry.ts +20 -0
- package/src/collections/loader.ts +49 -0
- package/src/cron/cron-loader.ts +89 -0
- package/src/cron/cron-routes.test.ts +265 -0
- package/src/cron/cron-routes.ts +85 -0
- package/src/cron/cron-scheduler.test.ts +547 -0
- package/src/cron/cron-scheduler.ts +576 -0
- package/src/cron/cron-store.ts +163 -0
- package/src/cron/index.ts +6 -0
- package/src/db/interfaces.ts +60 -0
- package/src/email/index.ts +18 -0
- package/src/email/smtp-email-service.ts +91 -0
- package/src/email/templates.ts +388 -0
- package/src/email/types.ts +105 -0
- package/src/functions/function-loader.ts +119 -0
- package/src/functions/function-routes.ts +31 -0
- package/src/functions/index.ts +3 -0
- package/src/history/history-routes.ts +129 -0
- package/src/history/index.ts +2 -0
- package/src/index.ts +66 -0
- package/src/init.ts +737 -0
- package/src/serve-spa.ts +81 -0
- package/src/services/driver-registry.ts +182 -0
- package/src/singleton.test.ts +28 -0
- package/src/singleton.ts +70 -0
- package/src/storage/LocalStorageController.ts +365 -0
- package/src/storage/S3StorageController.ts +298 -0
- package/src/storage/index.ts +43 -0
- package/src/storage/routes.ts +264 -0
- package/src/storage/storage-registry.ts +187 -0
- package/src/storage/types.ts +134 -0
- package/src/types/index.ts +27 -0
- package/src/utils/dev-port.ts +176 -0
- package/src/utils/logger.ts +143 -0
- package/src/utils/logging.ts +38 -0
- package/src/utils/request-logger.ts +66 -0
- package/src/utils/sql.ts +38 -0
- package/test/admin-routes.test.ts +640 -0
- package/test/api-generator.test.ts +501 -0
- package/test/ast-schema-editor.test.ts +63 -0
- package/test/auth-middleware-hono.test.ts +556 -0
- package/test/auth-routes.test.ts +1047 -0
- package/test/backend-hooks-admin.test.ts +394 -0
- package/test/backend-hooks-data.test.ts +408 -0
- package/test/driver-registry.test.ts +282 -0
- package/test/error-propagation.test.ts +226 -0
- package/test/errors-hono.test.ts +133 -0
- package/test/errors.test.ts +155 -0
- package/test/jwt-security.test.ts +182 -0
- package/test/jwt.test.ts +324 -0
- package/test/middleware.test.ts +300 -0
- package/test/password.test.ts +165 -0
- package/test/query-parser.test.ts +263 -0
- package/test/rate-limiter.test.ts +102 -0
- package/test/safe-compare.test.ts +66 -0
- package/test/singleton.test.ts +59 -0
- package/test/storage-local.test.ts +271 -0
- package/test/storage-registry.test.ts +282 -0
- package/test/storage-routes.test.ts +222 -0
- package/test/storage-s3.test.ts +304 -0
- package/test-ast.ts +28 -0
- package/test.ts +6 -0
- package/test_output.txt +1133 -0
- package/tsconfig.json +49 -0
- package/tsconfig.prod.json +20 -0
- package/vite.config.ts +80 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { QueryOptions } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Map PostgREST-style operators to Rebase WhereFilterOp
|
|
5
|
+
*/
|
|
6
|
+
export function mapOperator(op: string): string | null {
|
|
7
|
+
switch (op) {
|
|
8
|
+
case "eq": return "==";
|
|
9
|
+
case "neq": return "!=";
|
|
10
|
+
case "gt": return ">";
|
|
11
|
+
case "gte": return ">=";
|
|
12
|
+
case "lt": return "<";
|
|
13
|
+
case "lte": return "<=";
|
|
14
|
+
case "in": return "in";
|
|
15
|
+
case "nin": return "not-in";
|
|
16
|
+
case "cs": return "array-contains";
|
|
17
|
+
case "csa": return "array-contains-any";
|
|
18
|
+
default: return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse query parameters into QueryOptions
|
|
24
|
+
*/
|
|
25
|
+
export function parseQueryOptions(query: Record<string, unknown>): QueryOptions {
|
|
26
|
+
const options: QueryOptions = {};
|
|
27
|
+
|
|
28
|
+
// Pagination
|
|
29
|
+
if (query.limit) options.limit = parseInt(String(query.limit));
|
|
30
|
+
if (query.offset) options.offset = parseInt(String(query.offset));
|
|
31
|
+
if (query.page) {
|
|
32
|
+
const page = parseInt(String(query.page));
|
|
33
|
+
const limit = options.limit || 20;
|
|
34
|
+
options.offset = (page - 1) * limit;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Filtering
|
|
38
|
+
options.where = {};
|
|
39
|
+
|
|
40
|
+
// Legacy JSON where clause
|
|
41
|
+
if (query.where) {
|
|
42
|
+
try {
|
|
43
|
+
const parsedWhere = typeof query.where === "string"
|
|
44
|
+
? JSON.parse(query.where)
|
|
45
|
+
: query.where;
|
|
46
|
+
if (typeof parsedWhere !== "object" || parsedWhere === null || Array.isArray(parsedWhere)) {
|
|
47
|
+
throw new Error("Filter must be a JSON object");
|
|
48
|
+
}
|
|
49
|
+
Object.assign(options.where, parsedWhere);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
const message = e instanceof Error ? e.message : "malformed JSON";
|
|
52
|
+
const err = new Error(`Invalid 'where' filter: ${message}`) as Error & { code?: string; statusCode?: number };
|
|
53
|
+
err.code = "BAD_REQUEST";
|
|
54
|
+
err.statusCode = 400;
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// PostgREST style filtering
|
|
60
|
+
const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "where", "include", "fields", "searchString"];
|
|
61
|
+
for (const [key, rawValue] of Object.entries(query)) {
|
|
62
|
+
if (reservedQueryKeys.includes(key)) continue;
|
|
63
|
+
|
|
64
|
+
const value = Array.isArray(rawValue) ? rawValue[rawValue.length - 1] : rawValue;
|
|
65
|
+
|
|
66
|
+
if (typeof value === "string") {
|
|
67
|
+
const parts = value.split(".");
|
|
68
|
+
if (parts.length >= 2) {
|
|
69
|
+
const op = parts[0];
|
|
70
|
+
const val = parts.slice(1).join(".");
|
|
71
|
+
const rebaseOp = mapOperator(op);
|
|
72
|
+
|
|
73
|
+
if (rebaseOp) {
|
|
74
|
+
let parsedVal: string | number | boolean | null | (string | number | boolean | null)[] = val;
|
|
75
|
+
// Attempt to parse primitive types or arrays
|
|
76
|
+
if (val === "true") parsedVal = true;
|
|
77
|
+
else if (val === "false") parsedVal = false;
|
|
78
|
+
else if (val === "null") parsedVal = null;
|
|
79
|
+
else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
|
|
80
|
+
else if (val.startsWith("(")) {
|
|
81
|
+
// Array for 'in' or 'not-in' ops (e.g. (1,2,3) or (a,b,c))
|
|
82
|
+
const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
|
|
83
|
+
parsedVal = arrayContent.split(",").map(v => {
|
|
84
|
+
const trimmed = v.trim();
|
|
85
|
+
if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
|
|
86
|
+
if (trimmed === "true") return true;
|
|
87
|
+
if (trimmed === "false") return false;
|
|
88
|
+
if (trimmed === "null") return null;
|
|
89
|
+
return trimmed;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
options.where[key] = [rebaseOp, parsedVal];
|
|
94
|
+
} else {
|
|
95
|
+
// Fallback: assume implicit eq if the dot wasn't an operator (e.g. email or float)
|
|
96
|
+
let parsedVal: string | number | boolean | null = value;
|
|
97
|
+
if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
|
|
98
|
+
options.where[key] = ["==", parsedVal];
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
// Implicit eq
|
|
102
|
+
let parsedVal: string | number | boolean | null = value;
|
|
103
|
+
if (value === "true") parsedVal = true;
|
|
104
|
+
else if (value === "false") parsedVal = false;
|
|
105
|
+
else if (value === "null") parsedVal = null;
|
|
106
|
+
else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
|
|
107
|
+
|
|
108
|
+
options.where[key] = ["==", parsedVal];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (Object.keys(options.where).length === 0) {
|
|
114
|
+
delete options.where;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Sorting
|
|
118
|
+
if (query.orderBy) {
|
|
119
|
+
try {
|
|
120
|
+
options.orderBy = typeof query.orderBy === "string"
|
|
121
|
+
? JSON.parse(query.orderBy)
|
|
122
|
+
: query.orderBy;
|
|
123
|
+
} catch {
|
|
124
|
+
// Try simple format: "field:direction"
|
|
125
|
+
if (typeof query.orderBy === "string") {
|
|
126
|
+
const [field, direction] = query.orderBy.split(":");
|
|
127
|
+
const dir = (direction === "desc" ? "desc" : "asc") as "asc" | "desc";
|
|
128
|
+
options.orderBy = [
|
|
129
|
+
{
|
|
130
|
+
field,
|
|
131
|
+
direction: dir
|
|
132
|
+
}
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Relation includes
|
|
139
|
+
if (query.include) {
|
|
140
|
+
const includeStr = String(query.include).trim();
|
|
141
|
+
if (includeStr === "*") {
|
|
142
|
+
options.include = ["*"];
|
|
143
|
+
} else {
|
|
144
|
+
options.include = includeStr.split(",").map(s => s.trim()).filter(Boolean);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Field selection
|
|
149
|
+
if (query.fields) {
|
|
150
|
+
const fieldsStr = String(query.fields).trim();
|
|
151
|
+
options.fields = fieldsStr.split(",").map(s => s.trim()).filter(Boolean);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return options;
|
|
155
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { AstSchemaEditor } from "./ast-schema-editor";
|
|
3
|
+
import { errorHandler } from "./errors";
|
|
4
|
+
import { HonoEnv } from "./types";
|
|
5
|
+
|
|
6
|
+
export function createSchemaEditorRoutes(collectionsDir: string): Hono<HonoEnv> {
|
|
7
|
+
const router = new Hono<HonoEnv>();
|
|
8
|
+
router.onError(errorHandler);
|
|
9
|
+
const editor = new AstSchemaEditor(collectionsDir);
|
|
10
|
+
|
|
11
|
+
router.post("/property/save", async (c) => {
|
|
12
|
+
const body = await c.req.json();
|
|
13
|
+
const { collectionId, propertyKey, propertyConfig } = body;
|
|
14
|
+
await editor.saveProperty(collectionId, propertyKey, propertyConfig);
|
|
15
|
+
return c.json({ success: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
router.post("/property/delete", async (c) => {
|
|
19
|
+
const body = await c.req.json();
|
|
20
|
+
const { collectionId, propertyKey } = body;
|
|
21
|
+
await editor.deleteProperty(collectionId, propertyKey);
|
|
22
|
+
return c.json({ success: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
router.post("/collection/save", async (c) => {
|
|
26
|
+
const body = await c.req.json();
|
|
27
|
+
const { collectionId, collectionData } = body;
|
|
28
|
+
await editor.saveCollection(collectionId, collectionData);
|
|
29
|
+
return c.json({ success: true });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
router.post("/collection/delete", async (c) => {
|
|
33
|
+
const body = await c.req.json();
|
|
34
|
+
const { collectionId } = body;
|
|
35
|
+
await editor.deleteCollection(collectionId);
|
|
36
|
+
return c.json({ success: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return router;
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { cors } from "hono/cors";
|
|
3
|
+
import { secureHeaders } from "hono/secure-headers";
|
|
4
|
+
import { graphqlServer } from "@hono/graphql-server";
|
|
5
|
+
import { serve } from "@hono/node-server";
|
|
6
|
+
import { GraphQLSchemaGenerator } from "./graphql/graphql-schema-generator";
|
|
7
|
+
import { RestApiGenerator } from "./rest/api-generator";
|
|
8
|
+
import { DataDriver, EntityCollection, Relation } from "@rebasepro/types";
|
|
9
|
+
import { ApiConfig, HonoEnv } from "./types";
|
|
10
|
+
import { loadCollectionsFromDirectory } from "../collections/loader";
|
|
11
|
+
import { createSchemaEditorRoutes } from "./schema-editor-routes";
|
|
12
|
+
import { createAuthMiddleware, requireAuth, requireAdmin } from "../auth/middleware";
|
|
13
|
+
import { errorHandler } from "./errors";
|
|
14
|
+
import { generateOpenApiSpec } from "./openapi-generator";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Simplified API server that leverages existing Rebase infrastructure
|
|
18
|
+
* Can be used standalone or mounted on existing Hono app
|
|
19
|
+
*/
|
|
20
|
+
export class RebaseApiServer {
|
|
21
|
+
private app: Hono<HonoEnv>;
|
|
22
|
+
private router: Hono<HonoEnv>;
|
|
23
|
+
private config: ApiConfig;
|
|
24
|
+
private driver: DataDriver;
|
|
25
|
+
|
|
26
|
+
private constructor(config: ApiConfig & { driver: DataDriver }) {
|
|
27
|
+
this.config = {
|
|
28
|
+
basePath: "/api",
|
|
29
|
+
enableGraphQL: true,
|
|
30
|
+
enableREST: true,
|
|
31
|
+
pagination: {
|
|
32
|
+
defaultLimit: 20,
|
|
33
|
+
maxLimit: 100
|
|
34
|
+
},
|
|
35
|
+
...config
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.driver = config.driver;
|
|
39
|
+
|
|
40
|
+
this.app = new Hono<HonoEnv>();
|
|
41
|
+
this.router = new Hono<HonoEnv>();
|
|
42
|
+
|
|
43
|
+
this.setupMiddleware();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Factory method to create an asynchronously initialized ApiServer instance
|
|
48
|
+
*/
|
|
49
|
+
public static async create(config: ApiConfig & { driver: DataDriver }): Promise<RebaseApiServer> {
|
|
50
|
+
if (config.collectionsDir && (!config.collections || config.collections.length === 0)) {
|
|
51
|
+
config.collections = await loadCollectionsFromDirectory(config.collectionsDir);
|
|
52
|
+
} else if (!config.collections) {
|
|
53
|
+
config.collections = [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const server = new RebaseApiServer(config);
|
|
57
|
+
server.setupRoutes();
|
|
58
|
+
// Since we mount routes directly to router, we can let consumer attach it
|
|
59
|
+
server.app.route("/", server.router);
|
|
60
|
+
|
|
61
|
+
// Hono global error handler on the root app
|
|
62
|
+
server.app.onError(errorHandler);
|
|
63
|
+
server.router.onError(errorHandler);
|
|
64
|
+
|
|
65
|
+
return server;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Setup Hono middleware
|
|
70
|
+
*/
|
|
71
|
+
private setupMiddleware(): void {
|
|
72
|
+
// Security headers
|
|
73
|
+
this.router.use("/*", secureHeaders());
|
|
74
|
+
|
|
75
|
+
// CORS — only applied if explicitly configured via `cors` option.
|
|
76
|
+
// If omitted, the user is expected to configure CORS on their own
|
|
77
|
+
// Hono app before mounting the API (recommended approach).
|
|
78
|
+
if (this.config.cors) {
|
|
79
|
+
const origin = this.config.cors.origin;
|
|
80
|
+
this.router.use("/*", cors({
|
|
81
|
+
origin: typeof origin === "boolean"
|
|
82
|
+
? (origin ? ((o: string) => o) : "")
|
|
83
|
+
: (origin ?? "*"),
|
|
84
|
+
credentials: this.config.cors.credentials ?? false
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Auth middleware
|
|
89
|
+
this.router.use("/*", createAuthMiddleware({
|
|
90
|
+
driver: this.driver,
|
|
91
|
+
requireAuth: this.config.requireAuth ?? true,
|
|
92
|
+
validator: this.config.authValidator
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Setup API routes using existing services
|
|
98
|
+
*/
|
|
99
|
+
private setupRoutes(): void {
|
|
100
|
+
const basePath = this.config.basePath!;
|
|
101
|
+
|
|
102
|
+
// Health check
|
|
103
|
+
this.router.get(`${basePath}/health`, (c) => {
|
|
104
|
+
return c.json({
|
|
105
|
+
status: "healthy",
|
|
106
|
+
timestamp: new Date().toISOString(),
|
|
107
|
+
collections: this.config.collections?.map((col: EntityCollection) => col.slug) || [],
|
|
108
|
+
driver: this.driver.key
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Collections metadata endpoint
|
|
113
|
+
this.router.get(`${basePath}/collections`, (c) => {
|
|
114
|
+
const collectionsMetadata = (this.config.collections || []).map((col: EntityCollection) => ({
|
|
115
|
+
slug: col.slug,
|
|
116
|
+
name: col.name,
|
|
117
|
+
singularName: col.singularName,
|
|
118
|
+
description: col.description,
|
|
119
|
+
properties: Object.keys(col.properties),
|
|
120
|
+
relations: (col as EntityCollection & { relations?: Relation[] }).relations?.map((r: Relation) => ({
|
|
121
|
+
relationName: r.relationName,
|
|
122
|
+
target: typeof r.target === "function" ? r.target().slug : r.target,
|
|
123
|
+
cardinality: r.cardinality,
|
|
124
|
+
direction: r.direction
|
|
125
|
+
})) || []
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
return c.json({ data: collectionsMetadata });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// GraphQL endpoint
|
|
132
|
+
if (this.config.enableGraphQL) {
|
|
133
|
+
const schemaGenerator = new GraphQLSchemaGenerator(this.config.collections || [], this.driver);
|
|
134
|
+
const schema = schemaGenerator.generateSchema();
|
|
135
|
+
|
|
136
|
+
// Context is automatically passed to resolvers via contextValue containing Hono's 'c'
|
|
137
|
+
this.router.use(`${basePath}/graphql`, graphqlServer({
|
|
138
|
+
schema
|
|
139
|
+
}));
|
|
140
|
+
|
|
141
|
+
// Lightweight GraphiQL IDE
|
|
142
|
+
if (process.env.NODE_ENV !== "production") {
|
|
143
|
+
this.router.get(`${basePath}/graphiql`, (c) => {
|
|
144
|
+
return c.html(`<!DOCTYPE html>
|
|
145
|
+
<html>
|
|
146
|
+
<head>
|
|
147
|
+
<meta charset=utf-8/>
|
|
148
|
+
<title>Rebase GraphiQL</title>
|
|
149
|
+
<link rel="stylesheet" href="https://unpkg.com/graphiql/graphiql.min.css" />
|
|
150
|
+
<style>body,html,#graphiql{height:100%;margin:0;width:100%;}</style>
|
|
151
|
+
</head>
|
|
152
|
+
<body>
|
|
153
|
+
<div id="graphiql">Loading...</div>
|
|
154
|
+
<script crossorigin src="https://unpkg.com/react/umd/react.production.min.js"></script>
|
|
155
|
+
<script crossorigin src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>
|
|
156
|
+
<script src="https://unpkg.com/graphiql/graphiql.min.js"></script>
|
|
157
|
+
<script>
|
|
158
|
+
const fetcher = GraphiQL.createFetcher({ url: '${basePath}/graphql' });
|
|
159
|
+
ReactDOM.render(
|
|
160
|
+
React.createElement(GraphiQL, { fetcher }),
|
|
161
|
+
document.getElementById('graphiql'),
|
|
162
|
+
);
|
|
163
|
+
</script>
|
|
164
|
+
</body>
|
|
165
|
+
</html>`);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (this.config.enableREST) {
|
|
171
|
+
const restGenerator = new RestApiGenerator(this.config.collections || [], this.driver);
|
|
172
|
+
const restRoutes = restGenerator.generateRoutes();
|
|
173
|
+
this.router.route(basePath, restRoutes);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Schema Editor endpoints
|
|
177
|
+
if (this.config.collectionsDir) {
|
|
178
|
+
if (process.env.NODE_ENV === "production") {
|
|
179
|
+
console.warn("[RebaseApiServer] Schema Editor is disabled in production environments for security.");
|
|
180
|
+
} else {
|
|
181
|
+
// Auth middlewares applied to schema-editor via the router prefix
|
|
182
|
+
// MUST be declared before .route() so they execute first
|
|
183
|
+
this.router.use(`${basePath}/schema-editor/*`, requireAuth, requireAdmin);
|
|
184
|
+
const schemaEditorRoutes = createSchemaEditorRoutes(this.config.collectionsDir);
|
|
185
|
+
this.router.route(`${basePath}/schema-editor`, schemaEditorRoutes);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// OpenAPI endpoint
|
|
190
|
+
this.router.get(`${basePath}/docs`, (c) => {
|
|
191
|
+
const openApiSpec = generateOpenApiSpec(this.config.collections || [], {
|
|
192
|
+
basePath: this.config.basePath,
|
|
193
|
+
requireAuth: this.config.requireAuth ?? true
|
|
194
|
+
});
|
|
195
|
+
return c.json(openApiSpec);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Simple Swagger UI
|
|
199
|
+
if (process.env.NODE_ENV !== "production") {
|
|
200
|
+
this.router.get(`${basePath}/swagger`, (c) => {
|
|
201
|
+
return c.html(`
|
|
202
|
+
<!DOCTYPE html>
|
|
203
|
+
<html>
|
|
204
|
+
<head>
|
|
205
|
+
<title>Rebase API Documentation</title>
|
|
206
|
+
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui.css" />
|
|
207
|
+
</head>
|
|
208
|
+
<body>
|
|
209
|
+
<div id="swagger-ui"></div>
|
|
210
|
+
<script src="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui-bundle.js"></script>
|
|
211
|
+
<script>
|
|
212
|
+
SwaggerUIBundle({
|
|
213
|
+
url: '${basePath}/docs',
|
|
214
|
+
dom_id: '#swagger-ui'
|
|
215
|
+
});
|
|
216
|
+
</script>
|
|
217
|
+
</body>
|
|
218
|
+
</html>
|
|
219
|
+
`);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get the Hono router with all API routes
|
|
226
|
+
*/
|
|
227
|
+
getRouter(): Hono<HonoEnv> {
|
|
228
|
+
return this.router;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get the standalone Hono app
|
|
233
|
+
*/
|
|
234
|
+
getApp(): Hono<HonoEnv> {
|
|
235
|
+
return this.app;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Start the server (standalone mode) via @hono/node-server
|
|
240
|
+
*/
|
|
241
|
+
listen(port = 3000, callback?: () => void): void {
|
|
242
|
+
serve({
|
|
243
|
+
fetch: this.app.fetch,
|
|
244
|
+
port
|
|
245
|
+
}, () => {
|
|
246
|
+
if (callback) callback();
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
package/src/api/types.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { EntityCollection } from "@rebasepro/types";
|
|
2
|
+
import { AuthResult } from "../auth/middleware";
|
|
3
|
+
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
4
|
+
import { DataDriver } from "@rebasepro/types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hono Environment Variables
|
|
8
|
+
* Passed to generic Hono<HonoEnv> to type `c.get()`
|
|
9
|
+
*/
|
|
10
|
+
export type HonoEnv = {
|
|
11
|
+
Variables: {
|
|
12
|
+
user?: AuthResult | { userId?: string, roles?: string[] };
|
|
13
|
+
driver?: DataDriver;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Configuration for API generation
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Configuration for API generation
|
|
22
|
+
*/
|
|
23
|
+
export interface ApiConfig {
|
|
24
|
+
collections?: EntityCollection[];
|
|
25
|
+
collectionsDir?: string;
|
|
26
|
+
basePath?: string;
|
|
27
|
+
enableGraphQL?: boolean;
|
|
28
|
+
enableREST?: boolean;
|
|
29
|
+
cors?: {
|
|
30
|
+
origin?: string | string[] | boolean;
|
|
31
|
+
credentials?: boolean;
|
|
32
|
+
};
|
|
33
|
+
/** Whether auth is required for API endpoints (default: true) */
|
|
34
|
+
requireAuth?: boolean;
|
|
35
|
+
/** Optional custom validator for authentication */
|
|
36
|
+
authValidator?: (c: import("hono").Context<import("./types").HonoEnv>) => Promise<AuthResult>;
|
|
37
|
+
pagination?: {
|
|
38
|
+
defaultLimit: number;
|
|
39
|
+
maxLimit: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Context passed to resolvers and handlers
|
|
45
|
+
*/
|
|
46
|
+
export interface ApiContext {
|
|
47
|
+
user?: AuthResult;
|
|
48
|
+
collections: Map<string, EntityCollection>;
|
|
49
|
+
db: NodePgDatabase; // Drizzle DB instance
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Standard API response format
|
|
54
|
+
*/
|
|
55
|
+
export interface ApiResponse<T = unknown> {
|
|
56
|
+
data?: T;
|
|
57
|
+
error?: {
|
|
58
|
+
message: string;
|
|
59
|
+
code?: string;
|
|
60
|
+
details?: unknown;
|
|
61
|
+
};
|
|
62
|
+
meta?: {
|
|
63
|
+
total?: number;
|
|
64
|
+
page?: number;
|
|
65
|
+
limit?: number;
|
|
66
|
+
hasMore?: boolean;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Query options for API endpoints
|
|
72
|
+
*/
|
|
73
|
+
export interface QueryOptions {
|
|
74
|
+
limit?: number;
|
|
75
|
+
offset?: number;
|
|
76
|
+
where?: Record<string, unknown>;
|
|
77
|
+
orderBy?: Array<{ field: string; direction: "asc" | "desc" }>;
|
|
78
|
+
include?: string[];
|
|
79
|
+
/** Columns to return in the response (field-level selection) */
|
|
80
|
+
fields?: string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Relation resolution configuration
|
|
85
|
+
*/
|
|
86
|
+
export interface RelationConfig {
|
|
87
|
+
relationName: string;
|
|
88
|
+
depth?: number;
|
|
89
|
+
include?: string[];
|
|
90
|
+
}
|