@rebasepro/server-postgres 0.12.1-canary.gf5f1d39 → 0.13.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.
Files changed (92) hide show
  1. package/dist/PostgresBackendDriver.d.ts +1 -1
  2. package/dist/PostgresBootstrapper.d.ts +25 -1
  3. package/dist/auth/services.d.ts +21 -0
  4. package/dist/backup/backup-service.d.ts +10 -1
  5. package/dist/backup/pg-tools.d.ts +47 -0
  6. package/dist/backup-service-CD8o_1Sl.js +8999 -0
  7. package/dist/backup-service-CD8o_1Sl.js.map +1 -0
  8. package/dist/cli-helpers.d.ts +39 -0
  9. package/dist/connection-BuZ97wsr.js +250 -0
  10. package/dist/connection-BuZ97wsr.js.map +1 -0
  11. package/dist/connection.d.ts +42 -0
  12. package/dist/ensure-collection-policies-ViG8XiPn.js +57 -0
  13. package/dist/ensure-collection-policies-ViG8XiPn.js.map +1 -0
  14. package/dist/ensure-collection-tables-CBQdOETu.js +650 -0
  15. package/dist/ensure-collection-tables-CBQdOETu.js.map +1 -0
  16. package/dist/index.es.js +1231 -10031
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/policy-CeA1JcxP.js +105 -0
  19. package/dist/policy-CeA1JcxP.js.map +1 -0
  20. package/dist/schema/auth-schema.d.ts +83 -144
  21. package/dist/schema/ensure-collection-policies.d.ts +60 -0
  22. package/dist/schema/ensure-collection-tables.d.ts +44 -2
  23. package/dist/schema/generate-postgres-ddl-logic.d.ts +135 -1
  24. package/dist/schema/introspect-db-constraints.d.ts +57 -0
  25. package/dist/schema/introspect-db-logic.d.ts +94 -5
  26. package/dist/schema/introspect-db-queries.d.ts +119 -0
  27. package/dist/schema/introspect-db-structure.d.ts +263 -0
  28. package/dist/schema/introspect-db-types.d.ts +11 -0
  29. package/dist/services/FetchService.d.ts +4 -1
  30. package/dist/services/RelationService.d.ts +24 -1
  31. package/dist/services/channel-bus/index.d.ts +1 -7
  32. package/dist/services/collection-helpers.d.ts +36 -2
  33. package/dist/services/dataService.d.ts +3 -1
  34. package/dist/services/row-pipeline.d.ts +1 -1
  35. package/dist/{src-BbFOPJ1S.js → src-DlPBctw_.js} +299 -173
  36. package/dist/src-DlPBctw_.js.map +1 -0
  37. package/dist/{src-Zqwaw3P5.js → src-DoU9yPqq.js} +3 -159
  38. package/dist/src-DoU9yPqq.js.map +1 -0
  39. package/dist/utils/connection-string.d.ts +29 -0
  40. package/dist/utils/drizzle-conditions.d.ts +5 -4
  41. package/dist/utils/pg-error-utils.d.ts +35 -0
  42. package/dist/websocket-B2LsrINK.js +530 -0
  43. package/dist/websocket-B2LsrINK.js.map +1 -0
  44. package/package.json +14 -14
  45. package/src/PostgresAdapter.ts +21 -2
  46. package/src/PostgresBackendDriver.ts +4 -0
  47. package/src/PostgresBootstrapper.ts +192 -33
  48. package/src/auth/ensure-tables.ts +164 -9
  49. package/src/auth/services.ts +24 -2
  50. package/src/backup/backup-cli.ts +41 -2
  51. package/src/backup/backup-service.ts +38 -5
  52. package/src/backup/pg-tools.ts +96 -3
  53. package/src/cli-helpers.ts +70 -0
  54. package/src/cli.ts +44 -26
  55. package/src/collections/validate-relations.ts +15 -0
  56. package/src/connection.ts +73 -0
  57. package/src/data-transformer.ts +9 -3
  58. package/src/databasePoolManager.ts +5 -2
  59. package/src/schema/auth-schema.ts +30 -19
  60. package/src/schema/ensure-collection-policies.ts +105 -0
  61. package/src/schema/ensure-collection-tables.test.ts +105 -9
  62. package/src/schema/ensure-collection-tables.ts +220 -32
  63. package/src/schema/generate-drizzle-schema-logic.ts +42 -6
  64. package/src/schema/generate-postgres-ddl-logic.ts +382 -19
  65. package/src/schema/introspect-db-constraints.ts +385 -0
  66. package/src/schema/introspect-db-inference.ts +18 -8
  67. package/src/schema/introspect-db-logic.ts +385 -71
  68. package/src/schema/introspect-db-queries.ts +326 -0
  69. package/src/schema/introspect-db-structure.ts +670 -0
  70. package/src/schema/introspect-db-types.ts +56 -0
  71. package/src/schema/introspect-db.ts +37 -80
  72. package/src/schema/introspect-runtime.test.ts +56 -8
  73. package/src/schema/introspect-runtime.ts +31 -9
  74. package/src/security/policy-drift.test.ts +11 -3
  75. package/src/services/BranchService.ts +66 -28
  76. package/src/services/FetchService.ts +90 -14
  77. package/src/services/PersistService.ts +20 -6
  78. package/src/services/RelationService.ts +249 -48
  79. package/src/services/channel-bus/index.ts +0 -9
  80. package/src/services/collection-helpers.ts +69 -3
  81. package/src/services/dataService.ts +3 -1
  82. package/src/services/realtimeService.ts +3 -3
  83. package/src/services/row-pipeline.ts +1 -1
  84. package/src/utils/connection-string.ts +58 -0
  85. package/src/utils/drizzle-conditions.ts +31 -6
  86. package/src/utils/pg-error-utils.ts +65 -0
  87. package/src/websocket.ts +18 -9
  88. package/dist/chunk-DSJWtz9O.js +0 -40
  89. package/dist/ensure-collection-tables-CNTcZGvn.js +0 -304
  90. package/dist/ensure-collection-tables-CNTcZGvn.js.map +0 -1
  91. package/dist/src-BbFOPJ1S.js.map +0 -1
  92. package/dist/src-Zqwaw3P5.js.map +0 -1
