@rebasepro/server-postgres 0.10.1-canary.d8d45b2 → 0.10.1-canary.ed8caed

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.
@@ -5,6 +5,7 @@ import type { VectorSearchParams } from "@rebasepro/types";
5
5
  import { RelationService } from "./RelationService";
6
6
  import { DrizzleClient } from "../interfaces";
7
7
  import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
8
+ import { type NestedPathHop } from "./nested-path";
8
9
  /**
9
10
  * Service for handling all row read operations.
10
11
  * Handles fetching, searching, counting, and filtering rows.
@@ -65,6 +66,22 @@ export declare class FetchService {
65
66
  * Extract cursor pagination conditions from startAfter options.
66
67
  */
67
68
  private buildCursorConditions;
69
+ /**
70
+ * Compile "rows reachable from this parent" into a `WHERE` condition on the
71
+ * target table, so a nested listing can run as an ordinary collection query.
72
+ */
73
+ private buildRelationScope;
74
+ /**
75
+ * Whether `id` is actually reachable at `collectionPath`.
76
+ *
77
+ * Trivially true for a root path. For a nested one it is a real question:
78
+ * the path resolves to the target collection, and matching on the primary
79
+ * key alone made the parent segment decorative — `authors/1/posts/43`
80
+ * returned post 43 whoever wrote it, and the REST layer's delete then
81
+ * deleted it. A row that is not under this parent is reported as absent,
82
+ * which is what a caller addressing it through the parent should see.
83
+ */
84
+ private isAddressableUnder;
68
85
  /**
69
86
  * Fetch a single row by ID
70
87
  */
@@ -83,6 +100,8 @@ export declare class FetchService {
83
100
  databaseId?: string;
84
101
  vectorSearch?: VectorSearchParams;
85
102
  logical?: LogicalCondition;
103
+ /** Narrow to the rows reachable from a parent through a relation. */
104
+ relatedTo?: NestedPathHop;
86
105
  }): Promise<Record<string, unknown>[]>;
87
106
  /**
88
107
  * Fallback path used when db.query is unavailable.
@@ -118,10 +137,6 @@ export declare class FetchService {
118
137
  limit?: number;
119
138
  databaseId?: string;
120
139
  }): Promise<Record<string, unknown>[]>;
121
- /**
122
- * Fetch collection from multi-segment path
123
- */
124
- private fetchCollectionFromPath;
125
140
  /**
126
141
  * Count rows in a collection
127
142
  */
@@ -130,10 +145,6 @@ export declare class FetchService {
130
145
  searchString?: string;
131
146
  databaseId?: string;
132
147
  }): Promise<number>;
133
- /**
134
- * Count rows from multi-segment path
135
- */
136
- private countEntitiesFromPath;
137
148
  /**
138
149
  * Check if a field value is unique
139
150
  */
@@ -160,6 +171,8 @@ export declare class FetchService {
160
171
  searchString?: string;
161
172
  databaseId?: string;
162
173
  vectorSearch?: VectorSearchParams;
174
+ /** Narrow to the rows reachable from a parent through a relation. */
175
+ relatedTo?: NestedPathHop;
163
176
  }, include?: string[]): Promise<Record<string, unknown>[]>;
164
177
  /**
165
178
  * Fetch a single row with optional relation includes for REST API.
@@ -38,6 +38,18 @@ export declare class PersistService {
38
38
  * Delete all rows from a collection
39
39
  */
40
40
  deleteAll(collectionPath: string, _databaseId?: string): Promise<void>;
41
+ /**
42
+ * The column on the *target* table that records the parent, for a create
43
+ * under a nested one-to-many path.
44
+ *
45
+ * Returns `undefined` when the link is not a column at all (a multi-hop
46
+ * `joinPath`), so the caller writes the row without stamping anything.
47
+ *
48
+ * `relation.localKey` is deliberately not consulted: it names a column on
49
+ * the *source* table. Falling back to it here — which is what this used to
50
+ * do, and first — stamped the parent's own foreign key onto the child row.
51
+ */
52
+ private resolveParentForeignKeyColumn;
41
53
  /**
42
54
  * Save an row (create or update)
43
55
  *
@@ -1,6 +1,7 @@
1
1
  import { DrizzleClient } from "../interfaces";
2
2
  import { CollectionConfig, FilterValues, Relation } from "@rebasepro/types";
3
3
  import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
4
+ import type { NestedPathHop } from "./nested-path";
4
5
  /**
5
6
  * Service for handling all relation-related operations.
6
7
  * Handles fetching, updating, and managing row relations.
@@ -85,6 +86,35 @@ export declare class RelationService {
85
86
  filter?: FilterValues<Extract<keyof M, string>>;
86
87
  databaseId?: string;
87
88
  }): Promise<number>;
89
+ /**
90
+ * Count the target rows a parent reaches through `relation`, narrowed by
91
+ * `additionalFilters` (conditions on the target table).
92
+ *
93
+ * Shared by the public count and by {@link isRelated}, so "how many children
94
+ * does this parent have" and "is this row one of them" are answered by the
95
+ * same join — a membership test that reconstructed the join separately would
96
+ * be free to disagree with the listing it is supposed to gate.
97
+ */
98
+ private countRelatedRows;
99
+ /**
100
+ * Whether `targetId` is actually reachable from the parent named in `hop`.
101
+ *
102
+ * A nested address like `authors/1/posts/43` used to resolve to the target
103
+ * collection and then match on the primary key alone, so the parent segment
104
+ * decided nothing: the row came back, and was updated or deleted, whoever it
105
+ * belonged to. Reads, updates and deletes now all gate on this.
106
+ */
107
+ isRelated(hop: NestedPathHop, targetId: string | number): Promise<boolean>;
108
+ /**
109
+ * Remove the junction row linking a parent to `targetId`, leaving the target
110
+ * row itself alone.
111
+ *
112
+ * This is what `DELETE authors/1/tags/5` has to mean for a many-to-many: the
113
+ * target is shared, so deleting the row would remove the tag from every other
114
+ * post that uses it. It used to do exactly that — resolve the path to the
115
+ * `tags` table and delete by primary key.
116
+ */
117
+ unlinkRelatedEntity(tx: DrizzleClient, hop: NestedPathHop, targetId: string | number): Promise<void>;
88
118
  /**
89
119
  * Batch fetch related rows for multiple parent rows to avoid N+1 queries
90
120
  */
