@byline/db-postgres 3.0.2 → 3.1.1
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.
|
@@ -100,8 +100,9 @@ export declare class DocumentQueries implements IDocumentQueries {
|
|
|
100
100
|
/**
|
|
101
101
|
* Batch-fetch the version-locale availability sets from the
|
|
102
102
|
* `byline_document_version_locales` ledger. For each version returns the
|
|
103
|
-
* concrete locales its content is complete in (`availableLocales`,
|
|
104
|
-
* or `localeAgnostic: true` when the
|
|
103
|
+
* concrete locales its content is complete in (`availableLocales`, in
|
|
104
|
+
* configured content-locale order), or `localeAgnostic: true` when the
|
|
105
|
+
* version carries only the `'all'`
|
|
105
106
|
* sentinel (no localized content → renders identically in every locale).
|
|
106
107
|
* Drives the `_availableVersionLocales` read metadata. One indexed query per call.
|
|
107
108
|
*/
|
|
@@ -109,8 +110,9 @@ export declare class DocumentQueries implements IDocumentQueries {
|
|
|
109
110
|
/**
|
|
110
111
|
* Batch-fetch the editorial advertised-locale sets from
|
|
111
112
|
* `byline_document_available_locales` (document-grain). For each logical
|
|
112
|
-
* document returns the
|
|
113
|
-
*
|
|
113
|
+
* document returns the set of locales the editor has elected to advertise,
|
|
114
|
+
* in configured content-locale order. Surfaced on reads as
|
|
115
|
+
* `availableLocales` — the deliberate
|
|
114
116
|
* counterpart to the version-grain `_availableVersionLocales` ledger fact;
|
|
115
117
|
* the public advertised set is their intersection. One indexed query per
|
|
116
118
|
* call. See docs/I18N.md.
|
|
@@ -216,6 +218,20 @@ export declare class DocumentQueries implements IDocumentQueries {
|
|
|
216
218
|
created_at: Date;
|
|
217
219
|
updated_at: Date;
|
|
218
220
|
} | null>;
|
|
221
|
+
/**
|
|
222
|
+
* getCurrentPath — resolve a document's canonical (source-locale) path.
|
|
223
|
+
*
|
|
224
|
+
* Reuses `pathProjection` against `current_documents`, passing
|
|
225
|
+
* `requestedLocale: undefined` so the projection's fallback floor — the
|
|
226
|
+
* document's own `source_locale` (COALESCE-guarded to the default content
|
|
227
|
+
* locale for not-yet-anchored rows) — supplies the canonical path. Used by
|
|
228
|
+
* the lifecycle to populate `path` on the status-change / unpublish hook
|
|
229
|
+
* contexts. Returns `null` when no path row (or document) exists.
|
|
230
|
+
*/
|
|
231
|
+
getCurrentPath({ collection_id, document_id, }: {
|
|
232
|
+
collection_id: string;
|
|
233
|
+
document_id: string;
|
|
234
|
+
}): Promise<string | null>;
|
|
219
235
|
/**
|
|
220
236
|
* getDocumentById — gets the current version of a document by its logical document ID.
|
|
221
237
|
*
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// constructs query/command classes before initBylineCore() wires up the Pino
|
|
10
10
|
// logger. A future refactor could inject the logger at construction time by
|
|
11
11
|
// either deferring adapter construction or accepting a lazy logger parameter.
|
|
12
|
-
import { ERR_DATABASE, ERR_NOT_FOUND, getLogger } from '@byline/core';
|
|
12
|
+
import { ERR_DATABASE, ERR_NOT_FOUND, getLogger, orderByContentLocale } from '@byline/core';
|
|
13
13
|
import { and, desc, eq, inArray, isNotNull, sql } from 'drizzle-orm';
|
|
14
14
|
import { collections, currentDocumentsView, currentPublishedDocumentsView, documentAvailableLocales, documentPaths, documents, documentVersionLocales, documentVersions, metaStore, } from '../../database/schema/index.js';
|
|
15
15
|
import { extractFlattenedFieldValue, restoreFieldSetData } from './storage-restore.js';
|
|
@@ -133,8 +133,9 @@ export class DocumentQueries {
|
|
|
133
133
|
/**
|
|
134
134
|
* Batch-fetch the version-locale availability sets from the
|
|
135
135
|
* `byline_document_version_locales` ledger. For each version returns the
|
|
136
|
-
* concrete locales its content is complete in (`availableLocales`,
|
|
137
|
-
* or `localeAgnostic: true` when the
|
|
136
|
+
* concrete locales its content is complete in (`availableLocales`, in
|
|
137
|
+
* configured content-locale order), or `localeAgnostic: true` when the
|
|
138
|
+
* version carries only the `'all'`
|
|
138
139
|
* sentinel (no localized content → renders identically in every locale).
|
|
139
140
|
* Drives the `_availableVersionLocales` read metadata. One indexed query per call.
|
|
140
141
|
*/
|
|
@@ -160,15 +161,20 @@ export class DocumentQueries {
|
|
|
160
161
|
else
|
|
161
162
|
entry.availableLocales.push(row.locale);
|
|
162
163
|
}
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
// Stable, config-driven order — `_availableVersionLocales` is a set
|
|
165
|
+
// consumed via membership, so this just keeps its array order canonical
|
|
166
|
+
// (matching `availableLocales`). Falls back to a–z when no config.
|
|
167
|
+
for (const entry of result.values()) {
|
|
168
|
+
entry.availableLocales = orderByContentLocale(entry.availableLocales);
|
|
169
|
+
}
|
|
165
170
|
return result;
|
|
166
171
|
}
|
|
167
172
|
/**
|
|
168
173
|
* Batch-fetch the editorial advertised-locale sets from
|
|
169
174
|
* `byline_document_available_locales` (document-grain). For each logical
|
|
170
|
-
* document returns the
|
|
171
|
-
*
|
|
175
|
+
* document returns the set of locales the editor has elected to advertise,
|
|
176
|
+
* in configured content-locale order. Surfaced on reads as
|
|
177
|
+
* `availableLocales` — the deliberate
|
|
172
178
|
* counterpart to the version-grain `_availableVersionLocales` ledger fact;
|
|
173
179
|
* the public advertised set is their intersection. One indexed query per
|
|
174
180
|
* call. See docs/I18N.md.
|
|
@@ -192,8 +198,12 @@ export class DocumentQueries {
|
|
|
192
198
|
}
|
|
193
199
|
arr.push(row.locale);
|
|
194
200
|
}
|
|
195
|
-
|
|
196
|
-
|
|
201
|
+
// Project `availableLocales` in configured content-locale order (the set
|
|
202
|
+
// is order-insensitive). Read-time only — nothing persisted changes, and
|
|
203
|
+
// unknown/removed codes sort last rather than throwing. Falls back to a–z
|
|
204
|
+
// when no server config is registered (e.g. isolated storage tests).
|
|
205
|
+
for (const [did, arr] of result)
|
|
206
|
+
result.set(did, orderByContentLocale(arr));
|
|
197
207
|
return result;
|
|
198
208
|
}
|
|
199
209
|
/**
|
|
@@ -439,6 +449,26 @@ export class DocumentQueries {
|
|
|
439
449
|
updated_at: row.updated_at ?? new Date(),
|
|
440
450
|
};
|
|
441
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* getCurrentPath — resolve a document's canonical (source-locale) path.
|
|
454
|
+
*
|
|
455
|
+
* Reuses `pathProjection` against `current_documents`, passing
|
|
456
|
+
* `requestedLocale: undefined` so the projection's fallback floor — the
|
|
457
|
+
* document's own `source_locale` (COALESCE-guarded to the default content
|
|
458
|
+
* locale for not-yet-anchored rows) — supplies the canonical path. Used by
|
|
459
|
+
* the lifecycle to populate `path` on the status-change / unpublish hook
|
|
460
|
+
* contexts. Returns `null` when no path row (or document) exists.
|
|
461
|
+
*/
|
|
462
|
+
async getCurrentPath({ collection_id, document_id, }) {
|
|
463
|
+
const [row] = await this.db
|
|
464
|
+
.select({
|
|
465
|
+
path: this.pathProjection(sql `${currentDocumentsView.document_id}`, undefined, sql `${currentDocumentsView.source_locale}`),
|
|
466
|
+
})
|
|
467
|
+
.from(currentDocumentsView)
|
|
468
|
+
.where(and(eq(currentDocumentsView.collection_id, collection_id), eq(currentDocumentsView.document_id, document_id)))
|
|
469
|
+
.limit(1);
|
|
470
|
+
return row?.path ?? null;
|
|
471
|
+
}
|
|
442
472
|
/**
|
|
443
473
|
* getDocumentById — gets the current version of a document by its logical document ID.
|
|
444
474
|
*
|
|
@@ -181,4 +181,81 @@ describe('byline_document_paths integration', () => {
|
|
|
181
181
|
});
|
|
182
182
|
expect(result).toBe(null);
|
|
183
183
|
});
|
|
184
|
+
describe('getCurrentPath', () => {
|
|
185
|
+
it('resolves a document’s canonical path under its default source locale', async () => {
|
|
186
|
+
const canonicalPath = `current-path-${Date.now()}`;
|
|
187
|
+
const created = await commandBuilders.documents.createDocumentVersion({
|
|
188
|
+
collectionId: testCollection.id,
|
|
189
|
+
collectionVersion: 1,
|
|
190
|
+
collectionConfig: PathsCollectionConfig,
|
|
191
|
+
action: 'create',
|
|
192
|
+
documentData: { title: 'Has Path' },
|
|
193
|
+
path: canonicalPath,
|
|
194
|
+
locale: 'all',
|
|
195
|
+
status: 'draft',
|
|
196
|
+
});
|
|
197
|
+
const documentId = created.document.document_id;
|
|
198
|
+
const path = await queryBuilders.documents.getCurrentPath({
|
|
199
|
+
collection_id: testCollection.id,
|
|
200
|
+
document_id: documentId,
|
|
201
|
+
});
|
|
202
|
+
expect(path).toBe(canonicalPath);
|
|
203
|
+
});
|
|
204
|
+
it('follows the source-locale anchor after a document is re-anchored', async () => {
|
|
205
|
+
const canonicalPath = `reanchor-path-${Date.now()}`;
|
|
206
|
+
// Create locale-agnostic content (ledger carries the 'all' sentinel) so
|
|
207
|
+
// the document is "complete" in any target and re-anchoring is eligible.
|
|
208
|
+
const created = await commandBuilders.documents.createDocumentVersion({
|
|
209
|
+
collectionId: testCollection.id,
|
|
210
|
+
collectionVersion: 1,
|
|
211
|
+
collectionConfig: PathsCollectionConfig,
|
|
212
|
+
action: 'create',
|
|
213
|
+
documentData: { title: 'Re-anchor me' },
|
|
214
|
+
path: canonicalPath,
|
|
215
|
+
locale: 'all',
|
|
216
|
+
status: 'draft',
|
|
217
|
+
});
|
|
218
|
+
const documentId = created.document.document_id;
|
|
219
|
+
// Flip the document's source locale from the default ('en') to 'fr'.
|
|
220
|
+
// reAnchorDocument moves the path row onto the new source locale,
|
|
221
|
+
// keeping the slug. getCurrentPath passes requestedLocale: undefined, so
|
|
222
|
+
// its fallback floor is COALESCE(source_locale, default) — it must now
|
|
223
|
+
// resolve via the 'fr' anchor, not the global default 'en'.
|
|
224
|
+
const result = await commandBuilders.documents.reAnchorDocument({
|
|
225
|
+
documentId,
|
|
226
|
+
targetLocale: 'fr',
|
|
227
|
+
});
|
|
228
|
+
expect(result.status).toBe('reanchored');
|
|
229
|
+
const path = await queryBuilders.documents.getCurrentPath({
|
|
230
|
+
collection_id: testCollection.id,
|
|
231
|
+
document_id: documentId,
|
|
232
|
+
});
|
|
233
|
+
expect(path).toBe(canonicalPath);
|
|
234
|
+
});
|
|
235
|
+
it('returns null when the document has no path row', async () => {
|
|
236
|
+
// Create a version without a `path` — no document_paths row is written.
|
|
237
|
+
const created = await commandBuilders.documents.createDocumentVersion({
|
|
238
|
+
collectionId: testCollection.id,
|
|
239
|
+
collectionVersion: 1,
|
|
240
|
+
collectionConfig: PathsCollectionConfig,
|
|
241
|
+
action: 'create',
|
|
242
|
+
documentData: { title: 'No Path' },
|
|
243
|
+
locale: 'all',
|
|
244
|
+
status: 'draft',
|
|
245
|
+
});
|
|
246
|
+
const documentId = created.document.document_id;
|
|
247
|
+
const path = await queryBuilders.documents.getCurrentPath({
|
|
248
|
+
collection_id: testCollection.id,
|
|
249
|
+
document_id: documentId,
|
|
250
|
+
});
|
|
251
|
+
expect(path).toBe(null);
|
|
252
|
+
});
|
|
253
|
+
it('returns null for a non-existent document', async () => {
|
|
254
|
+
const path = await queryBuilders.documents.getCurrentPath({
|
|
255
|
+
collection_id: testCollection.id,
|
|
256
|
+
document_id: crypto.randomUUID(),
|
|
257
|
+
});
|
|
258
|
+
expect(path).toBe(null);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
184
261
|
});
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@byline/db-postgres",
|
|
3
3
|
"private": false,
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.1.1",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
"pg": "^8.21.0",
|
|
58
58
|
"uuid": "^14.0.0",
|
|
59
59
|
"zod": "^4.4.3",
|
|
60
|
-
"@byline/
|
|
61
|
-
"@byline/
|
|
62
|
-
"@byline/
|
|
60
|
+
"@byline/auth": "3.1.1",
|
|
61
|
+
"@byline/core": "3.1.1",
|
|
62
|
+
"@byline/admin": "3.1.1"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@biomejs/biome": "2.4.15",
|