@hollware/server 0.4.0 → 0.6.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.
@@ -1,4 +1,4 @@
1
- import { a as findInvitation, c as invitationById, d as redeemInvitation, f as saveChallenge, h as userHandle, i as createUser, m as touchPasskey, o as findPasskey, p as takeChallenge, r as createSession, t as addPasskey, u as passkeysOf } from "./store-CsuER6J7.mjs";
1
+ import { a as findInvitation, c as invitationById, d as redeemInvitation, f as saveChallenge, h as userHandle, i as createUser, m as touchPasskey, o as findPasskey, p as takeChallenge, r as createSession, t as addPasskey, u as passkeysOf } from "./store-CYMjP0iN.mjs";
2
2
  import { HollwareError, newId } from "@hollware/core";
3
3
  import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse } from "@simplewebauthn/server";
4
4
  import { isoBase64URL } from "@simplewebauthn/server/helpers";
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AcknowledgedWarning, EntryDocument, EntryLookup, EntryRef, HollwareConfig, OperationByName, OperationName, ResolvedSite, Scope, Verdict, describeSite, getEntry, getTranslations, listEntries } from "@hollware/core";
1
+ import { AcknowledgedWarning, EntryDocument, EntryLookup, EntryRef, HollwareConfig, OperationByName, OperationName, ResolvedSite, Scope, Verdict, describeSite, getEntry, getTranslations, listEntries, search } from "@hollware/core";
2
2
  import * as z from "zod";
3
3
  import { WorkerEntrypoint } from "cloudflare:workers";
4
4
  //#region src/ports.d.ts
@@ -102,6 +102,27 @@ interface ListQuery {
102
102
  /** The `nextCursor` of the previous page. */
103
103
  readonly cursor?: string;
104
104
  }
105
+ /** A full-text search of the published entries of a site. */
106
+ interface SearchQuery {
107
+ /** What the person or the agent typed: its words are matched, its punctuation is not. */
108
+ readonly text: string;
109
+ readonly collection?: string;
110
+ readonly locale?: string;
111
+ readonly limit: number;
112
+ }
113
+ /** An entry that a search found, best first. */
114
+ interface SearchHit {
115
+ readonly entry: EntrySummaryView;
116
+ /** The words around the match in the text, the match between `[` and `]`. */
117
+ readonly snippet: string;
118
+ /** How well it matches: higher is better. */
119
+ readonly score: number;
120
+ }
121
+ /** An entry close to another by meaning, and how close: higher is closer. */
122
+ interface RelatedEntry {
123
+ readonly id: string;
124
+ readonly score: number;
125
+ }
105
126
  /** The answer to a write that the store could not make. */
