@byline/client 4.8.0 → 4.9.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.
@@ -186,7 +186,7 @@ export declare class CollectionHandle<TFields extends Record<string, any> = Reco
186
186
  */
187
187
  history<F = TFields>(documentId: string, options?: HistoryOptions): Promise<FindResult<F>>;
188
188
  /**
189
- * Fetch the document-grain audit log for a single document (docs/06-auth-and-security/02-auditability.md —
189
+ * Fetch the document-grain audit log for a single document (docs/07-auth-and-security/02-auditability.md —
190
190
  * Workstream 3): the non-versioned system-field writes (path,
191
191
  * available-locales), in-place status transitions, and the deletion event
192
192
  * the immutable version stream deliberately does not record an actor for.
@@ -8,7 +8,7 @@
8
8
  import { applyAfterRead, assertActorCanPerform, buildSearchDocument, changeDocumentStatus, createDocument, createReadContext, createRichTextDocumentReader, deleteDocument, ERR_VALIDATION, parseSort, parseWhere, placeTreeNode as placeTreeNodeLifecycle, populateDocuments, populateRichTextFields, removeFromTree as removeFromTreeLifecycle, resolveIdentityField, restoreDocumentVersion, unpublishDocument, updateDocument, } from '@byline/core';
9
9
  import { getReadSecurityDomain, resolveReadRequestContext, resolveReadSecurityFilters, } from './read-context.js';
10
10
  import { shapeDocument, shapePopulatedInPlace } from './response.js';
11
- import { finalizeSearchHits } from './search.js';
11
+ import { assertSearchQueryLength, finalizeSearchHits } from './search.js';
12
12
  /**
13
13
  * A handle scoped to a single collection. Provides read (and eventually write)
14
14
  * operations against that collection's documents.
@@ -130,6 +130,7 @@ export class CollectionHandle {
130
130
  * Throws `ERR_VALIDATION` when no provider is registered.
131
131
  */
