@rebasepro/server-core 0.1.0 → 0.2.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.
Files changed (148) hide show
  1. package/LICENSE +22 -6
  2. package/dist/common/src/util/entities.d.ts +2 -2
  3. package/dist/common/src/util/relations.d.ts +1 -1
  4. package/dist/{index-DXVBFp5V.js → index-BZoAtuqi.js} +6 -2
  5. package/dist/index-BZoAtuqi.js.map +1 -0
  6. package/dist/index.es.js +15909 -16083
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/index.umd.js +15847 -16017
  9. package/dist/index.umd.js.map +1 -1
  10. package/dist/server-core/src/auth/adapter-middleware.d.ts +33 -0
  11. package/dist/server-core/src/auth/admin-routes.d.ts +6 -0
  12. package/dist/server-core/src/auth/auth-overrides.d.ts +139 -0
  13. package/dist/server-core/src/auth/builtin-auth-adapter.d.ts +49 -0
  14. package/dist/server-core/src/auth/crypto-utils.d.ts +16 -0
  15. package/dist/server-core/src/auth/custom-auth-adapter.d.ts +39 -0
  16. package/dist/server-core/src/auth/index.d.ts +7 -0
  17. package/dist/server-core/src/auth/interfaces.d.ts +2 -0
  18. package/dist/server-core/src/auth/middleware.d.ts +18 -0
  19. package/dist/server-core/src/auth/rls-scope.d.ts +31 -0
  20. package/dist/server-core/src/auth/routes.d.ts +7 -1
  21. package/dist/server-core/src/env.d.ts +131 -0
  22. package/dist/server-core/src/index.d.ts +2 -0
  23. package/dist/server-core/src/init.d.ts +62 -3
  24. package/dist/types/src/controllers/auth.d.ts +9 -8
  25. package/dist/types/src/controllers/client.d.ts +3 -0
  26. package/dist/types/src/types/auth_adapter.d.ts +356 -0
  27. package/dist/types/src/types/collections.d.ts +67 -2
  28. package/dist/types/src/types/database_adapter.d.ts +94 -0
  29. package/dist/types/src/types/entity_actions.d.ts +7 -1
  30. package/dist/types/src/types/entity_callbacks.d.ts +1 -1
  31. package/dist/types/src/types/entity_views.d.ts +36 -1
  32. package/dist/types/src/types/index.d.ts +2 -0
  33. package/dist/types/src/types/plugins.d.ts +1 -1
  34. package/dist/types/src/types/properties.d.ts +24 -5
  35. package/dist/types/src/types/property_config.d.ts +6 -2
  36. package/dist/types/src/types/relations.d.ts +1 -1
  37. package/dist/types/src/types/translations.d.ts +8 -0
  38. package/dist/types/src/users/user.d.ts +5 -0
  39. package/package.json +26 -26
  40. package/src/api/errors.ts +1 -1
  41. package/src/api/graphql/graphql-schema-generator.ts +7 -0
  42. package/src/api/openapi-generator.ts +13 -1
  43. package/src/api/rest/api-generator-count.test.ts +14 -12
  44. package/src/api/rest/query-parser.ts +2 -20
  45. package/src/auth/adapter-middleware.ts +83 -0
  46. package/src/auth/admin-routes.ts +36 -43
  47. package/src/auth/auth-overrides.ts +172 -0
  48. package/src/auth/builtin-auth-adapter.ts +384 -0
  49. package/src/auth/crypto-utils.ts +31 -0
  50. package/src/auth/custom-auth-adapter.ts +85 -0
  51. package/src/auth/index.ts +10 -0
  52. package/src/auth/interfaces.ts +2 -0
  53. package/src/auth/jwt.ts +3 -1
  54. package/src/auth/middleware.ts +2 -46
  55. package/src/auth/rls-scope.ts +58 -0
  56. package/src/auth/routes.ts +74 -32
  57. package/src/cron/cron-scheduler.test.ts +9 -9
  58. package/src/cron/cron-scheduler.ts +1 -1
  59. package/src/env.ts +224 -0
  60. package/src/index.ts +4 -0
  61. package/src/init.ts +355 -135
  62. package/src/storage/routes.ts +1 -19
  63. package/src/utils/logging.ts +3 -3
  64. package/test/admin-routes.test.ts +10 -4
  65. package/test/auth-routes.test.ts +2 -2
  66. package/test/backend-hooks-admin.test.ts +32 -12
  67. package/test/custom-auth-adapter.test.ts +177 -0
  68. package/test/env.test.ts +138 -0
  69. package/test/query-parser.test.ts +0 -29
  70. package/tsconfig.json +3 -0
  71. package/app/frontend/node_modules/esbuild/LICENSE.md +0 -21
  72. package/app/frontend/node_modules/esbuild/README.md +0 -3
  73. package/app/frontend/node_modules/esbuild/bin/esbuild +0 -220
  74. package/app/frontend/node_modules/esbuild/install.js +0 -285
  75. package/app/frontend/node_modules/esbuild/lib/main.d.ts +0 -705
  76. package/app/frontend/node_modules/esbuild/lib/main.js +0 -2239
  77. package/app/frontend/node_modules/esbuild/package.json +0 -46
  78. package/dist/index-DXVBFp5V.js.map +0 -1
  79. package/examples/firebase/node_modules/esbuild/LICENSE.md +0 -21
  80. package/examples/firebase/node_modules/esbuild/README.md +0 -3
  81. package/examples/firebase/node_modules/esbuild/bin/esbuild +0 -220
  82. package/examples/firebase/node_modules/esbuild/install.js +0 -285
  83. package/examples/firebase/node_modules/esbuild/lib/main.d.ts +0 -705
  84. package/examples/firebase/node_modules/esbuild/lib/main.js +0 -2239
  85. package/examples/firebase/node_modules/esbuild/package.json +0 -46
  86. package/examples/medmot-staging/frontend/node_modules/esbuild/LICENSE.md +0 -21
  87. package/examples/medmot-staging/frontend/node_modules/esbuild/README.md +0 -3
  88. package/examples/medmot-staging/frontend/node_modules/esbuild/bin/esbuild +0 -220
  89. package/examples/medmot-staging/frontend/node_modules/esbuild/install.js +0 -285
  90. package/examples/medmot-staging/frontend/node_modules/esbuild/lib/main.d.ts +0 -705
  91. package/examples/medmot-staging/frontend/node_modules/esbuild/lib/main.js +0 -2239
  92. package/examples/medmot-staging/frontend/node_modules/esbuild/package.json +0 -46
  93. package/examples/sdk-demo/node_modules/esbuild/LICENSE.md +0 -21
  94. package/examples/sdk-demo/node_modules/esbuild/README.md +0 -3
  95. package/examples/sdk-demo/node_modules/esbuild/bin/esbuild +0 -223
  96. package/examples/sdk-demo/node_modules/esbuild/install.js +0 -289
  97. package/examples/sdk-demo/node_modules/esbuild/lib/main.d.ts +0 -716
  98. package/examples/sdk-demo/node_modules/esbuild/lib/main.js +0 -2242
  99. package/examples/sdk-demo/node_modules/esbuild/package.json +0 -49
  100. package/packages/client/node_modules/esbuild/LICENSE.md +0 -21
  101. package/packages/client/node_modules/esbuild/README.md +0 -3
  102. package/packages/client/node_modules/esbuild/bin/esbuild +0 -220
  103. package/packages/client/node_modules/esbuild/install.js +0 -285
  104. package/packages/client/node_modules/esbuild/lib/main.d.ts +0 -705
  105. package/packages/client/node_modules/esbuild/lib/main.js +0 -2239
  106. package/packages/client/node_modules/esbuild/package.json +0 -46
  107. package/packages/client-postgresql/node_modules/esbuild/LICENSE.md +0 -21
  108. package/packages/client-postgresql/node_modules/esbuild/README.md +0 -3
  109. package/packages/client-postgresql/node_modules/esbuild/bin/esbuild +0 -220
  110. package/packages/client-postgresql/node_modules/esbuild/install.js +0 -285
  111. package/packages/client-postgresql/node_modules/esbuild/lib/main.d.ts +0 -705
  112. package/packages/client-postgresql/node_modules/esbuild/lib/main.js +0 -2239
  113. package/packages/client-postgresql/node_modules/esbuild/package.json +0 -46
  114. package/packages/common/node_modules/esbuild/LICENSE.md +0 -21
  115. package/packages/common/node_modules/esbuild/README.md +0 -3
  116. package/packages/common/node_modules/esbuild/bin/esbuild +0 -220
  117. package/packages/common/node_modules/esbuild/install.js +0 -285
  118. package/packages/common/node_modules/esbuild/lib/main.d.ts +0 -705
  119. package/packages/common/node_modules/esbuild/lib/main.js +0 -2239
  120. package/packages/common/node_modules/esbuild/package.json +0 -46
  121. package/packages/server-mongodb/node_modules/esbuild/LICENSE.md +0 -21
  122. package/packages/server-mongodb/node_modules/esbuild/README.md +0 -3
  123. package/packages/server-mongodb/node_modules/esbuild/bin/esbuild +0 -220
  124. package/packages/server-mongodb/node_modules/esbuild/install.js +0 -285
  125. package/packages/server-mongodb/node_modules/esbuild/lib/main.d.ts +0 -705
  126. package/packages/server-mongodb/node_modules/esbuild/lib/main.js +0 -2239
  127. package/packages/server-mongodb/node_modules/esbuild/package.json +0 -46
  128. package/packages/server-postgresql/node_modules/esbuild/LICENSE.md +0 -21
  129. package/packages/server-postgresql/node_modules/esbuild/README.md +0 -3
  130. package/packages/server-postgresql/node_modules/esbuild/bin/esbuild +0 -220
  131. package/packages/server-postgresql/node_modules/esbuild/install.js +0 -285
  132. package/packages/server-postgresql/node_modules/esbuild/lib/main.d.ts +0 -705
  133. package/packages/server-postgresql/node_modules/esbuild/lib/main.js +0 -2239
  134. package/packages/server-postgresql/node_modules/esbuild/package.json +0 -46
  135. package/packages/types/node_modules/esbuild/LICENSE.md +0 -21
  136. package/packages/types/node_modules/esbuild/README.md +0 -3
  137. package/packages/types/node_modules/esbuild/bin/esbuild +0 -220
  138. package/packages/types/node_modules/esbuild/install.js +0 -285
  139. package/packages/types/node_modules/esbuild/lib/main.d.ts +0 -705
  140. package/packages/types/node_modules/esbuild/lib/main.js +0 -2239
  141. package/packages/types/node_modules/esbuild/package.json +0 -46
  142. package/packages/utils/node_modules/esbuild/LICENSE.md +0 -21
  143. package/packages/utils/node_modules/esbuild/README.md +0 -3
  144. package/packages/utils/node_modules/esbuild/bin/esbuild +0 -220
  145. package/packages/utils/node_modules/esbuild/install.js +0 -285
  146. package/packages/utils/node_modules/esbuild/lib/main.d.ts +0 -705
  147. package/packages/utils/node_modules/esbuild/lib/main.js +0 -2239
  148. package/packages/utils/node_modules/esbuild/package.json +0 -46