106
127
  type WriteRefusal = {
107
128
  readonly outcome: 'conflict';
@@ -140,6 +161,17 @@ interface StoredReview {
140
161
  readonly decidedAt: string | null;
141
162
  readonly note: string | null;
142
163
  }
164
+ /** A comment on a review (v0.6). */
165
+ interface StoredReviewComment {
166
+ readonly id: string;
167
+ readonly reviewId: string;
168
+ readonly authorId: string;
169
+ readonly authorName: string | null;
170
+ /** What the client that wrote it said about itself, or `null`. */
171
+ readonly authorMeta: Readonly<Record<string, unknown>> | null;
172
+ readonly body: string;
173
+ readonly createdAt: string;
174
+ }
143
175
  /** A scheduled publication, as the cron trigger reads it. */
144
176
  interface StoredSchedule {
145
177
  readonly entryId: string;
@@ -179,6 +211,15 @@ interface ContentStore {
179
211
  readonly entries: readonly EntrySummaryView[];
180
212
  readonly nextCursor: string | null;
181
213
  }>;
214
+ /**
215
+ * The published entries whose title, tags or text hold every word of the query, best first:
216
+ * the title weighs most, then the tags, then the text (bm25).
217
+ */
218
+ searchEntries(site: string, query: SearchQuery): Promise<readonly SearchHit[]>;
219
+ /** The entries of those ids that the view has, in the order of the ids. */
220
+ findSummaries(site: string, ids: readonly string[], view: View): Promise<readonly EntrySummaryView[]>;
221
+ /** The entries closest to one by meaning, closest first (ADR-030); `[]` when it has no vectors. */
222
+ relatedOf(site: string, entryId: string): Promise<readonly RelatedEntry[]>;
182
223
  /** The entries of a translation group, one per locale, in the given view. */
183
224
  listTranslations(site: string, collection: string, translationGroup: string, view: View): Promise<readonly EntrySummaryView[]>;
184
225
  /** What cross-entry contracts read, scoped to the site. */
@@ -265,6 +306,19 @@ interface ContentStore {
265
306
  readonly status: ReviewStatus;
266
307
  readonly limit: number;
267
308
  }): Promise<readonly StoredReview[]>;
309
+ /** Adds a comment to a review, if it is still pending. */
310
+ commentReview(site: string, write: {
311
+ readonly reviewId: string;
312
+ readonly actor: Actor;
313
+ readonly body: string;
314
+ }): Promise<{
315
+ readonly outcome: 'commented';
316
+ readonly comment: StoredReviewComment;
317
+ } | {
318
+ readonly outcome: 'not_pending' | 'not_found';
319
+ }>;
320
+ /** The comments of these reviews, oldest first, by review. */
321
+ reviewComments(site: string, reviewIds: readonly string[]): Promise<ReadonlyMap<string, readonly StoredReviewComment[]>>;
268
322
  /** Closes a pending review with a note, without publishing. */
269
323
  requestChanges(site: string, write: {
270
324
  readonly id: string;
@@ -290,6 +344,83 @@ interface ContentStore {
290
344
  interface Clock {
291
345
  now(): Date;
292
346
  }
347
+ /** Turns texts into vectors, all of the same length. */
348
+ interface Embedder {
349
+ /** The model, which the stored vectors name: another model makes them stale. */
350
+ readonly model: string;
351
+ embed(texts: readonly string[]): Promise<number[][]>;
352
+ }
353
+ /** What a vector says about where it comes from, to filter and to read a match back. */
354
+ interface VectorMetadata {
355
+ readonly site: string;
356
+ readonly entry: string;
357
+ readonly collection: string;
358
+ readonly locale: string;
359
+ /** The piece of the entry: 0 for its summary. */
360
+ readonly piece: number;
361
+ /** The start of the piece's text, for the snippet of a match. */
362
+ readonly excerpt: string;
363
+ }
364
+ interface StoredVector {
365
+ /** `<entry id>:<piece>`. */
366
+ readonly id: string;
367
+ readonly values: readonly number[];
368
+ readonly metadata: VectorMetadata;
369
+ }
370
+ interface VectorMatch {
371
+ readonly id: string;
372
+ /** How close: higher is closer. */
373
+ readonly score: number;
374
+ readonly metadata: VectorMetadata;
375
+ }
376
+ interface VectorIndex {
377
+ upsert(vectors: readonly StoredVector[]): Promise<void>;
378
+ remove(ids: readonly string[]): Promise<void>;
379
+ /** The stored values of those vectors; an id the index does not have is left out. */
380
+ get(ids: readonly string[]): Promise<readonly {
381
+ readonly id: string;
382
+ readonly values: readonly number[];
383
+ }[]>;
384
+ /** The `topK` vectors nearest to `values` among those of the site, narrowed as asked. */
385
+ query(values: readonly number[], options: {
386
+ readonly topK: number;
387
+ readonly site: string;
388
+ readonly collection?: string;
389
+ readonly locale?: string;
390
+ /** Only the summaries, one vector per entry. */
391
+ readonly summariesOnly?: boolean;
392
+ }): Promise<readonly VectorMatch[]>;
393
+ }
394
+ /** What semantic search needs: both services, or the deployment has none. */
395
+ interface Semantic {
396
+ readonly embedder: Embedder;
397
+ readonly index: VectorIndex;
398
+ }
399
+ /** A page view, as a site's beacon reports it. */
400
+ interface PageView {
401
+ readonly site: string;
402
+ /** The path of the page, such as `/blog/edge-caching/`. */
403
+ readonly path: string;
404
+ /** The two letters of the reader's country, or `XX` when it is not known. */
405
+ readonly country: string;
406
+ }
407
+ /** Where page views are counted. */
408
+ interface AnalyticsWriter {
409
+ record(view: PageView): void;
410
+ }
411
+ /** A page and how often it was read. */
412
+ interface PageCount {
413
+ readonly path: string;
414
+ readonly views: number;
415
+ }
416
+ /** Reads the counts back. */
417
+ interface AnalyticsReader {
418
+ /** The `limit` most read paths of the site in the last `days`, most read first. */
419
+ topPages(site: string, query: {
420
+ readonly days: number;
421
+ readonly limit: number;
422
+ }): Promise<readonly PageCount[]>;
423
+ }
293
424
  //#endregion
294
425
  //#region src/events/run.d.ts
295
426
  /** A message of the events Queue: the site and the id of an outbox row. */
@@ -371,6 +502,10 @@ interface OperationContext {
371
502
  * the Queue now rather than at the next sweep of the cron trigger (ADR-025). Absent in tests.
372
503
  */
373
504
  readonly notify?: (eventIds: readonly string[]) => void;
505
+ /** The model and the vector index, when the Worker has both (ADR-030); absent or `null` without. */
506
+ readonly semantic?: Semantic | null;
507
+ /** The counts of page views, when the deployment can read them (ADR-031); absent or `null` without. */
508
+ readonly analytics?: AnalyticsReader | null;
374
509
  }
375
510
  /** The handler of one operation: its parsed input in, its output out. */
376
511
  type OperationHandler<Name extends OperationName> = (input: z.output<OperationByName<Name>['input']>, context: OperationContext) => Promise<z.output<OperationByName<Name>['output']>>;
@@ -418,6 +553,32 @@ export interface HollwareEnv {
418
553
  readonly ASSETS?: Fetcher;
419
554
  /** The Queue of publication events (ADR-025); without it, the cron trigger delivers them. */
420
555
  readonly EVENTS?: EventQueue;
556
+ /**
557
+ * Workers AI and a Vectorize index (v0.5): with both, every published entry is embedded and
558
+ * `search` can be semantic; without either, search is full text only.
559
+ */
560
+ readonly AI?: Ai;
561
+ /** `wrangler types` names a Vectorize binding `VectorizeIndex`; either works. */
562
+ readonly VECTORIZE?: Vectorize | VectorizeIndex;
563
+ /** The embedding model of Workers AI, `@cf/baai/bge-m3` when not set; its index must match it. */
564
+ readonly HOLLWARE_EMBEDDING_MODEL?: string;
565
+ /**
566
+ * Analytics (ADR-031): the Analytics Engine dataset that the beacon of the sites writes to, and
567
+ * what `content_stats` reads it with: the account, an API token with Account Analytics Read
568
+ * (a secret), and the dataset's name when it is not `hollware_page_views`.
569
+ */
570
+ readonly ANALYTICS?: AnalyticsEngineDataset;
571
+ readonly HOLLWARE_ANALYTICS_ACCOUNT_ID?: string;
572
+ readonly HOLLWARE_ANALYTICS_TOKEN?: string;
573
+ readonly HOLLWARE_ANALYTICS_DATASET?: string;
574
+ /**
575
+ * The agents of @hollware/agents (v0.6), over a service binding to their named entrypoint: the
576
+ * cron trigger of Hollware wakes them every ten minutes, so they need no cron trigger of their
577
+ * own (the free plan has five for the account). `wrangler types` writes it as a plain
578
+ * `Service`, as it cannot see the agents' entrypoint, so any binding goes in: one without `run`
579
+ * is not called.
580
+ */
581
+ readonly AGENTS?: object;
421
582
  }
422
583
  type Output<Spec extends {
423
584
  output: z.ZodType;
@@ -429,6 +590,8 @@ type Output<Spec extends {
429
590
  export interface HollwareRpc {
430
591
  getEntry(site: string, collection: string, slug: string, locale: string, options?: {
431
592
  readonly formats?: readonly ('markdown' | 'document' | 'html')[];
593
+ /** Also the published entries closest by meaning (ADR-030). */
594
+ readonly related?: boolean;
432
595
  }): Promise<Output<typeof getEntry>>;
433
596
  listEntries(site: string, collection: string, options?: {
434
597
  readonly locale?: string;
@@ -438,6 +601,13 @@ export interface HollwareRpc {
438
601
  }): Promise<Output<typeof listEntries>>;
439
602
  getTranslations(site: string, collection: string, translationGroup: string): Promise<Output<typeof getTranslations>>;
440
603
  describeSite(site: string): Promise<Output<typeof describeSite>>;
604
+ /** The published entries that match `query`, best first: by its words, by meaning, or both. */
605
+ search(site: string, query: string, options?: {
606
+ readonly collection?: string;
607
+ readonly locale?: string;
608
+ readonly limit?: number;
609
+ readonly mode?: 'fulltext' | 'semantic' | 'hybrid';
610
+ }): Promise<Output<typeof search>>;
441
611
  }
442
612
  /**
443
613
  * What `createHollware(config)` returns. The user's Worker is
@@ -464,4 +634,4 @@ export type CreateHollware = (config: HollwareConfig) => HollwareWorker;
464
634
  */
465
635
  export declare const createHollware: CreateHollware;
466
636
  //#endregion
467
- export type { Actor, Clock, ContentStore, EntrySummaryView, EntryView, EventMessage, Handlers, ListQuery, NewRevision, OperationContext, OperationHandler, OutboxEvent, Principal, PublishWrite, ReviewStatus, RevisionSummary, StoredEntry, StoredPublication, StoredReview, StoredRevision, StoredSchedule, View, WriteRefusal };
637
+ export type { Actor, AnalyticsReader, AnalyticsWriter, Clock, ContentStore, Embedder, EntrySummaryView, EntryView, EventMessage, Handlers, ListQuery, NewRevision, OperationContext, OperationHandler, OutboxEvent, PageCount, PageView, Principal, PublishWrite, RelatedEntry, ReviewStatus, RevisionSummary, SearchHit, SearchQuery, Semantic, StoredEntry, StoredPublication, StoredReview, StoredReviewComment, StoredRevision, StoredSchedule, StoredVector, VectorIndex, VectorMatch, VectorMetadata, View, WriteRefusal };