@@ -0,0 +1,263 @@
1
+ /**
2
+ * What a schema's *structure* says about the app on top of it.
3
+ *
4
+ * Introspection has always been a table mirror: one table in, one collection
5
+ * out, one nav entry each, every column a form field. A schema of thirty tables
6
+ * produces thirty sidebar entries, and a panel whose navigation is a list of
7
+ * table names reads as a database browser however good the fields are — which
8
+ * is the actual complaint about generated admin panels, and is structural, not
9
+ * cosmetic.
10
+ *
11
+ * Most of what separates the eight nouns a user navigates by from the thirty
12
+ * tables underneath them is written down in the schema already: which tables
13
+ * only exist to join two others, which are small referenced code lists, which
14
+ * rows cannot outlive a parent row. This module reads that.
15
+ *
16
+ * ## Structure only
17
+ *
18
+ * Nothing here looks at a column or table *name*. Name heuristics — `status`,
19
+ * `*_url`, `image`, `created_at` — are wrong exactly when a schema is not in
20
+ * English, or is domain-specific, or spells things differently, and they are
21
+ * wrong silently. Every rule below is a fact the database enforces: key
22
+ * composition, foreign-key direction and delete rule, uniqueness, nullability,
23
+ * declared type and length, generated-ness, row count.
24
+ *
25
+ * That constraint has a cost, and it is worth stating: a schema that declares
26
+ * nothing beyond `NOT NULL` gives this module very little to work with, and it
27
+ * returns `entity` for everything rather than guessing. Under-classifying is
28
+ * the intended failure mode. A table wrongly hidden from the navigation is a
29
+ * table the user cannot find; a table wrongly left in it is merely the status
30
+ * quo.
31
+ *
32
+ * Pure module: no I/O. Row counts come in on {@link SchemaMetadata.rowCounts},
33
+ * which the caller fills from {@link ./introspect-db-queries.countRowsUpTo} for
34
+ * the tables {@link lookupCandidates} names.
35
+ */
36
+ import type { ForeignKeyRow, SchemaMetadata, TableColumn, TableMeta } from "./introspect-db-logic";
37
+ import type { CheckFactsByTable } from "./introspect-db-constraints";
38
+ /**
39
+ * The row count above which a referenced table is a real entity rather than a
40
+ * code list. Deliberately low: `pagila.category` has 16 rows and `language` 6,
41
+ * while `actor` has 200 and `country` 109 — the gap between "a fixed set
42
+ * somebody typed once" and "data the app accumulates" is wide, and picking a
43
+ * number in the middle of it costs nothing.
44
+ */
45
+ export declare const LOOKUP_MAX_ROWS = 50;
46
+ /**
47
+ * The most payload columns a code list may carry. A code list is an id, a
48
+ * label, and perhaps a sort key or a flag; past that it is a table with
49
+ * attributes, which is an entity.
50
+ */
51
+ export declare const LOOKUP_MAX_PAYLOAD_COLUMNS = 3;
52
+ /**
53
+ * The most enum values a board can usefully have as columns. A kanban with
54
+ * thirty columns is a horizontally scrolling table.
55
+ */
56
+ export declare const KANBAN_MAX_VALUES = 12;
57
+ /** Below this, a "board" is one or two columns — a filter, not a board. */
58
+ export declare const KANBAN_MIN_VALUES = 2;
59
+ /**
60
+ * How many columns a generated list view shows before it stops being readable.
61
+ * Only applied when a table has more properties than this; a six-column table
62
+ * gets no `listProperties` at all rather than a restatement of its own columns.
63
+ */
64
+ export declare const LIST_PROPERTIES_CAP = 6;
65
+ /**
66
+ * What a table *is*, structurally.
67
+ *
68
+ * - `entity` — a thing the app is about. Gets a collection and a nav entry.
69
+ * - `junction` — exists only to relate two other tables. Gets no collection at
70
+ * all; it becomes a many-to-many relation on both sides.
71
+ * - `lookup` — a small, referenced, self-contained code list. Gets a collection,
72
+ * grouped away from the entities rather than listed beside them.
73
+ * - `owned-child` — rows that belong to exactly one parent row and are reached
74
+ * through it. Gets a collection (it is a real table with real rows, and the
75
+ * API still serves it) but no nav entry: it already renders as a tab on its
76
+ * parent.
77
+ */
78
+ export type TableRole = "entity" | "junction" | "lookup" | "owned-child";
79
+ /**
80
+ * Why a table was called someone's child, weakest last.
81
+ *
82
+ * Carried into the generated file as a comment. A reader who disagrees with the
83
+ * classification needs to see what it was based on to know which line to change.
84
+ */
85
+ export type OwnershipEvidence =
86
+ /** The only foreign key declared `ON DELETE CASCADE`. */
87
+ "cascade-delete"
88
+ /** The only foreign key that is part of the table's primary key. */
89
+ | "identifying-key"
90
+ /** The only foreign key that is `NOT NULL`. */
91
+ | "sole-required-key"
92
+ /** First column of a composite primary key made entirely of foreign keys. */
93
+ | "leading-key-column";
94
+ export interface JunctionShape {
95
+ sourceTable: string;
96
+ sourceColumn: string;
97
+ targetTable: string;
98
+ targetColumn: string;
99
+ }
100
+ export interface TableClassification {
101
+ table: string;
102
+ role: TableRole;
103
+ /** One line, in prose, for the generated file. */
104
+ reason: string;
105
+ /** Set when `role === "owned-child"`. */
106
+ owner?: {
107
+ table: string;
108
+ column: string;
109
+ evidence: OwnershipEvidence;
110
+ };
111
+ /** Set when `role === "junction"`. */
112
+ junction?: JunctionShape;
113
+ }
114
+ /**
115
+ * A timestamp the database maintains: a temporal column defaulting to the
116
+ * transaction clock.
117
+ *
118
+ * This is the structural stand-in for the `created_at`/`updated_at` name check.
119
+ * It is strictly better than the name: it catches `fecha_creacion` and
120
+ * `last_update` (pagila's spelling, which the name list misses), and it does not
121
+ * fire on a user-editable `created_at date` column that has no default and which
122
+ * the name check would wrongly make read-only.
123
+ */
124
+ export declare function isAutoTimestamp(column: TableColumn): boolean;
125
+ /** A key the database fills in: identity, serial, or a uuid-generating default. */
126
+ export declare function isGeneratedKey(column: TableColumn): boolean;
127
+ /** A column Postgres computes; writing to it is an error. */
128
+ export declare function isGeneratedColumn(column: TableColumn): boolean;
129
+ /**
130
+ * Types that exist to be searched or indexed, never to be typed into.
131
+ *
132
+ * A `tsvector` column is a derived search index — maintained by a trigger, a
133
+ * generated expression, or an application job — and its contents are lexeme
134
+ * positions, not text. Pagila's `film.fulltext` is one, and introspection used
135
+ * to emit it as an ordinary required string: a mandatory form field whose
136
+ * correct value no user can produce, on the sixth column of the list view.
137
+ */
138
+ export declare function isDerivedIndexColumn(column: TableColumn): boolean;
139
+ /** Anything the user cannot meaningfully edit, whatever the reason. */
140
+ export declare function isReadOnlyColumn(column: TableColumn): boolean;
141
+ /**
142
+ * A string column with a declared maximum length.
143
+ *
144
+ * `varchar(50)` and `text` are the same type to an application but not to the
145
+ * author: choosing a bound is a statement that the value is short and
146
+ * label-like, which is what makes this usable for picking a display column.
147
+ */
148
+ export declare function isBoundedString(column: TableColumn): boolean;
149
+ /**
150
+ * A column carrying data rather than structure: not a key, not a foreign key,
151
+ * not a database-maintained timestamp, not computed.
152
+ *
153
+ * The count of these is what tells a pure join table from an association that
154
+ * carries its own attributes — `northwind.order_details` has the key shape of a
155
+ * junction and three payload columns, so it is not one.
156
+ */
157
+ export declare function isPayloadColumn(column: TableColumn, pks: string[], fkColumns: Set<string>): boolean;
158
+ /** One foreign key, with its columns grouped back together. */
159
+ export interface ForeignKeyConstraint {
160
+ name: string;
161
+ table: string;
162
+ columns: string[];
163
+ foreignTable: string;
164
+ foreignColumns: string[];
165
+ deleteRule?: string;
166
+ }
167
+ /**
168
+ * Groups per-column foreign key rows back into constraints.
169
+ *
170
+ * Rows arrive one per referencing column. A composite key looks exactly like two
171
+ * separate keys until they are grouped by constraint name, and the difference
172
+ * matters: two single-column keys to two tables can be a junction, one
173
+ * two-column key never is.
174
+ */
175
+ export declare function groupForeignKeys(fks: ForeignKeyRow[]): ForeignKeyConstraint[];
176
+ /**
177
+ * Names the tables whose classification depends on a row count.
178
+ *
179
+ * The caller counts these — and only these — before calling
180
+ * {@link classifyTables}. On a schema of any size this is a handful of tables,
181
+ * and the count itself is capped (see `countRowsUpTo`), so the whole extra cost
182
+ * is bounded regardless of how much data the database holds.
183
+ */
184
+ export declare function lookupCandidates(metadata: SchemaMetadata, tables: Map<string, TableMeta>): string[];
185
+ /**
186
+ * Classifies every table in the schema.
187
+ *
188
+ * Order matters: junction is the most specific and most consequential (the
189
+ * table disappears), so it is tested first; then lookup, which needs no
190
+ * ownership reasoning; then ownership. Anything unmatched is an entity, which
191
+ * is also what every rule falls back to when its evidence is ambiguous.
192
+ */
193
+ export declare function classifyTables(metadata: SchemaMetadata, tables: Map<string, TableMeta>): Map<string, TableClassification>;
194
+ /**
195
+ * The columns a property-level derivation needs, resolved once.
196
+ */
197
+ export interface ColumnFacts {
198
+ column: TableColumn;
199
+ isPk: boolean;
200
+ isFk: boolean;
201
+ /** Covered by a single-column unique constraint or unique index. */
202
+ isUniqueAlone: boolean;
203
+ isAutoTimestamp: boolean;
204
+ isGenerated: boolean;
205
+ /** Allowed values, from a Postgres enum type or a readable CHECK. */
206
+ enumValues?: string[];
207
+ propType: string;
208
+ }
209
+ export declare function buildColumnFacts(meta: TableMeta, metadata: SchemaMetadata, enumMap: Map<string, string[]>, checkFacts: CheckFactsByTable): Map<string, ColumnFacts>;
210
+ /**
211
+ * The column that identifies a row to a human.
212
+ *
213
+ * Structural, in three rungs, strongest first:
214
+ *
215
+ * 1. A single-column unique constraint on a required string. This is as close
216
+ * as a schema comes to declaring "this is what a row is called": it is the
217
+ * column a person looks a row up by, and the database guarantees it picks
218
+ * out one row.
219
+ * 2. The first required string that declares a length, when the table also has
220
+ * strings that do not. Choosing `varchar(n)` for one column and `text` for
221
+ * another is the author distinguishing a label from prose.
222
+ * 3. The first required string in declaration order. Weak, but it is the same
223
+ * rung the panel's own fallback stands on, and column order carries real
224
+ * information — the identifying column of a table is written near the top of
225
+ * it, in every schema, in every language.
226
+ *
227
+ * Deliberately not: a column called `name`, or `title`. That works on English
228
+ * schemas written by someone who read the same tutorial. This picks
229
+ * `film.title`, `actor.first_name` and `category.name` out of pagila without
230
+ * knowing what any of those words mean.
231
+ */
232
+ export declare function deriveTitleProperty(facts: Map<string, ColumnFacts>): string | undefined;
233
+ /**
234
+ * The enum column a board should have as its columns.
235
+ *
236
+ * A board needs a small, closed, always-present set of states. `NOT NULL` is
237
+ * required because a null has no column to sit in; the bounds keep out
238
+ * two-state flags (a filter, not a board) and long code lists (a scrolling
239
+ * table). The first qualifying column in declaration order wins, so the output
240
+ * is stable across runs.
241
+ */
242
+ export declare function deriveKanbanProperty(facts: Map<string, ColumnFacts>): string | undefined;
243
+ /**
244
+ * The column a list should be sorted by, newest first.
245
+ *
246
+ * Only when the table has exactly one database-maintained timestamp. With two —
247
+ * a created and an updated stamp — the two orderings differ and the schema does
248
+ * not say which the user means, so neither is chosen.
249
+ */
250
+ export declare function deriveSort(facts: Map<string, ColumnFacts>): [string, "desc"] | undefined;
251
+ /**
252
+ * The first `LIST_PROPERTIES_CAP` visible properties, or nothing.
253
+ *
254
+ * Returning nothing when the table is already narrow matters: `listProperties`
255
+ * that restates every column is config the reader has to check against the
256
+ * property list to discover it does nothing, and it silently stops new columns
257
+ * from appearing in the list view when someone adds one later.
258
+ *
259
+ * `hidden` names the properties already marked `hideFromCollection` — spending
260
+ * one of six columns on a value the list does not render is worse than not
261
+ * capping at all.
262
+ */
263
+ export declare function deriveListProperties(propertiesOrder: string[], hidden?: ReadonlySet<string>): string[] | undefined;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The PostgreSQL type → Rebase property type mapping.
3
+ *
4
+ * Split out of `introspect-db-logic` so that the structural analysis can use it
5
+ * without importing the generator, which imports the analysis. Re-exported from
6
+ * `introspect-db-logic` so existing callers keep their import path.
7
+ */
8
+ /**
9
+ * Map a PostgreSQL data type to a Rebase property type.
10
+ */
11
+ export declare function mapPgType(dataType: string): string;
@@ -141,7 +141,7 @@ export declare class FetchService {
141
141
  /**
142
142
  * Fallback path used when db.query is unavailable.
143
143
  *
144
- * The primary path runs the results through `toCmsRow`, which maps
144
+ * The primary path runs the results through `toFlatRow`, which maps
145
145
  * relations from what drizzle already nested — no query per row. This one
146
146
  * has no nesting to read, so it resolves relations itself, in batches.
147
147
  *
@@ -177,6 +177,7 @@ export declare class FetchService {
177
177
  */
178
178
  count<M extends Record<string, unknown>>(collectionPath: string, options?: {
179
179
  filter?: FilterValues<Extract<keyof M, string>>;
180
+ logical?: LogicalCondition;
180
181
  searchString?: string;
181
182
  databaseId?: string;
182
183
  }): Promise<number>;
@@ -198,6 +199,8 @@ export declare class FetchService {
198
199
  */
199
200
  fetchCollectionForRest<M extends Record<string, unknown>>(collectionPath: string, options?: {
200
201
  filter?: FilterValues<Extract<keyof M, string>>;
202
+ /** An `or(...)`/`and(...)` group, applied alongside `filter`. */
203
+ logical?: LogicalCondition;
201
204
  orderBy?: string;
202
205
  order?: "desc" | "asc";
203
206
  limit?: number;
@@ -1,5 +1,5 @@
1
1
  import { DrizzleClient } from "../interfaces";
2
- import { CollectionConfig, FilterValues, ResolvedRelation, ResolvedManyToMany } from "@rebasepro/types";
2
+ import { CollectionConfig, FilterValues, ResolvedRelation, ResolvedManyToMany, ResolvedHasMany, ResolvedHasOne } from "@rebasepro/types";
3
3
  import { type ResolvedVia } from "@rebasepro/types";
4
4
  import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
5
5
  import type { NestedPathHop } from "./nested-path";
@@ -56,6 +56,29 @@ export declare class RelationService {
56
56
  * hand a tenant's rows to its neighbour — say so instead.
57
57
  */
58
58
  private assertSingleKeyAddressable;
59
+ /**
60
+ * What the target's foreign key holds, for each of these parent rows.
61
+ *
62
+ * Ordinarily the parent's id, and then this is free. When the relation
63
+ * declares a `sourceKey` the two are different values, and the mapping
64
+ * between them lives in the source table — so it costs one SELECT, issued
65
+ * once for the whole batch rather than per parent.
66
+ *
67
+ * Both directions come back because both are needed and deriving one from
68
+ * the other by hand is how a batch loader ends up attributing a child to the
69
+ * wrong parent: reads translate id → key to build the WHERE, and then
70
+ * translate key → id to attribute each row that comes back.
71
+ */
72
+ /**
73
+ * The value a related row's foreign key must hold to belong to this parent.
74
+ *
75
+ * `undefined` when the parent's source key is null — which is not an error
76
+ * here, only in the callers that were about to write it. Exposed for
77
+ * {@link PersistService}, which stamps this onto a child created under a
78
+ * nested path and would otherwise write the id and lose the row.
79
+ */
80
+ parentKeyValue(parentCollection: CollectionConfig, relation: ResolvedHasOne | ResolvedHasMany, parentId: string | number, db?: DrizzleClient): Promise<string | number | undefined>;
81
+ private resolveSourceKeys;
59
82
  /**
60
83
  * Fetch rows related to a parent row through a specific relation
61
84
  */
@@ -15,7 +15,7 @@
15
15
  * `types/channel_bus.ts` for the contract such a package implements.
16
16
  */
17
17
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
18
- import { type ChannelBus, type ChannelBusConfig, type ChannelBusSetting } from "@rebasepro/types";
18
+ import { type ChannelBus, type ChannelBusSetting } from "@rebasepro/types";
19
19
  export * from "./ChannelBus";
20
20
  export { PostgresChannelBus, CHANNEL_BUS_NOTIFY_CHANNEL, PG_NOTIFY_MAX_PAYLOAD_BYTES, DEFAULT_BATCH_WINDOW_MS, parseChannelBusFrame, parseChannelBusPayload } from "./PostgresChannelBus";
21
21
  export interface ChannelBusDeps {
@@ -37,12 +37,6 @@ export interface ChannelBusDeps {
37
37
  * mean silently discarding the object the application handed us.
38
38
  */
39
39
  export declare function resolveChannelBusSetting(configured?: ChannelBusSetting): ChannelBusSetting;
40
- /**
41
- * @deprecated Use {@link resolveChannelBusSetting}, which also accepts a
42
- * supplied {@link ChannelBus} instance. Kept as a narrow alias so existing
43
- * config-only callers keep their exact types.
44
- */
45
- export declare function resolveChannelBusConfig(configured?: ChannelBusConfig): ChannelBusConfig;
46
40
  /**
47
41
  * Produce the bus a setting asks for.
48
42
  *
@@ -1,7 +1,7 @@
1
1
  import { PgTable, AnyPgColumn } from "drizzle-orm/pg-core";
2
- import { CollectionConfig } from "@rebasepro/types";
2
+ import { CollectionConfig, ResolvedHasMany, ResolvedHasOne } from "@rebasepro/types";
3
3
  import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
4
- export { buildCompositeId, parseIdValues, COMPOSITE_ID_SEPARATOR } from "@rebasepro/common";
4
+ export { buildCompositeId, parseIdValues, isAddressableId, COMPOSITE_ID_SEPARATOR } from "@rebasepro/common";
5
5
  export type { PrimaryKeyInfo } from "@rebasepro/common";
6
6
  import type { PrimaryKeyInfo } from "@rebasepro/common";
7
7
  /**
@@ -22,6 +22,17 @@ export interface DrizzleColumnMeta {
22
22
  }
23
23
  /** Safely extract Drizzle column metadata from a column object. */
24
24
  export declare function getColumnMeta(col: AnyPgColumn): DrizzleColumnMeta;
25
+ /**
26
+ * Whether an address could name a row in this table, judged by the columns.
27
+ *
28
+ * {@link getPrimaryKeys} lets a config's `isId: "uuid"` win over the schema, so
29
+ * its `isUUID` is a claim rather than a fact — right for deriving addresses,
30
+ * wrong for refusing a query. Here the Drizzle column type decides, because it
31
+ * is what Postgres will enforce: a `uuid` column meets `/c/products/new` with
32
+ * `22P02`, which aborts the surrounding transaction and turns every later
33
+ * statement into an unrelated-looking `25P02`.
34
+ */
35
+ export declare function idCanAddressTable(id: string | number, table: PgTable, idInfoArray: PrimaryKeyInfo[]): boolean;
25
36
  export declare function getCollectionByPath(collectionPath: string, registry: PostgresCollectionRegistry): CollectionConfig;
26
37
  export declare function getTableForCollection(collection: CollectionConfig, registry: PostgresCollectionRegistry): PgTable<any>;
27
38
  /**
@@ -55,6 +66,29 @@ export declare function getPrimaryKeys(collection: CollectionConfig, registry: P
55
66
  * wrong and which collection it is wrong about.
56
67
  */
57
68
  export declare function requirePrimaryKeys(collection: CollectionConfig, registry: PostgresCollectionRegistry): PrimaryKeyInfo[];
69
+ /**
70
+ * The column on the *source* table that a `hasOne`/`hasMany` link points at.
71
+ *
72
+ * `sourceKey` is authored when the two sides join on a natural key — an
73
+ * external identity id, a SKU — and left off when they join on the row id,
74
+ * which is the overwhelming majority. That makes `undefined` the only optional
75
+ * field on a resolved relation, so it gets exactly one reader: this function.
76
+ * Every consumer that needs the column asks here, and none of them re-derives
77
+ * "or else the primary key" for itself. That is the whole point — the fallback
78
+ * chains this codebase removed from relation resolution were dangerous because
79
+ * they were *duplicated* and could disagree, not because they existed.
80
+ */
81
+ export declare function sourceKeyField(relation: ResolvedHasOne | ResolvedHasMany, sourceCollection: CollectionConfig, registry: PostgresCollectionRegistry): string;
82
+ /**
83
+ * Whether this link joins on something other than the source's primary key.
84
+ *
85
+ * Callers that hold a parent *id* — which is most of them, since an id is what
86
+ * a URL carries — must translate it to the source key's value before it can be
87
+ * compared with the target's foreign key. Those that hold the parent *row*, or
88
+ * that build a correlated subquery over the source table, can read the column
89
+ * directly and skip the lookup.
90
+ */
91
+ export declare function joinsOnNaturalKey(relation: ResolvedHasOne | ResolvedHasMany, sourceCollection: CollectionConfig, registry: PostgresCollectionRegistry): boolean;
58
92
  /**
59
93
  * Collections whose key the *browser* cannot resolve, and what it will do
60
94
  * instead.
@@ -1,4 +1,4 @@
1
- import { FilterValues } from "@rebasepro/types";
1
+ import { FilterValues, LogicalCondition } from "@rebasepro/types";
2
2
  import type { VectorSearchParams } from "@rebasepro/types";
3
3
  import { FetchService } from "./FetchService";
4
4
  import { PersistService } from "./PersistService";
@@ -60,6 +60,8 @@ export declare class DataService implements DataRepository {
60
60
  */
61
61
  count<M extends Record<string, unknown>>(collectionPath: string, options?: {
62
62
  filter?: FilterValues<Extract<keyof M, string>>;
63
+ /** An `or(...)`/`and(...)` group, applied alongside `filter`. */
64
+ logical?: LogicalCondition;
63
65
  searchString?: string;
64
66
  databaseId?: string;
65
67
  }): Promise<number>;
@@ -47,7 +47,7 @@ export declare function relationTargetAddress(targetRow: Record<string, unknown>
47
47
  * expects real types. The row's own address is *not* among the columns — it is
48
48
  * derived by the consumer from the collection's primary keys.
49
49
  */
50
- export declare function toCmsRow(row: Record<string, unknown>, collection: CollectionConfig, registry: PostgresCollectionRegistry): Record<string, unknown>;
50
+ export declare function toFlatRow(row: Record<string, unknown>, collection: CollectionConfig, registry: PostgresCollectionRegistry): Record<string, unknown>;
51
51
  /**
52
52
  * The row REST serves: every column under its own name, with the value Postgres
53
53
  * returned, and relations inlined as the target's columns.