@byline/db-mysql 4.8.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 +373 -0
- package/README.md +283 -0
- package/dist/database/schema/auth.d.ts +951 -0
- package/dist/database/schema/auth.js +226 -0
- package/dist/database/schema/common.d.ts +168 -0
- package/dist/database/schema/common.js +130 -0
- package/dist/database/schema/index.d.ts +3419 -0
- package/dist/database/schema/index.js +895 -0
- package/dist/database/schema/schema-pins.test.node.d.ts +52 -0
- package/dist/database/schema/schema-pins.test.node.js +398 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +151 -0
- package/dist/lib/boot-check.d.ts +22 -0
- package/dist/lib/boot-check.js +42 -0
- package/dist/lib/boot-check.test.node.d.ts +8 -0
- package/dist/lib/boot-check.test.node.js +46 -0
- package/dist/lib/db-manager.d.ts +54 -0
- package/dist/lib/db-manager.js +49 -0
- package/dist/lib/test-db.d.ts +37 -0
- package/dist/lib/test-db.js +111 -0
- package/dist/lib/test-helper.d.ts +33 -0
- package/dist/lib/test-helper.js +68 -0
- package/dist/modules/admin/admin-permissions-repository.d.ts +35 -0
- package/dist/modules/admin/admin-permissions-repository.js +101 -0
- package/dist/modules/admin/admin-preferences-repository.d.ts +62 -0
- package/dist/modules/admin/admin-preferences-repository.js +156 -0
- package/dist/modules/admin/admin-roles-repository.d.ts +11 -0
- package/dist/modules/admin/admin-roles-repository.js +209 -0
- package/dist/modules/admin/admin-store.d.ts +20 -0
- package/dist/modules/admin/admin-store.js +30 -0
- package/dist/modules/admin/admin-users-repository.d.ts +11 -0
- package/dist/modules/admin/admin-users-repository.js +303 -0
- package/dist/modules/admin/index.d.ts +27 -0
- package/dist/modules/admin/index.js +27 -0
- package/dist/modules/admin/refresh-tokens-repository.d.ts +34 -0
- package/dist/modules/admin/refresh-tokens-repository.js +160 -0
- package/dist/modules/audit/audit-commands.d.ts +29 -0
- package/dist/modules/audit/audit-commands.js +40 -0
- package/dist/modules/audit/audit-queries.d.ts +41 -0
- package/dist/modules/audit/audit-queries.js +169 -0
- package/dist/modules/counters/counters-commands.d.ts +54 -0
- package/dist/modules/counters/counters-commands.js +121 -0
- package/dist/modules/counters/tests/counters-concurrency.test.d.ts +8 -0
- package/dist/modules/counters/tests/counters-concurrency.test.js +111 -0
- package/dist/modules/storage/classify-error.d.ts +34 -0
- package/dist/modules/storage/classify-error.js +50 -0
- package/dist/modules/storage/classify-error.test.node.d.ts +8 -0
- package/dist/modules/storage/classify-error.test.node.js +89 -0
- package/dist/modules/storage/normalize-row.d.ts +55 -0
- package/dist/modules/storage/normalize-row.js +135 -0
- package/dist/modules/storage/normalize-row.test.node.d.ts +8 -0
- package/dist/modules/storage/normalize-row.test.node.js +89 -0
- package/dist/modules/storage/storage-commands.d.ts +443 -0
- package/dist/modules/storage/storage-commands.js +1263 -0
- package/dist/modules/storage/storage-insert.d.ts +21 -0
- package/dist/modules/storage/storage-insert.js +152 -0
- package/dist/modules/storage/storage-queries.d.ts +805 -0
- package/dist/modules/storage/storage-queries.js +1815 -0
- package/dist/modules/storage/storage-store-manifest.d.ts +77 -0
- package/dist/modules/storage/storage-store-manifest.js +168 -0
- package/dist/modules/storage/storage-utils.d.ts +49 -0
- package/dist/modules/storage/storage-utils.js +76 -0
- package/dist/modules/storage/tests/dialect-pins.integration.test.d.ts +8 -0
- package/dist/modules/storage/tests/dialect-pins.integration.test.js +266 -0
- package/dist/modules/storage/tests/storage-commands.test.d.ts +8 -0
- package/dist/modules/storage/tests/storage-commands.test.js +324 -0
- package/dist/modules/storage/tests/storage-document-paths.test.d.ts +8 -0
- package/dist/modules/storage/tests/storage-document-paths.test.js +214 -0
- package/dist/modules/storage/tests/storage-document-tree.test.d.ts +8 -0
- package/dist/modules/storage/tests/storage-document-tree.test.js +361 -0
- package/dist/modules/storage/tests/storage-queries.test.d.ts +8 -0
- package/dist/modules/storage/tests/storage-queries.test.js +685 -0
- package/dist/modules/storage/tests/storage-status-and-lifecycle.test.d.ts +8 -0
- package/dist/modules/storage/tests/storage-status-and-lifecycle.test.js +268 -0
- package/package.json +91 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*
|
|
8
|
+
* The MySQL storage read path. Ported from
|
|
9
|
+
* `packages/db-postgres/src/modules/storage/storage-queries.ts`.
|
|
10
|
+
*
|
|
11
|
+
* Task 10A landed the UNION ALL reconstruction path (`getDocumentById`,
|
|
12
|
+
* `getDocumentByVersion`, `getDocumentHistory`) and a `findDocuments` scoped
|
|
13
|
+
* to what the `versioning` + `field-types` conformance suites needed.
|
|
14
|
+
*
|
|
15
|
+
* Task 10B (this port) completes the surface: the `DocumentFilter[]`
|
|
16
|
+
* predicate compiler (`$and`/`$or` combinators, relation hops,
|
|
17
|
+
* document-column filters), `findDocuments`' `pathFilter`/`query` (LIKE
|
|
18
|
+
* search)/`sort` (`LEFT JOIN LATERAL` field sort), `getDocumentByPath`,
|
|
19
|
+
* `getDocumentsByVersionIds`, `getDocumentsByDocumentIds`,
|
|
20
|
+
* `getPublishedVersion`, `getPublishedDocumentIds`,
|
|
21
|
+
* `getDocumentCountsByStatus`, order-key / tree reads, `getCurrentPath`, and
|
|
22
|
+
* `getDocumentSystemFieldsForUpdate`. `DocumentQueries` now declares
|
|
23
|
+
* `implements IDocumentQueries`.
|
|
24
|
+
*/
|
|
25
|
+
import type { CollectionDefinition, DocumentFilter, FieldFilterOperator, FieldSort, FlattenedStore, ICollectionQueries, IDocumentQueries, MissingLocalePolicy, ReadMode } from '@byline/core';
|
|
26
|
+
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
|
27
|
+
import type * as schema from '../../database/schema/index.js';
|
|
28
|
+
import type { DBManager } from '../../lib/db-manager.js';
|
|
29
|
+
type DatabaseConnection = MySql2Database<typeof schema>;
|
|
30
|
+
/**
|
|
31
|
+
* CollectionQueries
|
|
32
|
+
*
|
|
33
|
+
* Identical to pg's — a plain query-builder read against `byline_collections`,
|
|
34
|
+
* with no dialect-specific SQL anywhere.
|
|
35
|
+
*/
|
|
36
|
+
export declare class CollectionQueries implements ICollectionQueries {
|
|
37
|
+
private db;
|
|
38
|
+
constructor(db: DatabaseConnection);
|
|
39
|
+
getAllCollections(): Promise<{
|
|
40
|
+
created_at: Date;
|
|
41
|
+
updated_at: Date;
|
|
42
|
+
id: string;
|
|
43
|
+
path: string;
|
|
44
|
+
singular: string;
|
|
45
|
+
plural: string;
|
|
46
|
+
config: unknown;
|
|
47
|
+
version: number;
|
|
48
|
+
schema_hash: string | null;
|
|
49
|
+
}[]>;
|
|
50
|
+
getCollectionByPath(path: string): Promise<{
|
|
51
|
+
config: unknown;
|
|
52
|
+
created_at: Date;
|
|
53
|
+
id: string;
|
|
54
|
+
path: string;
|
|
55
|
+
plural: string;
|
|
56
|
+
schema_hash: string | null;
|
|
57
|
+
singular: string;
|
|
58
|
+
updated_at: Date;
|
|
59
|
+
version: number;
|
|
60
|
+
} | undefined>;
|
|
61
|
+
getCollectionById(id: string): Promise<{
|
|
62
|
+
config: unknown;
|
|
63
|
+
created_at: Date;
|
|
64
|
+
id: string;
|
|
65
|
+
path: string;
|
|
66
|
+
plural: string;
|
|
67
|
+
schema_hash: string | null;
|
|
68
|
+
singular: string;
|
|
69
|
+
updated_at: Date;
|
|
70
|
+
version: number;
|
|
71
|
+
} | undefined>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* DocumentQueries
|
|
75
|
+
*/
|
|
76
|
+
export declare class DocumentQueries implements IDocumentQueries {
|
|
77
|
+
private db;
|
|
78
|
+
private transactionDb;
|
|
79
|
+
private collections;
|
|
80
|
+
private defaultContentLocale;
|
|
81
|
+
private collectionPathCache;
|
|
82
|
+
constructor(db: DatabaseConnection, collections: readonly CollectionDefinition[], defaultContentLocale: string, transactionDb: DBManager);
|
|
83
|
+
/**
|
|
84
|
+
* Lock the logical document row before reading its document-grain system
|
|
85
|
+
* fields. Every audited system-field writer takes this same parent-row lock,
|
|
86
|
+
* so path and advertised-locale snapshots serialize without locking a
|
|
87
|
+
* variable set of child rows.
|
|
88
|
+
*
|
|
89
|
+
* Runs on `this.transactionDb.get()` — the ambient `withTransaction`
|
|
90
|
+
* executor when one is open, else the pool — so `FOR UPDATE` (unchanged
|
|
91
|
+
* syntax from pg) actually takes its lock inside the caller's transaction.
|
|
92
|
+
* A lock taken on a bare pool connection would release at the end of its
|
|
93
|
+
* own implicit transaction and serialise nothing; see the §H ruling in the
|
|
94
|
+
* Task 10B report for why `transactionDb` is a required constructor
|
|
95
|
+
* parameter rather than a silently-defaulted one.
|
|
96
|
+
*/
|
|
97
|
+
getDocumentSystemFieldsForUpdate({ collection_id, document_id, }: {
|
|
98
|
+
collection_id: string;
|
|
99
|
+
document_id: string;
|
|
100
|
+
}): Promise<{
|
|
101
|
+
source_locale: string;
|
|
102
|
+
path: string | null;
|
|
103
|
+
availableLocales: string[];
|
|
104
|
+
} | null>;
|
|
105
|
+
/**
|
|
106
|
+
* Resolve a collection UUID to its CollectionDefinition by looking up the
|
|
107
|
+
* collection's path in the DB and matching it against the injected array.
|
|
108
|
+
*/
|
|
109
|
+
private getDefinitionForCollection;
|
|
110
|
+
/**
|
|
111
|
+
* Pick the Drizzle view reference to read from based on `readMode`.
|
|
112
|
+
*
|
|
113
|
+
* - `'any'` (default) → `current_documents` — the latest version of
|
|
114
|
+
* each logical document, regardless of status.
|
|
115
|
+
* - `'published'` → `current_published_documents` — the latest
|
|
116
|
+
* version whose status is `'published'`, falling back past newer
|
|
117
|
+
* drafts so public readers keep seeing previously-published
|
|
118
|
+
* content while editors work on an unpublished draft.
|
|
119
|
+
*
|
|
120
|
+
* Both views share the same row shape, so the returned reference is
|
|
121
|
+
* drop-in substitutable at every select/where site.
|
|
122
|
+
*/
|
|
123
|
+
private pickCurrentView;
|
|
124
|
+
/**
|
|
125
|
+
* Build the locale priority chain for fallback resolution:
|
|
126
|
+
* `[requested, floor]`, deduplicated when both are the same. The floor is
|
|
127
|
+
* the document's own `source_locale` anchor when known (so a re-anchored
|
|
128
|
+
* document, or any document read after the global default is switched, falls
|
|
129
|
+
* back to the locale it was actually authored in) — otherwise the configured
|
|
130
|
+
* global default, which is correct for not-yet-anchored rows and for
|
|
131
|
+
* row-less lookups (findByPath). See docs/07-internationalization/index.md.
|
|
132
|
+
*/
|
|
133
|
+
private buildLocaleChain;
|
|
134
|
+
/**
|
|
135
|
+
* Build the `onMissingLocale: 'omit'` availability gate — an EXISTS against
|
|
136
|
+
* the version-locale ledger (`byline_document_version_locales`) that keeps
|
|
137
|
+
* only documents available in the requested locale. The `'all'` sentinel row
|
|
138
|
+
* covers locale-agnostic documents (no localized content). Returns `null`
|
|
139
|
+
* when the gate does not apply — a non-`'omit'` policy (`'empty'` /
|
|
140
|
+
* `'fallback'` / unset), or the admin sentinel `'all'` read — so callers can
|
|
141
|
+
* conditionally push it into a WHERE. No dialect-specific SQL — ports
|
|
142
|
+
* unchanged from pg.
|
|
143
|
+
*/
|
|
144
|
+
private localeAvailabilityExists;
|
|
145
|
+
/**
|
|
146
|
+
* Batch-fetch the version-locale availability sets from the
|
|
147
|
+
* `byline_document_version_locales` ledger. For each version returns the
|
|
148
|
+
* concrete locales its content is complete in (`availableLocales`, in
|
|
149
|
+
* configured content-locale order), or `localeAgnostic: true` when the
|
|
150
|
+
* version carries only the `'all'` sentinel. Drives the
|
|
151
|
+
* `_availableVersionLocales` read metadata. One indexed query per call.
|
|
152
|
+
* Plain query-builder code — ports unchanged from pg.
|
|
153
|
+
*/
|
|
154
|
+
private getAvailableLocalesByVersion;
|
|
155
|
+
/**
|
|
156
|
+
* Batch-fetch the editorial advertised-locale sets from
|
|
157
|
+
* `byline_document_available_locales` (document-grain). For each logical
|
|
158
|
+
* document returns the set of locales the editor has elected to advertise,
|
|
159
|
+
* in configured content-locale order. Surfaced on reads as
|
|
160
|
+
* `availableLocales`. One indexed query per call. Plain query-builder
|
|
161
|
+
* code — ports unchanged from pg.
|
|
162
|
+
*/
|
|
163
|
+
private getAdvertisedLocalesByDocument;
|
|
164
|
+
/**
|
|
165
|
+
* Emit a SQL fragment that resolves the path string for a document via
|
|
166
|
+
* the locale priority chain. Used as a projected column expression
|
|
167
|
+
* inside `SELECT` lists.
|
|
168
|
+
*
|
|
169
|
+
* Postgres:
|
|
170
|
+
* ```sql
|
|
171
|
+
* (SELECT path FROM byline_document_paths
|
|
172
|
+
* WHERE document_id = <docIdSql> AND locale = ANY(<chain>)
|
|
173
|
+
* ORDER BY array_position(<chain>, locale) LIMIT 1)
|
|
174
|
+
* ```
|
|
175
|
+
* MySQL has no `ANY(ARRAY[...])` or `array_position` — the locale-chain
|
|
176
|
+
* conversion (design spec §2): `IN (…)` for the membership test, and
|
|
177
|
+
* `ORDER BY FIELD(locale, …)` for the priority order. `FIELD()` returns
|
|
178
|
+
* the 1-based position of the first argument within the remaining
|
|
179
|
+
* argument list (0 if absent); since the `WHERE … IN (chain)` clause
|
|
180
|
+
* already restricts every candidate row to a locale that's a member of
|
|
181
|
+
* the chain, `FIELD()` can never actually return 0 here — every row that
|
|
182
|
+
* reaches the `ORDER BY` has a genuine position, and ascending order
|
|
183
|
+
* therefore picks the earliest (highest-priority, i.e. most-requested)
|
|
184
|
+
* chain entry first, exactly matching `array_position`'s semantics.
|
|
185
|
+
* Verified live against MySQL 9.7.1 (see the Task 10A report) and pinned
|
|
186
|
+
* by `storage-queries.test.ts`'s locale-chain-ordering test.
|
|
187
|
+
*/
|
|
188
|
+
private pathProjection;
|
|
189
|
+
/**
|
|
190
|
+
* Emit a SQL fragment that resolves a `(collection_id, path)` tuple to a
|
|
191
|
+
* `document_id` via the locale priority chain. Used inside `WHERE` clauses
|
|
192
|
+
* for findByPath-style lookups. Same `IN (…)` / `FIELD()` locale-chain
|
|
193
|
+
* conversion as `pathProjection` — see that method's docblock. Returns
|
|
194
|
+
* NULL when no row matches in any locale, which makes the outer `=`
|
|
195
|
+
* predicate fail cleanly (no document found).
|
|
196
|
+
*/
|
|
197
|
+
private resolveDocumentIdByPath;
|
|
198
|
+
/**
|
|
199
|
+
* Project list for `current_documents` / `current_published_documents`
|
|
200
|
+
* reads, with `path` resolved through the locale priority chain. Used
|
|
201
|
+
* everywhere a read previously did `.select()` (which auto-pulls every
|
|
202
|
+
* view column) — `path` is no longer projected by the views, so call
|
|
203
|
+
* sites must list the projection explicitly.
|
|
204
|
+
*/
|
|
205
|
+
private viewProjection;
|
|
206
|
+
/**
|
|
207
|
+
* Project list for direct `byline_document_versions` reads (history,
|
|
208
|
+
* version-by-id lookups). Mirrors `viewProjection` but against the
|
|
209
|
+
* underlying table — `path` is sourced from `byline_document_paths` via
|
|
210
|
+
* the locale priority chain, since it no longer lives on the version row.
|
|
211
|
+
*/
|
|
212
|
+
private documentVersionsProjection;
|
|
213
|
+
/**
|
|
214
|
+
* Resolve the single effective content locale a version should be restored
|
|
215
|
+
* in, walking the fallback chain (`[requested, default]`) and returning the
|
|
216
|
+
* first locale the version is *available* in. Pure JS/data-structure code
|
|
217
|
+
* — no SQL — ports unchanged from pg. See pg's docblock for the full
|
|
218
|
+
* phase-1 availability rule.
|
|
219
|
+
*/
|
|
220
|
+
private resolveEffectiveLocale;
|
|
221
|
+
/**
|
|
222
|
+
* Reconstruct document fields from unified row values using schema-aware
|
|
223
|
+
* restoration. Meta rows (from store_meta) are converted to
|
|
224
|
+
* FlattenedFieldValue entries so that restoreFieldSetData can inject
|
|
225
|
+
* _id and _type for blocks and array items inline. Pure JS/core-delegate
|
|
226
|
+
* code — ports unchanged from pg.
|
|
227
|
+
*/
|
|
228
|
+
private reconstructFromUnifiedRows;
|
|
229
|
+
/**
|
|
230
|
+
* getCurrentVersionMetadata — narrow metadata fetch for the current version.
|
|
231
|
+
*
|
|
232
|
+
* Hits `current_documents` only; no field reconstruction, no meta fetch,
|
|
233
|
+
* no path subquery. Used by lifecycle operations (status changes,
|
|
234
|
+
* restore, delete checks) that only need `document_version_id` /
|
|
235
|
+
* `status` / timestamps before mutating.
|
|
236
|
+
*/
|
|
237
|
+
getCurrentVersionMetadata({ collection_id, document_id, }: {
|
|
238
|
+
collection_id: string;
|
|
239
|
+
document_id: string;
|
|
240
|
+
}): Promise<{
|
|
241
|
+
document_version_id: string;
|
|
242
|
+
document_id: string;
|
|
243
|
+
collection_id: string;
|
|
244
|
+
status: string;
|
|
245
|
+
created_at: Date;
|
|
246
|
+
updated_at: Date;
|
|
247
|
+
} | null>;
|
|
248
|
+
/**
|
|
249
|
+
* getCurrentPath — resolve a document's canonical (source-locale) path.
|
|
250
|
+
*
|
|
251
|
+
* Reuses `pathProjection` against `current_documents`, passing
|
|
252
|
+
* `requestedLocale: undefined` so the projection's fallback floor — the
|
|
253
|
+
* document's own `source_locale` (COALESCE-guarded to the default content
|
|
254
|
+
* locale for not-yet-anchored rows) — supplies the canonical path. Used by
|
|
255
|
+
* the lifecycle to populate `path` on the status-change / unpublish hook
|
|
256
|
+
* contexts. Returns `null` when no path row (or document) exists.
|
|
257
|
+
*/
|
|
258
|
+
getCurrentPath({ collection_id, document_id, }: {
|
|
259
|
+
collection_id: string;
|
|
260
|
+
document_id: string;
|
|
261
|
+
}): Promise<string | null>;
|
|
262
|
+
/**
|
|
263
|
+
* getDocumentById — gets the current version of a document by its logical document ID.
|
|
264
|
+
*
|
|
265
|
+
* When `lenient` is true, schema-mismatch warnings emitted during
|
|
266
|
+
* reconstruction are surfaced on the returned object as `restoreWarnings`
|
|
267
|
+
* rather than thrown. This is the admin edit path's "best-effort load"
|
|
268
|
+
* mode for documents written under a previous collection schema.
|
|
269
|
+
*/
|
|
270
|
+
getDocumentById({ collection_id, document_id, locale, reconstruct, readMode, filters, lenient, onMissingLocale, }: {
|
|
271
|
+
collection_id: string;
|
|
272
|
+
document_id: string;
|
|
273
|
+
locale?: string;
|
|
274
|
+
reconstruct?: boolean;
|
|
275
|
+
readMode?: ReadMode;
|
|
276
|
+
filters?: DocumentFilter[];
|
|
277
|
+
lenient?: boolean;
|
|
278
|
+
onMissingLocale?: MissingLocalePolicy;
|
|
279
|
+
}): Promise<{
|
|
280
|
+
document_version_id: string;
|
|
281
|
+
document_id: string;
|
|
282
|
+
path: string;
|
|
283
|
+
source_locale: string;
|
|
284
|
+
status: string | null;
|
|
285
|
+
event_type: string;
|
|
286
|
+
created_at: Date;
|
|
287
|
+
updated_at: Date;
|
|
288
|
+
created_by: string | null;
|
|
289
|
+
fields: any;
|
|
290
|
+
availableLocales: string[];
|
|
291
|
+
_availableVersionLocales: string[];
|
|
292
|
+
_localeAgnostic: boolean;
|
|
293
|
+
restoreWarnings?: string[] | undefined;
|
|
294
|
+
} | {
|
|
295
|
+
document_version_id: string;
|
|
296
|
+
document_id: string;
|
|
297
|
+
path: string;
|
|
298
|
+
source_locale: string;
|
|
299
|
+
status: string | null;
|
|
300
|
+
event_type: string;
|
|
301
|
+
created_at: Date;
|
|
302
|
+
updated_at: Date;
|
|
303
|
+
created_by: string | null;
|
|
304
|
+
fields: FlattenedStore[];
|
|
305
|
+
} | null>;
|
|
306
|
+
/**
|
|
307
|
+
* getDocumentByPath — resolves `(collection_id, path)` through the locale
|
|
308
|
+
* priority chain to a document_id, then reads and reconstructs its
|
|
309
|
+
* current version. See `resolveDocumentIdByPath` for the locale-chain
|
|
310
|
+
* SQL.
|
|
311
|
+
*/
|
|
312
|
+
getDocumentByPath({ collection_id, path, locale, reconstruct, readMode, filters, onMissingLocale, }: {
|
|
313
|
+
collection_id: string;
|
|
314
|
+
path: string;
|
|
315
|
+
locale?: string;
|
|
316
|
+
reconstruct: boolean;
|
|
317
|
+
readMode?: ReadMode;
|
|
318
|
+
filters?: DocumentFilter[];
|
|
319
|
+
onMissingLocale?: MissingLocalePolicy;
|
|
320
|
+
}): Promise<{
|
|
321
|
+
document_version_id: string;
|
|
322
|
+
document_id: string;
|
|
323
|
+
path: string;
|
|
324
|
+
source_locale: string;
|
|
325
|
+
status: string | null;
|
|
326
|
+
event_type: string;
|
|
327
|
+
created_at: Date;
|
|
328
|
+
updated_at: Date;
|
|
329
|
+
created_by: string | null;
|
|
330
|
+
fields: any;
|
|
331
|
+
availableLocales: string[];
|
|
332
|
+
_availableVersionLocales: string[];
|
|
333
|
+
_localeAgnostic: boolean;
|
|
334
|
+
} | {
|
|
335
|
+
availableLocales?: undefined;
|
|
336
|
+
_availableVersionLocales?: undefined;
|
|
337
|
+
_localeAgnostic?: undefined;
|
|
338
|
+
document_version_id: string;
|
|
339
|
+
document_id: string;
|
|
340
|
+
path: string;
|
|
341
|
+
source_locale: string;
|
|
342
|
+
status: string | null;
|
|
343
|
+
event_type: string;
|
|
344
|
+
created_at: Date;
|
|
345
|
+
updated_at: Date;
|
|
346
|
+
created_by: string | null;
|
|
347
|
+
fields: FlattenedStore[];
|
|
348
|
+
} | null>;
|
|
349
|
+
/**
|
|
350
|
+
* getDocumentByVersion — fetches a specific version and reconstructs its fields.
|
|
351
|
+
*/
|
|
352
|
+
getDocumentByVersion({ document_version_id, locale, collection_id, filters, }: {
|
|
353
|
+
document_version_id: string;
|
|
354
|
+
locale?: string;
|
|
355
|
+
collection_id?: string;
|
|
356
|
+
filters?: DocumentFilter[];
|
|
357
|
+
}): Promise<any | null>;
|
|
358
|
+
/**
|
|
359
|
+
* getDocumentsByVersionIds — fetches and reconstructs multiple documents by
|
|
360
|
+
* version ID. Used for batch loading a known set of versions (e.g.
|
|
361
|
+
* migration scripts, tests).
|
|
362
|
+
*/
|
|
363
|
+
getDocumentsByVersionIds({ document_version_ids, locale, }: {
|
|
364
|
+
document_version_ids: string[];
|
|
365
|
+
locale?: string;
|
|
366
|
+
}): Promise<any[]>;
|
|
367
|
+
/**
|
|
368
|
+
* getDocumentsByDocumentIds — batch-fetch current versions for a list of
|
|
369
|
+
* logical document IDs, with optional selective field loading.
|
|
370
|
+
*
|
|
371
|
+
* Resolves each document_id to its current version via the
|
|
372
|
+
* `current_documents` view (soft-deleted documents are excluded by the
|
|
373
|
+
* view definition), then delegates to `reconstructDocuments` for the
|
|
374
|
+
* shared field + meta reconstruction path.
|
|
375
|
+
*
|
|
376
|
+
* Primary consumer is the client API's relationship populate pass —
|
|
377
|
+
* `store_relation` rows carry `target_document_id` (not version ID), so
|
|
378
|
+
* populate collects those IDs and resolves them here in one round trip.
|
|
379
|
+
*/
|
|
380
|
+
getDocumentsByDocumentIds({ collection_id, document_ids, locale, fields, readMode, filters, }: {
|
|
381
|
+
collection_id: string;
|
|
382
|
+
document_ids: string[];
|
|
383
|
+
locale?: string;
|
|
384
|
+
fields?: string[];
|
|
385
|
+
readMode?: ReadMode;
|
|
386
|
+
filters?: DocumentFilter[];
|
|
387
|
+
}): Promise<any[]>;
|
|
388
|
+
/**
|
|
389
|
+
* getDocumentHistory — paginated version history for a document,
|
|
390
|
+
* including soft-deleted versions.
|
|
391
|
+
*/
|
|
392
|
+
getDocumentHistory({ collection_id, document_id, locale, page, page_size, order, desc: descending, filters, }: {
|
|
393
|
+
collection_id: string;
|
|
394
|
+
document_id: string;
|
|
395
|
+
locale?: string;
|
|
396
|
+
page?: number;
|
|
397
|
+
page_size?: number;
|
|
398
|
+
order?: string;
|
|
399
|
+
desc?: boolean;
|
|
400
|
+
query?: string;
|
|
401
|
+
filters?: DocumentFilter[];
|
|
402
|
+
}): Promise<{
|
|
403
|
+
documents: any[];
|
|
404
|
+
meta: {
|
|
405
|
+
total: number;
|
|
406
|
+
page: number;
|
|
407
|
+
page_size: number;
|
|
408
|
+
total_pages: number;
|
|
409
|
+
order: string;
|
|
410
|
+
desc: boolean;
|
|
411
|
+
};
|
|
412
|
+
}>;
|
|
413
|
+
/**
|
|
414
|
+
* getPublishedVersion
|
|
415
|
+
*
|
|
416
|
+
* Find the latest version of a document that has a specific status
|
|
417
|
+
* (defaults to 'published'). Queries `document_versions` directly so it
|
|
418
|
+
* can find a published version even when a newer draft exists.
|
|
419
|
+
*
|
|
420
|
+
* Returns minimal version metadata (not reconstructed content), or null
|
|
421
|
+
* if no version with the requested status exists.
|
|
422
|
+
*/
|
|
423
|
+
getPublishedVersion({ collection_id, document_id, status, }: {
|
|
424
|
+
collection_id: string;
|
|
425
|
+
document_id: string;
|
|
426
|
+
status?: string;
|
|
427
|
+
}): Promise<{
|
|
428
|
+
document_version_id: string;
|
|
429
|
+
document_id: string;
|
|
430
|
+
status: string;
|
|
431
|
+
created_at: Date;
|
|
432
|
+
updated_at: Date;
|
|
433
|
+
} | null>;
|
|
434
|
+
/**
|
|
435
|
+
* getPublishedDocumentIds
|
|
436
|
+
*
|
|
437
|
+
* Given a list of document IDs, return the subset that have at least one
|
|
438
|
+
* version with the requested status (defaults to 'published'). Uses a
|
|
439
|
+
* single batch query instead of per-document lookups.
|
|
440
|
+
*/
|
|
441
|
+
getPublishedDocumentIds({ collection_id, document_ids, status, }: {
|
|
442
|
+
collection_id: string;
|
|
443
|
+
document_ids: string[];
|
|
444
|
+
status?: string;
|
|
445
|
+
}): Promise<Set<string>>;
|
|
446
|
+
/**
|
|
447
|
+
* getLastOrderKey
|
|
448
|
+
*
|
|
449
|
+
* Largest `order_key` currently in use for the given collection. Used
|
|
450
|
+
* at create-time on `orderable: true` collections to append the new
|
|
451
|
+
* row at the end. Returns `null` when no keyed rows exist yet.
|
|
452
|
+
*/
|
|
453
|
+
getLastOrderKey({ collection_id }: {
|
|
454
|
+
collection_id: string;
|
|
455
|
+
}): Promise<string | null>;
|
|
456
|
+
/**
|
|
457
|
+
* getNeighborOrderKeys
|
|
458
|
+
*
|
|
459
|
+
* Resolve the `order_key` values bracketing a target gap in one query.
|
|
460
|
+
* `before_document_id` is the doc the moved row should land *after*;
|
|
461
|
+
* `after_document_id` is the doc it should land *before*. Either or
|
|
462
|
+
* both may be null (append / prepend / empty collection).
|
|
463
|
+
*
|
|
464
|
+
* Resolves both keys in a single round trip to keep the read consistent
|
|
465
|
+
* with the next-key computation that follows in the caller.
|
|
466
|
+
*/
|
|
467
|
+
getNeighborOrderKeys({ collection_id, before_document_id, after_document_id, }: {
|
|
468
|
+
collection_id: string;
|
|
469
|
+
before_document_id: string | null;
|
|
470
|
+
after_document_id: string | null;
|
|
471
|
+
}): Promise<{
|
|
472
|
+
left: string | null;
|
|
473
|
+
right: string | null;
|
|
474
|
+
}>;
|
|
475
|
+
/**
|
|
476
|
+
* getCanonicalDocumentOrder
|
|
477
|
+
*
|
|
478
|
+
* Returns every document in the collection in its canonical list-view
|
|
479
|
+
* order: `order_key ASC NULLS LAST, created_at DESC`. Used by the reorder
|
|
480
|
+
* server fn for backfill and recovery from key corruption.
|
|
481
|
+
*
|
|
482
|
+
* Divergence from pg: MySQL has no `NULLS LAST`. MySQL's own `ASC`
|
|
483
|
+
* default sorts NULL *first* (the opposite of pg's `NULLS LAST`), so the
|
|
484
|
+
* `(col IS NULL) ASC, col ASC` emulation idiom — the same one
|
|
485
|
+
* `buildDocumentOrderClause` uses — is required here too.
|
|
486
|
+
*/
|
|
487
|
+
getCanonicalDocumentOrder({ collection_id, }: {
|
|
488
|
+
collection_id: string;
|
|
489
|
+
}): Promise<Array<{
|
|
490
|
+
id: string;
|
|
491
|
+
order_key: string | null;
|
|
492
|
+
}>>;
|
|
493
|
+
/**
|
|
494
|
+
* getTreeAncestors — see {@link IDocumentQueries.getTreeAncestors}.
|
|
495
|
+
*
|
|
496
|
+
* Recursive CTE walking `parent_document_id` upward from the given node.
|
|
497
|
+
* Returns ancestors root-first (`ORDER BY depth DESC`), each with a 1-based
|
|
498
|
+
* depth (1 = immediate parent). Empty for a root or unplaced node.
|
|
499
|
+
*
|
|
500
|
+
* Status-at-edge: in `published` mode each hop must resolve in
|
|
501
|
+
* `byline_current_published_documents`, so the walk stops at the first
|
|
502
|
+
* unpublished ancestor rather than skipping it (a truncated spine the splat
|
|
503
|
+
* handler turns into a 404). `any` mode walks the raw edges unchanged.
|
|
504
|
+
*
|
|
505
|
+
* `WITH RECURSIVE` syntax is unchanged from pg (the 8.0.14 engine floor
|
|
506
|
+
* guarantees support); the `::uuid` cast on `document_id` is dropped —
|
|
507
|
+
* MySQL has no cast syntax for a UUID-shaped `CHAR(36)` column, and the
|
|
508
|
+
* driver already binds the plain string correctly.
|
|
509
|
+
*/
|
|
510
|
+
getTreeAncestors({ document_id, maxDepth, readMode, locale, filters, }: {
|
|
511
|
+
document_id: string;
|
|
512
|
+
maxDepth?: number;
|
|
513
|
+
readMode?: ReadMode;
|
|
514
|
+
locale?: string;
|
|
515
|
+
filters?: DocumentFilter[];
|
|
516
|
+
}): Promise<Array<{
|
|
517
|
+
document_id: string;
|
|
518
|
+
depth: number;
|
|
519
|
+
}>>;
|
|
520
|
+
/**
|
|
521
|
+
* getTreeChildren — see {@link IDocumentQueries.getTreeChildren}.
|
|
522
|
+
*
|
|
523
|
+
* Immediate children of a node ordered by the per-parent `order_key`.
|
|
524
|
+
* `parentDocumentId: null` returns the collection's root nodes; the join to
|
|
525
|
+
* `byline_documents` scopes roots to the collection (they have no parent to
|
|
526
|
+
* scope by). Plain query-builder code — ports unchanged from pg.
|
|
527
|
+
*/
|
|
528
|
+
getTreeChildren({ collectionId, parentDocumentId, }: {
|
|
529
|
+
collectionId: string;
|
|
530
|
+
parentDocumentId: string | null;
|
|
531
|
+
}): Promise<Array<{
|
|
532
|
+
document_id: string;
|
|
533
|
+
order_key: string;
|
|
534
|
+
}>>;
|
|
535
|
+
/**
|
|
536
|
+
* getTreeParent — see {@link IDocumentQueries.getTreeParent}.
|
|
537
|
+
*
|
|
538
|
+
* Single indexed lookup on the edge table by `child_document_id` (unique).
|
|
539
|
+
* No row → *unplaced*; a row with a null parent → *root*; a row with a parent
|
|
540
|
+
* → *child*. Distinguishes the unplaced/root states that `getTreeAncestors`
|
|
541
|
+
* (which returns `[]` for both) conflates. `::uuid` cast dropped, same as
|
|
542
|
+
* `getTreeAncestors`.
|
|
543
|
+
*/
|
|
544
|
+
getTreeParent({ document_id, readMode, locale, filters, }: {
|
|
545
|
+
document_id: string;
|
|
546
|
+
readMode?: ReadMode;
|
|
547
|
+
locale?: string;
|
|
548
|
+
filters?: DocumentFilter[];
|
|
549
|
+
}): Promise<{
|
|
550
|
+
placed: boolean;
|
|
551
|
+
parentDocumentId: string | null;
|
|
552
|
+
parentRedacted?: true;
|
|
553
|
+
}>;
|
|
554
|
+
/**
|
|
555
|
+
* getTreeSubtree — see {@link IDocumentQueries.getTreeSubtree}.
|
|
556
|
+
*
|
|
557
|
+
* Recursive CTE descending from the requested root (or the collection's
|
|
558
|
+
* roots when `rootDocumentId` is null). Each row carries a `/`-joined path
|
|
559
|
+
* of ancestor `order_key`s; ordering by that path yields a pre-order
|
|
560
|
+
* depth-first walk (a parent's path is a prefix of its children's, and `/`
|
|
561
|
+
* (0x2F) sorts below every key character in an ascii_bin comparison).
|
|
562
|
+
* Status-at-edge: every node — anchor included — must exist in the chosen
|
|
563
|
+
* current-documents view, so an unpublished node and its whole subtree
|
|
564
|
+
* drop out in `published` mode.
|
|
565
|
+
*
|
|
566
|
+
* Three divergences from pg, all found (or, for the third, reproduced)
|
|
567
|
+
* live — not assumed from docs:
|
|
568
|
+
*
|
|
569
|
+
* - `s.path || '/' || r.order_key` (pg's `||` string concat) is MySQL
|
|
570
|
+
* boolean OR unless `PIPES_AS_CONCAT` is in `sql_mode` — confirmed
|
|
571
|
+
* against this database's `@@sql_mode` (it isn't set), and confirmed
|
|
572
|
+
* `SELECT 'a' || 'b'` returns `0`, not `'ab'`. `CONCAT(s.path, '/',
|
|
573
|
+
* r.order_key)` is the portable form.
|
|
574
|
+
* - `ORDER BY path COLLATE "C"` drops the explicit collation: `order_key`
|
|
575
|
+
* (and therefore the CONCAT'd `path` column, once the width fix below
|
|
576
|
+
* is in place) is already `ascii_bin` — byte-comparable — end to end
|
|
577
|
+
* (`varcharByteSorted`, `database/schema/common.ts`), confirmed live
|
|
578
|
+
* that `CONCAT()` over two `ascii_bin` operands plus a `'/'` literal
|
|
579
|
+
* stays `ascii_bin` rather than falling back to the connection's
|
|
580
|
+
* `utf8mb4_0900_ai_ci` default (which would reorder mixed-case keys
|
|
581
|
+
* incorrectly, the same class of bug `varcharByteSorted` exists to
|
|
582
|
+
* prevent on the base columns).
|
|
583
|
+
* - pg's `r.order_key::text AS path` cast does **two** jobs, not one —
|
|
584
|
+
* an earlier version of this docblock dropped the cast on collation
|
|
585
|
+
* grounds alone and missed the second job. In Postgres, `::text`
|
|
586
|
+
* also makes the anchor column **unbounded** (`text` has no length
|
|
587
|
+
* cap), which an accumulating concatenation needs. MySQL infers a
|
|
588
|
+
* recursive CTE's column types from the *non-recursive* (anchor) leg
|
|
589
|
+
* only — a bare `r.order_key` reference in the anchor makes `path`
|
|
590
|
+
* inherit `order_key`'s declared `varchar(128)` width
|
|
591
|
+
* (`varcharByteSorted`, `database/schema/index.ts:299`), so every
|
|
592
|
+
* recursive iteration's `CONCAT` is silently constrained to 128
|
|
593
|
+
* bytes regardless of how the SELECT list is written. Reproduced
|
|
594
|
+
* live against this server (`STRICT_TRANS_TABLES` is in `sql_mode`,
|
|
595
|
+
* so it's a hard `ER_DATA_TOO_LONG`, not silent truncation):
|
|
596
|
+
* `WITH RECURSIVE t AS (SELECT CAST('ab' AS CHAR(128) CHARACTER SET
|
|
597
|
+
* ascii) AS p, 1 AS n UNION ALL SELECT CONCAT(p,'/','abc…xyz'), n+1
|
|
598
|
+
* FROM t WHERE n<6) …` → `ERROR 1406 (22001): Data too long for
|
|
599
|
+
* column 'p' at row 1`. The threshold — `Σ len(order_key) + depth − 1
|
|
600
|
+
* > 128` — is far more reachable than the `cte_max_recursion_depth`
|
|
601
|
+
* ceiling this method's own docblock elsewhere flags: roughly 11–40
|
|
602
|
+
* tree levels for typical fractional-index keys, lower once keys
|
|
603
|
+
* have grown through repeated same-position reordering. Fix: widen
|
|
604
|
+
* the anchor's `path` column explicitly —
|
|
605
|
+
* `CAST(r.order_key AS CHAR(4096) CHARACTER SET ascii) COLLATE
|
|
606
|
+
* ascii_bin` — doing both jobs the pg cast did: width (4096 bytes,
|
|
607
|
+
* generous headroom over the reachable-depth threshold above) *and*
|
|
608
|
+
* collation (`CHARACTER SET ascii` alone resolves to
|
|
609
|
+
* `ascii_general_ci`, not `ascii_bin` — confirmed live that a bare
|
|
610
|
+
* `CAST(… AS CHAR(4096))` with no `CHARACTER SET`/`COLLATE` at all
|
|
611
|
+
* reverts to the *connection's* default collation, not the source
|
|
612
|
+
* column's, so both clauses are required, not just one).
|
|
613
|
+
*/
|
|
614
|
+
getTreeSubtree({ collectionId, rootDocumentId, maxDepth, readMode, locale, filters, }: {
|
|
615
|
+
collectionId: string;
|
|
616
|
+
rootDocumentId?: string | null;
|
|
617
|
+
maxDepth?: number;
|
|
618
|
+
readMode?: ReadMode;
|
|
619
|
+
locale?: string;
|
|
620
|
+
filters?: DocumentFilter[];
|
|
621
|
+
}): Promise<Array<{
|
|
622
|
+
document_id: string;
|
|
623
|
+
parent_document_id: string | null;
|
|
624
|
+
depth: number;
|
|
625
|
+
order_key: string;
|
|
626
|
+
}>>;
|
|
627
|
+
/**
|
|
628
|
+
* getDocumentCountsByStatus
|
|
629
|
+
*
|
|
630
|
+
* Returns a count of current documents grouped by workflow status for a
|
|
631
|
+
* given collection. Uses the `current_documents` view so each logical
|
|
632
|
+
* document is counted once (at its latest/current version).
|
|
633
|
+
*/
|
|
634
|
+
getDocumentCountsByStatus({ collection_id, filters, }: {
|
|
635
|
+
collection_id: string;
|
|
636
|
+
filters?: DocumentFilter[];
|
|
637
|
+
}): Promise<Array<{
|
|
638
|
+
status: string;
|
|
639
|
+
count: number;
|
|
640
|
+
}>>;
|
|
641
|
+
/**
|
|
642
|
+
* reconstructDocuments — retrieve field values and reconstruct multiple documents.
|
|
643
|
+
* Supports selective field loading via the `fields` parameter. Pure
|
|
644
|
+
* JS/core-delegate orchestration — ports unchanged from pg (the
|
|
645
|
+
* dialect-specific work is one level down, in
|
|
646
|
+
* `getAllFieldValuesForMultipleVersions`).
|
|
647
|
+
*/
|
|
648
|
+
private reconstructDocuments;
|
|
649
|
+
/**
|
|
650
|
+
* Gets all field values for a single document version.
|
|
651
|
+
* Delegates to the multi-version dynamic UNION ALL builder.
|
|
652
|
+
*/
|
|
653
|
+
private getAllFieldValues;
|
|
654
|
+
/**
|
|
655
|
+
* Gets field values for multiple versions in a single query — the UNION
|
|
656
|
+
* ALL that is the whole point of this task.
|
|
657
|
+
*
|
|
658
|
+
* When `storeTypes` is provided, only those store tables are included in
|
|
659
|
+
* the UNION ALL — this is the selective field loading optimisation for
|
|
660
|
+
* list views that only need a subset of fields.
|
|
661
|
+
*
|
|
662
|
+
* Locale-chain conversion (design spec §2, the store-row locale
|
|
663
|
+
* condition): pg's `locale = ANY(ARRAY[...]) ` → MySQL `locale IN (...)`.
|
|
664
|
+
* No `ORDER BY`/`FIELD()` needed here — this is a membership filter, not a
|
|
665
|
+
* priority pick (unlike `pathProjection`, every matching locale row is
|
|
666
|
+
* kept; `resolveEffectiveLocale`/`restoreFieldSetData` pick the effective
|
|
667
|
+
* one in JS afterwards).
|
|
668
|
+
*
|
|
669
|
+
* `db.execute()` on the mysql2 driver returns a `[rows, fields]` tuple
|
|
670
|
+
* (unlike pg's `{ rows }` result object) — see `storage-commands.ts` for
|
|
671
|
+
* the established pattern this mirrors.
|
|
672
|
+
*/
|
|
673
|
+
private getAllFieldValuesForMultipleVersions;
|
|
674
|
+
/**
|
|
675
|
+
* findDocuments — field-level filtered, sorted, paginated query.
|
|
676
|
+
*
|
|
677
|
+
* Each `FieldFilter` becomes an EXISTS subquery against the appropriate EAV
|
|
678
|
+
* store table. A `RelationFilter` becomes a nested EXISTS that joins
|
|
679
|
+
* `store_relation` to the target collection's current-documents view
|
|
680
|
+
* (selected by `readMode` so draft leaks can't happen through filter
|
|
681
|
+
* predicates) and recurses into its own `nested` filters. A `FieldSort`
|
|
682
|
+
* becomes a LEFT JOIN LATERAL to pull the sort value into the outer query
|
|
683
|
+
* — unchanged syntax from pg (the 8.0.14 engine floor guarantees LATERAL
|
|
684
|
+
* support), confirmed live against this container. Document-level
|
|
685
|
+
* conditions (status, path) are applied directly on the current_documents
|
|
686
|
+
* view.
|
|
687
|
+
*/
|
|
688
|
+
findDocuments({ collection_id, filters, status, pathFilter, query, sort, orderBy, orderDirection, locale, page, pageSize, fields: requestedFields, readMode, onMissingLocale, }: {
|
|
689
|
+
collection_id: string;
|
|
690
|
+
filters?: DocumentFilter[];
|
|
691
|
+
status?: string;
|
|
692
|
+
pathFilter?: {
|
|
693
|
+
operator: FieldFilterOperator;
|
|
694
|
+
value: string;
|
|
695
|
+
};
|
|
696
|
+
query?: string;
|
|
697
|
+
sort?: FieldSort;
|
|
698
|
+
orderBy?: string;
|
|
699
|
+
orderDirection?: 'asc' | 'desc';
|
|
700
|
+
locale?: string;
|
|
701
|
+
page?: number;
|
|
702
|
+
pageSize?: number;
|
|
703
|
+
fields?: string[];
|
|
704
|
+
readMode?: ReadMode;
|
|
705
|
+
onMissingLocale?: MissingLocalePolicy;
|
|
706
|
+
}): Promise<{
|
|
707
|
+
documents: any[];
|
|
708
|
+
total: number;
|
|
709
|
+
}>;
|
|
710
|
+
/**
|
|
711
|
+
* Compile status and `beforeRead` visibility for one tree edge endpoint.
|
|
712
|
+
* Ports unchanged from pg — the `EXISTS` shape has no dialect-specific
|
|
713
|
+
* SQL of its own (its nested `buildFilterExists` calls carry the
|
|
714
|
+
* dialect-specific bits).
|
|
715
|
+
*/
|
|
716
|
+
private buildTreeVisibility;
|
|
717
|
+
/**
|
|
718
|
+
* Build an EXISTS subquery for a single DocumentFilter. Dispatches on
|
|
719
|
+
* `kind` — field filters emit a direct EXISTS against the field's EAV
|
|
720
|
+
* store; relation filters emit a nested EXISTS that joins through
|
|
721
|
+
* `store_relation` to the target collection's current-documents view
|
|
722
|
+
* and recurses against the target's own stores; combinator filters
|
|
723
|
+
* emit a parenthesised AND/OR group; document-column filters emit a
|
|
724
|
+
* direct comparison on the outer scope's status/path column. Ports
|
|
725
|
+
* unchanged from pg — the dispatch itself has no dialect-specific SQL.
|
|
726
|
+
*/
|
|
727
|
+
private buildFilterExists;
|
|
728
|
+
/**
|
|
729
|
+
* Build a parenthesised AND/OR group from a CombinatorFilter. Each child
|
|
730
|
+
* compiles through `buildFilterExists` recursively, so combinators nest
|
|
731
|
+
* freely and inherit the outer scope. Ports unchanged from pg.
|
|
732
|
+
*/
|
|
733
|
+
private buildCombinatorGroup;
|
|
734
|
+
/**
|
|
735
|
+
* Compile a `DocumentColumnFilter` against the outer scope's `status`,
|
|
736
|
+
* `path`, or `id` column. Plain comparison — no EXISTS — because the
|
|
737
|
+
* column lives directly on the outer relation (current-documents view),
|
|
738
|
+
* not in the EAV stores. Ports unchanged from pg.
|
|
739
|
+
*/
|
|
740
|
+
private buildDocColumnFilter;
|
|
741
|
+
/**
|
|
742
|
+
* Build an EXISTS subquery for a single field-level filter. Ports
|
|
743
|
+
* unchanged from pg (aside from `buildFilterCondition`'s `ILIKE` → `LIKE`
|
|
744
|
+
* one level down).
|
|
745
|
+
*/
|
|
746
|
+
private buildFieldExists;
|
|
747
|
+
/**
|
|
748
|
+
* Build a nested EXISTS subquery for a cross-collection relation filter.
|
|
749
|
+
*
|
|
750
|
+
* Joins `store_relation` to the target collection's current-documents
|
|
751
|
+
* view (`current_published_documents` under `readMode: 'published'`,
|
|
752
|
+
* `current_documents` otherwise — so a draft target doesn't leak when
|
|
753
|
+
* the outer read is in published mode), then recurses each nested
|
|
754
|
+
* filter against the target version's own `td.id`.
|
|
755
|
+
*
|
|
756
|
+
* `hasMany` relations store one row per item at an indexed field name
|
|
757
|
+
* (`<field>.0`, `<field>.1`, …), so the field match switches to a prefix
|
|
758
|
+
* match; single relations match the exact name.
|
|
759
|
+
*
|
|
760
|
+
* The `quantifier` selects the set semantics over the relation's
|
|
761
|
+
* (resolving) targets — see pg's docblock for the full 'some'/'none'/
|
|
762
|
+
* 'every' semantics; this method ports the SQL shape unchanged.
|
|
763
|
+
*/
|
|
764
|
+
private buildRelationExists;
|
|
765
|
+
/**
|
|
766
|
+
* Build a comparison condition for a filter operator.
|
|
767
|
+
*
|
|
768
|
+
* `$contains` divergence (design spec §2, elected — not "fixed"): pg's
|
|
769
|
+
* `ILIKE` has no MySQL equivalent, so this compiles to plain `LIKE`. The
|
|
770
|
+
* store `value` columns keep the schema's default `utf8mb4_0900_ai_ci`
|
|
771
|
+
* collation, which is case- *and* accent-insensitive — a strictly wider
|
|
772
|
+
* match than pg's case-insensitive-only `ILIKE`. `byline_document_paths
|
|
773
|
+
* .path` is the one column this operator also reaches (via
|
|
774
|
+
* `buildDocColumnFilter`'s `path` branch) that is NOT `ai_ci` — it's
|
|
775
|
+
* `utf8mb4_bin` (`varcharCaseSensitive`, `database/schema/common.ts`), so
|
|
776
|
+
* a `$contains` against `path` stays byte-exact and matches pg exactly.
|
|
777
|
+
* Both behaviours fall out of the column's own collation; nothing here
|
|
778
|
+
* special-cases `path` vs a store value.
|
|
779
|
+
*/
|
|
780
|
+
private buildFilterCondition;
|
|
781
|
+
/**
|
|
782
|
+
* Build an ORDER BY clause for a document-level column. `path` is
|
|
783
|
+
* intentionally not sortable here — see pg's docblock.
|
|
784
|
+
*
|
|
785
|
+
* Divergence from pg (found by reasoning, confirmed live against this
|
|
786
|
+
* container): MySQL has no `NULLS LAST`. For `DESC`, MySQL already sorts
|
|
787
|
+
* NULL last by default, so `d.order_key DESC` needs no help. For `ASC`,
|
|
788
|
+
* MySQL's default sorts NULL *first* — the opposite of pg's `NULLS LAST`
|
|
789
|
+
* — so the emulation idiom `ORDER BY (col IS NULL), col ASC` is required
|
|
790
|
+
* to match pg's behaviour (`(col IS NULL)` evaluates 0/1; ascending puts
|
|
791
|
+
* real values, which evaluate to 0, before NULLs, which evaluate to 1).
|
|
792
|
+
*/
|
|
793
|
+
private buildDocumentOrderClause;
|
|
794
|
+
/**
|
|
795
|
+
* Converts a union field row back into an array of FlattenedStore that
|
|
796
|
+
* the reconstruction utilities expect. Pure JS mapping — ports unchanged
|
|
797
|
+
* from pg.
|
|
798
|
+
*/
|
|
799
|
+
private convertUnionRowToFlattenedStores;
|
|
800
|
+
}
|
|
801
|
+
export declare function createQueryBuilders(db: DatabaseConnection, collections: readonly CollectionDefinition[], defaultContentLocale: string, transactionDb: DBManager): {
|
|
802
|
+
collections: CollectionQueries;
|
|
803
|
+
documents: DocumentQueries;
|
|
804
|
+
};
|
|
805
|
+
export {};
|