@@ -24,3 +24,5 @@ export * from "./data_source";
24
24
  export * from "./cron";
25
25
  export * from "./backend_hooks";
26
26
  export * from "./component_ref";
27
+ export * from "./auth_adapter";
28
+ export * from "./database_adapter";
@@ -250,7 +250,7 @@ export interface PluginFormActionProps<USER extends User = User, EC extends Enti
250
250
  disabled: boolean;
251
251
  formContext?: FormContext;
252
252
  context: RebaseContext<USER>;
253
- openEntityMode: "side_panel" | "full_screen" | "split";
253
+ openEntityMode: "side_panel" | "full_screen" | "split" | "dialog";
254
254
  }
255
255
  /**
256
256
  * Parameters passed to the field builder wrap function.
@@ -1,5 +1,5 @@
1
1
  import type { ComponentRef } from "./component_ref";
2
- import type { EntityReference, EntityRelation, EntityValues, GeoPoint, Entity } from "./entities";
2
+ import type { EntityReference, EntityRelation, EntityValues, GeoPoint, Entity, Vector } from "./entities";
3
3
  import type { Relation, JoinStep, OnAction } from "./relations";
4
4
  import type { EntityCollection, FilterValues } from "./collections";
5
5
  import type { ColorKey, ColorScheme } from "./chips";
@@ -31,8 +31,8 @@ export type PropertyCallbacks<T = unknown, M extends Record<string, unknown> = R
31
31
  /**
32
32
  * @group Entity properties
33
33
  */
