@rebasepro/server-postgres 0.17.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -1
- package/README.md +31 -0
- package/dist/backup-argv.d.ts +19 -0
- package/dist/branch-argv.d.ts +28 -0
- package/dist/branch-prune.d.ts +77 -0
- package/dist/cli-collections-path.d.ts +31 -0
- package/dist/cli-errors.d.ts +123 -0
- package/dist/cli-flags.d.ts +101 -0
- package/dist/cli-helpers.d.ts +94 -10
- package/dist/collection-index-C7LopMyX.js +247 -0
- package/dist/collection-index-C7LopMyX.js.map +1 -0
- package/dist/databasePoolManager.d.ts +27 -0
- package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-CagOzEPS.js} +6 -6
- package/dist/{ensure-collection-policies-UCqgv_8c.js.map → ensure-collection-policies-CagOzEPS.js.map} +1 -1
- package/dist/{ensure-collection-tables-DgVixhX3.js → ensure-collection-tables-DbAhrjnG.js} +614 -52
- package/dist/ensure-collection-tables-DbAhrjnG.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.es.js +19307 -3059
- package/dist/index.es.js.map +1 -1
- package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-ClJoTboT.js} +53 -189
- package/dist/rls-enforcement-ClJoTboT.js.map +1 -0
- package/dist/schema/column-type-drift.d.ts +79 -0
- package/dist/schema/doctor.d.ts +42 -0
- package/dist/schema/ensure-collection-tables.d.ts +90 -0
- package/dist/schema/generate-next-step.d.ts +30 -0
- package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
- package/dist/schema/generated-schema-staleness.d.ts +37 -0
- package/dist/schema/introspect-db-queries.d.ts +34 -0
- package/dist/security/rls-enforcement.d.ts +63 -2
- package/dist/services/BranchService.d.ts +86 -1
- package/dist/services/collection-helpers.d.ts +1 -1
- package/dist/services/realtimeService.d.ts +30 -2
- package/dist/services/row-pipeline.d.ts +12 -1
- package/dist/utils/pg-error-utils.d.ts +3 -1
- package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
- package/package.json +32 -27
- package/dist/backup-service-DCk7KhhL.js +0 -9044
- package/dist/backup-service-DCk7KhhL.js.map +0 -1
- package/dist/collection-index-BRUg10H5.js +0 -5690
- package/dist/collection-index-BRUg10H5.js.map +0 -1
- package/dist/connection-GOKU3Hu5.js +0 -277
- package/dist/connection-GOKU3Hu5.js.map +0 -1
- package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
- package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
- package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
- package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
- package/dist/src-DiB5RP2Z.js +0 -541
- package/dist/src-DiB5RP2Z.js.map +0 -1
- package/dist/websocket-BZ4H5wUz.js +0 -8198
- package/dist/websocket-BZ4H5wUz.js.map +0 -1
- package/src/PostgresAdapter.ts +0 -91
- package/src/PostgresBackendDriver.ts +0 -1857
- package/src/PostgresBootstrapper.ts +0 -1157
- package/src/auth/ensure-tables.ts +0 -896
- package/src/auth/schema-version.ts +0 -260
- package/src/auth/services.ts +0 -1610
- package/src/backup/backup-cli.ts +0 -520
- package/src/backup/backup-cron.ts +0 -212
- package/src/backup/backup-logic.ts +0 -93
- package/src/backup/backup-service.ts +0 -485
- package/src/backup/index.ts +0 -12
- package/src/backup/pg-tools.ts +0 -403
- package/src/backup/retention.ts +0 -86
- package/src/cli-errors.ts +0 -265
- package/src/cli-helpers.ts +0 -569
- package/src/cli-output.ts +0 -43
- package/src/cli.ts +0 -1339
- package/src/collections/PostgresCollectionRegistry.ts +0 -108
- package/src/collections/buildRegistry.ts +0 -68
- package/src/collections/validate-relations.ts +0 -402
- package/src/connection.ts +0 -333
- package/src/data-transformer.ts +0 -845
- package/src/databasePoolManager.ts +0 -95
- package/src/history/HistoryService.ts +0 -254
- package/src/history/ensure-history-table.ts +0 -53
- package/src/index.ts +0 -19
- package/src/interfaces.ts +0 -60
- package/src/module-dir.ts +0 -7
- package/src/schema/atlas-argv.ts +0 -94
- package/src/schema/auth-schema.ts +0 -298
- package/src/schema/auth-users-columns.ts +0 -131
- package/src/schema/carved-out-migration.ts +0 -404
- package/src/schema/classify-change.ts +0 -436
- package/src/schema/collection-index.ts +0 -427
- package/src/schema/collections-schema-version.ts +0 -103
- package/src/schema/destructive-sql.ts +0 -94
- package/src/schema/doctor-cli.ts +0 -66
- package/src/schema/doctor-policy-checks.ts +0 -105
- package/src/schema/doctor.ts +0 -837
- package/src/schema/drizzle-ddl.ts +0 -33
- package/src/schema/dynamic-tables.test.ts +0 -302
- package/src/schema/dynamic-tables.ts +0 -293
- package/src/schema/ensure-collection-policies.ts +0 -198
- package/src/schema/ensure-collection-tables.test.ts +0 -518
- package/src/schema/ensure-collection-tables.ts +0 -1267
- package/src/schema/generate-drizzle-schema-logic.ts +0 -957
- package/src/schema/generate-drizzle-schema.ts +0 -143
- package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
- package/src/schema/generate-postgres-ddl.ts +0 -180
- package/src/schema/generate-schema-commit.ts +0 -267
- package/src/schema/generated-schema-staleness.ts +0 -284
- package/src/schema/introspect-db-constraints.ts +0 -385
- package/src/schema/introspect-db-inference.ts +0 -255
- package/src/schema/introspect-db-logic.ts +0 -1387
- package/src/schema/introspect-db-naming.ts +0 -15
- package/src/schema/introspect-db-project.ts +0 -78
- package/src/schema/introspect-db-queries.ts +0 -326
- package/src/schema/introspect-db-structure.ts +0 -670
- package/src/schema/introspect-db-types.ts +0 -56
- package/src/schema/introspect-db.ts +0 -257
- package/src/schema/introspect-runtime.test.ts +0 -260
- package/src/schema/introspect-runtime.ts +0 -327
- package/src/schema/non-sql-collections.test.ts +0 -131
- package/src/schema/rls-bootstrap-sql.ts +0 -288
- package/src/schema/search-column.ts +0 -643
- package/src/schema/test-schema.ts +0 -11
- package/src/schema/vector-index.ts +0 -491
- package/src/security/anonymous-grants.test.ts +0 -73
- package/src/security/policy-drift.test.ts +0 -435
- package/src/security/policy-drift.ts +0 -473
- package/src/security/rls-enforcement.ts +0 -505
- package/src/services/BranchService.ts +0 -326
- package/src/services/FetchService.ts +0 -2115
- package/src/services/PersistService.ts +0 -557
- package/src/services/RelationService.ts +0 -978
- package/src/services/RelationWriteService.ts +0 -653
- package/src/services/cdc/CdcListener.ts +0 -103
- package/src/services/cdc/junction-tables.ts +0 -91
- package/src/services/cdc/trigger-cdc.ts +0 -173
- package/src/services/channel-bus/ChannelBus.ts +0 -44
- package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
- package/src/services/channel-bus/index.ts +0 -114
- package/src/services/channel-history.ts +0 -424
- package/src/services/channel-presence.ts +0 -185
- package/src/services/collection-helpers.ts +0 -420
- package/src/services/dataService.ts +0 -256
- package/src/services/index.ts +0 -14
- package/src/services/junction-writes.ts +0 -295
- package/src/services/nested-path.ts +0 -145
- package/src/services/pg-notify-listener.ts +0 -151
- package/src/services/realtimeService.ts +0 -2545
- package/src/services/row-pipeline.ts +0 -240
- package/src/services/write-denial.ts +0 -55
- package/src/types.ts +0 -4
- package/src/utils/connection-string.ts +0 -58
- package/src/utils/drizzle-conditions.ts +0 -2743
- package/src/utils/pg-array-null-patch.ts +0 -42
- package/src/utils/pg-error-utils.ts +0 -391
- package/src/websocket.ts +0 -855
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
import { PgTable, AnyPgColumn } from "drizzle-orm/pg-core";
|
|
2
|
-
import { getTableColumns } from "drizzle-orm";
|
|
3
|
-
import { CollectionConfig, Property, ResolvedHasMany, ResolvedHasOne } from "@rebasepro/types";
|
|
4
|
-
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
|
|
5
|
-
import { fieldKeyForColumn, getTableName } from "@rebasepro/common";
|
|
6
|
-
import { ApiError, logger } from "@rebasepro/server";
|
|
7
|
-
|
|
8
|
-
// Row identity is derived on both sides of the wire — the driver parses an
|
|
9
|
-
// incoming address into key columns, the admin derives one from a served row —
|
|
10
|
-
// so the implementation lives in `common` and both agree by construction.
|
|
11
|
-
export { buildCompositeId, parseIdValues, isAddressableId, COMPOSITE_ID_SEPARATOR } from "@rebasepro/common";
|
|
12
|
-
export type { PrimaryKeyInfo } from "@rebasepro/common";
|
|
13
|
-
import { buildCompositeId, COMPOSITE_ID_SEPARATOR, getDeclaredPrimaryKeys, isAddressableId } from "@rebasepro/common";
|
|
14
|
-
import type { PrimaryKeyInfo } from "@rebasepro/common";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Shared helper functions for row operations.
|
|
18
|
-
* These are used by FetchService, PersistService, and RelationService.
|
|
19
|
-
*
|
|
20
|
-
* All functions that need collection/table lookups require an explicit
|
|
21
|
-
* `PostgresCollectionRegistry` instance — there is no global singleton.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Interface for Drizzle column metadata introspection.
|
|
26
|
-
* Replaces unsafe `as Record<string, unknown>` double-cast chains.
|
|
27
|
-
*/
|
|
28
|
-
export interface DrizzleColumnMeta {
|
|
29
|
-
columnType?: string;
|
|
30
|
-
dataType?: string;
|
|
31
|
-
primary?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Safely extract Drizzle column metadata from a column object. */
|
|
35
|
-
export function getColumnMeta(col: AnyPgColumn): DrizzleColumnMeta {
|
|
36
|
-
const raw = col as unknown as Record<string | symbol, unknown>;
|
|
37
|
-
return {
|
|
38
|
-
columnType: typeof raw.columnType === "string" ? raw.columnType : undefined,
|
|
39
|
-
dataType: typeof raw.dataType === "string" ? raw.dataType : undefined,
|
|
40
|
-
primary: typeof raw.primary === "boolean" ? raw.primary : undefined
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Whether an address could name a row in this table, judged by the columns.
|
|
46
|
-
*
|
|
47
|
-
* {@link getPrimaryKeys} lets a config's `isId: "uuid"` win over the schema, so
|
|
48
|
-
* its `isUUID` is a claim rather than a fact — right for deriving addresses,
|
|
49
|
-
* wrong for refusing a query. Here the Drizzle column type decides, because it
|
|
50
|
-
* is what Postgres will enforce: a `uuid` column meets `/c/products/new` with
|
|
51
|
-
* `22P02`, which aborts the surrounding transaction and turns every later
|
|
52
|
-
* statement into an unrelated-looking `25P02`.
|
|
53
|
-
*/
|
|
54
|
-
export function idCanAddressTable(
|
|
55
|
-
id: string | number,
|
|
56
|
-
table: PgTable,
|
|
57
|
-
idInfoArray: PrimaryKeyInfo[]
|
|
58
|
-
): boolean {
|
|
59
|
-
const columnBacked = idInfoArray.map(info => {
|
|
60
|
-
const col = table[info.fieldName as keyof typeof table] as AnyPgColumn | undefined;
|
|
61
|
-
const meta = col ? getColumnMeta(col) : undefined;
|
|
62
|
-
// No column to ask (a key the schema does not carry) leaves the id
|
|
63
|
-
// addressable: refusing on a guess would 404 rows that do exist.
|
|
64
|
-
if (!meta?.columnType) return info;
|
|
65
|
-
return { ...info,
|
|
66
|
-
isUUID: meta.columnType === "PgUUID" };
|
|
67
|
-
});
|
|
68
|
-
return isAddressableId(id, columnBacked);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function getCollectionByPath(collectionPath: string, registry: PostgresCollectionRegistry): CollectionConfig {
|
|
72
|
-
const collection = registry.getCollectionByPath(collectionPath);
|
|
73
|
-
if (!collection) {
|
|
74
|
-
const registered = registry.getCollections().map(c => c.slug).join(", ");
|
|
75
|
-
throw new Error(`Collection not found: ${collectionPath}. Registered collections: [${registered}]`);
|
|
76
|
-
}
|
|
77
|
-
return collection;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Reject a write naming something that is not a column of the table.
|
|
82
|
-
*
|
|
83
|
-
* Drizzle builds INSERT from `Object.entries(table[Symbol.Columns])` and UPDATE
|
|
84
|
-
* from `Object.keys(tableColumns)`, so a key the table does not carry is not
|
|
85
|
-
* rejected by anything — it is *left out of the statement*. The insert answers
|
|
86
|
-
* 201 having stored nothing under that name; the update, if the key was the
|
|
87
|
-
* only one, builds `update "posts" set where …` and Postgres raises a syntax
|
|
88
|
-
* error (SQLSTATE 42601), which is neither class 22 nor 23 and so surfaces as a
|
|
89
|
-
* 500 for what is a caller's typo.
|
|
90
|
-
*
|
|
91
|
-
* That makes this the last honest place to check, and the only one every write
|
|
92
|
-
* passes through. `assertKnownWriteFields` in the REST layer checks the same
|
|
93
|
-
* thing against the *config* and is skipped on four paths — `strictWrites:
|
|
94
|
-
* false`, a collection declaring no properties, an auth adapter that owns the
|
|
95
|
-
* body's shape, and a nested route whose target cannot be walked — and it never
|
|
96
|
-
* sees an in-process `rebase.data` write at all.
|
|
97
|
-
*
|
|
98
|
-
* It also gives `strictWrites: false` a truthful implementation. The flag is
|
|
99
|
-
* documented for "a column that really does exist which the config never
|
|
100
|
-
* declared", and skipping the config check alone could not deliver that: the
|
|
101
|
-
* value was dropped a layer later regardless. Skipping the config check and
|
|
102
|
-
* keeping this one does exactly what the flag says — the column must exist,
|
|
103
|
-
* the property need not.
|
|
104
|
-
*/
|
|
105
|
-
export function assertWritableColumns(
|
|
106
|
-
values: Record<string, unknown>,
|
|
107
|
-
table: PgTable,
|
|
108
|
-
collectionPath: string
|
|
109
|
-
): void {
|
|
110
|
-
// Only a real Drizzle table carries a column list. A stand-in that does not
|
|
111
|
-
// (a test double, a registry entry built by hand) has nothing to check
|
|
112
|
-
// against, and inventing an answer from its own keys would reject writes
|
|
113
|
-
// over the shape of the double rather than the shape of the table.
|
|
114
|
-
const columns = getTableColumns(table) as Record<string, unknown> | undefined;
|
|
115
|
-
if (!columns || Object.keys(columns).length === 0) return;
|
|
116
|
-
|
|
117
|
-
const unknown = Object.keys(values).filter(key => !(key in columns));
|
|
118
|
-
if (unknown.length === 0) return;
|
|
119
|
-
|
|
120
|
-
// The offending keys, and deliberately not the list of real ones: this
|
|
121
|
-
// error is reachable on paths where the REST field check was skipped, and
|
|
122
|
-
// an `excludeFromApi` column is documented as never being served to a
|
|
123
|
-
// caller. Naming what was sent is the actionable half anyway.
|
|
124
|
-
throw ApiError.badRequest(
|
|
125
|
-
`'${collectionPath}' has no column${unknown.length > 1 ? "s" : ""} ` +
|
|
126
|
-
`${unknown.map(key => `'${key}'`).join(", ")}, so the value${unknown.length > 1 ? "s" : ""} ` +
|
|
127
|
-
"would have been dropped before the statement was built.",
|
|
128
|
-
"VALIDATION_UNKNOWN_FIELDS"
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* A relation whose names do not resolve against the registered schema.
|
|
134
|
-
*
|
|
135
|
-
* Every one of these used to be a `logger.warn` followed by `continue`, so a
|
|
136
|
-
* save reported success for a relation it had not written and a read answered
|
|
137
|
-
* `[]` for one it could not resolve. `assertRelationsResolve` (validate-relations)
|
|
138
|
-
* fails boot on the same defects, which is where they belong — a server that
|
|
139
|
-
* refuses to start is recoverable in a minute. This is the second line, for the
|
|
140
|
-
* paths that assemble a registry by hand, and it exists so that "cannot resolve"
|
|
141
|
-
* is never again reported as "done".
|
|
142
|
-
*
|
|
143
|
-
* @param label `<collection>.<relation>`
|
|
144
|
-
* @param detail what does not resolve, in terms of the schema
|
|
145
|
-
*/
|
|
146
|
-
export function relationMisconfigured(label: string, detail: string): ApiError {
|
|
147
|
-
return ApiError.internal(
|
|
148
|
-
`Relation '${label}' does not resolve against the registered schema: ${detail}. ` +
|
|
149
|
-
"The operation was refused rather than skipped — silently dropping it would report " +
|
|
150
|
-
"success for a write that never happened, or emptiness for rows that exist. Run " +
|
|
151
|
-
"`rebase schema generate` if the generated schema is older than the database.",
|
|
152
|
-
"RELATION_MISCONFIGURED"
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function getTableForCollection(collection: CollectionConfig, registry: PostgresCollectionRegistry): PgTable<any> {
|
|
157
|
-
const tableName = getTableName(collection);
|
|
158
|
-
const table = registry.getTable(tableName);
|
|
159
|
-
if (!table) {
|
|
160
|
-
throw new Error(`Table not found for collection '${collection.slug}' (table: ${tableName})`);
|
|
161
|
-
}
|
|
162
|
-
return table;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* The key columns a collection's rows are addressed by.
|
|
167
|
-
*
|
|
168
|
-
* Three tiers, in order: properties marked `isId`, the primary keys of the
|
|
169
|
-
* drizzle schema, and finally a column literally named `id`. Only the first is
|
|
170
|
-
* visible to the browser, which is why a key known only to drizzle is reported
|
|
171
|
-
* at boot — see {@link warnOnKeysTheAdminCannotResolve}.
|
|
172
|
-
*
|
|
173
|
-
* Returns `[]` when nothing resolves, rather than throwing. It used to open by
|
|
174
|
-
* resolving the table, which throws when there is none — so the `isId` tier,
|
|
175
|
-
* which needs no table at all, was unreachable for exactly the collections
|
|
176
|
-
* most likely to have no table registered. Every caller that wanted "no keys"
|
|
177
|
-
* to mean "no keys" had to spell that out in a try/catch.
|
|
178
|
-
*
|
|
179
|
-
* Callers that cannot proceed without a key must say so themselves, naming the
|
|
180
|
-
* collection: an empty array here means "this collection has no address", which
|
|
181
|
-
* is a different answer in a notification (broadcast a wildcard) than in a save
|
|
182
|
-
* (fail).
|
|
183
|
-
*/
|
|
184
|
-
export function getPrimaryKeys(collection: CollectionConfig, registry: PostgresCollectionRegistry): PrimaryKeyInfo[] {
|
|
185
|
-
// Explicitly declared `isId` properties win, and need no table.
|
|
186
|
-
if (collection.properties) {
|
|
187
|
-
const idProps = Object.entries(collection.properties)
|
|
188
|
-
.filter(([_, prop]) => "isId" in (prop as object) && Boolean((prop as { isId?: unknown }).isId))
|
|
189
|
-
.map(([key, prop]) => ({
|
|
190
|
-
fieldName: key,
|
|
191
|
-
type: prop.type === "number" ? "number" as const : "string" as const,
|
|
192
|
-
isUUID: (prop as { isId?: unknown }).isId === "uuid"
|
|
193
|
-
}));
|
|
194
|
-
|
|
195
|
-
if (idProps.length > 0) {
|
|
196
|
-
return idProps;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// The remaining tiers read the drizzle schema, so they need the table. A
|
|
201
|
-
// collection without one — another engine's, or simply unregistered — has
|
|
202
|
-
// nothing more to offer.
|
|
203
|
-
const table = registry.getTable(getTableName(collection));
|
|
204
|
-
if (!table) return [];
|
|
205
|
-
|
|
206
|
-
// Otherwise infer from Drizzle schema
|
|
207
|
-
const keys: PrimaryKeyInfo[] = [];
|
|
208
|
-
for (const [key, colRaw] of Object.entries(table)) {
|
|
209
|
-
const col = colRaw as AnyPgColumn;
|
|
210
|
-
if (col && typeof col === "object" && "primary" in col && col.primary) {
|
|
211
|
-
const meta = getColumnMeta(col);
|
|
212
|
-
const type = col.dataType === "number" || meta.columnType === "PgSerial" || meta.columnType === "PgInteger" ? "number" : "string";
|
|
213
|
-
const isUUID = meta.columnType === "PgUUID";
|
|
214
|
-
keys.push({ fieldName: key,
|
|
215
|
-
type,
|
|
216
|
-
isUUID });
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// A collection that declares no primary key gets the implicit `id` column,
|
|
221
|
-
// which is what the schema generators emit for it — so read it back the
|
|
222
|
-
// same way rather than reporting the table as keyless.
|
|
223
|
-
if (keys.length === 0 && "id" in table) {
|
|
224
|
-
const idCol = table["id" as keyof typeof table] as AnyPgColumn;
|
|
225
|
-
const idMeta = getColumnMeta(idCol);
|
|
226
|
-
const type = idCol.dataType === "number" || idMeta.columnType === "PgSerial" || idMeta.columnType === "PgInteger" ? "number" : "string";
|
|
227
|
-
const isUUID = idMeta.columnType === "PgUUID";
|
|
228
|
-
keys.push({ fieldName: "id",
|
|
229
|
-
type,
|
|
230
|
-
isUUID });
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return keys;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* The key columns, for callers that cannot do their job without one.
|
|
238
|
-
*
|
|
239
|
-
* {@link getPrimaryKeys} answers "what keys, if any" and returns `[]` for a
|
|
240
|
-
* collection with no address. Most of this driver, though, is building a WHERE
|
|
241
|
-
* clause and has no meaning without a key — for those, an empty array is not an
|
|
242
|
-
* answer, and indexing `[0]` into it produces `Cannot read properties of
|
|
243
|
-
* undefined` three frames from where the real problem is. This says what is
|
|
244
|
-
* wrong and which collection it is wrong about.
|
|
245
|
-
*/
|
|
246
|
-
export function requirePrimaryKeys(collection: CollectionConfig, registry: PostgresCollectionRegistry): PrimaryKeyInfo[] {
|
|
247
|
-
const keys = getPrimaryKeys(collection, registry);
|
|
248
|
-
if (keys.length === 0) {
|
|
249
|
-
throw new Error(
|
|
250
|
-
`Collection '${collection.slug}' has no primary key, so its rows cannot be addressed. ` +
|
|
251
|
-
`Mark the key property with \`isId\` in its config, or register a table whose schema declares one.`
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
return keys;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* The column on the *source* table that a `hasOne`/`hasMany` link points at.
|
|
259
|
-
*
|
|
260
|
-
* `sourceKey` is authored when the two sides join on a natural key — an
|
|
261
|
-
* external identity id, a SKU — and left off when they join on the row id,
|
|
262
|
-
* which is the overwhelming majority. That makes `undefined` the only optional
|
|
263
|
-
* field on a resolved relation, so it gets exactly one reader: this function.
|
|
264
|
-
* Every consumer that needs the column asks here, and none of them re-derives
|
|
265
|
-
* "or else the primary key" for itself. That is the whole point — the fallback
|
|
266
|
-
* chains this codebase removed from relation resolution were dangerous because
|
|
267
|
-
* they were *duplicated* and could disagree, not because they existed.
|
|
268
|
-
*/
|
|
269
|
-
export function sourceKeyField(
|
|
270
|
-
relation: ResolvedHasOne | ResolvedHasMany,
|
|
271
|
-
sourceCollection: CollectionConfig,
|
|
272
|
-
registry: PostgresCollectionRegistry
|
|
273
|
-
): string {
|
|
274
|
-
// A **field**, as the name says: callers index the Drizzle table and the
|
|
275
|
-
// rows it returns with what comes back from here, and both are keyed by the
|
|
276
|
-
// wire name. `sourceKey` is authored in *column* terms, like every other
|
|
277
|
-
// link on a relation, so it is translated here rather than at each of the
|
|
278
|
-
// call sites — where it used to be `tenant_id` looked up on a table whose
|
|
279
|
-
// key is `tenantId`, finding nothing.
|
|
280
|
-
if (relation.sourceKey) return fieldKeyForColumn(sourceCollection, relation.sourceKey);
|
|
281
|
-
return requirePrimaryKeys(sourceCollection, registry)[0].fieldName;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Whether this link joins on something other than the source's primary key.
|
|
286
|
-
*
|
|
287
|
-
* Callers that hold a parent *id* — which is most of them, since an id is what
|
|
288
|
-
* a URL carries — must translate it to the source key's value before it can be
|
|
289
|
-
* compared with the target's foreign key. Those that hold the parent *row*, or
|
|
290
|
-
* that build a correlated subquery over the source table, can read the column
|
|
291
|
-
* directly and skip the lookup.
|
|
292
|
-
*/
|
|
293
|
-
export function joinsOnNaturalKey(
|
|
294
|
-
relation: ResolvedHasOne | ResolvedHasMany,
|
|
295
|
-
sourceCollection: CollectionConfig,
|
|
296
|
-
registry: PostgresCollectionRegistry
|
|
297
|
-
): boolean {
|
|
298
|
-
if (!relation.sourceKey) return false;
|
|
299
|
-
// Compared as fields, because the right-hand side is one.
|
|
300
|
-
return fieldKeyForColumn(sourceCollection, relation.sourceKey)
|
|
301
|
-
!== requirePrimaryKeys(sourceCollection, registry)[0].fieldName;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Collections whose key the *browser* cannot resolve, and what it will do
|
|
306
|
-
* instead.
|
|
307
|
-
*
|
|
308
|
-
* The two sides resolve keys from different evidence. This driver reads, in
|
|
309
|
-
* order: properties marked `isId`, the primary keys of the Drizzle schema, then
|
|
310
|
-
* a column literally named `id`. The admin shares the `CollectionConfig` — it
|
|
311
|
-
* compiles the same collection files into its bundle — but never the Drizzle
|
|
312
|
-
* schema, so the middle tier is invisible to it.
|
|
313
|
-
*
|
|
314
|
-
* Nothing can normalize this at runtime: the server does not serve the admin
|
|
315
|
-
* its collections, so a key resolved here cannot be handed over there. The
|
|
316
|
-
* config files are the only thing both sides read, so the fix is an edit to
|
|
317
|
-
* them, and the most this can do is say exactly which edit.
|
|
318
|
-
*
|
|
319
|
-
* Two shapes, and the second is the dangerous one:
|
|
320
|
-
*
|
|
321
|
-
* - No `isId`, no `id` property → the admin resolves no address, warns in the
|
|
322
|
-
* console, and rows cannot be opened or linked.
|
|
323
|
-
* - No `isId`, but an `id` property that is *not* the key → the admin addresses
|
|
324
|
-
* rows by `id` while this driver reads the address as the real key. Nothing
|
|
325
|
-
* errors: the addresses look right and route wrong.
|
|
326
|
-
*/
|
|
327
|
-
export function findUnresolvableKeyCollections(
|
|
328
|
-
collections: CollectionConfig[],
|
|
329
|
-
registry: PostgresCollectionRegistry
|
|
330
|
-
): { collection: CollectionConfig; keys: PrimaryKeyInfo[]; shadowedByIdProperty: boolean }[] {
|
|
331
|
-
const findings: { collection: CollectionConfig; keys: PrimaryKeyInfo[]; shadowedByIdProperty: boolean }[] = [];
|
|
332
|
-
|
|
333
|
-
for (const collection of collections) {
|
|
334
|
-
// Declared `isId` is the tier both sides share: if it is there, they agree.
|
|
335
|
-
if (getDeclaredPrimaryKeys(collection).length > 0) continue;
|
|
336
|
-
|
|
337
|
-
// No registered table resolves to no keys, and a collection this cannot
|
|
338
|
-
// resolve a key for is one it has nothing to say about.
|
|
339
|
-
const keys = getPrimaryKeys(collection, registry);
|
|
340
|
-
if (keys.length === 0) continue;
|
|
341
|
-
|
|
342
|
-
// A single key named `id` is the last tier on both sides: they agree
|
|
343
|
-
// without anything being declared.
|
|
344
|
-
if (keys.length === 1 && keys[0].fieldName === "id") continue;
|
|
345
|
-
|
|
346
|
-
findings.push({
|
|
347
|
-
collection,
|
|
348
|
-
keys,
|
|
349
|
-
shadowedByIdProperty: Boolean(collection.properties?.id)
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return findings;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Report the collections from {@link findUnresolvableKeyCollections} at boot,
|
|
358
|
-
* with the edit that fixes each one.
|
|
359
|
-
*
|
|
360
|
-
* Grouped by failure, not by collection: the shadowed case is a routing bug and
|
|
361
|
-
* the silent case is a missing feature, and they deserve different urgency.
|
|
362
|
-
*/
|
|
363
|
-
export function warnOnKeysTheAdminCannotResolve(
|
|
364
|
-
collections: CollectionConfig[],
|
|
365
|
-
registry: PostgresCollectionRegistry
|
|
366
|
-
): void {
|
|
367
|
-
const findings = findUnresolvableKeyCollections(collections, registry);
|
|
368
|
-
if (findings.length === 0) return;
|
|
369
|
-
|
|
370
|
-
const edit = (f: { collection: CollectionConfig; keys: PrimaryKeyInfo[] }) =>
|
|
371
|
-
`${f.collection.slug}: mark ${f.keys.map(k => `\`${k.fieldName}\``).join(" and ")} with ` +
|
|
372
|
-
`\`isId: ${f.keys[0].isUUID ? "\"uuid\"" : f.keys[0].type === "number" ? "\"increment\"" : "true"}\``;
|
|
373
|
-
|
|
374
|
-
const shadowed = findings.filter(f => f.shadowedByIdProperty);
|
|
375
|
-
const silent = findings.filter(f => !f.shadowedByIdProperty);
|
|
376
|
-
|
|
377
|
-
if (shadowed.length > 0) {
|
|
378
|
-
logger.warn(
|
|
379
|
-
`⚠️ These collections declare no \`isId\`, and their key is only in the drizzle schema — but they ` +
|
|
380
|
-
`do have a property called \`id\`. The admin has no way to know \`id\` is not the key, so it will ` +
|
|
381
|
-
`address rows by it while this server reads the address as the real key: the links look right and ` +
|
|
382
|
-
`route wrong. Nothing will error.\n\n` +
|
|
383
|
-
shadowed.map(f => ` • ${edit(f)}`).join("\n") + "\n"
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (silent.length > 0) {
|
|
388
|
-
logger.warn(
|
|
389
|
-
`⚠️ These collections declare no \`isId\`, and their key is only in the drizzle schema, which the ` +
|
|
390
|
-
`admin never sees. It will resolve no address for their rows, so detail links, caching and ` +
|
|
391
|
-
`relations will not work for them:\n\n` +
|
|
392
|
-
silent.map(f => ` • ${edit(f)}`).join("\n") + "\n"
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* The address of a row: derived from the collection's primary keys, because a
|
|
399
|
-
* row does not carry one — it is exactly its columns.
|
|
400
|
-
*
|
|
401
|
-
* Falls back to a literal `id` column, for a row that reached us from somewhere
|
|
402
|
-
* other than this driver. Returns `""` when there is no key and no `id` —
|
|
403
|
-
* callers decide what that means, since "unaddressable" is a different answer
|
|
404
|
-
* in a notification (broadcast a wildcard) than in a save (fail).
|
|
405
|
-
*/
|
|
406
|
-
export function deriveRowAddress(
|
|
407
|
-
row: Record<string, unknown>,
|
|
408
|
-
collection: CollectionConfig,
|
|
409
|
-
registry: PostgresCollectionRegistry
|
|
410
|
-
): string {
|
|
411
|
-
const composite = buildCompositeId(row, getPrimaryKeys(collection, registry));
|
|
412
|
-
// An all-empty composite is indistinguishable from a missing key, and
|
|
413
|
-
// `buildCompositeId` returns "" for no keys at all.
|
|
414
|
-
if (composite && composite.split(COMPOSITE_ID_SEPARATOR).some(part => part !== "")) {
|
|
415
|
-
return composite;
|
|
416
|
-
}
|
|
417
|
-
if (row.id !== undefined && row.id !== null) return String(row.id);
|
|
418
|
-
return "";
|
|
419
|
-
}
|
|
420
|
-
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
// import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
2
|
-
import { FilterValues, LogicalCondition, OrderByTuple } from "@rebasepro/types";
|
|
3
|
-
import type { VectorSearchParams } from "@rebasepro/types";
|
|
4
|
-
import { FetchService } from "./FetchService";
|
|
5
|
-
import { PersistService } from "./PersistService";
|
|
6
|
-
import { RelationService } from "./RelationService";
|
|
7
|
-
import { DataRepository, FetchCollectionOptions, SearchOptions, CountOptions, DrizzleClient } from "../interfaces";
|
|
8
|
-
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
|
|
9
|
-
|
|
10
|
-
// Re-export data transformer functions for external use
|
|
11
|
-
export { sanitizeAndConvertDates, serializeDataToServer, parseDataFromServer } from "../data-transformer";
|
|
12
|
-
|
|
13
|
-
// Re-export service classes for direct use
|
|
14
|
-
export { FetchService } from "./FetchService";
|
|
15
|
-
export { PersistService } from "./PersistService";
|
|
16
|
-
export { RelationService } from "./RelationService";
|
|
17
|
-
|
|
18
|
-
// Re-export interfaces
|
|
19
|
-
export * from "../interfaces";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* DataService - Facade for row operations.
|
|
23
|
-
*
|
|
24
|
-
* This class provides a unified API for row CRUD operations by delegating
|
|
25
|
-
* to specialized services:
|
|
26
|
-
* - FetchService: Read operations (fetch, search, count)
|
|
27
|
-
* - PersistService: Write operations (save, delete)
|
|
28
|
-
* - RelationService: Relation operations (fetch related, update relations)
|
|
29
|
-
*
|
|
30
|
-
* Implements the DataRepository interface for database abstraction.
|
|
31
|
-
*/
|
|
32
|
-
export class DataService implements DataRepository {
|
|
33
|
-
private fetchService: FetchService;
|
|
34
|
-
private persistService: PersistService;
|
|
35
|
-
|
|
36
|
-
constructor(private db: DrizzleClient, private registry: PostgresCollectionRegistry) {
|
|
37
|
-
this.fetchService = new FetchService(db, registry);
|
|
38
|
-
this.persistService = new PersistService(db, registry);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// =============================================================
|
|
42
|
-
// READ OPERATIONS - Delegated to FetchService
|
|
43
|
-
// =============================================================
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Fetch a single row by ID
|
|
47
|
-
*/
|
|
48
|
-
async fetchOne<M extends Record<string, unknown>>(
|
|
49
|
-
collectionPath: string,
|
|
50
|
-
id: string | number,
|
|
51
|
-
databaseId?: string
|
|
52
|
-
): Promise<Record<string, unknown> | undefined> {
|
|
53
|
-
return this.fetchService.fetchOne<M>(collectionPath, id, databaseId);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Fetch a collection of rows with optional filtering, ordering, and pagination
|
|
58
|
-
*/
|
|
59
|
-
async fetchCollection<M extends Record<string, unknown>>(
|
|
60
|
-
collectionPath: string,
|
|
61
|
-
options: {
|
|
62
|
-
filter?: FilterValues<Extract<keyof M, string>>;
|
|
63
|
-
/** An `or(...)`/`and(...)` group, applied alongside `filter`. */
|
|
64
|
-
logical?: LogicalCondition;
|
|
65
|
-
orderBy?: string | OrderByTuple[];
|
|
66
|
-
order?: "desc" | "asc";
|
|
67
|
-
limit?: number;
|
|
68
|
-
offset?: number;
|
|
69
|
-
startAfter?: Record<string, unknown>;
|
|
70
|
-
searchString?: string;
|
|
71
|
-
databaseId?: string;
|
|
72
|
-
vectorSearch?: VectorSearchParams;
|
|
73
|
-
} = {}
|
|
74
|
-
): Promise<Record<string, unknown>[]> {
|
|
75
|
-
return this.fetchService.fetchCollection<M>(collectionPath, options);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Search rows by text
|
|
80
|
-
*/
|
|
81
|
-
async searchRows<M extends Record<string, unknown>>(
|
|
82
|
-
collectionPath: string,
|
|
83
|
-
searchString: string,
|
|
84
|
-
options: {
|
|
85
|
-
filter?: FilterValues<Extract<keyof M, string>>;
|
|
86
|
-
/** An `or(...)`/`and(...)` group, applied alongside `filter`. */
|
|
87
|
-
logical?: LogicalCondition;
|
|
88
|
-
orderBy?: string | OrderByTuple[];
|
|
89
|
-
order?: "desc" | "asc";
|
|
90
|
-
limit?: number;
|
|
91
|
-
databaseId?: string;
|
|
92
|
-
searchExplain?: boolean;
|
|
93
|
-
} = {}
|
|
94
|
-
): Promise<Record<string, unknown>[]> {
|
|
95
|
-
return this.fetchService.searchRows<M>(collectionPath, searchString, options);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Count rows in a collection
|
|
100
|
-
*/
|
|
101
|
-
async count<M extends Record<string, unknown>>(
|
|
102
|
-
collectionPath: string,
|
|
103
|
-
options: {
|
|
104
|
-
filter?: FilterValues<Extract<keyof M, string>>;
|
|
105
|
-
/** An `or(...)`/`and(...)` group, applied alongside `filter`. */
|
|
106
|
-
logical?: LogicalCondition;
|
|
107
|
-
searchString?: string;
|
|
108
|
-
databaseId?: string;
|
|
109
|
-
/** Only the `threshold` narrows the count — see `FetchService.count`. */
|
|
110
|
-
vectorSearch?: VectorSearchParams;
|
|
111
|
-
} = {}
|
|
112
|
-
): Promise<number> {
|
|
113
|
-
return this.fetchService.count<M>(collectionPath, options);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Check if a field value is unique in a collection
|
|
118
|
-
*/
|
|
119
|
-
async checkUniqueField(
|
|
120
|
-
collectionPath: string,
|
|
121
|
-
fieldName: string,
|
|
122
|
-
value: unknown,
|
|
123
|
-
excludeEntityId?: string,
|
|
124
|
-
databaseId?: string
|
|
125
|
-
): Promise<boolean> {
|
|
126
|
-
return this.fetchService.checkUniqueField(collectionPath, fieldName, value, excludeEntityId, databaseId);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Fetch rows related to a parent row
|
|
131
|
-
*/
|
|
132
|
-
async fetchRelatedEntities<M extends Record<string, unknown>>(
|
|
133
|
-
parentCollectionPath: string,
|
|
134
|
-
parentId: string | number,
|
|
135
|
-
relationKey: string,
|
|
136
|
-
options: {
|
|
137
|
-
filter?: FilterValues<Extract<keyof M, string>>;
|
|
138
|
-
orderBy?: string | OrderByTuple[];
|
|
139
|
-
order?: "desc" | "asc";
|
|
140
|
-
limit?: number;
|
|
141
|
-
startAfter?: Record<string, unknown>;
|
|
142
|
-
searchString?: string;
|
|
143
|
-
databaseId?: string;
|
|
144
|
-
} = {}
|
|
145
|
-
): Promise<Record<string, unknown>[]> {
|
|
146
|
-
const rows = await this.fetchService.getRelationService().fetchRelatedEntities<M>(
|
|
147
|
-
parentCollectionPath,
|
|
148
|
-
parentId,
|
|
149
|
-
relationKey,
|
|
150
|
-
options
|
|
151
|
-
);
|
|
152
|
-
return rows.map(e => ({ ...e.values, id: e.id }));
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// =============================================================
|
|
156
|
-
// WRITE OPERATIONS - Delegated to PersistService
|
|
157
|
-
// =============================================================
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Save an row (create or update)
|
|
161
|
-
*/
|
|
162
|
-
async save<M extends Record<string, unknown>>(
|
|
163
|
-
collectionPath: string,
|
|
164
|
-
values: Partial<M>,
|
|
165
|
-
id?: string | number,
|
|
166
|
-
databaseId?: string,
|
|
167
|
-
options?: { upsert?: boolean }
|
|
168
|
-
): Promise<Record<string, unknown>> {
|
|
169
|
-
return this.persistService.save<M>(collectionPath, values, id, databaseId, options);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Delete an row by ID
|
|
174
|
-
*/
|
|
175
|
-
async delete(
|
|
176
|
-
collectionPath: string,
|
|
177
|
-
id: string | number,
|
|
178
|
-
databaseId?: string
|
|
179
|
-
): Promise<void> {
|
|
180
|
-
return this.persistService.delete(collectionPath, id, databaseId);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Delete all rows from a collection
|
|
185
|
-
*/
|
|
186
|
-
async deleteAll(collectionPath: string, databaseId?: string): Promise<void> {
|
|
187
|
-
return this.persistService.deleteAll(collectionPath, databaseId);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Execute raw SQL
|
|
193
|
-
*/
|
|
194
|
-
async executeSql(sqlText: string, params?: unknown[]): Promise<Record<string, unknown>[]> {
|
|
195
|
-
if (process.env.NODE_ENV !== "production") {
|
|
196
|
-
console.debug("Executing raw SQL:", sqlText, params?.length ? `with ${params.length} params` : "");
|
|
197
|
-
}
|
|
198
|
-
const { sql } = await import("drizzle-orm");
|
|
199
|
-
|
|
200
|
-
let result;
|
|
201
|
-
if (params && params.length > 0) {
|
|
202
|
-
// Build a parameterized query using Drizzle's sql tagged template.
|
|
203
|
-
// Split the SQL text on $1, $2, … placeholders and interleave
|
|
204
|
-
// with sql.param() calls so the underlying pg driver binds them safely.
|
|
205
|
-
const parts = sqlText.split(/\$(\d+)/);
|
|
206
|
-
const chunks: ReturnType<typeof sql.raw | typeof sql.param>[] = [];
|
|
207
|
-
for (let i = 0; i < parts.length; i++) {
|
|
208
|
-
if (i % 2 === 0) {
|
|
209
|
-
// Literal SQL text fragment
|
|
210
|
-
if (parts[i].length > 0) {
|
|
211
|
-
chunks.push(sql.raw(parts[i]));
|
|
212
|
-
}
|
|
213
|
-
} else {
|
|
214
|
-
// Parameter reference — $N (1-indexed)
|
|
215
|
-
const paramIndex = Number(parts[i]) - 1;
|
|
216
|
-
chunks.push(sql.param(params[paramIndex]));
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
const query = sql.join(chunks, sql.raw(""));
|
|
220
|
-
result = await this.db.execute(query);
|
|
221
|
-
} else {
|
|
222
|
-
result = await this.db.execute(sql.raw(sqlText));
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const rows = result.rows;
|
|
226
|
-
if (process.env.NODE_ENV !== "production") {
|
|
227
|
-
console.debug(`SQL executed successfully. Returned ${Array.isArray(rows) ? rows.length : "non-array"} rows.`);
|
|
228
|
-
}
|
|
229
|
-
return rows as Record<string, unknown>[];
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// =============================================================
|
|
233
|
-
// SERVICE ACCESSORS
|
|
234
|
-
// =============================================================
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Get the underlying FetchService for advanced use
|
|
238
|
-
*/
|
|
239
|
-
getFetchService(): FetchService {
|
|
240
|
-
return this.fetchService;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Get the underlying PersistService for advanced use
|
|
245
|
-
*/
|
|
246
|
-
getPersistService(): PersistService {
|
|
247
|
-
return this.persistService;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Get the underlying RelationService for advanced use
|
|
252
|
-
*/
|
|
253
|
-
getRelationService(): RelationService {
|
|
254
|
-
return this.fetchService.getRelationService();
|
|
255
|
-
}
|
|
256
|
-
}
|
package/src/services/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// Re-export all service classes
|
|
2
|
-
export { FetchService } from "./FetchService";
|
|
3
|
-
export { PersistService } from "./PersistService";
|
|
4
|
-
export { RelationService } from "./RelationService";
|
|
5
|
-
|
|
6
|
-
// Re-export helper functions
|
|
7
|
-
export {
|
|
8
|
-
getCollectionByPath,
|
|
9
|
-
getTableForCollection,
|
|
10
|
-
getPrimaryKeys,
|
|
11
|
-
deriveRowAddress,
|
|
12
|
-
parseIdValues,
|
|
13
|
-
buildCompositeId
|
|
14
|
-
} from "./collection-helpers";
|