@rebasepro/server-postgresql 0.0.1-canary.4d4fb3e → 0.0.1-canary.5584634

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 (143) hide show
  1. package/dist/common/src/collections/CollectionRegistry.d.ts +8 -0
  2. package/dist/common/src/util/entities.d.ts +22 -0
  3. package/dist/common/src/util/relations.d.ts +14 -4
  4. package/dist/common/src/util/resolutions.d.ts +1 -1
  5. package/dist/index.es.js +1254 -591
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +1254 -591
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +17 -29
  10. package/dist/server-postgresql/src/auth/services.d.ts +7 -3
  11. package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +1 -1
  12. package/dist/server-postgresql/src/connection.d.ts +34 -1
  13. package/dist/server-postgresql/src/data-transformer.d.ts +26 -4
  14. package/dist/server-postgresql/src/databasePoolManager.d.ts +2 -2
  15. package/dist/server-postgresql/src/schema/auth-schema.d.ts +139 -38
  16. package/dist/server-postgresql/src/schema/doctor-cli.d.ts +2 -0
  17. package/dist/server-postgresql/src/schema/doctor.d.ts +43 -0
  18. package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +1 -1
  19. package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +82 -0
  20. package/dist/server-postgresql/src/schema/introspect-db.d.ts +1 -0
  21. package/dist/server-postgresql/src/schema/test-schema.d.ts +24 -0
  22. package/dist/server-postgresql/src/services/EntityFetchService.d.ts +22 -8
  23. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +1 -1
  24. package/dist/server-postgresql/src/services/RelationService.d.ts +11 -5
  25. package/dist/server-postgresql/src/services/entity-helpers.d.ts +16 -2
  26. package/dist/server-postgresql/src/services/entityService.d.ts +8 -6
  27. package/dist/server-postgresql/src/services/realtimeService.d.ts +2 -0
  28. package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +2 -2
  29. package/dist/types/src/controllers/auth.d.ts +2 -0
  30. package/dist/types/src/controllers/client.d.ts +119 -7
  31. package/dist/types/src/controllers/collection_registry.d.ts +4 -3
  32. package/dist/types/src/controllers/customization_controller.d.ts +7 -1
  33. package/dist/types/src/controllers/data.d.ts +34 -7
  34. package/dist/types/src/controllers/data_driver.d.ts +20 -28
  35. package/dist/types/src/controllers/database_admin.d.ts +2 -2
  36. package/dist/types/src/controllers/email.d.ts +34 -0
  37. package/dist/types/src/controllers/index.d.ts +1 -0
  38. package/dist/types/src/controllers/local_config_persistence.d.ts +4 -4
  39. package/dist/types/src/controllers/navigation.d.ts +5 -5
  40. package/dist/types/src/controllers/registry.d.ts +6 -3
  41. package/dist/types/src/controllers/side_entity_controller.d.ts +7 -6
  42. package/dist/types/src/controllers/storage.d.ts +24 -26
  43. package/dist/types/src/rebase_context.d.ts +8 -4
  44. package/dist/types/src/types/backend.d.ts +4 -1
  45. package/dist/types/src/types/builders.d.ts +5 -4
  46. package/dist/types/src/types/chips.d.ts +1 -1
  47. package/dist/types/src/types/collections.d.ts +169 -125
  48. package/dist/types/src/types/cron.d.ts +102 -0
  49. package/dist/types/src/types/data_source.d.ts +1 -1
  50. package/dist/types/src/types/entity_actions.d.ts +8 -8
  51. package/dist/types/src/types/entity_callbacks.d.ts +15 -15
  52. package/dist/types/src/types/entity_link_builder.d.ts +1 -1
  53. package/dist/types/src/types/entity_overrides.d.ts +2 -1
  54. package/dist/types/src/types/entity_views.d.ts +8 -8
  55. package/dist/types/src/types/export_import.d.ts +3 -3
  56. package/dist/types/src/types/index.d.ts +1 -0
  57. package/dist/types/src/types/plugins.d.ts +72 -18
  58. package/dist/types/src/types/properties.d.ts +118 -33
  59. package/dist/types/src/types/relations.d.ts +1 -1
  60. package/dist/types/src/types/slots.d.ts +30 -6
  61. package/dist/types/src/types/translations.d.ts +44 -0
  62. package/dist/types/src/types/user_management_delegate.d.ts +1 -0
  63. package/drizzle-test/0000_woozy_junta.sql +6 -0
  64. package/drizzle-test/0001_youthful_arachne.sql +1 -0
  65. package/drizzle-test/0002_lively_dragon_lord.sql +2 -0
  66. package/drizzle-test/0003_mean_king_cobra.sql +2 -0
  67. package/drizzle-test/meta/0000_snapshot.json +47 -0
  68. package/drizzle-test/meta/0001_snapshot.json +48 -0
  69. package/drizzle-test/meta/0002_snapshot.json +38 -0
  70. package/drizzle-test/meta/0003_snapshot.json +48 -0
  71. package/drizzle-test/meta/_journal.json +34 -0
  72. package/drizzle-test-out/0000_tan_trauma.sql +6 -0
  73. package/drizzle-test-out/0001_rapid_drax.sql +1 -0
  74. package/drizzle-test-out/meta/0000_snapshot.json +44 -0
  75. package/drizzle-test-out/meta/0001_snapshot.json +54 -0
  76. package/drizzle-test-out/meta/_journal.json +20 -0
  77. package/drizzle.test.config.ts +10 -0
  78. package/package.json +89 -89
  79. package/scratch.ts +41 -0
  80. package/src/PostgresBackendDriver.ts +63 -79
  81. package/src/PostgresBootstrapper.ts +7 -8
  82. package/src/auth/ensure-tables.ts +158 -86
  83. package/src/auth/services.ts +109 -50
  84. package/src/cli.ts +317 -16
  85. package/src/collections/PostgresCollectionRegistry.ts +6 -6
  86. package/src/connection.ts +70 -48
  87. package/src/data-transformer.ts +155 -116
  88. package/src/databasePoolManager.ts +6 -5
  89. package/src/history/HistoryService.ts +3 -12
  90. package/src/interfaces.ts +3 -3
  91. package/src/schema/auth-schema.ts +26 -3
  92. package/src/schema/doctor-cli.ts +47 -0
  93. package/src/schema/doctor.ts +595 -0
  94. package/src/schema/generate-drizzle-schema-logic.ts +204 -57
  95. package/src/schema/generate-drizzle-schema.ts +6 -6
  96. package/src/schema/introspect-db-logic.ts +592 -0
  97. package/src/schema/introspect-db.ts +211 -0
  98. package/src/schema/test-schema.ts +11 -0
  99. package/src/services/BranchService.ts +5 -5
  100. package/src/services/EntityFetchService.ts +317 -188
  101. package/src/services/EntityPersistService.ts +15 -17
  102. package/src/services/RelationService.ts +299 -37
  103. package/src/services/entity-helpers.ts +39 -13
  104. package/src/services/entityService.ts +11 -9
  105. package/src/services/realtimeService.ts +58 -29
  106. package/src/utils/drizzle-conditions.ts +25 -24
  107. package/src/websocket.ts +52 -21
  108. package/test/auth-services.test.ts +131 -39
  109. package/test/batch-many-to-many-regression.test.ts +573 -0
  110. package/test/branchService.test.ts +22 -12
  111. package/test/data-transformer-hardening.test.ts +417 -0
  112. package/test/data-transformer.test.ts +175 -0
  113. package/test/doctor.test.ts +182 -0
  114. package/test/entityService.errors.test.ts +31 -16
  115. package/test/entityService.relations.test.ts +155 -59
  116. package/test/entityService.subcollection-search.test.ts +107 -57
  117. package/test/entityService.test.ts +105 -47
  118. package/test/generate-drizzle-schema.test.ts +262 -69
  119. package/test/historyService.test.ts +31 -16
  120. package/test/introspect-db-generation.test.ts +436 -0
  121. package/test/introspect-db-utils.test.ts +389 -0
  122. package/test/n-plus-one-regression.test.ts +314 -0
  123. package/test/postgresDataDriver.test.ts +260 -168
  124. package/test/realtimeService.test.ts +70 -39
  125. package/test/relation-pipeline-gaps.test.ts +637 -0
  126. package/test/relations.test.ts +492 -39
  127. package/test/unmapped-tables-safety.test.ts +345 -0
  128. package/test-drizzle-bug.ts +18 -0
  129. package/test-drizzle-out/0000_cultured_freak.sql +7 -0
  130. package/test-drizzle-out/0001_tiresome_professor_monster.sql +1 -0
  131. package/test-drizzle-out/meta/0000_snapshot.json +55 -0
  132. package/test-drizzle-out/meta/0001_snapshot.json +63 -0
  133. package/test-drizzle-out/meta/_journal.json +20 -0
  134. package/test-drizzle-prompt.sh +2 -0
  135. package/test-policy-prompt.sh +3 -0
  136. package/test-programmatic.ts +30 -0
  137. package/test-programmatic2.ts +59 -0
  138. package/test-schema-no-policies.ts +12 -0
  139. package/test_drizzle_mock.js +2 -2
  140. package/test_find_changed.mjs +3 -1
  141. package/test_hash.js +14 -0
  142. package/tsconfig.json +1 -1
  143. package/vite.config.ts +5 -5
