@rebasepro/server-core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-errors.txt +52 -0
- package/dist/api/ast-schema-editor.d.ts +21 -0
- package/dist/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
- package/dist/api/errors.d.ts +53 -0
- package/dist/api/graphql/graphql-schema-generator.d.ts +44 -0
- package/dist/api/graphql/index.d.ts +1 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/logs-routes.d.ts +37 -0
- package/dist/api/openapi-generator.d.ts +16 -0
- package/dist/api/rest/api-generator.d.ts +76 -0
- package/dist/api/rest/index.d.ts +1 -0
- package/dist/api/rest/query-parser.d.ts +8 -0
- package/dist/api/schema-editor-routes.d.ts +3 -0
- package/dist/api/server.d.ts +40 -0
- package/dist/api/types.d.ts +104 -0
- package/dist/auth/adapter-middleware.d.ts +43 -0
- package/dist/auth/admin-roles-route.d.ts +18 -0
- package/dist/auth/admin-user-ops.d.ts +79 -0
- package/dist/auth/admin-users-route.d.ts +28 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +39 -0
- package/dist/auth/api-keys/api-key-permission-guard.d.ts +32 -0
- package/dist/auth/api-keys/api-key-routes.d.ts +20 -0
- package/dist/auth/api-keys/api-key-store.d.ts +35 -0
- package/dist/auth/api-keys/api-key-types.d.ts +96 -0
- package/dist/auth/api-keys/index.d.ts +17 -0
- package/dist/auth/apple-oauth.d.ts +30 -0
- package/dist/auth/auth-hooks.d.ts +242 -0
- package/dist/auth/bitbucket-oauth.d.ts +11 -0
- package/dist/auth/builtin-auth-adapter.d.ts +51 -0
- package/dist/auth/crypto-utils.d.ts +16 -0
- package/dist/auth/custom-auth-adapter.d.ts +39 -0
- package/dist/auth/discord-oauth.d.ts +14 -0
- package/dist/auth/facebook-oauth.d.ts +14 -0
- package/dist/auth/github-oauth.d.ts +15 -0
- package/dist/auth/gitlab-oauth.d.ts +13 -0
- package/dist/auth/google-oauth.d.ts +47 -0
- package/dist/auth/index.d.ts +37 -0
- package/dist/auth/interfaces.d.ts +431 -0
- package/dist/auth/jwt.d.ts +55 -0
- package/dist/auth/linkedin-oauth.d.ts +18 -0
- package/dist/auth/magic-link-routes.d.ts +30 -0
- package/dist/auth/mfa-crypto.d.ts +23 -0
- package/dist/auth/mfa-routes.d.ts +7 -0
- package/dist/auth/mfa.d.ts +49 -0
- package/dist/auth/microsoft-oauth.d.ts +16 -0
- package/dist/auth/middleware.d.ts +113 -0
- package/dist/auth/password.d.ts +22 -0
- package/dist/auth/rate-limiter.d.ts +50 -0
- package/dist/auth/reset-password-admin.d.ts +29 -0
- package/dist/auth/rls-scope.d.ts +31 -0
- package/dist/auth/routes.d.ts +35 -0
- package/dist/auth/session-routes.d.ts +27 -0
- package/dist/auth/slack-oauth.d.ts +12 -0
- package/dist/auth/spotify-oauth.d.ts +12 -0
- package/dist/auth/twitter-oauth.d.ts +18 -0
- package/dist/backend-CIxN4FVm.js +15 -0
- package/dist/backend-CIxN4FVm.js.map +1 -0
- package/dist/base64-js-C_frYBkI.js +1687 -0
- package/dist/base64-js-C_frYBkI.js.map +1 -0
- package/dist/chunk-Dze3rakg.js +42 -0
- package/dist/collections/BackendCollectionRegistry.d.ts +13 -0
- package/dist/collections/loader.d.ts +5 -0
- package/dist/cron/cron-loader.d.ts +17 -0
- package/dist/cron/cron-routes.d.ts +14 -0
- package/dist/cron/cron-scheduler.d.ts +106 -0
- package/dist/cron/cron-store.d.ts +32 -0
- package/dist/cron/index.d.ts +6 -0
- package/dist/db/interfaces.d.ts +18 -0
- package/dist/dist-DMO-zF6D.js +832 -0
- package/dist/dist-DMO-zF6D.js.map +1 -0
- package/dist/email/index.d.ts +6 -0
- package/dist/email/smtp-email-service.d.ts +25 -0
- package/dist/email/templates.d.ts +50 -0
- package/dist/email/types.d.ts +126 -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/functions/define-function.d.ts +50 -0
- package/dist/functions/function-loader.d.ts +17 -0
- package/dist/functions/function-routes.d.ts +10 -0
- package/dist/functions/index.d.ts +5 -0
- package/dist/history/history-routes.d.ts +23 -0
- package/dist/history/index.d.ts +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.es.js +61322 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +103633 -0
- package/dist/index.umd.js.map +1 -0
- 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/init.d.ts +314 -0
- package/dist/jws-BqRRaK11.js +616 -0
- package/dist/jws-BqRRaK11.js.map +1 -0
- package/dist/jwt-BETC8a1J.js +3560 -0
- package/dist/jwt-BETC8a1J.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/serve-spa.d.ts +30 -0
- package/dist/services/driver-registry.d.ts +78 -0
- package/dist/services/routed-realtime-service.d.ts +43 -0
- package/dist/services/webhook-service.d.ts +29 -0
- package/dist/singleton.d.ts +35 -0
- package/dist/src-CB2PIpBe.js +1182 -0
- package/dist/src-CB2PIpBe.js.map +1 -0
- package/dist/src-DjzOT1kG.js +29746 -0
- package/dist/src-DjzOT1kG.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +43 -0
- package/dist/storage/LocalStorageController.d.ts +46 -0
- package/dist/storage/S3StorageController.d.ts +36 -0
- package/dist/storage/image-transform.d.ts +50 -0
- package/dist/storage/index.d.ts +31 -0
- package/dist/storage/routes.d.ts +70 -0
- package/dist/storage/storage-registry.d.ts +78 -0
- package/dist/storage/tus-handler.d.ts +53 -0
- package/dist/storage/types.d.ts +128 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/utils/dev-port.d.ts +37 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logging.d.ts +9 -0
- package/dist/utils/request-id.d.ts +4 -0
- package/dist/utils/request-logger.d.ts +19 -0
- package/dist/utils/sql.d.ts +27 -0
- package/package.json +22 -14
- package/src/api/graphql/graphql-schema-generator.ts +5 -5
- package/src/api/openapi-generator.ts +2 -2
- package/src/api/rest/api-generator.ts +44 -123
- package/src/api/rest/query-parser.ts +6 -23
- package/src/api/types.ts +2 -2
- package/src/auth/auth-hooks.ts +1 -1
- package/src/auth/builtin-auth-adapter.ts +1 -2
- package/src/auth/interfaces.ts +11 -2
- package/src/auth/middleware.ts +4 -4
- package/src/auth/routes.ts +1 -2
- package/src/functions/define-function.ts +59 -0
- package/src/functions/function-loader.ts +16 -0
- package/src/functions/index.ts +2 -0
- package/src/index.ts +70 -37
- package/src/init.ts +78 -21
- package/src/storage/GCSStorageController.ts +334 -0
- package/src/storage/index.ts +9 -3
- package/src/storage/routes.ts +191 -23
- package/src/storage/tus-handler.ts +16 -4
- package/src/storage/types.ts +25 -3
- package/test/api-generator.test.ts +1 -1
- package/test/auth-config-types.test.ts +40 -0
- package/test/define-function.test.ts +45 -0
- package/test/storage-routes.test.ts +160 -0
- package/test/backend-hooks-data.test.ts +0 -477
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SQL } from "drizzle-orm";
|
|
2
|
+
/**
|
|
3
|
+
* Returns a SQL chunk calling `auth.uid()` — the current user's ID.
|
|
4
|
+
* This is a Supabase-style helper function created in the `auth` schema
|
|
5
|
+
* that reads `app.user_id` set per-transaction by `withAuth()`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* sql`${table.user_id} = ${authUid()}`
|
|
9
|
+
*/
|
|
10
|
+
export declare const authUid: () => SQL;
|
|
11
|
+
/**
|
|
12
|
+
* Returns a SQL chunk calling `auth.roles()` — the current user's roles
|
|
13
|
+
* as a comma-separated string.
|
|
14
|
+
* Reads `app.user_roles` set per-transaction by `withAuth()`.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* sql`auth.roles() ~ 'admin'`
|
|
18
|
+
*/
|
|
19
|
+
export declare const authRoles: () => SQL;
|
|
20
|
+
/**
|
|
21
|
+
* Returns a SQL chunk calling `auth.jwt()` — the full JWT claims as JSONB.
|
|
22
|
+
* Reads `app.jwt` set per-transaction by `withAuth()`.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* sql`auth.jwt()->>'sub'`
|
|
26
|
+
*/
|
|
27
|
+
export declare const authJwt: () => SQL;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/server-core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"description": "Database-Agnostic Backend Core for Rebase",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -30,13 +30,6 @@
|
|
|
30
30
|
"backend",
|
|
31
31
|
"database"
|
|
32
32
|
],
|
|
33
|
-
"scripts": {
|
|
34
|
-
"watch": "vite build --watch",
|
|
35
|
-
"build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json",
|
|
36
|
-
"test:lint": "eslint \"src/**\" --quiet",
|
|
37
|
-
"test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest --passWithNoTests --forceExit",
|
|
38
|
-
"clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
|
|
39
|
-
},
|
|
40
33
|
"exports": {
|
|
41
34
|
".": {
|
|
42
35
|
"types": "./dist/index.d.ts",
|
|
@@ -51,10 +44,6 @@
|
|
|
51
44
|
"@aws-sdk/s3-request-presigner": "^3.1068.0",
|
|
52
45
|
"@hono/graphql-server": "0.7.0",
|
|
53
46
|
"@hono/node-server": "2.0.4",
|
|
54
|
-
"@rebasepro/client": "workspace:*",
|
|
55
|
-
"@rebasepro/common": "workspace:*",
|
|
56
|
-
"@rebasepro/types": "workspace:*",
|
|
57
|
-
"@rebasepro/utils": "workspace:*",
|
|
58
47
|
"drizzle-orm": "^0.45.2",
|
|
59
48
|
"google-auth-library": "^10.7.0",
|
|
60
49
|
"graphql": "^16.14.2",
|
|
@@ -64,7 +53,11 @@
|
|
|
64
53
|
"sharp": "^0.35.1",
|
|
65
54
|
"ts-morph": "28.0.0",
|
|
66
55
|
"ws": "^8.21.0",
|
|
67
|
-
"zod": "^4.4.3"
|
|
56
|
+
"zod": "^4.4.3",
|
|
57
|
+
"@rebasepro/client": "0.8.0",
|
|
58
|
+
"@rebasepro/common": "0.8.0",
|
|
59
|
+
"@rebasepro/types": "0.8.0",
|
|
60
|
+
"@rebasepro/utils": "0.8.0"
|
|
68
61
|
},
|
|
69
62
|
"devDependencies": {
|
|
70
63
|
"@types/jest": "^30.0.0",
|
|
@@ -81,8 +74,23 @@
|
|
|
81
74
|
"typescript": "^6.0.3",
|
|
82
75
|
"vite": "^8.0.16"
|
|
83
76
|
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"@google-cloud/storage": "^7.0.0"
|
|
79
|
+
},
|
|
80
|
+
"peerDependenciesMeta": {
|
|
81
|
+
"@google-cloud/storage": {
|
|
82
|
+
"optional": true
|
|
83
|
+
}
|
|
84
|
+
},
|
|
84
85
|
"gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
|
|
85
86
|
"publishConfig": {
|
|
86
87
|
"access": "public"
|
|
88
|
+
},
|
|
89
|
+
"scripts": {
|
|
90
|
+
"watch": "vite build --watch",
|
|
91
|
+
"build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json",
|
|
92
|
+
"test:lint": "eslint \"src/**\" --quiet",
|
|
93
|
+
"test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest --passWithNoTests --forceExit",
|
|
94
|
+
"clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
|
|
87
95
|
}
|
|
88
|
-
}
|
|
96
|
+
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
GraphQLInputObjectType,
|
|
12
12
|
GraphQLInputFieldConfig
|
|
13
13
|
} from "graphql";
|
|
14
|
-
import { DataDriver, EntityCollection, FetchCollectionProps, Property, Entity } from "@rebasepro/types";
|
|
14
|
+
import { DataDriver, EntityCollection, FetchCollectionProps, Property, Entity, getCollectionDataPath } from "@rebasepro/types";
|
|
15
15
|
import { isOperationAllowed, type ApiKeyOperation } from "../../auth/api-keys/api-key-permission-guard";
|
|
16
16
|
import type { ApiKeyMasked } from "../../auth/api-keys/api-key-types";
|
|
17
17
|
|
|
@@ -219,7 +219,7 @@ export class GraphQLSchemaGenerator {
|
|
|
219
219
|
const ds = ctx?.get?.("driver") as DataDriver | undefined ?? (ctx as Record<string, unknown> | undefined)?.driver as DataDriver | undefined;
|
|
220
220
|
if (!ds) throw new Error("Scoped driver not available");
|
|
221
221
|
const entity = await ds.fetchEntity({
|
|
222
|
-
path: collection
|
|
222
|
+
path: getCollectionDataPath(collection),
|
|
223
223
|
entityId: args.id,
|
|
224
224
|
collection
|
|
225
225
|
});
|
|
@@ -256,7 +256,7 @@ defaultValue: 0 },
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
const entities = await ds.fetchCollection({
|
|
259
|
-
path: collection
|
|
259
|
+
path: getCollectionDataPath(collection),
|
|
260
260
|
collection,
|
|
261
261
|
filter,
|
|
262
262
|
limit: args.limit,
|
|
@@ -321,7 +321,7 @@ defaultValue: 0 },
|
|
|
321
321
|
const ds = ctx?.get?.("driver") as DataDriver | undefined ?? (ctx as Record<string, unknown> | undefined)?.driver as DataDriver | undefined;
|
|
322
322
|
if (!ds) throw new Error("Scoped driver not available");
|
|
323
323
|
const entity = await ds.saveEntity({
|
|
324
|
-
path: collection
|
|
324
|
+
path: getCollectionDataPath(collection),
|
|
325
325
|
entityId: args.id,
|
|
326
326
|
values: args.input,
|
|
327
327
|
collection,
|
|
@@ -344,7 +344,7 @@ defaultValue: 0 },
|
|
|
344
344
|
const ds = ctx?.get?.("driver") as DataDriver | undefined ?? (ctx as Record<string, unknown> | undefined)?.driver as DataDriver | undefined;
|
|
345
345
|
if (!ds) throw new Error("Scoped driver not available");
|
|
346
346
|
const existingEntity = await ds.fetchEntity({
|
|
347
|
-
path: collection
|
|
347
|
+
path: getCollectionDataPath(collection),
|
|
348
348
|
entityId: args.id,
|
|
349
349
|
collection
|
|
350
350
|
});
|
|
@@ -491,9 +491,9 @@ function convertPropertyToSchema(property: Property): Record<string, unknown> {
|
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
if (sp.email) base.format = "email";
|
|
494
|
-
if (sp.url) base.format = "uri";
|
|
494
|
+
if (sp.ui?.url) base.format = "uri";
|
|
495
495
|
if (sp.storage) base.format = "uri";
|
|
496
|
-
if (sp.markdown) base.description = (base.description || "") + " (Markdown)";
|
|
496
|
+
if (sp.ui?.markdown) base.description = (base.description || "") + " (Markdown)";
|
|
497
497
|
|
|
498
498
|
return base;
|
|
499
499
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { AuthAdapter, DataDriver, Entity, EntityCollection,
|
|
2
|
+
import { AuthAdapter, DataDriver, Entity, EntityCollection, RestFetchService, getCollectionDataPath } 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
|
-
|
|
8
|
+
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -16,31 +16,21 @@ export class RestApiGenerator {
|
|
|
16
16
|
private collections: EntityCollection[];
|
|
17
17
|
private router: Hono<HonoEnv>;
|
|
18
18
|
private driver: DataDriver;
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
private authAdapter?: AuthAdapter;
|
|
21
21
|
|
|
22
22
|
constructor(
|
|
23
23
|
collections: EntityCollection[],
|
|
24
24
|
driver: DataDriver,
|
|
25
|
-
dataHooks?: DataHooks,
|
|
26
25
|
authAdapter?: AuthAdapter
|
|
27
26
|
) {
|
|
28
27
|
this.collections = collections;
|
|
29
28
|
this.driver = driver;
|
|
30
|
-
this.dataHooks = dataHooks;
|
|
31
29
|
this.authAdapter = authAdapter;
|
|
32
30
|
this.router = new Hono<HonoEnv>();
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
private buildHookContext(c: { get: (key: string) => unknown }, method: BackendHookContext["method"]): BackendHookContext {
|
|
37
|
-
const user = c.get("user") as { userId: string; roles?: string[] } | undefined;
|
|
38
|
-
return {
|
|
39
|
-
requestUser: user ? { userId: user.userId,
|
|
40
|
-
roles: user.roles ?? [] } : undefined,
|
|
41
|
-
method
|
|
42
|
-
};
|
|
43
|
-
}
|
|
33
|
+
|
|
44
34
|
|
|
45
35
|
/**
|
|
46
36
|
* Generate REST routes using existing DataDriver
|
|
@@ -124,7 +114,7 @@ roles: user.roles ?? [] } : undefined,
|
|
|
124
114
|
|
|
125
115
|
const driver = this.getScopedDriver(c);
|
|
126
116
|
const fetchService = this.getFetchService(driver);
|
|
127
|
-
|
|
117
|
+
|
|
128
118
|
|
|
129
119
|
// Use include-aware path when available
|
|
130
120
|
if (fetchService) {
|
|
@@ -132,7 +122,7 @@ roles: user.roles ?? [] } : undefined,
|
|
|
132
122
|
let entities = await fetchService.fetchCollectionForRest(
|
|
133
123
|
collectionPath,
|
|
134
124
|
{
|
|
135
|
-
filter: queryOptions.where
|
|
125
|
+
filter: queryOptions.where,
|
|
136
126
|
limit: queryOptions.limit,
|
|
137
127
|
offset: queryOptions.offset,
|
|
138
128
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
@@ -143,7 +133,7 @@ roles: user.roles ?? [] } : undefined,
|
|
|
143
133
|
queryOptions.include
|
|
144
134
|
);
|
|
145
135
|
|
|
146
|
-
|
|
136
|
+
|
|
147
137
|
|
|
148
138
|
const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
|
|
149
139
|
|
|
@@ -161,7 +151,7 @@ roles: user.roles ?? [] } : undefined,
|
|
|
161
151
|
// Fallback path
|
|
162
152
|
let entities = await this.fetchRawCollection(driver, resolvedCollection, queryOptions, searchString);
|
|
163
153
|
|
|
164
|
-
|
|
154
|
+
|
|
165
155
|
|
|
166
156
|
const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
|
|
167
157
|
|
|
@@ -184,7 +174,7 @@ roles: user.roles ?? [] } : undefined,
|
|
|
184
174
|
const queryOptions = parseQueryOptions(queryDict);
|
|
185
175
|
const driver = this.getScopedDriver(c);
|
|
186
176
|
const fetchService = this.getFetchService(driver);
|
|
187
|
-
|
|
177
|
+
|
|
188
178
|
|
|
189
179
|
// Use include-aware path when available
|
|
190
180
|
if (fetchService) {
|
|
@@ -199,10 +189,7 @@ roles: user.roles ?? [] } : undefined,
|
|
|
199
189
|
throw ApiError.notFound("Entity not found");
|
|
200
190
|
}
|
|
201
191
|
|
|
202
|
-
|
|
203
|
-
if (!entity) {
|
|
204
|
-
throw ApiError.notFound("Entity not found");
|
|
205
|
-
}
|
|
192
|
+
|
|
206
193
|
|
|
207
194
|
return c.json(entity);
|
|
208
195
|
}
|
|
@@ -214,10 +201,7 @@ roles: user.roles ?? [] } : undefined,
|
|
|
214
201
|
throw ApiError.notFound("Entity not found");
|
|
215
202
|
}
|
|
216
203
|
|
|
217
|
-
|
|
218
|
-
if (!entity) {
|
|
219
|
-
throw ApiError.notFound("Entity not found");
|
|
220
|
-
}
|
|
204
|
+
|
|
221
205
|
|
|
222
206
|
return c.json(entity);
|
|
223
207
|
});
|
|
@@ -228,13 +212,11 @@ roles: user.roles ?? [] } : undefined,
|
|
|
228
212
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
229
213
|
const driver = this.getScopedDriver(c);
|
|
230
214
|
const path = collection.slug;
|
|
231
|
-
|
|
215
|
+
|
|
232
216
|
|
|
233
217
|
let body = await c.req.json().catch(() => ({}));
|
|
234
218
|
|
|
235
|
-
|
|
236
|
-
body = await this.dataHooks.beforeSave(path, body, undefined, hookCtx);
|
|
237
|
-
}
|
|
219
|
+
|
|
238
220
|
|
|
239
221
|
const isAuth = collection.auth;
|
|
240
222
|
const isAuthCollection = isAuth === true || (isAuth && typeof isAuth === "object" && isAuth.enabled === true);
|
|
@@ -263,11 +245,7 @@ values: entity.values as Record<string, unknown> },
|
|
|
263
245
|
|
|
264
246
|
const response = this.formatResponse(entity) as Record<string, unknown>;
|
|
265
247
|
|
|
266
|
-
|
|
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
|
-
}
|
|
248
|
+
|
|
271
249
|
|
|
272
250
|
return c.json({
|
|
273
251
|
...response,
|
|
@@ -285,11 +263,7 @@ values: entity.values as Record<string, unknown> },
|
|
|
285
263
|
|
|
286
264
|
const response = this.formatResponse(entity);
|
|
287
265
|
|
|
288
|
-
|
|
289
|
-
Promise.resolve(this.dataHooks.afterSave(path, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
290
|
-
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
291
|
-
});
|
|
292
|
-
}
|
|
266
|
+
|
|
293
267
|
|
|
294
268
|
return c.json(response, 201);
|
|
295
269
|
} catch (error) {
|
|
@@ -315,10 +289,10 @@ values: entity.values as Record<string, unknown> },
|
|
|
315
289
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
316
290
|
const id = c.req.param("id");
|
|
317
291
|
const driver = this.getScopedDriver(c);
|
|
318
|
-
|
|
292
|
+
|
|
319
293
|
|
|
320
294
|
const existingEntity = await driver.fetchEntity({
|
|
321
|
-
path: collection
|
|
295
|
+
path: getCollectionDataPath(collection),
|
|
322
296
|
entityId: String(id),
|
|
323
297
|
collection: resolvedCollection
|
|
324
298
|
});
|
|
@@ -329,12 +303,10 @@ values: entity.values as Record<string, unknown> },
|
|
|
329
303
|
|
|
330
304
|
let body = await c.req.json().catch(() => ({}));
|
|
331
305
|
|
|
332
|
-
|
|
333
|
-
body = await this.dataHooks.beforeSave(collection.slug, body, String(id), hookCtx);
|
|
334
|
-
}
|
|
306
|
+
|
|
335
307
|
|
|
336
308
|
const entity = await driver.saveEntity({
|
|
337
|
-
path: collection
|
|
309
|
+
path: getCollectionDataPath(collection),
|
|
338
310
|
entityId: String(id),
|
|
339
311
|
values: body,
|
|
340
312
|
collection: resolvedCollection,
|
|
@@ -343,11 +315,7 @@ values: entity.values as Record<string, unknown> },
|
|
|
343
315
|
|
|
344
316
|
const response = this.formatResponse(entity);
|
|
345
317
|
|
|
346
|
-
|
|
347
|
-
Promise.resolve(this.dataHooks.afterSave(collection.slug, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
348
|
-
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
349
|
-
});
|
|
350
|
-
}
|
|
318
|
+
|
|
351
319
|
|
|
352
320
|
return c.json(response);
|
|
353
321
|
} catch (error) {
|
|
@@ -371,10 +339,10 @@ values: entity.values as Record<string, unknown> },
|
|
|
371
339
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
372
340
|
const id = c.req.param("id");
|
|
373
341
|
const driver = this.getScopedDriver(c);
|
|
374
|
-
|
|
342
|
+
|
|
375
343
|
|
|
376
344
|
const existingEntity = await driver.fetchEntity({
|
|
377
|
-
path: collection
|
|
345
|
+
path: getCollectionDataPath(collection),
|
|
378
346
|
entityId: String(id),
|
|
379
347
|
collection: resolvedCollection
|
|
380
348
|
});
|
|
@@ -383,20 +351,14 @@ values: entity.values as Record<string, unknown> },
|
|
|
383
351
|
throw ApiError.notFound("Entity not found");
|
|
384
352
|
}
|
|
385
353
|
|
|
386
|
-
|
|
387
|
-
await this.dataHooks.beforeDelete(collection.slug, String(id), hookCtx);
|
|
388
|
-
}
|
|
354
|
+
|
|
389
355
|
|
|
390
356
|
await driver.deleteEntity({
|
|
391
357
|
entity: existingEntity,
|
|
392
358
|
collection: resolvedCollection
|
|
393
359
|
});
|
|
394
360
|
|
|
395
|
-
|
|
396
|
-
Promise.resolve(this.dataHooks.afterDelete(collection.slug, String(id), hookCtx)).catch(err => {
|
|
397
|
-
logger.error("[BackendHooks] data.afterDelete error", { error: err instanceof Error ? err.message : err });
|
|
398
|
-
});
|
|
399
|
-
}
|
|
361
|
+
|
|
400
362
|
|
|
401
363
|
return new Response(null, { status: 204 });
|
|
402
364
|
});
|
|
@@ -464,7 +426,7 @@ entityId };
|
|
|
464
426
|
|
|
465
427
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
466
428
|
|
|
467
|
-
|
|
429
|
+
|
|
468
430
|
|
|
469
431
|
if (parsed.entityId === "count") {
|
|
470
432
|
// GET /parent/:parentId/child/count — count child entities
|
|
@@ -474,7 +436,7 @@ entityId };
|
|
|
474
436
|
|
|
475
437
|
const total = driver.countEntities ? await driver.countEntities({
|
|
476
438
|
path: parsed.collectionPath,
|
|
477
|
-
filter: queryOptions.where
|
|
439
|
+
filter: queryOptions.where,
|
|
478
440
|
searchString
|
|
479
441
|
}) : 0;
|
|
480
442
|
|
|
@@ -487,11 +449,7 @@ entityId };
|
|
|
487
449
|
});
|
|
488
450
|
if (!entity) throw ApiError.notFound("Entity not found");
|
|
489
451
|
|
|
490
|
-
|
|
491
|
-
const hookResult = await this.applyAfterRead(c.req.param("parent"), flatResult, hookCtx);
|
|
492
|
-
if (!hookResult) throw ApiError.notFound("Entity not found");
|
|
493
|
-
|
|
494
|
-
return c.json(hookResult);
|
|
452
|
+
return c.json(this.flattenEntity(entity));
|
|
495
453
|
} else {
|
|
496
454
|
// GET /parent/:parentId/child — list entities
|
|
497
455
|
const queryDict = c.req.queries();
|
|
@@ -499,19 +457,18 @@ entityId };
|
|
|
499
457
|
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : undefined;
|
|
500
458
|
const entities = await driver.fetchCollection({
|
|
501
459
|
path: parsed.collectionPath,
|
|
502
|
-
filter: queryOptions.where
|
|
460
|
+
filter: queryOptions.where,
|
|
503
461
|
limit: queryOptions.limit,
|
|
504
462
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
505
463
|
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
506
464
|
searchString
|
|
507
465
|
});
|
|
508
466
|
|
|
509
|
-
|
|
510
|
-
flatEntities = await this.applyAfterReadBatch(c.req.param("parent"), flatEntities, hookCtx);
|
|
467
|
+
const flatEntities = entities.map(e => this.flattenEntity(e));
|
|
511
468
|
|
|
512
469
|
const total = driver.countEntities ? await driver.countEntities({
|
|
513
470
|
path: parsed.collectionPath,
|
|
514
|
-
filter: queryOptions.where
|
|
471
|
+
filter: queryOptions.where,
|
|
515
472
|
searchString
|
|
516
473
|
}) : flatEntities.length;
|
|
517
474
|
|
|
@@ -536,14 +493,12 @@ entityId };
|
|
|
536
493
|
if (!parsed || parsed.entityId) return next();
|
|
537
494
|
|
|
538
495
|
const driver = this.getScopedDriver(c);
|
|
539
|
-
|
|
496
|
+
|
|
540
497
|
|
|
541
498
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
542
499
|
let body = await c.req.json().catch(() => ({}));
|
|
543
500
|
|
|
544
|
-
|
|
545
|
-
body = await this.dataHooks.beforeSave(parsed.collectionPath, body, undefined, hookCtx);
|
|
546
|
-
}
|
|
501
|
+
|
|
547
502
|
|
|
548
503
|
const entity = await driver.saveEntity({
|
|
549
504
|
path: parsed.collectionPath,
|
|
@@ -553,11 +508,7 @@ entityId };
|
|
|
553
508
|
|
|
554
509
|
const response = this.formatResponse(entity);
|
|
555
510
|
|
|
556
|
-
|
|
557
|
-
Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
558
|
-
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
559
|
-
});
|
|
560
|
-
}
|
|
511
|
+
|
|
561
512
|
|
|
562
513
|
return c.json(response, 201);
|
|
563
514
|
});
|
|
@@ -571,15 +522,13 @@ entityId };
|
|
|
571
522
|
if (!parsed || !parsed.entityId) return next();
|
|
572
523
|
|
|
573
524
|
const driver = this.getScopedDriver(c);
|
|
574
|
-
|
|
525
|
+
|
|
575
526
|
|
|
576
527
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
577
528
|
|
|
578
529
|
let body = await c.req.json().catch(() => ({}));
|
|
579
530
|
|
|
580
|
-
|
|
581
|
-
body = await this.dataHooks.beforeSave(parsed.collectionPath, body, parsed.entityId, hookCtx);
|
|
582
|
-
}
|
|
531
|
+
|
|
583
532
|
|
|
584
533
|
const entity = await driver.saveEntity({
|
|
585
534
|
path: parsed.collectionPath,
|
|
@@ -590,11 +539,7 @@ entityId };
|
|
|
590
539
|
|
|
591
540
|
const response = this.formatResponse(entity);
|
|
592
541
|
|
|
593
|
-
|
|
594
|
-
Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
595
|
-
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
596
|
-
});
|
|
597
|
-
}
|
|
542
|
+
|
|
598
543
|
|
|
599
544
|
return c.json(response);
|
|
600
545
|
});
|
|
@@ -608,7 +553,7 @@ entityId };
|
|
|
608
553
|
if (!parsed || !parsed.entityId) return next();
|
|
609
554
|
|
|
610
555
|
const driver = this.getScopedDriver(c);
|
|
611
|
-
|
|
556
|
+
|
|
612
557
|
|
|
613
558
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
614
559
|
|
|
@@ -619,17 +564,11 @@ entityId };
|
|
|
619
564
|
|
|
620
565
|
if (!existingEntity) throw ApiError.notFound("Entity not found");
|
|
621
566
|
|
|
622
|
-
|
|
623
|
-
await this.dataHooks.beforeDelete(parsed.collectionPath, parsed.entityId, hookCtx);
|
|
624
|
-
}
|
|
567
|
+
|
|
625
568
|
|
|
626
569
|
await driver.deleteEntity({ entity: existingEntity });
|
|
627
570
|
|
|
628
|
-
|
|
629
|
-
Promise.resolve(this.dataHooks.afterDelete(parsed.collectionPath, parsed.entityId, hookCtx)).catch(err => {
|
|
630
|
-
logger.error("[BackendHooks] data.afterDelete error", { error: err instanceof Error ? err.message : err });
|
|
631
|
-
});
|
|
632
|
-
}
|
|
571
|
+
|
|
633
572
|
|
|
634
573
|
return new Response(null, { status: 204 });
|
|
635
574
|
});
|
|
@@ -686,9 +625,9 @@ entityId };
|
|
|
686
625
|
*/
|
|
687
626
|
private async fetchRawCollection(driver: DataDriver, collection: EntityCollection, queryOptions: QueryOptions, searchString?: string) {
|
|
688
627
|
const entities = await driver.fetchCollection({
|
|
689
|
-
path: collection
|
|
628
|
+
path: getCollectionDataPath(collection),
|
|
690
629
|
collection,
|
|
691
|
-
filter: queryOptions.where
|
|
630
|
+
filter: queryOptions.where,
|
|
692
631
|
limit: queryOptions.limit,
|
|
693
632
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
694
633
|
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
@@ -705,9 +644,9 @@ entityId };
|
|
|
705
644
|
*/
|
|
706
645
|
private async countRawEntities(driver: DataDriver, collection: EntityCollection, queryOptions: QueryOptions, searchString?: string): Promise<number> {
|
|
707
646
|
return driver.countEntities ? await driver.countEntities({
|
|
708
|
-
path: collection
|
|
647
|
+
path: getCollectionDataPath(collection),
|
|
709
648
|
collection,
|
|
710
|
-
filter: queryOptions.where
|
|
649
|
+
filter: queryOptions.where,
|
|
711
650
|
searchString
|
|
712
651
|
}) : 0;
|
|
713
652
|
}
|
|
@@ -717,7 +656,7 @@ entityId };
|
|
|
717
656
|
*/
|
|
718
657
|
private async fetchRawEntity(driver: DataDriver, collection: EntityCollection, entityId: string) {
|
|
719
658
|
const entity = await driver.fetchEntity({
|
|
720
|
-
path: collection
|
|
659
|
+
path: getCollectionDataPath(collection),
|
|
721
660
|
entityId,
|
|
722
661
|
collection
|
|
723
662
|
});
|
|
@@ -725,23 +664,5 @@ entityId };
|
|
|
725
664
|
return entity ? this.flattenEntity(entity) : null;
|
|
726
665
|
}
|
|
727
666
|
|
|
728
|
-
/**
|
|
729
|
-
* Apply data.afterRead hook to a single entity.
|
|
730
|
-
* Returns the transformed entity, or null to filter it out.
|
|
731
|
-
*/
|
|
732
|
-
private async applyAfterRead(slug: string, entity: Record<string, unknown>, ctx: BackendHookContext): Promise<Record<string, unknown> | null> {
|
|
733
|
-
if (!this.dataHooks?.afterRead) return entity;
|
|
734
|
-
return this.dataHooks.afterRead(slug, entity, ctx);
|
|
735
|
-
}
|
|
736
667
|
|
|
737
|
-
/**
|
|
738
|
-
* Apply data.afterRead hook to an array of entities, filtering out nulls.
|
|
739
|
-
*/
|
|
740
|
-
private async applyAfterReadBatch(slug: string, entities: Record<string, unknown>[], ctx: BackendHookContext): Promise<Record<string, unknown>[]> {
|
|
741
|
-
if (!this.dataHooks?.afterRead) return entities;
|
|
742
|
-
const results = await Promise.all(
|
|
743
|
-
entities.map(e => this.applyAfterRead(slug, e, ctx))
|
|
744
|
-
);
|
|
745
|
-
return results.filter((e): e is Record<string, unknown> => e !== null);
|
|
746
|
-
}
|
|
747
668
|
}
|
|
@@ -1,25 +1,8 @@
|
|
|
1
|
-
import type { VectorSearchParams, LogicalCondition, FilterCondition } from "@rebasepro/types";
|
|
1
|
+
import type { VectorSearchParams, LogicalCondition, FilterCondition, WhereFilterOp } from "@rebasepro/types";
|
|
2
|
+
import { toCanonicalOp } from "@rebasepro/types";
|
|
3
|
+
export const mapOperator = (op: string) => toCanonicalOp(op) ?? null;
|
|
2
4
|
import { QueryOptions } from "../types";
|
|
3
5
|
|
|
4
|
-
/**
|
|
5
|
-
* Map PostgREST-style operators to Rebase WhereFilterOp
|
|
6
|
-
*/
|
|
7
|
-
export function mapOperator(op: string): string | null {
|
|
8
|
-
switch (op) {
|
|
9
|
-
case "eq": return "==";
|
|
10
|
-
case "neq": return "!=";
|
|
11
|
-
case "gt": return ">";
|
|
12
|
-
case "gte": return ">=";
|
|
13
|
-
case "lt": return "<";
|
|
14
|
-
case "lte": return "<=";
|
|
15
|
-
case "in": return "in";
|
|
16
|
-
case "nin": return "not-in";
|
|
17
|
-
case "cs": return "array-contains";
|
|
18
|
-
case "csa": return "array-contains-any";
|
|
19
|
-
default: return null;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
6
|
function getLastValue(val: unknown): unknown {
|
|
24
7
|
if (Array.isArray(val)) {
|
|
25
8
|
return val[val.length - 1];
|
|
@@ -60,7 +43,7 @@ value: true };
|
|
|
60
43
|
valStr = rest;
|
|
61
44
|
}
|
|
62
45
|
|
|
63
|
-
const rebaseOp = (
|
|
46
|
+
const rebaseOp = (toCanonicalOp(op) ?? "==") as FilterCondition["operator"];
|
|
64
47
|
let parsedVal: unknown = valStr;
|
|
65
48
|
if (valStr === "true") parsedVal = true;
|
|
66
49
|
else if (valStr === "false") parsedVal = false;
|
|
@@ -158,7 +141,7 @@ export function parseQueryOptions(query: Record<string, unknown>): QueryOptions
|
|
|
158
141
|
if (reservedQueryKeys.includes(key)) continue;
|
|
159
142
|
|
|
160
143
|
const rawValues = Array.isArray(rawValue) ? rawValue : [rawValue];
|
|
161
|
-
const conditions: [
|
|
144
|
+
const conditions: [WhereFilterOp, unknown][] = [];
|
|
162
145
|
|
|
163
146
|
for (const value of rawValues) {
|
|
164
147
|
if (typeof value === "string") {
|
|
@@ -166,7 +149,7 @@ export function parseQueryOptions(query: Record<string, unknown>): QueryOptions
|
|
|
166
149
|
if (parts.length >= 2) {
|
|
167
150
|
const op = parts[0];
|
|
168
151
|
const val = parts.slice(1).join(".");
|
|
169
|
-
const rebaseOp =
|
|
152
|
+
const rebaseOp = toCanonicalOp(op);
|
|
170
153
|
|
|
171
154
|
if (rebaseOp) {
|
|
172
155
|
let parsedVal: string | number | boolean | null | (string | number | boolean | null)[] = val;
|
package/src/api/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EntityCollection, VectorSearchParams, LogicalCondition } from "@rebasepro/types";
|
|
1
|
+
import { EntityCollection, VectorSearchParams, LogicalCondition, FilterValues } from "@rebasepro/types";
|
|
2
2
|
import { AuthResult } from "../auth/middleware";
|
|
3
3
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
4
4
|
import { DataDriver } from "@rebasepro/types";
|
|
@@ -84,7 +84,7 @@ export interface ApiResponse<T = unknown> {
|
|
|
84
84
|
export interface QueryOptions {
|
|
85
85
|
limit?: number;
|
|
86
86
|
offset?: number;
|
|
87
|
-
where?:
|
|
87
|
+
where?: FilterValues<string>;
|
|
88
88
|
logical?: LogicalCondition;
|
|
89
89
|
orderBy?: Array<{ field: string; direction: "asc" | "desc" }>;
|
|
90
90
|
include?: string[];
|
package/src/auth/auth-hooks.ts
CHANGED
|
@@ -190,7 +190,7 @@ export interface AuthHooks {
|
|
|
190
190
|
* response and returns a (potentially enriched) response.
|
|
191
191
|
*
|
|
192
192
|
* Use cases:
|
|
193
|
-
* - Inject tokens from external auth systems (
|
|
193
|
+
* - Inject tokens from external auth systems (custom provider tokens, etc.)
|
|
194
194
|
* - Add project-specific metadata to the response
|
|
195
195
|
* - Enrich the user object with data from external sources
|
|
196
196
|
*
|
|
@@ -56,8 +56,7 @@ export interface BuiltinAuthAdapterConfig {
|
|
|
56
56
|
/** Default role to assign to new users. */
|
|
57
57
|
defaultRole?: string;
|
|
58
58
|
/** OAuth providers to register. */
|
|
59
|
-
|
|
60
|
-
oauthProviders?: OAuthProvider<any>[];
|
|
59
|
+
oauthProviders?: OAuthProvider<unknown>[];
|
|
61
60
|
/** Static service key for server-to-server auth. */
|
|
62
61
|
serviceKey?: string;
|
|
63
62
|
/** Auth hooks for customizing password, credentials, lifecycle, etc. */
|