132
132
  async search(options) {
133
+ assertSearchQueryLength(options.query);
133
134
  const readMode = resolveReadMode(options.status);
134
135
  const readCtx = createReadContext();
135
136
  const requestContext = await this.resolveAndAssertRead(readMode, readCtx);
@@ -144,6 +145,7 @@ export class CollectionHandle {
144
145
  }
145
146
  const results = await provider.search({
146
147
  query: options.query,
148
+ matching: options.matching,
147
149
  collectionPath: this.definition.path,
148
150
  locale: options.locale ?? this.client.defaultLocale,
149
151
  status: readMode,
@@ -240,7 +242,7 @@ export class CollectionHandle {
240
242
  if (provider == null || this.definition.search == null)
241
243
  return result;
242
244
  // Clear the slice so deleted documents don't leave orphan rows.
243
- await provider.reindex?.({ collectionPath: this.definition.path });
245
+ await provider.reindex({ collectionPath: this.definition.path });
244
246
  const pageSize = 100;
245
247
  let page = 1;
246
248
  for (;;) {
@@ -524,7 +526,7 @@ export class CollectionHandle {
524
526
  };
525
527
  }
526
528
  /**
527
- * Fetch the document-grain audit log for a single document (docs/06-auth-and-security/02-auditability.md —
529
+ * Fetch the document-grain audit log for a single document (docs/07-auth-and-security/02-auditability.md —
528
530
  * Workstream 3): the non-versioned system-field writes (path,
529
531
  * available-locales), in-place status transitions, and the deletion event
530
532
  * the immutable version stream deliberately does not record an actor for.
package/dist/response.js CHANGED
@@ -43,7 +43,7 @@ export function shapeDocument(raw) {
43
43
  // `created_by` / `event_type`).
44
44
  // `created_by` is NULL on rows written before audit wiring or by
45
45
  // internal tooling without a request context — omitted here, rendered as
46
- // "unknown" by consumers. See docs/06-auth-and-security/02-auditability.md — Workstream 1.
46
+ // "unknown" by consumers. See docs/07-auth-and-security/02-auditability.md — Workstream 1.
47
47
  if (typeof raw.created_by === 'string') {
48
48
  shaped.createdBy = raw.created_by;
49
49
  }
@@ -55,7 +55,7 @@ export function shapeDocument(raw) {
55
55
  // Storage raw keys already match the surface names (passthrough) —
56
56
  // `availableLocales` is the editorial advertised set (document-grain,
57
57
  // stored), `_availableVersionLocales` is the structural ledger fact
58
- // (version-grain, computed). See docs/07-internationalization/index.md.
58
+ // (version-grain, computed). See docs/08-internationalization/index.md.
59
59
  if (Array.isArray(raw.availableLocales)) {
60
60
  shaped.availableLocales = raw.availableLocales;
61
61
  }
package/dist/search.d.ts CHANGED
@@ -58,3 +58,4 @@ export declare function finalizeSearchHits(params: FinalizeSearchHitsParams): Pr
58
58
  * ability error only when the actor can read none of the members.
59
59
  */
60
60
  export declare function zoneSearch(client: BylineClient<any>, options: ZoneSearchOptions): Promise<ClientSearchResults>;
61
+ export declare function assertSearchQueryLength(query: string): void;
package/dist/search.js CHANGED
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import { assertActorCanPerform, createReadContext, ERR_VALIDATION, getCollectionAdminConfig, resolveItemViewColumns, resolveSearchZones, } from '@byline/core';
8
+ import { assertActorCanPerform, createReadContext, ERR_VALIDATION, getCollectionAdminConfig, MAX_SEARCH_QUERY_LENGTH, resolveItemViewColumns, resolveSearchZones, } from '@byline/core';
9
9
  import { resolveReadRequestContext, resolveReadSecurityFilters } from './read-context.js';
10
10
  /**
11
11
  * Authorise (and optionally hydrate) provider hits, per collection:
@@ -127,6 +127,7 @@ export async function finalizeSearchHits(params) {
127
127
  * ability error only when the actor can read none of the members.
128
128
  */
129
129
  export async function zoneSearch(client, options) {
130
+ assertSearchQueryLength(options.query);
130
131
  const provider = client.searchProvider;
131
132
  if (provider == null) {
132
133
  throw ERR_VALIDATION({
@@ -175,6 +176,7 @@ export async function zoneSearch(client, options) {
175
176
  }
176
177
  const results = await provider.search({
177
178
  query: options.query,
179
+ matching: options.matching,
178
180
  zone: options.zone,
179
181
  locale: options.locale ?? client.defaultLocale,
180
182
  status: readMode,
@@ -199,3 +201,14 @@ export async function zoneSearch(client, options) {
199
201
  ? { hits, total: hits.length }
200
202
  : { hits, total: results.total, facets: results.facets };
201
203
  }
204
+ export function assertSearchQueryLength(query) {
205
+ if (query.length <= MAX_SEARCH_QUERY_LENGTH)
206
+ return;
207
+ throw ERR_VALIDATION({
208
+ message: `Search query exceeds the maximum length of ${MAX_SEARCH_QUERY_LENGTH} UTF-16 code units.`,
209
+ details: {
210
+ maximumLength: MAX_SEARCH_QUERY_LENGTH,
211
+ actualLength: query.length,
212
+ },
213
+ });
214
+ }
@@ -0,0 +1,8 @@
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
+ export {};
@@ -0,0 +1,22 @@
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
+ import { MAX_SEARCH_QUERY_LENGTH } from '@byline/core';
9
+ import { describe, expect, it } from 'vitest';
10
+ import { assertSearchQueryLength } from './search.js';
11
+ describe('search query validation', () => {
12
+ it('accepts the portable query limit and rejects longer input', () => {
13
+ expect(() => assertSearchQueryLength('a'.repeat(MAX_SEARCH_QUERY_LENGTH))).not.toThrow();
14
+ expect(() => assertSearchQueryLength('a'.repeat(MAX_SEARCH_QUERY_LENGTH + 1))).toThrowError(expect.objectContaining({
15
+ code: 'ERR_VALIDATION',
16
+ details: {
17
+ maximumLength: MAX_SEARCH_QUERY_LENGTH,
18
+ actualLength: MAX_SEARCH_QUERY_LENGTH + 1,
19
+ },
20
+ }));
21
+ });
22
+ });
package/dist/types.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, MissingLocalePolicy, PopulateSpec, PredicateValue, QueryPredicate, ReadContext, ReadMode, RichTextPopulateFn, RichTextToTextFn, SearchFacetBucket, SearchHit, SearchProvider, ServerConfig, SlugifierFn, SortSpec } from '@byline/core';
9
+ import type { BylineLogger, CollectionDefinition, IDbAdapter, IStorageProvider, MissingLocalePolicy, PopulateSpec, PredicateValue, QueryPredicate, ReadContext, ReadMode, RichTextPopulateFn, RichTextToTextFn, SearchFacetBucket, SearchHit, SearchMatching, SearchProvider, ServerConfig, SlugifierFn, SortSpec } from '@byline/core';
10
10
  export type { FilterOperators, PredicateValue, QueryPredicate, SearchFacetBucket, SearchHit, SearchProvider, SearchResults, SortDirection, SortSpec, } from '@byline/core';
11
11
  export interface BylineClientConfig {
12
12
  /**
@@ -183,6 +183,8 @@ interface StatusControls {
183
183
  export interface CollectionSearchOptions extends BeforeReadControls {
184
184
  /** Free-text query string. */
185
185
  query: string;
186
+ /** Provider-neutral term and phrase matching semantics. */
187
+ matching?: SearchMatching;
186
188
  /** Restrict to a single content locale (defaults to the client default). */
187
189
  locale?: string;
188
190
  /** Read mode — `'published'` (default) or `'any'`. */
@@ -215,6 +217,8 @@ export interface CollectionSearchOptions extends BeforeReadControls {
215
217
  export interface ZoneSearchOptions extends BeforeReadControls {
216
218
  /** Free-text query string. */
217
219
  query: string;
220
+ /** Provider-neutral term and phrase matching semantics. */
221
+ matching?: SearchMatching;
218
222
  /** The zone (named cross-collection scope) to search. */
219
223
  zone: string;
220
224
  /** Restrict to a single content locale (defaults to the client default). */
@@ -253,7 +257,10 @@ export interface ReindexResult {
253
257
  collectionPath: string;
254
258
  /** Number of published documents walked. */
255
259
  documents: number;
256
- /** Number of `(document, locale)` index rows upserted. */
260
+ /**
261
+ * Currently mirrors `documents` — the provider upserts are the source of
262
+ * truth for how many `(document, locale)` rows were written.
263
+ */
257
264
  indexed: number;
258
265
  }
259
266
  /**
@@ -275,7 +282,7 @@ export interface ReindexResult {
275
282
  * in every locale.
276
283
  *
277
284
  * Availability is the version-grain ledger described in
278
- * `docs/07-internationalization/index.md`. Relationship population always behaves
285
+ * `docs/08-internationalization/index.md`. Relationship population always behaves
279
286
  * as `'fallback'` so a populated tree never has holes.
280
287
  */
281
288
  interface MissingLocaleControls {
@@ -349,7 +356,7 @@ export interface HistoryOptions extends BeforeReadControls {
349
356
  }
350
357
  /**
351
358
  * Options for `CollectionHandle.auditLog(documentId, options)`. The
352
- * document-grain audit log (docs/06-auth-and-security/02-auditability.md — Workstream 3) records the
359
+ * document-grain audit log (docs/07-auth-and-security/02-auditability.md — Workstream 3) records the
353
360
  * non-versioned changes the immutable version stream does not capture an
354
361
  * actor for: system-field writes (path, available-locales), in-place status
355
362
  * transitions, and the deletion event. Entries are newest-first and paged;
@@ -390,7 +397,7 @@ export interface CreateOptions {
390
397
  * The editorial advertised-locale set, stored document-grain in
391
398
  * `byline_document_available_locales`. When omitted, a new document starts
392
399
  * with an empty set; an explicit array (empty included) is stored verbatim.
393
- * Surfaced on reads as `availableLocales`. See `docs/07-internationalization/index.md`.
400
+ * Surfaced on reads as `availableLocales`. See `docs/08-internationalization/index.md`.
394
401
  */
395
402
  availableLocales?: string[];
396
403
  }
@@ -406,7 +413,7 @@ export interface UpdateOptions {
406
413
  * The editorial advertised-locale set. When omitted, the existing set
407
414
  * carries forward unchanged (sticky — document-grain, like `path`); an
408
415
  * explicit array (empty included) replaces it wholesale. Surfaced on reads
409
- * as `availableLocales`. See `docs/07-internationalization/index.md`.
416
+ * as `availableLocales`. See `docs/08-internationalization/index.md`.
410
417
  */
411
418
  availableLocales?: string[];
412
419
  }
@@ -552,7 +559,7 @@ export interface ClientDocument<F = Record<string, any>> {
552
559
  * yardstick). Stable, document-grain. Surfaced so consumers / the admin can
553
560
  * indicate a document whose primary language differs from the system default.
554
561
  * Present on `find` / `findById` / `findByPath`. See
555
- * `docs/07-internationalization/index.md`.
562
+ * `docs/08-internationalization/index.md`.
556
563
  */
557
564
  sourceLocale?: string;
558
565
  /** When this version was created. */
@@ -565,7 +572,7 @@ export interface ClientDocument<F = Record<string, any>> {
565
572
  * the creator of the current version is also "who last updated the
566
573
  * document". Absent on rows written before audit wiring or by
567
574
  * internal tooling without a request context. Raw id only — display-name
568
- * resolution is an admin-realm concern (see docs/06-auth-and-security/02-auditability.md — Workstream 1).
575
+ * resolution is an admin-realm concern (see docs/07-auth-and-security/02-auditability.md — Workstream 1).
569
576
  */
570
577
  createdBy?: string;
571
578
  /**
@@ -593,7 +600,7 @@ export interface ClientDocument<F = Record<string, any>> {
593
600
  * `_availableVersionLocales` fact below; the public advertised set is their
594
601
  * intersection (`availableLocales ∩ _availableVersionLocales`), which the
595
602
  * host composes for hreflang / sitemap / "Also available in…" menus.
596
- * Present on `find` / `findById` / `findByPath`. See `docs/07-internationalization/index.md`.
603
+ * Present on `find` / `findById` / `findByPath`. See `docs/08-internationalization/index.md`.
597
604
  */
598
605
  availableLocales?: string[];
599
606
  /**
@@ -603,7 +610,7 @@ export interface ClientDocument<F = Record<string, any>> {
603
610
  * Present on `find` / `findById` / `findByPath` (absent on version/history
604
611
  * reads); the published-available set on a normal (published) read. The
605
612
  * structural fact reconciled against the editorial `availableLocales` above.
606
- * See `docs/07-internationalization/index.md`.
613
+ * See `docs/08-internationalization/index.md`.
607
614
  */
608
615
  _availableVersionLocales?: string[];
609
616
  /**
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": "4.8.0",
5
+ "version": "4.9.0",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },
@@ -44,8 +44,8 @@
44
44
  ],
45
45
  "dependencies": {
46
46
  "uuid": "^14.0.1",
47
- "@byline/auth": "4.8.0",
48
- "@byline/core": "4.8.0"
47
+ "@byline/auth": "4.9.0",
48
+ "@byline/core": "4.9.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@biomejs/biome": "2.5.4",
@@ -58,8 +58,8 @@
58
58
  "tsx": "^4.23.1",
59
59
  "typescript": "^7.0.2",
60
60
  "vitest": "^4.1.10",
61
- "@byline/search-postgres": "4.8.0",
62
- "@byline/db-postgres": "4.8.0"
61
+ "@byline/db-postgres": "4.9.0",
62
+ "@byline/search-postgres": "4.9.0"
63
63
  },
64
64
  "publishConfig": {
65
65
  "access": "public",