@byline/client 3.13.3 → 3.15.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/dist/client.d.ts +4 -1
- package/dist/client.js +7 -0
- package/dist/collection-handle.d.ts +44 -2
- package/dist/collection-handle.js +160 -1
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +68 -2
- package/package.json +4 -4
package/dist/client.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
import { type RequestContext } from '@byline/auth';
|
|
9
|
-
import type { BylineLogger, CollectionDefinition, IDbAdapter, IStorageProvider, RichTextPopulateFn, SlugifierFn } from '@byline/core';
|
|
9
|
+
import type { BylineLogger, CollectionDefinition, IDbAdapter, IStorageProvider, RichTextPopulateFn, RichTextToTextFn, SearchProvider, SlugifierFn } from '@byline/core';
|
|
10
10
|
import { CollectionHandle } from './collection-handle.js';
|
|
11
11
|
import type { BylineClientConfig } from './types.js';
|
|
12
12
|
/**
|
|
@@ -22,6 +22,9 @@ export declare class BylineClient {
|
|
|
22
22
|
readonly defaultLocale: string;
|
|
23
23
|
readonly slugifier: SlugifierFn | undefined;
|
|
24
24
|
readonly richTextPopulate: RichTextPopulateFn | undefined;
|
|
25
|
+
readonly search: SearchProvider | undefined;
|
|
26
|
+
readonly richTextToText: RichTextToTextFn | undefined;
|
|
27
|
+
readonly contentLocales: string[];
|
|
25
28
|
/** Cache: collection path → database row id + schema version. */
|
|
26
29
|
private collectionRecordCache;
|
|
27
30
|
/** The raw `requestContext` config value, stored for per-call resolution. */
|
package/dist/client.js
CHANGED
|
@@ -56,6 +56,9 @@ export class BylineClient {
|
|
|
56
56
|
defaultLocale;
|
|
57
57
|
slugifier;
|
|
58
58
|
richTextPopulate;
|
|
59
|
+
search;
|
|
60
|
+
richTextToText;
|
|
61
|
+
contentLocales;
|
|
59
62
|
/** Cache: collection path → database row id + schema version. */
|
|
60
63
|
collectionRecordCache = new Map();
|
|
61
64
|
/** The raw `requestContext` config value, stored for per-call resolution. */
|
|
@@ -82,6 +85,10 @@ export class BylineClient {
|
|
|
82
85
|
this.slugifier = config.slugifier ?? fromConfig?.slugifier;
|
|
83
86
|
this.richTextPopulate =
|
|
84
87
|
config.richTextPopulate ?? fromConfig?.fields?.richText?.populate ?? undefined;
|
|
88
|
+
this.search = config.search ?? fromConfig?.search ?? undefined;
|
|
89
|
+
this.richTextToText = config.richTextToText ?? fromConfig?.fields?.richText?.toText ?? undefined;
|
|
90
|
+
this.contentLocales = config.contentLocales ??
|
|
91
|
+
fromConfig?.i18n?.content?.locales ?? [this.defaultLocale];
|
|
85
92
|
this.requestContextSource = config.requestContext;
|
|
86
93
|
}
|
|
87
94
|
/**
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import type { AuditLogPage, ChangeStatusResult, CollectionDefinition, CreateDocumentResult, DeleteDocumentResult, RestoreVersionResult, UnpublishResult, UpdateDocumentResult } from '@byline/core';
|
|
8
|
+
import type { AuditLogPage, ChangeStatusResult, CollectionDefinition, CreateDocumentResult, DeleteDocumentResult, RestoreVersionResult, SearchResults, UnpublishResult, UpdateDocumentResult } from '@byline/core';
|
|
9
9
|
import type { BylineClient } from './client.js';
|
|
10
|
-
import type { AuditLogOptions, ClientDocument, CreateOptions, FindByIdOptions, FindByPathOptions, FindByVersionOptions, FindOneOptions, FindOptions, FindResult, GetAncestorsOptions, GetSubtreeOptions, HistoryOptions, PlaceTreeNodeOptions, TreeNode, UpdateOptions } from './types.js';
|
|
10
|
+
import type { AuditLogOptions, ClientDocument, CollectionSearchOptions, CreateOptions, FindByIdOptions, FindByPathOptions, FindByVersionOptions, FindOneOptions, FindOptions, FindResult, GetAncestorsOptions, GetSubtreeOptions, HistoryOptions, PlaceTreeNodeOptions, ReindexResult, TreeNode, UpdateOptions } from './types.js';
|
|
11
11
|
/**
|
|
12
12
|
* A handle scoped to a single collection. Provides read (and eventually write)
|
|
13
13
|
* operations against that collection's documents.
|
|
@@ -33,6 +33,48 @@ export declare class CollectionHandle {
|
|
|
33
33
|
* document matches.
|
|
34
34
|
*/
|
|
35
35
|
findOne<F = Record<string, any>>(options?: FindOneOptions<F>): Promise<ClientDocument<F> | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Ranked full-text search scoped to this collection, delegated to the
|
|
38
|
+
* registered `SearchProvider` (see `ServerConfig.search`). Returns the
|
|
39
|
+
* lightweight hit tier — `title`, `path`, `score`, and matched-snippet
|
|
40
|
+
* `highlights` — enough to render a results list without hydration; fetch
|
|
41
|
+
* the hit ids via `findById` when a richer item is needed.
|
|
42
|
+
*
|
|
43
|
+
* Asserts the collection `read` ability first (same gate as the other
|
|
44
|
+
* reads), and defaults `status` to `'published'`, so a public viewer only
|
|
45
|
+
* sees published content — which is also all the index holds, since
|
|
46
|
+
* indexing is published-only.
|
|
47
|
+
*
|
|
48
|
+
* Throws `ERR_VALIDATION` when no provider is registered.
|
|
49
|
+
*/
|
|
50
|
+
search(options: CollectionSearchOptions): Promise<SearchResults>;
|
|
51
|
+
/**
|
|
52
|
+
* Re-sync one document into the search index across every content locale.
|
|
53
|
+
* Reads the document's *published* view per locale (`onMissingLocale: 'omit'`)
|
|
54
|
+
* and `upsert`s where present, `remove`s where absent — so publish, unpublish,
|
|
55
|
+
* draft-over-published, and plain edits all converge on the same idempotent
|
|
56
|
+
* path. The index always mirrors what a public reader can see.
|
|
57
|
+
*/
|
|
58
|
+
indexDocument(documentId: string): Promise<void>;
|
|
59
|
+
/** Remove one document from the search index entirely (all locales). */
|
|
60
|
+
removeFromIndex(documentId: string): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Rebuild this collection's entire search index from its published
|
|
63
|
+
* documents. Clears the existing slice (dropping orphans for deleted docs),
|
|
64
|
+
* then walks every published document (paginated) and re-indexes it. Used for
|
|
65
|
+
* first-time backfill, after a `search` config change, or a driver swap.
|
|
66
|
+
*
|
|
67
|
+
* Asserts the collection `reindex` ability. No-op (returns zero counts) when
|
|
68
|
+
* the collection doesn't opt into search or no provider is registered.
|
|
69
|
+
*/
|
|
70
|
+
reindex(): Promise<ReindexResult>;
|
|
71
|
+
/**
|
|
72
|
+
* Build the populate map for this collection's facet relation fields so each
|
|
73
|
+
* target arrives with the two fields the assembler needs: its `counter` field
|
|
74
|
+
* (the aggregation id) and its identity field (`useAsTitle`, the term).
|
|
75
|
+
* Returns `undefined` when the collection declares no facets.
|
|
76
|
+
*/
|
|
77
|
+
private buildSearchFacetPopulateMap;
|
|
36
78
|
/**
|
|
37
79
|
* Find a document by its logical document ID.
|
|
38
80
|
*/
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import { applyAfterRead, applyBeforeRead, assertActorCanPerform, changeDocumentStatus, createDocument, createReadContext, deleteDocument, ERR_VALIDATION, mergePredicates, parseSort, parseWhere, placeTreeNode as placeTreeNodeLifecycle, populateDocuments, populateRichTextFields, removeFromTree as removeFromTreeLifecycle, restoreDocumentVersion, unpublishDocument, updateDocument, } from '@byline/core';
|
|
8
|
+
import { applyAfterRead, applyBeforeRead, assertActorCanPerform, buildSearchDocument, changeDocumentStatus, createDocument, createReadContext, deleteDocument, ERR_VALIDATION, mergePredicates, parseSort, parseWhere, placeTreeNode as placeTreeNodeLifecycle, populateDocuments, populateRichTextFields, removeFromTree as removeFromTreeLifecycle, resolveIdentityField, restoreDocumentVersion, unpublishDocument, updateDocument, } from '@byline/core';
|
|
9
9
|
import { shapeDocument, shapePopulatedInPlace } from './response.js';
|
|
10
10
|
/**
|
|
11
11
|
* A handle scoped to a single collection. Provides read (and eventually write)
|
|
@@ -103,6 +103,165 @@ export class CollectionHandle {
|
|
|
103
103
|
});
|
|
104
104
|
return result.docs[0] ?? null;
|
|
105
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Ranked full-text search scoped to this collection, delegated to the
|
|
108
|
+
* registered `SearchProvider` (see `ServerConfig.search`). Returns the
|
|
109
|
+
* lightweight hit tier — `title`, `path`, `score`, and matched-snippet
|
|
110
|
+
* `highlights` — enough to render a results list without hydration; fetch
|
|
111
|
+
* the hit ids via `findById` when a richer item is needed.
|
|
112
|
+
*
|
|
113
|
+
* Asserts the collection `read` ability first (same gate as the other
|
|
114
|
+
* reads), and defaults `status` to `'published'`, so a public viewer only
|
|
115
|
+
* sees published content — which is also all the index holds, since
|
|
116
|
+
* indexing is published-only.
|
|
117
|
+
*
|
|
118
|
+
* Throws `ERR_VALIDATION` when no provider is registered.
|
|
119
|
+
*/
|
|
120
|
+
async search(options) {
|
|
121
|
+
await this.resolveAndAssertRead();
|
|
122
|
+
const provider = this.client.search;
|
|
123
|
+
if (provider == null) {
|
|
124
|
+
throw ERR_VALIDATION({
|
|
125
|
+
message: 'No search provider is registered. Register one on ServerConfig.search — ' +
|
|
126
|
+
'see `@byline/search-postgres` → `postgresSearch()` for the built-in driver.',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return provider.search({
|
|
130
|
+
query: options.query,
|
|
131
|
+
collectionPath: this.definition.path,
|
|
132
|
+
locale: options.locale ?? this.client.defaultLocale,
|
|
133
|
+
status: resolveReadMode(options.status),
|
|
134
|
+
where: options.where,
|
|
135
|
+
facets: options.facets,
|
|
136
|
+
limit: options.limit,
|
|
137
|
+
offset: options.offset,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// -------------------------------------------------------------------------
|
|
141
|
+
// Search index maintenance
|
|
142
|
+
//
|
|
143
|
+
// `indexDocument` / `removeFromIndex` are the canonical per-document sync the
|
|
144
|
+
// collection lifecycle hooks call; `reindex` is the bulk rebuild (backfill /
|
|
145
|
+
// config change / driver swap). All are no-ops when the collection doesn't
|
|
146
|
+
// opt into search or no provider is registered. System operations — reads use
|
|
147
|
+
// `_bypassBeforeRead` so the index reflects every published document.
|
|
148
|
+
// -------------------------------------------------------------------------
|
|
149
|
+
/**
|
|
150
|
+
* Re-sync one document into the search index across every content locale.
|
|
151
|
+
* Reads the document's *published* view per locale (`onMissingLocale: 'omit'`)
|
|
152
|
+
* and `upsert`s where present, `remove`s where absent — so publish, unpublish,
|
|
153
|
+
* draft-over-published, and plain edits all converge on the same idempotent
|
|
154
|
+
* path. The index always mirrors what a public reader can see.
|
|
155
|
+
*/
|
|
156
|
+
async indexDocument(documentId) {
|
|
157
|
+
const provider = this.client.search;
|
|
158
|
+
if (provider == null || this.definition.search == null)
|
|
159
|
+
return;
|
|
160
|
+
const populate = this.buildSearchFacetPopulateMap();
|
|
161
|
+
for (const locale of this.client.contentLocales) {
|
|
162
|
+
const view = await this.findById(documentId, {
|
|
163
|
+
locale,
|
|
164
|
+
status: 'published',
|
|
165
|
+
onMissingLocale: 'omit',
|
|
166
|
+
populate,
|
|
167
|
+
_bypassBeforeRead: true,
|
|
168
|
+
});
|
|
169
|
+
if (view == null) {
|
|
170
|
+
await provider.remove({ collectionPath: this.definition.path, documentId, locale });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
await provider.upsert(buildSearchDocument({
|
|
174
|
+
documentId: view.id,
|
|
175
|
+
locale,
|
|
176
|
+
status: view.status,
|
|
177
|
+
path: view.path,
|
|
178
|
+
fields: view.fields,
|
|
179
|
+
updatedAt: view.updatedAt,
|
|
180
|
+
}, this.definition, {
|
|
181
|
+
locale,
|
|
182
|
+
richTextToText: this.client.richTextToText,
|
|
183
|
+
resolveTargetDefinition: (path) => this.client.collections.find((c) => c.path === path) ?? null,
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/** Remove one document from the search index entirely (all locales). */
|
|
188
|
+
async removeFromIndex(documentId) {
|
|
189
|
+
const provider = this.client.search;
|
|
190
|
+
if (provider == null)
|
|
191
|
+
return;
|
|
192
|
+
await provider.remove({ collectionPath: this.definition.path, documentId });
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Rebuild this collection's entire search index from its published
|
|
196
|
+
* documents. Clears the existing slice (dropping orphans for deleted docs),
|
|
197
|
+
* then walks every published document (paginated) and re-indexes it. Used for
|
|
198
|
+
* first-time backfill, after a `search` config change, or a driver swap.
|
|
199
|
+
*
|
|
200
|
+
* Asserts the collection `reindex` ability. No-op (returns zero counts) when
|
|
201
|
+
* the collection doesn't opt into search or no provider is registered.
|
|
202
|
+
*/
|
|
203
|
+
async reindex() {
|
|
204
|
+
const requestContext = await this.client.resolveRequestContext();
|
|
205
|
+
assertActorCanPerform(requestContext, this.definition.path, 'reindex');
|
|
206
|
+
const provider = this.client.search;
|
|
207
|
+
const result = { collectionPath: this.definition.path, documents: 0, indexed: 0 };
|
|
208
|
+
if (provider == null || this.definition.search == null)
|
|
209
|
+
return result;
|
|
210
|
+
// Clear the slice so deleted documents don't leave orphan rows.
|
|
211
|
+
await provider.reindex?.({ collectionPath: this.definition.path });
|
|
212
|
+
const pageSize = 100;
|
|
213
|
+
let page = 1;
|
|
214
|
+
for (;;) {
|
|
215
|
+
const batch = await this.find({
|
|
216
|
+
status: 'published',
|
|
217
|
+
page,
|
|
218
|
+
pageSize,
|
|
219
|
+
_bypassBeforeRead: true,
|
|
220
|
+
});
|
|
221
|
+
for (const doc of batch.docs) {
|
|
222
|
+
await this.indexDocument(doc.id);
|
|
223
|
+
result.documents++;
|
|
224
|
+
}
|
|
225
|
+
if (page >= batch.meta.totalPages || batch.docs.length === 0)
|
|
226
|
+
break;
|
|
227
|
+
page++;
|
|
228
|
+
}
|
|
229
|
+
// `indexed` mirrors documents × locales present; the provider upserts are
|
|
230
|
+
// the source of truth, so report the document count walked.
|
|
231
|
+
result.indexed = result.documents;
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Build the populate map for this collection's facet relation fields so each
|
|
236
|
+
* target arrives with the two fields the assembler needs: its `counter` field
|
|
237
|
+
* (the aggregation id) and its identity field (`useAsTitle`, the term).
|
|
238
|
+
* Returns `undefined` when the collection declares no facets.
|
|
239
|
+
*/
|
|
240
|
+
buildSearchFacetPopulateMap() {
|
|
241
|
+
const facets = this.definition.search?.facets;
|
|
242
|
+
if (facets == null || facets.length === 0)
|
|
243
|
+
return undefined;
|
|
244
|
+
const map = {};
|
|
245
|
+
for (const decl of facets) {
|
|
246
|
+
const name = typeof decl === 'string' ? decl : decl.field;
|
|
247
|
+
const field = this.definition.fields.find((f) => f.name === name);
|
|
248
|
+
if (field == null || field.type !== 'relation')
|
|
249
|
+
continue;
|
|
250
|
+
const targetPath = field.targetCollection;
|
|
251
|
+
const targetDef = targetPath
|
|
252
|
+
? this.client.collections.find((c) => c.path === targetPath)
|
|
253
|
+
: undefined;
|
|
254
|
+
const select = [];
|
|
255
|
+
const idField = targetDef?.fields.find((f) => f.type === 'counter')?.name;
|
|
256
|
+
const termField = targetDef ? resolveIdentityField(targetDef) : undefined;
|
|
257
|
+
if (idField)
|
|
258
|
+
select.push(idField);
|
|
259
|
+
if (termField)
|
|
260
|
+
select.push(termField);
|
|
261
|
+
map[name] = select.length > 0 ? { select } : true;
|
|
262
|
+
}
|
|
263
|
+
return Object.keys(map).length > 0 ? map : undefined;
|
|
264
|
+
}
|
|
106
265
|
/**
|
|
107
266
|
* Find a document by its logical document ID.
|
|
108
267
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
export { type AuditLogEntry, type AuditLogPage, type ChangeStatusResult, type CreateDocumentResult, type CycleRelationValue, createReadContext, type DeleteDocumentResult, ERR_CONFLICT, ERR_INVALID_TRANSITION, ERR_NOT_FOUND, ERR_READ_BUDGET_EXCEEDED, ERR_VALIDATION, type PopulatedRelationValue, type PopulateFieldOptions, type PopulateFieldSpec, type PopulateMap, type PopulateSpec, type ReadContext, type ReadMode, type RestoreVersionResult, type UnpublishResult, type UnresolvedRelationValue, type UpdateDocumentResult, } from '@byline/core';
|
|
9
9
|
export { BylineClient, createBylineClient } from './client.js';
|
|
10
10
|
export { CollectionHandle } from './collection-handle.js';
|
|
11
|
-
export type { AuditLogOptions, BylineClientConfig, ClientDocument, CreateOptions, FilterOperators, FindByIdOptions, FindByPathOptions, FindOneOptions, FindOptions, FindResult, GetAncestorsOptions, GetSubtreeOptions, PlaceTreeNodeOptions, PopulatedRelation, SortDirection, SortSpec, TreeNode, UpdateOptions, WhereClause, WhereValue, WithPopulated, } from './types.js';
|
|
11
|
+
export type { AuditLogOptions, BylineClientConfig, ClientDocument, CollectionSearchOptions, CreateOptions, FilterOperators, FindByIdOptions, FindByPathOptions, FindOneOptions, FindOptions, FindResult, GetAncestorsOptions, GetSubtreeOptions, PlaceTreeNodeOptions, PopulatedRelation, ReindexResult, SearchFacetBucket, SearchHit, SearchResults, SortDirection, SortSpec, TreeNode, UpdateOptions, WhereClause, WhereValue, WithPopulated, WithPopulatedMany, } from './types.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
import type { RequestContext } from '@byline/auth';
|
|
9
|
-
import type { BylineLogger, CollectionDefinition, IDbAdapter, IStorageProvider, MissingLocalePolicy, PopulateSpec, PredicateValue, QueryPredicate, ReadContext, ReadMode, RichTextPopulateFn, ServerConfig, SlugifierFn, SortSpec } from '@byline/core';
|
|
10
|
-
export type { FilterOperators, PredicateValue, QueryPredicate, SortDirection, SortSpec, } from '@byline/core';
|
|
9
|
+
import type { BylineLogger, CollectionDefinition, IDbAdapter, IStorageProvider, MissingLocalePolicy, PopulateSpec, PredicateValue, QueryPredicate, ReadContext, ReadMode, RichTextPopulateFn, RichTextToTextFn, SearchProvider, ServerConfig, SlugifierFn, SortSpec } from '@byline/core';
|
|
10
|
+
export type { FilterOperators, PredicateValue, QueryPredicate, SearchFacetBucket, SearchHit, SearchProvider, SearchResults, SortDirection, SortSpec, } from '@byline/core';
|
|
11
11
|
export interface BylineClientConfig {
|
|
12
12
|
/**
|
|
13
13
|
* Convenience shorthand: pass an already-resolved `ServerConfig` (typically
|
|
@@ -34,6 +34,26 @@ export interface BylineClientConfig {
|
|
|
34
34
|
collections?: CollectionDefinition[];
|
|
35
35
|
/** Optional storage provider — needed for delete file cleanup. */
|
|
36
36
|
storage?: IStorageProvider;
|
|
37
|
+
/**
|
|
38
|
+
* Optional search provider (`ServerConfig.search`). When present,
|
|
39
|
+
* `collection(path).search(...)` delegates ranked queries to it. When
|
|
40
|
+
* omitted (and not provided via the `config` shorthand's `search`), calling
|
|
41
|
+
* `search()` throws a clear error pointing at provider registration.
|
|
42
|
+
*/
|
|
43
|
+
search?: SearchProvider;
|
|
44
|
+
/**
|
|
45
|
+
* Optional richtext plain-text extractor (`ServerConfig.fields.richText.toText`).
|
|
46
|
+
* Used by the search-indexing methods (`indexDocument` / `reindex`) to feed
|
|
47
|
+
* a collection's searchable `body` from rich-text fields. When omitted, those
|
|
48
|
+
* fields are skipped.
|
|
49
|
+
*/
|
|
50
|
+
richTextToText?: RichTextToTextFn;
|
|
51
|
+
/**
|
|
52
|
+
* The installation's content locales (`ServerConfig.i18n.content.locales`).
|
|
53
|
+
* The search-indexing methods iterate them to index one row per
|
|
54
|
+
* `(document, locale)`. Falls back to `[defaultLocale]` when not provided.
|
|
55
|
+
*/
|
|
56
|
+
contentLocales?: string[];
|
|
37
57
|
/**
|
|
38
58
|
* Optional logger. Used by the write path to emit structured events
|
|
39
59
|
* from `document-lifecycle`. When omitted, the client falls back to
|
|
@@ -138,6 +158,38 @@ interface PopulateControls {
|
|
|
138
158
|
interface StatusControls {
|
|
139
159
|
status?: ReadMode;
|
|
140
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Options for `CollectionHandle.search(...)` — a ranked full-text query
|
|
163
|
+
* scoped to this collection, delegated to the registered `SearchProvider`.
|
|
164
|
+
* `collectionPath` is implied by the handle, so callers supply only the
|
|
165
|
+
* query and optional scoping. `status` defaults to `'published'` (public
|
|
166
|
+
* readers); pass `'any'` for admin contexts.
|
|
167
|
+
*/
|
|
168
|
+
export interface CollectionSearchOptions {
|
|
169
|
+
/** Free-text query string. */
|
|
170
|
+
query: string;
|
|
171
|
+
/** Restrict to a single content locale (defaults to the client default). */
|
|
172
|
+
locale?: string;
|
|
173
|
+
/** Read mode — `'published'` (default) or `'any'`. */
|
|
174
|
+
status?: ReadMode;
|
|
175
|
+
/** Structured filters AND-merged with the text query (driver-dependent). */
|
|
176
|
+
where?: QueryPredicate;
|
|
177
|
+
/** Field names to compute facet buckets for (driver-capability gated). */
|
|
178
|
+
facets?: string[];
|
|
179
|
+
/** Max hits to return. */
|
|
180
|
+
limit?: number;
|
|
181
|
+
/** Offset for pagination. */
|
|
182
|
+
offset?: number;
|
|
183
|
+
}
|
|
184
|
+
/** Outcome of a collection `reindex()` — counts for reporting. */
|
|
185
|
+
export interface ReindexResult {
|
|
186
|
+
/** Collection that was reindexed. */
|
|
187
|
+
collectionPath: string;
|
|
188
|
+
/** Number of published documents walked. */
|
|
189
|
+
documents: number;
|
|
190
|
+
/** Number of `(document, locale)` index rows upserted. */
|
|
191
|
+
indexed: number;
|
|
192
|
+
}
|
|
141
193
|
/**
|
|
142
194
|
* What a read does when the requested content locale is missing. Shared by
|
|
143
195
|
* every read method. `@byline/client` defaults this to `'fallback'`.
|
|
@@ -509,3 +561,17 @@ export interface PopulatedRelation<T> {
|
|
|
509
561
|
export type WithPopulated<F, K extends keyof F, Target> = {
|
|
510
562
|
[P in keyof F]: P extends K ? PopulatedRelation<Target> : F[P];
|
|
511
563
|
};
|
|
564
|
+
/**
|
|
565
|
+
* `hasMany` counterpart of {@link WithPopulated}: re-types key `K` as an
|
|
566
|
+
* **ordered array** of populated relation envelopes. Use for `hasMany: true`
|
|
567
|
+
* relation fields, whose populated value is `PopulatedRelation<Target>[]` (one
|
|
568
|
+
* envelope per referenced target, in stored order).
|
|
569
|
+
*
|
|
570
|
+
* ```ts
|
|
571
|
+
* type ArticlePopulated = WithPopulatedMany<ArticleFields, 'authors', AuthorFields>
|
|
572
|
+
* client.collection('articles').find<ArticlePopulated>({ populate: { authors: '*' } })
|
|
573
|
+
* ```
|
|
574
|
+
*/
|
|
575
|
+
export type WithPopulatedMany<F, K extends keyof F, Target> = {
|
|
576
|
+
[P in keyof F]: P extends K ? Array<PopulatedRelation<Target>> : F[P];
|
|
577
|
+
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@byline/client",
|
|
3
3
|
"private": false,
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.15.0",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"npm-run-all": "^4.1.5",
|
|
41
|
-
"@byline/auth": "3.
|
|
42
|
-
"@byline/core": "3.
|
|
41
|
+
"@byline/auth": "3.15.0",
|
|
42
|
+
"@byline/core": "3.15.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@biomejs/biome": "2.4.15",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"tsx": "^4.22.3",
|
|
52
52
|
"typescript": "6.0.3",
|
|
53
53
|
"vitest": "^4.1.7",
|
|
54
|
-
"@byline/db-postgres": "3.
|
|
54
|
+
"@byline/db-postgres": "3.15.0"
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public",
|