34
- export type DataType = "string" | "number" | "boolean" | "date" | "geopoint" | "reference" | "relation" | "array" | "map";
35
- export type Property = StringProperty | NumberProperty | BooleanProperty | DateProperty | GeopointProperty | ReferenceProperty | RelationProperty | ArrayProperty | MapProperty;
34
+ export type DataType = "string" | "number" | "boolean" | "date" | "geopoint" | "reference" | "relation" | "array" | "map" | "vector" | "binary";
35
+ export type Property = StringProperty | NumberProperty | BooleanProperty | DateProperty | GeopointProperty | ReferenceProperty | RelationProperty | ArrayProperty | MapProperty | VectorProperty | BinaryProperty;
36
36
  export type Properties = {
37
37
  [key: string]: Property;
38
38
  };
@@ -48,7 +48,7 @@ export type FirebaseProperties = {
48
48
  * A helper type to infer the underlying data type from a Property definition.
49
49
  * This is the core of the type inference system.
50
50
  */
51
- export type InferPropertyType<P extends Property> = P extends StringProperty ? string : P extends NumberProperty ? number : P extends BooleanProperty ? boolean : P extends DateProperty ? Date : P extends GeopointProperty ? GeoPoint : P extends ReferenceProperty ? EntityReference : P extends RelationProperty ? EntityRelation | EntityRelation[] : P extends ArrayProperty ? (P["of"] extends Property ? InferPropertyType<P["of"]>[] : unknown[]) : P extends MapProperty ? (P["properties"] extends Properties ? InferEntityType<P["properties"]> : Record<string, unknown>) : never;
51
+ export type InferPropertyType<P extends Property> = P extends StringProperty ? string : P extends NumberProperty ? number : P extends BooleanProperty ? boolean : P extends DateProperty ? Date : P extends GeopointProperty ? GeoPoint : P extends ReferenceProperty ? EntityReference : P extends RelationProperty ? EntityRelation | EntityRelation[] : P extends ArrayProperty ? (P["of"] extends Property ? InferPropertyType<P["of"]>[] : unknown[]) : P extends MapProperty ? (P["properties"] extends Properties ? InferEntityType<P["properties"]> : Record<string, unknown>) : P extends VectorProperty ? Vector : P extends BinaryProperty ? string : never;
52
52
  /**
53
53
  * Helper type that determines whether a property is required.
54
54
  * Uses direct structural matching against `{ validation: { required: true } }`
@@ -309,6 +309,25 @@ export interface BooleanProperty extends BaseProperty {
309
309
  */
310
310
  validation?: PropertyValidationSchema;
311
311
  }
312
+ /**
313
+ * @group Entity properties
314
+ */
315
+ export interface VectorUIConfig extends BaseUIConfig {
316
+ clearable?: boolean;
317
+ }
318
+ export interface VectorProperty extends BaseProperty {
319
+ ui?: VectorUIConfig;
320
+ type: "vector";
321
+ dimensions: number;
322
+ validation?: PropertyValidationSchema;
323
+ }
324
+ /**
325
+ * @group Entity properties
326
+ */
327
+ export interface BinaryProperty extends BaseProperty {
328
+ type: "binary";
329
+ validation?: PropertyValidationSchema;
330
+ }
312
331
  /**
313
332
  * @group Entity properties
314
333
  */