@@ -1,13 +1,13 @@
1
1
  import React from "react";
2
- import { Entity, EntityValues } from "./entities";
3
- import { EntityCollection } from "./collections";
2
+ import type { Entity, EntityValues } from "./entities";
3
+ import type { EntityCollection } from "./collections";
4
4
  /**
5
5
  * Context passed to custom fields and entity views.
6
- * This is the base definition — `@rebasepro/cms` re-exports a
6
+ * This is the base definition — `@rebasepro/admin` re-exports a
7
7
  * fully-typed version that narrows the `formex` field.
8
8
  * @group Form custom fields
9
9
  */
10
- export interface FormContext<M extends Record<string, any> = any> {
10
+ export interface FormContext<M extends Record<string, unknown> = Record<string, unknown>> {
11
11
  /**
12
12
  * Current values of the entity
13
13
  */
@@ -35,16 +35,16 @@ export interface FormContext<M extends Record<string, any> = any> {
35
35
  status: "new" | "existing" | "copy";
36
36
  entity?: Entity<M>;
37
37
  savingError?: Error;
38
- openEntityMode: "side_panel" | "full_screen";
38
+ openEntityMode: "side_panel" | "full_screen" | "split";
39
39
  /**
40
40
  * The underlying formex controller that powers the form.
41
- * Prefer importing `FormContext` from `@rebasepro/cms` for the
41
+ * Prefer importing `FormContext` from `@rebasepro/admin` for the
42
42
  * fully-typed `FormexController<M>` version.
43
43
  */
44
44
  formex: Record<string, unknown>;
45
45
  disabled: boolean;
46
46
  }
47
- export type EntityCustomView<M extends Record<string, any> = any> = {
47
+ export type EntityCustomView<M extends Record<string, unknown> = Record<string, unknown>> = {
48
48
  key: string;
49
49
  name: string;
50
50
  tabComponent?: React.ReactNode;
@@ -52,7 +52,7 @@ export type EntityCustomView<M extends Record<string, any> = any> = {
52
52
  Builder?: React.ComponentType<EntityCustomViewParams<M>>;
53
53
  position?: "start" | "end";
54
54
  };
55
- export interface EntityCustomViewParams<M extends Record<string, any> = any> {
55
+ export interface EntityCustomViewParams<M extends Record<string, unknown> = Record<string, unknown>> {
56
56
  collection: EntityCollection<M>;
57
57
  entity?: Entity<M>;
58
58
  modifiedValues?: EntityValues<M>;
@@ -1,6 +1,6 @@
1
- import { Entity } from "./entities";
2
- import { User } from "../users";
3
- import { RebaseContext } from "../rebase_context";
1
+ import type { Entity } from "./entities";
2
+ import type { User } from "../users";
3
+ import type { RebaseContext } from "../rebase_context";
4
4
  /**
5
5
  * You can use this configuration to add additional fields to the data
6
6
  * exports
@@ -20,3 +20,4 @@ export * from "./entity_actions";
20
20
  export * from "./property_config";
21
21
  export * from "./entity_views";
22
22
  export * from "./data_source";
23
+ export * from "./cron";
@@ -1,14 +1,68 @@
1
- import React from "react";
2
- import { EntityCollection } from "./collections";
3
- import { EntityStatus } from "./entities";
4
- import { Property } from "./properties";
5
- import { FormContext } from "./entity_views";
6
- import { RebaseContext } from "../rebase_context";
7
- import { NavigationGroupMapping, AppView } from "../controllers";
8
- import { UserManagementDelegate } from "./user_management_delegate";
9
- import { User } from "../users";
10
- import { SlotContribution } from "./slots";
11
- export type FieldProps<T = any, CustomProps = any, M extends Record<string, any> = any> = any;
1
+ import React, { PropsWithChildren } from "react";
2
+ import type { EntityCollection } from "./collections";
3
+ import type { EntityStatus } from "./entities";
4
+ import type { InferPropertyType, Property } from "./properties";
5
+ import type { FormContext } from "./entity_views";
6
+ import type { RebaseContext } from "../rebase_context";
7
+ import type { NavigationGroupMapping, AppView } from "../controllers/navigation";
8
+ import type { UserManagementDelegate } from "./user_management_delegate";
9
+ import type { User } from "../users";
10
+ import type { SlotContribution } from "./slots";
11
+ /**
12
+ * Props interface for custom field components.
13
+ *
14
+ * The `@rebasepro/admin` package re-exports a narrower version of this
15
+ * interface that adds `formex` and layout-specific fields. This base
16
+ * definition captures the core contract that every field renderer must
17
+ * satisfy, regardless of where it is rendered.
18
+ *
19
+ * @typeParam P - The property type this field is bound to
20
+ * @typeParam CustomProps - Extra props injected via the property's `customProps`
21
+ * @typeParam M - The entity model type
22
+ * @group Form custom fields
23
+ */
24
+ export interface FieldProps<P extends Property = Property, CustomProps = unknown, M extends Record<string, unknown> = Record<string, unknown>> {
25
+ /** Key of the property (e.g. "user.name" for a nested path) */
26
+ propertyKey: string;
27
+ /** Current value of this field */
28
+ value: InferPropertyType<P> | null;
29
+ /** Set value of field directly */
30
+ setValue: (value: InferPropertyType<P> | null, shouldValidate?: boolean) => void;
31
+ /** Set value of a different field directly */
32
+ setFieldValue: (propertyKey: string, value: unknown, shouldValidate?: boolean) => void;
33
+ /** Is the form currently submitting */
34
+ isSubmitting?: boolean;
35
+ /** Should this field show the error indicator */
36
+ showError?: boolean;
37
+ /** Error message for this field, or undefined if valid */
38
+ error?: string;
39
+ /** Has this field been touched */
40
+ touched?: boolean;
41
+ /** Property related to this field */
42
+ property: P;
43
+ /** Should this field include a description */
44
+ includeDescription?: boolean;
45
+ /** Flag to indicate that the underlying value has been updated in the driver */
46
+ underlyingValueHasChanged?: boolean;
47
+ /** Is this field part of an array */
48
+ partOfArray?: boolean;
49
+ /** Is this field part of a block */
50
+ partOfBlock?: boolean;
51
+ /** Display the child properties directly, without being wrapped in an extendable panel */
52
+ minimalistView?: boolean;
53
+ /** Should this field autofocus on mount */
54
+ autoFocus?: boolean;
55
+ /** Additional properties set by the developer */
56
+ customProps?: CustomProps;
57
+ /** Additional values related to the state of the form or the entity */
58
+ context: FormContext<M>;
59
+ /** Flag to indicate if this field should be disabled */
60
+ disabled?: boolean;
61
+ /** Size of the field */
62
+ size?: "small" | "medium" | "large";
63
+ /** Callback when internal property state changes (e.g. panel expansion) */
64
+ onPropertyChange?: (property: Partial<Property>) => void;
65
+ }
12
66
  /**
13
67
  * Interface used to define plugins for Rebase.
14
68
  * Plugins contribute UI via **slots**, wrap subtrees with **providers**,
@@ -72,11 +126,11 @@ export interface PluginProvider {
72
126
  * Typed loosely because extra props are passed via the `props` field;
73
127
  * strict signatures cause contravariance issues.
74
128
  */
75
- Component: React.ComponentType<any>;
129
+ Component: React.ComponentType<PropsWithChildren<Record<string, unknown>>>;
76
130
  /**
77
131
  * Additional props passed to the Component.
78
132
  */
79
- props?: Record<string, any>;
133
+ props?: Record<string, unknown>;
80
134
  }
81
135
  /**
82
136
  * Behavioral hooks that a plugin can provide.
@@ -155,7 +209,7 @@ export interface PluginLifecycle {
155
209
  * Called when a collection's visible entities change.
156
210
  * Useful for analytics, caching, or cross-plugin coordination.
157
211
  */
158
- onCollectionChange?: (slug: string, entities: any[]) => void;
212
+ onCollectionChange?: (slug: string, entities: unknown[]) => void;
159
213
  }
160
214
  /**
161
215
  * Configuration for wrapping form field components.
@@ -165,7 +219,7 @@ export interface FieldBuilderConfig {
165
219
  /**
166
220
  * Returns a wrapped field component, or null to skip wrapping.
167
221
  */
168
- wrap: <T>(params: PluginFieldBuilderParams) => React.ComponentType<FieldProps<any>> | null;
222
+ wrap: <T>(params: PluginFieldBuilderParams) => React.ComponentType<FieldProps<Property>> | null;
169
223
  /**
170
224
  * Optional guard — return false to skip wrapping for this field.
171
225
  */
@@ -175,7 +229,7 @@ export interface FieldBuilderConfig {
175
229
  * Props passed to home page collection card action components.
176
230
  * @group Models
177
231
  */
178
- export interface PluginHomePageActionsProps<EP extends object = object, M extends Record<string, any> = any, USER extends User = User, EC extends EntityCollection<M> = EntityCollection<M>> {
232
+ export interface PluginHomePageActionsProps<EP extends object = object, M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User, EC extends EntityCollection<M> = EntityCollection<M>> {
179
233
  slug: string;
180
234
  collection: EC;
181
235
  context: RebaseContext<USER>;
@@ -193,13 +247,13 @@ export interface PluginFormActionProps<USER extends User = User, EC extends Enti
193
247
  disabled: boolean;
194
248
  formContext?: FormContext;
195
249
  context: RebaseContext<USER>;
196
- openEntityMode: "side_panel" | "full_screen";
250
+ openEntityMode: "side_panel" | "full_screen" | "split";
197
251
  }
198
252
  /**
199
253
  * Parameters passed to the field builder wrap function.
200
254
  * @group Models
201
255
  */
202
- export type PluginFieldBuilderParams<M extends Record<string, any> = any, EC extends EntityCollection<M> = EntityCollection<M>> = {
256
+ export type PluginFieldBuilderParams<M extends Record<string, unknown> = Record<string, unknown>, EC extends EntityCollection<M> = EntityCollection<M>> = {
203
257
  fieldConfigId: string;
204
258
  propertyKey: string;
205
259
  property: Property;
@@ -1,11 +1,11 @@
1
1
  import React from "react";
2
- import { EntityReference, EntityRelation, EntityValues, GeoPoint, Entity } from "./entities";
3
- import { FilterValues } from "./collections";
4
- import { ColorKey, ColorScheme } from "./chips";
5
- import { AuthController } from "../controllers";
6
- import { Relation } from "./relations";
7
- import { EntityAfterReadProps, EntityBeforeSaveProps } from "./entity_callbacks";
8
- import { User } from "../users";
2
+ import type { EntityReference, EntityRelation, EntityValues, GeoPoint, Entity } from "./entities";
3
+ import type { Relation, JoinStep, OnAction } from "./relations";
4
+ import type { EntityCollection, FilterValues } from "./collections";
5
+ import type { ColorKey, ColorScheme } from "./chips";
6
+ import type { AuthController } from "../controllers/auth";
7
+ import type { EntityAfterReadProps, EntityBeforeSaveProps } from "./entity_callbacks";
8
+ import type { User } from "../users";
9
9
  /**
10
10
  * Callbacks/Hooks for individual property fields
11
11
  * @group Entity properties
@@ -14,7 +14,7 @@ export type PropertyCallbacks<T = unknown, M extends Record<string, unknown> = R
14
14
  /**
15
15
  * Callback used after fetching data, to transform the value before rendering
16
16
  */
17
- afterRead?(props: Omit<EntityAfterReadProps<M, USER>, 'entity'> & {
17
+ afterRead?(props: Omit<EntityAfterReadProps<M, USER>, "entity"> & {
18
18
  value: T;
19
19
  entity: Entity<M> | undefined;
20
20
  }): Promise<T> | T;
@@ -22,7 +22,7 @@ export type PropertyCallbacks<T = unknown, M extends Record<string, unknown> = R
22
22
  * Callback used before saving, after validation.
23
23
  * You can modify the value before it's saved.
24
24
  */
25
- beforeSave?(props: Omit<EntityBeforeSaveProps<M, USER>, 'values'> & {
25
+ beforeSave?(props: Omit<EntityBeforeSaveProps<M, USER>, "values"> & {
26
26
  value: T;
27
27
  previousValue: T | undefined;
28
28
  values: Partial<M>;
@@ -41,30 +41,49 @@ export type Properties = {
41
41
  * This is the core of the type inference system.
42
42
  */
43
43
  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;
44
+ /**
45
+ * Helper type that determines whether a property is required.
46
+ * Uses direct structural matching against `{ validation: { required: true } }`
47
+ * (without the optional marker on `validation`), which correctly narrows
48
+ * literal `true` while treating widened `boolean` as not-required.
49
+ */
50
+ type IsRequired<P extends Property> = P extends {
51
+ validation: {
52
+ required: true;
53
+ };
54
+ } ? true : false;
55
+ /**
56
+ * Extract keys from Properties where the property is required.
57
+ */
58
+ type RequiredPropertyKeys<P extends Properties> = {
59
+ [K in keyof P]: IsRequired<P[K]> extends true ? K : never;
60
+ }[keyof P];
61
+ /**
62
+ * Extract keys from Properties where the property is optional.
63
+ */
64
+ type OptionalPropertyKeys<P extends Properties> = {
65
+ [K in keyof P]: IsRequired<P[K]> extends true ? never : K;
66
+ }[keyof P];
44
67
  /**
45
68
  * A generic type that converts a `Properties` schema definition into a corresponding
46
69
  * TypeScript entity type. It correctly handles required and optional properties.
47
70
  *
71
+ * A property is considered required when it has `validation: { required: true }`.
72
+ * The `true` must be a literal type — if `required` is typed as `boolean`,
73
+ * the property will be treated as optional (use `as const` for literal inference).
74
+ *
48
75
  * @example
49
76
  * const productSchema = {
50
- * name: { type: 'string', validation: { required: true } },
51
- * price: { type: 'number' }
52
- * };
77
+ * name: { type: 'string', validation: { required: true } },
78
+ * price: { type: 'number' }
79
+ * } as const satisfies Properties;
53
80
  * type Product = InferEntityType<typeof productSchema>;
54
81
  * // Result: { name: string; price?: number; }
55
82
  */
56
83
  export type InferEntityType<P extends Properties> = {
57
- -readonly [K in keyof P as P[K] extends {
58
- validation?: {
59
- required: true;
60
- };
61
- } ? K : never]: InferPropertyType<P[K]>;
84
+ -readonly [K in RequiredPropertyKeys<P>]: InferPropertyType<P[K]>;
62
85
  } & {
63
- -readonly [K in keyof P as P[K] extends {
64
- validation?: {
65
- required: true;
66
- };
67
- } ? never : K]?: InferPropertyType<P[K]>;
86
+ -readonly [K in OptionalPropertyKeys<P>]?: InferPropertyType<P[K]>;
68
87
  };
69
88
  /**
70
89
  * Interface including all common properties of a CMS property.
@@ -396,14 +415,79 @@ export interface RelationProperty extends BaseProperty {
396
415
  */
397
416
  isId?: boolean;
398
417
  /**
399
- * The name of the relation this property refers to. This name must match
400
- * one of the `relationName`s defined in the top-level `relations` array
401
- * of the collection.
418
+ * The target collection this relation points to.
419
+ * When set, the framework treats this property as a self-contained relation
420
+ * definition and no separate `relations[]` entry is needed.
421
+ */
422
+ target?: () => EntityCollection;
423
+ /**
424
+ * Whether this property references one or many records.
425
+ * Defaults to `"one"`.
426
+ */
427
+ cardinality?: "one" | "many";
428
+ /**
429
+ * Which side owns the persistence for this relationship.
430
+ * - `"owning"`: The foreign key (for one-to-one) or junction table (for many-to-many) is on this collection.
431
+ * - `"inverse"`: The foreign key is on the target collection's table.
432
+ * Defaults to `"owning"`.
433
+ */
434
+ direction?: "owning" | "inverse";
435
+ /**
436
+ * The name of the corresponding relation on the target collection.
437
+ * Used for inverse relations to locate the owning side.
438
+ */
439
+ inverseRelationName?: string;
440
+ /**
441
+ * Column on THIS table that stores the foreign key to the target.
442
+ * Required when `direction` is `"owning"` and `cardinality` is `"one"`.
443
+ * Auto-inferred if not set.
444
+ * @example "author_id"
445
+ */
446
+ localKey?: string;
447
+ /**
448
+ * Column on the TARGET table that stores the foreign key back to this entity.
449
+ * Required when `direction` is `"inverse"`.
450
+ * Auto-inferred if not set.
451
+ * @example "post_id"
452
+ */
453
+ foreignKeyOnTarget?: string;
454
+ /**
455
+ * Junction table configuration for many-to-many relationships.
456
+ * Required when `cardinality` is `"many"` and `direction` is `"owning"`.
457
+ * Auto-inferred if not set.
458
+ */
459
+ through?: {
460
+ table: string;
461
+ sourceColumn: string;
462
+ targetColumn: string;
463
+ };
464
+ /**
465
+ * Explicit, ordered join path for advanced multi-hop relations.
466
+ * When set, overrides `localKey`, `foreignKeyOnTarget`, and `through`.
467
+ */
468
+ joinPath?: JoinStep[];
469
+ /**
470
+ * Cascade action on update.
471
+ */
472
+ onUpdate?: OnAction;
473
+ /**
474
+ * Cascade action on delete.
475
+ */
476
+ onDelete?: OnAction;
477
+ /**
478
+ * Overrides applied to the target collection when rendered as a subcollection tab.
479
+ */
480
+ overrides?: Partial<EntityCollection>;
481
+ /**
482
+ * Optional name for this relation. Defaults to the property key at runtime.
483
+ * Only needed when the relation name should differ from the property key,
484
+ * or for backward compatibility with existing `relations[]` entries.
402
485
  */
403
- relationName: string;
486
+ relationName?: string;
404
487
  /**
405
- * The resolved relation object.
406
- * This is set by the framework
488
+ * The resolved relation object, populated by the framework at normalization time.
489
+ * **Do not set manually** — it is computed from the inline fields above
490
+ * or looked up from the collection's `relations[]` array.
407
491
  */
408
492
  relation?: Relation;
409
493
  /**
@@ -765,7 +849,7 @@ export type StorageConfig = {
765
849
  * When set to true, this flag indicates that the bucket name will be
766
850
  * included in the saved storage path.
767
851
  *
768
- * E.g. `gs://my-bucket/path/to/file.png` instead of just `path/to/file.png`
852
+ * E.g. `s3://my-bucket/path/to/file.png` instead of just `path/to/file.png`
769
853
  *
770
854
  * Defaults to false.
771
855
  */
@@ -860,7 +944,7 @@ export interface ImageResize {
860
944
  * - `contain`: Scale down to fit within bounds, preserving aspect ratio (default)
861
945
  * - `cover`: Scale to fill bounds, preserving aspect ratio (may crop)
862
946
  */
863
- mode?: 'contain' | 'cover';
947
+ mode?: "contain" | "cover";
864
948
  /**
865
949
  * Output format for the resized image.
866
950
  * - `original`: Keep the original format (default)
@@ -868,7 +952,7 @@ export interface ImageResize {
868
952
  * - `png`: Convert to PNG
869
953
  * - `webp`: Convert to WebP
870
954
  */
871
- format?: 'original' | 'jpeg' | 'png' | 'webp';
955
+ format?: "original" | "jpeg" | "png" | "webp";
872
956
  /**
873
957
  * Quality for lossy formats (JPEG, WebP). Number between 0 and 100.
874
958
  * Higher is better quality but larger file size. Defaults to 80.
@@ -1048,11 +1132,11 @@ export interface ConditionContext {
1048
1132
  * Current form/entity values.
1049
1133
  * Date values are converted to Unix timestamps (milliseconds).
1050
1134
  */
1051
- values: Record<string, any>;
1135
+ values: Record<string, unknown>;
1052
1136
  /**
1053
1137
  * Previous values before the current edit session.
1054
1138
  */
1055
- previousValues: Record<string, any>;
1139
+ previousValues: Record<string, unknown>;
1056
1140
  /**
1057
1141
  * Current value of this property specifically.
1058
1142
  */
@@ -1089,3 +1173,4 @@ export interface ConditionContext {
1089
1173
  */
1090
1174
  now: number;
1091
1175
  }
1176
+ export {};
@@ -1,4 +1,4 @@
1
- import { EntityCollection } from "./collections";
1
+ import type { EntityCollection } from "./collections";
2
2
  /**
3
3
  * @group Models
4
4
  */
@@ -1,9 +1,9 @@
1
1
  import React from "react";
2
- import { CollectionActionsProps, EntityTableController, SelectionController, EntityCollection } from "./collections";
3
- import { Entity } from "./entities";
4
- import { PluginFormActionProps, PluginGenericProps, PluginHomePageActionsProps, PluginHomePageAdditionalCardsProps } from "./plugins";
5
- import { Property } from "./properties";
6
- import { RebaseContext } from "../rebase_context";
2
+ import type { CollectionActionsProps, EntityTableController, SelectionController, EntityCollection } from "./collections";
3
+ import type { Entity } from "./entities";
4
+ import type { PluginFormActionProps, PluginGenericProps, PluginHomePageActionsProps, PluginHomePageAdditionalCardsProps } from "./plugins";
5
+ import type { Property } from "./properties";
6
+ import type { RebaseContext } from "../rebase_context";
7
7
  /**
8
8
  * Registry mapping slot names to their component prop types.
9
9
  * Each key represents a UI extension point in the CMS.
@@ -14,6 +14,8 @@ export interface SlotRegistry {
14
14
  "home.cards": PluginHomePageAdditionalCardsProps;
15
15
  "home.children.start": PluginGenericProps;
16
16
  "home.children.end": PluginGenericProps;
17
+ /** Compact insight widget rendered inline in a home page collection card. */
18
+ "home.card.insight": HomeCardInsightSlotProps;
17
19
  "home.collection.actions": PluginHomePageActionsProps;
18
20
  /** Rendered below the logo in the sidebar drawer. */
19
21
  "navigation.header": NavigationSlotProps;
@@ -28,6 +30,8 @@ export interface SlotRegistry {
28
30
  "collection.toolbar": CollectionToolbarProps;
29
31
  /** Custom empty-state component when a collection has no data. */
30
32
  "collection.empty-state": CollectionEmptyStateProps;
33
+ /** Insight widgets rendered above the collection table. */
34
+ "collection.insights": CollectionInsightsSlotProps;
31
35
  "form.actions": PluginFormActionProps;
32
36
  "form.actions.top": PluginFormActionProps;
33
37
  /** Rendered before the form title / field list. */
@@ -74,7 +78,7 @@ export interface SlotContribution<K extends SlotName = SlotName> {
74
78
  /**
75
79
  * Additional props to merge into the slot props before rendering.
76
80
  */
77
- props?: Record<string, any>;
81
+ props?: Record<string, unknown>;
78
82
  /**
79
83
  * Ordering hint. Lower values render first. Defaults to 50.
80
84
  */
@@ -226,3 +230,23 @@ export interface GlobalSearchProps {
226
230
  export interface ShellToolbarProps {
227
231
  context: RebaseContext;
228
232
  }
233
+ /**
234
+ * Props for `collection.insights` slot.
235
+ * Insight widgets rendered above the collection table.
236
+ * @group Plugins
237
+ */
238
+ export interface CollectionInsightsSlotProps {
239
+ path: string;
240
+ collection: EntityCollection;
241
+ parentCollectionIds: string[];
242
+ }
243
+ /**
244
+ * Props for `home.card.insight` slot.
245
+ * Compact insight rendered inline in a home page collection card.
246
+ * @group Plugins
247
+ */
248
+ export interface HomeCardInsightSlotProps {
249
+ slug: string;
250
+ collection: EntityCollection;
251
+ context: RebaseContext;
252
+ }
@@ -44,6 +44,8 @@ export interface RebaseTranslations {
44
44
  clear_filter: string;
45
45
  clear_filter_sort: string;
46
46
  clear_sort: string;
47
+ /** Reset all active filters */
48
+ clear_all: string;
47
49
  no_items: string;
48
50
  no_entries_found: string;
49
51
  all_entries_loaded: string;
@@ -124,6 +126,20 @@ export interface RebaseTranslations {
124
126
  /** Shown in unsaved-changes dialogs */
125
127
  are_you_sure_leave: string;
126
128
  passkey_error_unsupported: string;
129
+ /** Snackbar message after a successful save */
130
+ saved_correctly: string;
131
+ /** Snackbar title when a beforeSave callback throws */
132
+ error_before_saving: string;
133
+ /** Snackbar title when an afterSave callback throws */
134
+ error_after_saving: string;
135
+ /** Snackbar title when the save itself fails */
136
+ error_saving_entity: string;
137
+ /** Alert shown when the entity does not exist in the database */
138
+ entity_does_not_exist: string;
139
+ /** Tooltip when the form has unsaved modifications */
140
+ form_modified: string;
141
+ /** Tooltip when the form is in sync with the database */
142
+ form_in_sync: string;
127
143
  admin: string;
128
144
  home: string;
129
145
  this_form_has_errors: string;
@@ -267,6 +283,8 @@ export interface RebaseTranslations {
267
283
  any_of_these: string;
268
284
  only_admins_edit_roles: string;
269
285
  error_user_not_found: string;
286
+ /** Placeholder / label for the "all roles" filter option */
287
+ all_roles: string;
270
288
  role: string;
271
289
  name_of_this_role: string;
272
290
  id_of_this_role: string;
@@ -373,6 +391,7 @@ export interface RebaseTranslations {
373
391
  no_filterable_properties: string;
374
392
  apply_filters: string;
375
393
  list: string;
394
+ table_view_mode: string;
376
395
  cards: string;
377
396
  board: string;
378
397
  initialize_kanban_order_desc: string;
@@ -380,6 +399,7 @@ export interface RebaseTranslations {
380
399
  kanban_view_requires_enum: string;
381
400
  no_enum_values_configured: string;
382
401
  items_need_backfill: string;
402
+ kanban_order_not_configured: string;
383
403
  initialize: string;
384
404
  confirm_multiple_delete: string;
385
405
  delete_entity_confirm_title: string;
@@ -823,4 +843,28 @@ export interface RebaseTranslations {
823
843
  studio_kanban_configure?: string;
824
844
  studio_missing_reference_error?: string;
825
845
  studio_new_collection_add?: string;
846
+ db_column_type?: string;
847
+ primary_key_unique_id?: string;
848
+ spread_children_as_columns?: string;
849
+ mode?: string;
850
+ timezone?: string;
851
+ target_collection?: string;
852
+ storage_file_name?: string;
853
+ storage_path?: string;
854
+ storage_max_size?: string;
855
+ storage_resize_mode?: string;
856
+ storage_output_format?: string;
857
+ storage_max_width?: string;
858
+ storage_max_height?: string;
859
+ storage_quality?: string;
860
+ storage_file_upload_config?: string;
861
+ storage_image_resize_config?: string;
862
+ storage_all_file_types?: string;
863
+ storage_allowed_file_types?: string;
864
+ storage_include_bucket_url?: string;
865
+ storage_save_url?: string;
866
+ datetime_automatic_value?: string;
867
+ markdown_paste_behavior?: string;
868
+ markdown_strip_html?: string;
869
+ markdown_convert_pasted?: string;
826
870
  }
@@ -51,6 +51,7 @@ export interface UserManagementDelegate<USER extends User = User> {
51
51
  offset?: number;
52
52
  orderBy?: string;
53
53
  orderDir?: "asc" | "desc";
54
+ roleId?: string;
54
55
  }) => Promise<{
55
56
  users: USER[];
56
57
  total: number;
@@ -0,0 +1,6 @@
1
+ CREATE TABLE "test" (
2
+ "id" text PRIMARY KEY NOT NULL
3
+ );
4
+ --> statement-breakpoint
5
+ ALTER TABLE "test" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
6
+ CREATE POLICY "old_policy" ON "test" AS PERMISSIVE FOR SELECT TO public;
@@ -0,0 +1 @@
1
+ ALTER POLICY "old_policy" ON "test" TO public USING (true);
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "test" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint
2
+ DROP POLICY "old_policy" ON "test" CASCADE;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "test" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
2
+ CREATE POLICY "new_policy" ON "test" AS PERMISSIVE FOR SELECT TO public USING (true);
@@ -0,0 +1,47 @@
1
+ {
2
+ "id": "929ed59c-6874-4679-8649-8d3f7dd62890",
3
+ "prevId": "00000000-0000-0000-0000-000000000000",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.test": {
8
+ "name": "test",
9
+ "schema": "",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "text",
14
+ "primaryKey": true,
15
+ "notNull": true
16
+ }
17
+ },
18
+ "indexes": {},
19
+ "foreignKeys": {},
20
+ "compositePrimaryKeys": {},
21
+ "uniqueConstraints": {},
22
+ "policies": {
23
+ "old_policy": {
24
+ "name": "old_policy",
25
+ "as": "PERMISSIVE",
26
+ "for": "SELECT",
27
+ "to": [
28
+ "public"
29
+ ]
30
+ }
31
+ },
32
+ "checkConstraints": {},
33
+ "isRLSEnabled": false
34
+ }
35
+ },
36
+ "enums": {},
37
+ "schemas": {},
38
+ "sequences": {},
39
+ "roles": {},
40
+ "policies": {},
41
+ "views": {},
42
+ "_meta": {
43
+ "columns": {},
44
+ "schemas": {},
45
+ "tables": {}
46
+ }
47
+ }