@@ -0,0 +1,59 @@
1
+ import { CollectionConfig, Relation } from "@rebasepro/types";
2
+ import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
3
+ /**
4
+ * The last hop of a nested collection path, e.g. `authors/1/posts`.
5
+ *
6
+ * The walk that produces this was written out four separate times — in
7
+ * `FetchService.fetchCollectionFromPath`, `FetchService.countEntitiesFromPath`,
8
+ * `PersistService.save` and `CollectionRegistry.getCollectionByPath` — and had
9
+ * drifted, so the read path and the write path did not agree on which relation
10
+ * a path named. It lives here once now.
11
+ */
12
+ export interface NestedPathHop {
13
+ /** The collection the final relation is declared on (e.g. `authors`). */
14
+ parentCollection: CollectionConfig;
15
+ /** The parent's id as it appeared in the path, unparsed. */
16
+ parentId: string;
17
+ /** The path segment that named the relation (e.g. `posts`). */
18
+ relationKey: string;
19
+ relation: Relation;
20
+ /** `relation.target()`, resolved once. */
21
+ targetCollection: CollectionConfig;
22
+ }
23
+ /**
24
+ * True when `path` addresses rows through a relation rather than a root
25
+ * collection.
26
+ *
27
+ * Any separator at all counts — a root collection slug never contains one — so
28
+ * a malformed path like `collection/id` is a *broken* nested path and gets
29
+ * reported as one by {@link resolveNestedPath}, rather than being looked up as
30
+ * a root collection whose slug happens to contain a slash.
31
+ */
32
+ export declare function isNestedPath(path: string): boolean;
33
+ export declare function splitPathSegments(path: string): string[];
34
+ /**
35
+ * Walk a nested collection path down to the relation it ends in.
36
+ *
37
+ * Returns `undefined` for a plain root-collection path so callers can keep the
38
+ * root case on its existing code path. Throws when the path is malformed, or
39
+ * when a segment names a relation that does not exist — the same errors the
40
+ * individual walks used to raise, with the available names attached.
41
+ */
42
+ export declare function resolveNestedPath(path: string, registry: PostgresCollectionRegistry): NestedPathHop | undefined;
43
+ /**
44
+ * A relation reached through a junction table — many-to-many, or a multi-hop
45
+ * `joinPath`. The target row is shared with other parents, so writing "through"
46
+ * such a path addresses the *link*, not the row.
47
+ */
48
+ export declare function isJunctionBackedRelation(relation: Relation): boolean;
49
+ /**
50
+ * Reject a nested write whose final segment is a to-one relation.
51
+ *
52
+ * There is no column on the target row that records a to-one parent — the
53
+ * foreign key lives on the *parent* table. The write path used to fall through
54
+ * to `relation.localKey` here and stamp the parent's own FK column onto the
55
+ * target row, which either raised an opaque "column does not exist" or, when a
56
+ * column of that name happened to exist on the target, silently wrote the wrong
57
+ * one.
58
+ */
59
+ export declare function assertWritableThrough(hop: NestedPathHop, path: string): void;
@@ -113,6 +113,13 @@ function toCanonicalOp(op) {
113
113
  /**
114
114
  * Type guard for PostgreSQL collections.
115
115
  * Returns true if the collection uses the Postgres engine (or the default engine).
116
+ *
117
+ * Generic over the *input* type, and narrows by intersection rather than
118
+ * replacement. Narrowing to a bare `PostgresCollectionConfig` discarded whatever
119
+ * the caller actually had — most visibly the admin panel's view model, whose
120
+ * flattened presentation fields vanished the moment a collection passed through
121
+ * one of these guards.
122
+ *
116
123
  * @group Models
117
124
  */
118
125
  function isPostgresCollectionConfig(collection) {
@@ -326,4 +333,4 @@ function getDataSourceCapabilities(engine) {
326
333
  //#endregion
327
334
  export { isSchemaAdmin as a, getDeclaredSubcollections as c, REST_TO_CANONICAL as d, toCanonicalOp as f, isSQLAdmin as i, isPostgresCollectionConfig as l, Vector as m, getDataSourceCapabilities as n, ANONYMOUS_USER_ID as o, EntityRelation as p, isChannelBusInstance as r, policy as s, DEFAULT_DATA_SOURCE_KEY as t, NULL_OPS as u };
328
335
 
329
- //# sourceMappingURL=src-B0v4IKaI.js.map
336
+ //# sourceMappingURL=src-CBgtrPhJ.js.map