@@ -421,7 +440,7 @@ export interface RelationProperty extends BaseProperty {
421
440
  * When set, the framework treats this property as a self-contained relation
422
441
  * definition and no separate `relations[]` entry is needed.
423
442
  */
424
- target?: () => EntityCollection;
443
+ target?: string | (() => EntityCollection | string);
425
444
  /**
426
445
  * Whether this property references one or many records.
427
446
  * Defaults to `"one"`.
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { ArrayProperty, MapProperty, StringProperty, NumberProperty, BooleanProperty, DateProperty, GeopointProperty, ReferenceProperty, RelationProperty } from "./properties";
2
+ import { ArrayProperty, MapProperty, StringProperty, NumberProperty, BooleanProperty, DateProperty, GeopointProperty, ReferenceProperty, RelationProperty, VectorProperty, BinaryProperty } from "./properties";
3
3
  import { BaseProperty } from "./properties";
4
4
  type CMSBasePropertyNoName = Omit<BaseProperty, "name">;
5
5
  export type ConfigProperty = (Omit<StringProperty, "name"> & {
@@ -28,6 +28,10 @@ export type ConfigProperty = (Omit<StringProperty, "name"> & {
28
28
  } & CMSBasePropertyNoName) | (Omit<MapProperty, "name" | "properties"> & {
29
29
  name?: string;
30
30
  properties?: Record<string, ConfigProperty>;
31
+ } & CMSBasePropertyNoName) | (Omit<VectorProperty, "name"> & {
32
+ name?: string;
33
+ } & CMSBasePropertyNoName) | (Omit<BinaryProperty, "name"> & {
34
+ name?: string;
31
35
  } & CMSBasePropertyNoName);
32
36
  /**
33
37
  * This is the configuration object for a property.
@@ -66,5 +70,5 @@ export type PropertyConfig = {
66
70
  */
67
71
  description?: string;
68
72
  };
69
- export type PropertyConfigId = "text_field" | "multiline" | "markdown" | "url" | "email" | "user_select" | "select" | "multi_select" | "number_input" | "number_select" | "multi_number_select" | "file_upload" | "multi_file_upload" | "group" | "key_value" | "reference" | "reference_as_string" | "multi_references" | "relation" | "switch" | "date_time" | "repeat" | "custom_array" | "block";
73
+ export type PropertyConfigId = "text_field" | "multiline" | "markdown" | "url" | "email" | "user_select" | "select" | "multi_select" | "number_input" | "number_select" | "multi_number_select" | "file_upload" | "multi_file_upload" | "group" | "key_value" | "reference" | "reference_as_string" | "multi_references" | "relation" | "switch" | "date_time" | "repeat" | "custom_array" | "block" | "vector_input";
70
74
  export {};
@@ -17,7 +17,7 @@ export interface Relation {
17
17
  /**
18
18
  * The final collection you want to retrieve records from.
19
19
  */
20
- target: () => EntityCollection;
20
+ target: (() => EntityCollection) | any;
21
21
  /**
22
22
  * The nature of the relationship, determining if one or many records are returned.
23
23
  */
@@ -30,6 +30,8 @@ export interface RebaseTranslations {
30
30
  copy: string;
31
31
  delete: string;
32
32
  delete_not_allowed: string;
33
+ edit_entity?: string;
34
+ back_to_detail?: string;
33
35
  delete_confirmation_title: string;
34
36
  delete_confirmation_body: string;
35
37
  delete_multiple_confirmation_body: string;
@@ -392,6 +394,12 @@ export interface RebaseTranslations {
392
394
  submit: string;
393
395
  no_filterable_properties: string;
394
396
  apply_filters: string;
397
+ /** Label shown on the filter presets dropdown trigger */
398
+ filter_presets?: string;
399
+ /** Tooltip shown when hovering over a preset entry */
400
+ filter_preset_apply?: string;
401
+ /** Shown when a preset is active, with {{label}} interpolation */
402
+ filter_preset_active?: string;
395
403
  list: string;
396
404
  table_view_mode: string;
397
405
  cards: string;
@@ -42,5 +42,10 @@ export type User = {
42
42
  * The date and time when the user was created.
43
43
  */
44
44
  createdAt?: Date | string | null;
45
+ /**
46
+ * Additional metadata/custom claims associated with the user.
47
+ * Accessible by the frontend, but only writable by the backend.
48
+ */
49
+ readonly metadata?: Record<string, any>;
45
50
  getIdToken?: (forceRefresh?: boolean) => Promise<string>;
46
51
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/server-core",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.2.1",
5
5
  "description": "Database-Agnostic Backend Core for Rebase",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -33,44 +33,44 @@
33
33
  "exports": {
34
34
  ".": {
35
35
  "types": "./dist/server-core/src/index.d.ts",
36
- "development": "./src/index.ts",
36
+ "development": "./dist/index.es.js",
37
37
  "import": "./dist/index.es.js",
38
38
  "require": "./dist/index.umd.js"
39
39
  },
40
40
  "./package.json": "./package.json"
41
41
  },
42
42
  "dependencies": {
43
- "@aws-sdk/client-s3": "^3.654.0",
44
- "@aws-sdk/s3-request-presigner": "^3.654.0",
43
+ "@aws-sdk/client-s3": "^3.1050.0",
44
+ "@aws-sdk/s3-request-presigner": "^3.1050.0",
45
45
  "@hono/graphql-server": "0.7.0",
46
46
  "@hono/node-server": "1.19.12",
47
- "google-auth-library": "^9.0.0",
48
- "graphql": "^16.8.1",
49
- "hono": "^4.12.10",
50
- "jsonwebtoken": "^9.0.0",
51
- "nodemailer": "^6.9.0",
47
+ "drizzle-orm": "^0.44.7",
48
+ "google-auth-library": "^9.15.1",
49
+ "graphql": "^16.14.0",
50
+ "hono": "^4.12.21",
51
+ "jsonwebtoken": "^9.0.3",
52
+ "nodemailer": "^6.10.1",
52
53
  "ts-morph": "27.0.2",
53
- "ws": "^8.16.0",
54
- "zod": "^3.22.4",
55
- "@rebasepro/client": "0.1.0",
56
- "@rebasepro/common": "0.1.0",
57
- "@rebasepro/types": "0.1.0",
58
- "@rebasepro/utils": "0.1.0"
54
+ "ws": "^8.20.1",
55
+ "zod": "^3.25.76",
56
+ "@rebasepro/common": "0.2.1",
57
+ "@rebasepro/client": "0.2.1",
58
+ "@rebasepro/types": "0.2.1",
59
+ "@rebasepro/utils": "0.2.1"
59
60
  },
60
61
  "devDependencies": {
61
62
  "@types/jest": "^29.5.14",
62
- "@types/jsonwebtoken": "^9.0.0",
63
- "@types/node": "^20.10.5",
64
- "@types/nodemailer": "^6.4.0",
65
- "@types/react": "^19.0.8",
66
- "@types/react-dom": "^19.0.3",
67
- "@types/ws": "^8.5.10",
63
+ "@types/jsonwebtoken": "^9.0.10",
64
+ "@types/node": "^20.19.41",
65
+ "@types/nodemailer": "^6.4.23",
66
+ "@types/react": "^19.2.15",
67
+ "@types/react-dom": "^19.2.3",
68
+ "@types/ws": "^8.18.1",
69
+ "@vitejs/plugin-react": "^4.7.0",
68
70
  "jest": "^29.7.0",
69
71
  "ts-jest": "29.4.1",
70
- "typescript": "^5.0.0",
71
- "vite": "^5.0.0",
72
- "@rebasepro/common": "0.1.0",
73
- "@rebasepro/types": "0.1.0"
72
+ "typescript": "^5.9.3",
73
+ "vite": "^5.4.21"
74
74
  },
75
75
  "gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
76
76
  "publishConfig": {
@@ -80,7 +80,7 @@
80
80
  "watch": "vite build --watch",
81
81
  "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json",
82
82
  "test:lint": "eslint \"src/**\" --quiet",
83
- "test": "jest --passWithNoTests",
83
+ "test": "jest --passWithNoTests --forceExit",
84
84
  "clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
85
85
  }
86
86
  }
package/src/api/errors.ts CHANGED
@@ -108,7 +108,7 @@ export const errorHandler: ErrorHandler = (err, c) => {
108
108
  logMessage = `Database schema mismatch (${issue} missing): ${cause.message}. Did you forget to run migrations ('pnpm db:push' or 'pnpm db:migrate')?`;
109
109
  }
110
110
  } else if ("code" in error && error.code === "ENETUNREACH") {
111
- const netErr = error as unknown as PgLikeError;
111
+ const netErr = error as PgLikeError;
112
112
  logMessage = `Network unreachable. Cannot connect to service at ${netErr.address}:${netErr.port}.`;
113
113
  } else if ("code" in error && (error.code === "42703" || error.code === "42P01")) {
114
114
  const issue = error.code === "42703" ? "column" : "table";
@@ -89,6 +89,7 @@ export class GraphQLSchemaGenerator {
89
89
  let type;
90
90
 
91
91
  switch (property.type) {
92
+ case "binary":
92
93
  case "string":
93
94
  type = GraphQLString;
94
95
  break;
@@ -104,6 +105,9 @@ export class GraphQLSchemaGenerator {
104
105
  case "array":
105
106
  type = new GraphQLList(GraphQLString);
106
107
  break;
108
+ case "vector":
109
+ type = new GraphQLList(GraphQLFloat);
110
+ break;
107
111
  default:
108
112
  type = GraphQLString;
109
113
  }
@@ -143,6 +147,7 @@ export class GraphQLSchemaGenerator {
143
147
 
144
148
  private convertPropertyToInputType(property: Property) {
145
149
  switch (property.type) {
150
+ case "binary":
146
151
  case "string":
147
152
  return GraphQLString;
148
153
  case "number":
@@ -153,6 +158,8 @@ export class GraphQLSchemaGenerator {
153
158
  return GraphQLString;
154
159
  case "array":
155
160
  return new GraphQLList(GraphQLString);
161
+ case "vector":
162
+ return new GraphQLList(GraphQLFloat);
156
163
  default:
157
164
  return GraphQLString;
158
165
  }
@@ -1,4 +1,4 @@
1
- import { EntityCollection, Property, StringProperty, NumberProperty, ArrayProperty, MapProperty, Relation } from "@rebasepro/types";
1
+ import { EntityCollection, Property, StringProperty, NumberProperty, ArrayProperty, MapProperty, Relation, VectorProperty } from "@rebasepro/types";
2
2
 
3
3
  /**
4
4
  * OpenAPI 3.0.3 specification generator.
@@ -624,6 +624,18 @@ enum: [variantKey] },
624
624
  return base;
625
625
  }
626
626
 
627
+ case "vector": {
628
+ const vp = property as VectorProperty;
629
+ base.type = "array";
630
+ base.items = { type: "number" };
631
+ base.description = (base.description || "") + ` (Vector(${vp.dimensions}))`;
632
+ return base;
633
+ }
634
+ case "binary": {
635
+ base.type = "string";
636
+ base.description = (base.description || "") + " (Binary/Base64)";
637
+ return base;
638
+ }
627
639
  default:
628
640
  base.type = "string";
629
641
  return base;
@@ -1,18 +1,20 @@
1
1
  import { jest } from "@jest/globals";
2
2
  import { RestApiGenerator } from "./api-generator";
3
- import type { DataDriver, EntityCollection, FetchCollectionProps } from "@rebasepro/types";
3
+ import type { DataDriver, Entity, EntityCollection, FetchCollectionProps } from "@rebasepro/types";
4
4
 
5
5
  /**
6
6
  * Minimal mock DataDriver for testing.
7
7
  */
8
8
  function createMockDriver(overrides?: Partial<DataDriver>): DataDriver {
9
9
  return {
10
- fetchCollection: jest.fn().mockResolvedValue([]),
11
- fetchEntity: jest.fn().mockResolvedValue(null),
12
- saveEntity: jest.fn().mockResolvedValue({ id: "1", path: "test", values: {} }),
13
- deleteEntity: jest.fn().mockResolvedValue(undefined),
14
- countEntities: jest.fn().mockResolvedValue(0),
15
- ...overrides,
10
+ fetchCollection: jest.fn<DataDriver["fetchCollection"]>().mockResolvedValue([]),
11
+ fetchEntity: jest.fn<DataDriver["fetchEntity"]>().mockResolvedValue(undefined),
12
+ saveEntity: jest.fn<DataDriver["saveEntity"]>().mockResolvedValue({ id: "1",
13
+ path: "test",
14
+ values: {} } as Entity),
15
+ deleteEntity: jest.fn<DataDriver["deleteEntity"]>().mockResolvedValue(undefined),
16
+ countEntities: jest.fn<NonNullable<DataDriver["countEntities"]>>().mockResolvedValue(0),
17
+ ...overrides
16
18
  } as unknown as DataDriver;
17
19
  }
18
20
 
@@ -21,7 +23,7 @@ function createTestCollection(slug: string): EntityCollection {
21
23
  slug,
22
24
  name: slug.charAt(0).toUpperCase() + slug.slice(1),
23
25
  path: slug,
24
- properties: {},
26
+ properties: {}
25
27
  } as unknown as EntityCollection;
26
28
  }
27
29
 
@@ -31,7 +33,7 @@ describe("RestApiGenerator - Count Endpoint", () => {
31
33
 
32
34
  beforeEach(() => {
33
35
  driver = createMockDriver({
34
- countEntities: jest.fn().mockResolvedValue(42),
36
+ countEntities: jest.fn<NonNullable<DataDriver["countEntities"]>>().mockResolvedValue(42)
35
37
  });
36
38
  collection = createTestCollection("products");
37
39
  });
@@ -93,10 +95,10 @@ describe("RestApiGenerator - Count Endpoint", () => {
93
95
 
94
96
  it("GET /products/count should not be confused with GET /products/:id", async () => {
95
97
  // Ensure the count route is registered before the :id route
96
- const fetchEntity = jest.fn().mockResolvedValue(null);
98
+ const fetchEntity = jest.fn<DataDriver["fetchEntity"]>().mockResolvedValue(undefined);
97
99
  const driverCustom = createMockDriver({
98
- countEntities: jest.fn().mockResolvedValue(99),
99
- fetchEntity,
100
+ countEntities: jest.fn<NonNullable<DataDriver["countEntities"]>>().mockResolvedValue(99),
101
+ fetchEntity: fetchEntity as unknown as DataDriver["fetchEntity"]
100
102
  });
101
103
  const generator = new RestApiGenerator([collection], driverCustom);
102
104
  const app = generator.generateRoutes();
@@ -37,27 +37,9 @@ export function parseQueryOptions(query: Record<string, unknown>): QueryOptions
37
37
  // Filtering
38
38
  options.where = {};
39
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
40
 
59
- // PostgREST style filtering
60
- const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "where", "include", "fields", "searchString"];
41
+ // PostgREST-style filtering: ?field=op.value
42
+ const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString"];
61
43
  for (const [key, rawValue] of Object.entries(query)) {
62
44
  if (reservedQueryKeys.includes(key)) continue;
63
45
 
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Adapter Auth Middleware
3
+ *
4
+ * Creates a Hono middleware that delegates authentication to an `AuthAdapter`
5
+ * instead of hardcoded JWT verification. This is used when the user passes
6
+ * an `AuthAdapter` to `initializeRebaseBackend()`.
7
+ *
8
+ * The middleware:
9
+ * 1. Calls `adapter.verifyRequest(request)` to resolve the user
10
+ * 2. Scopes the DataDriver via `withAuth()` for RLS
11
+ * 3. Enforces auth (401) when `requireAuth` is true and no user is found
12
+ *
13
+ * The behavior is identical to `createAuthMiddleware()` — only the
14
+ * token verification strategy is pluggable.
15
+ */
16
+
17
+ import type { MiddlewareHandler } from "hono";
18
+ import type { DataDriver, AuthAdapter } from "@rebasepro/types";
19
+ import type { HonoEnv } from "../api/types";
20
+ import { scopeDataDriver } from "./rls-scope";
21
+
22
+ export interface AdapterAuthMiddlewareOptions {
23
+ /** The auth adapter to delegate verification to. */
24
+ adapter: AuthAdapter;
25
+ /** The DataDriver to scope via withAuth() for RLS. */
26
+ driver: DataDriver;
27
+ /**
28
+ * If true, return 401 when no valid user is resolved.
29
+ * Defaults to `true` (secure by default).
30
+ */
31
+ requireAuth?: boolean;
32
+ }
33
+
34
+ /**
35
+ * Create a Hono middleware that uses an `AuthAdapter` for request verification.
36
+ */
37
+ export function createAdapterAuthMiddleware(options: AdapterAuthMiddlewareOptions): MiddlewareHandler<HonoEnv> {
38
+ const { adapter, driver, requireAuth: enforceAuth = true } = options;
39
+
40
+ return async (c, next) => {
41
+ let authenticatedUser = null;
42
+
43
+ try {
44
+ authenticatedUser = await adapter.verifyRequest(c.req.raw);
45
+ } catch (error) {
46
+ // adapter.verifyRequest() threw — reject the request (fail closed)
47
+ return c.json({ error: { message: "Unauthorized", code: "UNAUTHORIZED" } }, 401);
48
+ }
49
+
50
+ if (authenticatedUser) {
51
+ // Authenticated — set user context and scope driver
52
+ c.set("user", {
53
+ userId: authenticatedUser.uid,
54
+ email: authenticatedUser.email,
55
+ roles: authenticatedUser.roles,
56
+ });
57
+ try {
58
+ c.set("driver", await scopeDataDriver(driver, {
59
+ uid: authenticatedUser.uid,
60
+ roles: authenticatedUser.roles,
61
+ }));
62
+ } catch (error) {
63
+ console.error("[AUTH-ADAPTER] RLS scoping failed for authenticated user:", error);
64
+ return c.json({ error: { message: "Internal authentication error", code: "INTERNAL_ERROR" } }, 500);
65
+ }
66
+ } else {
67
+ // Not authenticated — scope as anon for RLS evaluation
68
+ try {
69
+ c.set("driver", await scopeDataDriver(driver, { uid: "anon", roles: ["anon"] }));
70
+ } catch (error) {
71
+ console.error("[AUTH-ADAPTER] Failed to create anon-scoped driver:", error);
72
+ return c.json({ error: { message: "Server configuration error", code: "INTERNAL_ERROR" } }, 500);
73
+ }
74
+ }
75
+
76
+ // Enforce auth if required
77
+ if (enforceAuth && !c.get("user")) {
78
+ return c.json({ error: { message: "Unauthorized: Authentication required", code: "UNAUTHORIZED" } }, 401);
79
+ }
80
+
81
+ return next();
82
+ };
83
+ }
@@ -2,7 +2,8 @@ import { Hono } from "hono";
2
2
  import { ApiError, errorHandler } from "../api/errors";
3
3
  import type { AuthRepository } from "./interfaces";
4
4
  import { requireAuth, requireAdmin, createRequireAuth } from "./middleware";
5
- import { hashPassword, validatePasswordStrength } from "./password";
5
+ import type { AuthOverrides } from "./auth-overrides";
6
+ import { resolveAuthOverrides } from "./auth-overrides";
6
7
  import { AuthModuleConfig } from "./routes";
7
8
  import type { BackendHooks, AdminUser, AdminRole, BackendHookContext } from "@rebasepro/types";
8
9
 
@@ -17,6 +18,11 @@ interface AdminRouteOptions extends AuthModuleConfig {
17
18
  * Backend-level hooks for intercepting admin data.
18
19
  */
19
20
  hooks?: BackendHooks;
21
+ /**
22
+ * Auth overrides for customizing password hashing, credential
23
+ * verification, lifecycle hooks, etc.
24
+ */
25
+ overrides?: AuthOverrides;
20
26
  }
21
27
  import { HonoEnv } from "../api/types";
22
28
  import { randomBytes, createHash } from "crypto";
@@ -68,7 +74,8 @@ function hashToken(token: string): string {
68
74
  export function createAdminRoutes(config: AdminRouteOptions): Hono<HonoEnv> {
69
75
  const router = new Hono<HonoEnv>();
70
76
  const authRepo = config.authRepo;
71
- const { emailService, emailConfig, hooks } = config;
77
+ const { emailService, emailConfig, hooks, overrides } = config;
78
+ const ops = resolveAuthOverrides(overrides);
72
79
 
73
80
  /** Build a BackendHookContext from Hono's context object */
74
81
  function buildHookContext(c: { get: (key: string) => unknown }, method: BackendHookContext["method"]): BackendHookContext {
@@ -192,41 +199,20 @@ export function createAdminRoutes(config: AdminRouteOptions): Hono<HonoEnv> {
192
199
  const orderDir = c.req.query("orderDir") as "asc" | "desc" | undefined;
193
200
  const hookCtx = buildHookContext(c, "GET");
194
201
 
195
- // If pagination params are provided, use the paginated path
196
- if (limitParam !== undefined || search) {
197
- const limit = limitParam ? parseInt(limitParam, 10) : 25;
198
- const offset = offsetParam ? parseInt(offsetParam, 10) : 0;
199
-
200
- const result = await authRepo.listUsersPaginated({
201
- limit,
202
- offset,
203
- search: search || undefined,
204
- orderBy: orderBy || undefined,
205
- orderDir: orderDir || undefined,
206
- roleId: c.req.query("role") || undefined
207
- });
208
-
209
- let usersWithRoles: AdminUser[] = await Promise.all(
210
- result.users.map(async (u) => {
211
- const roles = await authRepo.getUserRoleIds(u.id);
212
- return toAdminUser(u, roles);
213
- })
214
- );
215
-
216
- usersWithRoles = await applyUserAfterReadBatch(usersWithRoles, hookCtx);
202
+ const limit = limitParam ? parseInt(limitParam, 10) : 25;
203
+ const offset = offsetParam ? parseInt(offsetParam, 10) : 0;
217
204
 
218
- return c.json({
219
- users: usersWithRoles,
220
- total: result.total,
221
- limit: result.limit,
222
- offset: result.offset
223
- });
224
- }
205
+ const result = await authRepo.listUsersPaginated({
206
+ limit,
207
+ offset,
208
+ search: search || undefined,
209
+ orderBy: orderBy || undefined,
210
+ orderDir: orderDir || undefined,
211
+ roleId: c.req.query("role") || undefined
212
+ });
225
213
 
226
- // Legacy: return all users (no pagination)
227
- const users = await authRepo.listUsers();
228
214
  let usersWithRoles: AdminUser[] = await Promise.all(
229
- users.map(async (u) => {
215
+ result.users.map(async (u) => {
230
216
  const roles = await authRepo.getUserRoleIds(u.id);
231
217
  return toAdminUser(u, roles);
232
218
  })
@@ -234,7 +220,12 @@ export function createAdminRoutes(config: AdminRouteOptions): Hono<HonoEnv> {
234
220
 
235
221
  usersWithRoles = await applyUserAfterReadBatch(usersWithRoles, hookCtx);
236
222
 
237
- return c.json({ users: usersWithRoles });
223
+ return c.json({
224
+ users: usersWithRoles,
225
+ total: result.total,
226
+ limit: result.limit,
227
+ offset: result.offset
228
+ });
238
229
  });
239
230
 
240
231
  router.get("/users/:userId", requireAdmin, async (c) => {
@@ -258,7 +249,8 @@ export function createAdminRoutes(config: AdminRouteOptions): Hono<HonoEnv> {
258
249
 
259
250
  router.post("/users", requireAdmin, async (c) => {
260
251
  const body = await c.req.json();
261
- let { email, displayName, password, roles } = body;
252
+ const { password } = body;
253
+ let { email, displayName, roles } = body;
262
254
 
263
255
  if (!email) {
264
256
  throw ApiError.badRequest("Email is required", "INVALID_INPUT");
@@ -281,11 +273,11 @@ export function createAdminRoutes(config: AdminRouteOptions): Hono<HonoEnv> {
281
273
  // Use provided password or auto-generate one
282
274
  const clearPassword = password || generateSecurePassword();
283
275
 
284
- const validation = validatePasswordStrength(clearPassword);
276
+ const validation = ops.validatePasswordStrength(clearPassword);
285
277
  if (!validation.valid) {
286
278
  throw ApiError.badRequest(validation.errors.join(". "), "WEAK_PASSWORD");
287
279
  }
288
- const passwordHash = await hashPassword(clearPassword);
280
+ const passwordHash = await ops.hashPassword(clearPassword);
289
281
 
290
282
  const user = await authRepo.createUser({
291
283
  email: email.toLowerCase(),
@@ -401,14 +393,14 @@ displayName: existing.displayName }, appName);
401
393
  console.error("Failed to send reset email:", emailError instanceof Error ? emailError.message : emailError);
402
394
  // Fall back to returning the temporary password
403
395
  const clearPassword = generateSecurePassword();
404
- const passwordHash = await hashPassword(clearPassword);
396
+ const passwordHash = await ops.hashPassword(clearPassword);
405
397
  await authRepo.updatePassword(existing.id, passwordHash);
406
398
  temporaryPassword = clearPassword;
407
399
  }
408
400
  } else {
409
401
  // No email service — generate password, set it, and return one-time
410
402
  const clearPassword = generateSecurePassword();
411
- const passwordHash = await hashPassword(clearPassword);
403
+ const passwordHash = await ops.hashPassword(clearPassword);
412
404
  await authRepo.updatePassword(existing.id, passwordHash);
413
405
  temporaryPassword = clearPassword;
414
406
  }
@@ -430,7 +422,8 @@ displayName: existing.displayName }, appName);
430
422
  router.put("/users/:userId", requireAdmin, async (c) => {
431
423
  const userId = c.req.param("userId");
432
424
  const body = await c.req.json();
433
- let { email, displayName, password, roles } = body;
425
+ const { password } = body;
426
+ let { email, displayName, roles } = body;
434
427
 
435
428
  const existing = await authRepo.getUserById(userId);
436
429
  if (!existing) {
@@ -451,11 +444,11 @@ displayName: existing.displayName }, appName);
451
444
  if (displayName !== undefined) updates.displayName = displayName;
452
445
 
453
446
  if (password) {
454
- const validation = validatePasswordStrength(password);
447
+ const validation = ops.validatePasswordStrength(password);
455
448
  if (!validation.valid) {
456
449
  throw ApiError.badRequest(validation.errors.join(". "), "WEAK_PASSWORD");
457
450
  }
458
- updates.passwordHash = await hashPassword(password);
451
+ updates.passwordHash = await ops.hashPassword(password);
459
452
  }
460
453
 
461
454
  if (Object.keys(updates).length > 0) {