@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.
@@ -558,6 +558,80 @@ const reviews = sqliteTable("reviews", {
558
558
  check("reviews_decided", sql`(${t.status} IN ('pending', 'cancelled')) OR (${t.decidedBy} IS NOT NULL AND ${t.decidedAt} IS NOT NULL)`),
559
559
  check("reviews_json", sql`json_valid(${t.acknowledged}) AND json_type(${t.acknowledged}) = 'array' AND (${t.requestedMeta} IS NULL OR json_valid(${t.requestedMeta}))`)
560
560
  ]);
561
+ /**
562
+ * The vectors of an entry, as derived data (v0.5): the published revision they were made from,
563
+ * with which model and which version of the chunker, and how many pieces there are. The vectors
564
+ * live in Vectorize under `<entry id>:<piece>`; this row says which exist and whether they are
565
+ * current. One row per entry: a new publication replaces it.
566
+ */
567
+ const entryEmbeddings = sqliteTable("entry_embeddings", {
568
+ id: id("emb"),
569
+ siteId: siteId(),
570
+ entryId: text("entry_id").notNull(),
571
+ revisionId: text("revision_id").notNull(),
572
+ model: text("model").notNull(),
573
+ chunkerVersion: text("chunker_version").notNull(),
574
+ /** How many pieces, so how many vectors: `<entry id>:0` to `<entry id>:<pieces - 1>`. */
575
+ pieces: integer("pieces").notNull(),
576
+ /**
577
+ * The entries closest to this one by their summaries, in its collection and locale, closest
578
+ * first, as JSON: `[{ "id": "ent_…", "score": 0.91 }]`. Found when its vectors were made.
579
+ */
580
+ related: text("related").notNull().default("[]"),
581
+ ...timestamps()
582
+ }, (t) => [
583
+ unique("entry_embeddings_id_site").on(t.id, t.siteId),
584
+ unique("entry_embeddings_entry").on(t.siteId, t.entryId),
585
+ foreignKey({
586
+ name: "entry_embeddings_site",
587
+ columns: [t.siteId],
588
+ foreignColumns: [sites.id]
589
+ }),
590
+ foreignKey({
591
+ name: "entry_embeddings_entry_fk",
592
+ columns: [t.entryId, t.siteId],
593
+ foreignColumns: [entries.id, entries.siteId]
594
+ }).onDelete("cascade"),
595
+ foreignKey({
596
+ name: "entry_embeddings_revision_fk",
597
+ columns: [t.revisionId, t.siteId],
598
+ foreignColumns: [revisions.id, revisions.siteId]
599
+ }),
600
+ check("entry_embeddings_pieces", sql`${t.pieces} >= 1`)
601
+ ]);
602
+ /**
603
+ * Comments on a review (v0.6): what a person or an agent says about the revision a review asks
604
+ * for, before someone decides. Only a pending review takes one; they stay after the decision.
605
+ */
606
+ const reviewComments = sqliteTable("review_comments", {
607
+ id: id("rvc"),
608
+ siteId: siteId(),
609
+ reviewId: text("review_id").notNull(),
610
+ authorId: text("author_id").notNull(),
611
+ /** What the client that wrote it said about itself, as JSON, or `null`. */
612
+ authorMeta: text("author_meta"),
613
+ body: text("body").notNull(),
614
+ ...timestamps()
615
+ }, (t) => [
616
+ unique("review_comments_id_site").on(t.id, t.siteId),
617
+ index("review_comments_review").on(t.siteId, t.reviewId, t.createdAt),
618
+ foreignKey({
619
+ name: "review_comments_site",
620
+ columns: [t.siteId],
621
+ foreignColumns: [sites.id]
622
+ }),
623
+ foreignKey({
624
+ name: "review_comments_review_fk",
625
+ columns: [t.reviewId, t.siteId],
626
+ foreignColumns: [reviews.id, reviews.siteId]
627
+ }),
628
+ foreignKey({
629
+ name: "review_comments_author",
630
+ columns: [t.authorId],
631
+ foreignColumns: [users.id]
632
+ }),
633
+ check("review_comments_body", sql`length(${t.body}) BETWEEN 1 AND 4000`)
634
+ ]);
561
635
  //#endregion
562
636
  //#region src/identity/secrets.ts
563
637
  /**
@@ -757,4 +831,4 @@ async function invitationById(db, id) {
757
831
  };
758
832
  }
759
833
  //#endregion
760
- export { effectiveScopes as A, revisionTags as C, tokens as D, sites as E, users as O, reviews as S, schedules as T, entries as _, findInvitation as a, outbox as b, invitationById as c, redeemInvitation as d, saveChallenge as f, deliveries as g, userHandle as h, createUser as i, isRole as j, ROLE_SCOPES as k, membershipsOf as l, touchPasskey as m, createInvitation as n, findPasskey as o, takeChallenge as p, createSession as r, findSession as s, addPasskey as t, passkeysOf as u, invitations as v, revisions as w, publications as x, members as y };
834
+ export { users as A, reviewComments as C, schedules as D, revisions as E, effectiveScopes as M, isRole as N, sites as O, publications as S, revisionTags as T, entries as _, findInvitation as a, members as b, invitationById as c, redeemInvitation as d, saveChallenge as f, deliveries as g, userHandle as h, createUser as i, ROLE_SCOPES as j, tokens as k, membershipsOf as l, touchPasskey as m, createInvitation as n, findPasskey as o, takeChallenge as p, createSession as r, findSession as s, addPasskey as t, passkeysOf as u, entryEmbeddings as v, reviews as w, outbox as x, invitations as y };
@@ -0,0 +1,47 @@
1
+ -- Written by hand (ADR-023), as drizzle-kit has no virtual tables. Full-text search (v0.5): an
2
+ -- FTS5 index of the published revision of each entry, its title, its tags and its text. The text
3
+ -- is taken from the stored Comark document, the strings at the text positions of its nodes, so
4
+ -- the Markdown's own marks are never indexed. `unicode61 remove_diacritics 2` matches
5
+ -- "migracion" with "migración".
6
+ --
7
+ -- The triggers keep it in the step that changes what is published, whichever write does it:
8
+ -- publish, a group, a schedule that comes due, unpublish. Only published content is in it.
9
+ CREATE VIRTUAL TABLE `entries_fts` USING fts5(
10
+ `title`,
11
+ `tags`,
12
+ `body`,
13
+ `entry_id` UNINDEXED,
14
+ `site_id` UNINDEXED,
15
+ `collection` UNINDEXED,
16
+ `locale` UNINDEXED,
17
+ tokenize = 'unicode61 remove_diacritics 2'
18
+ );
19
+ --> statement-breakpoint
20
+ CREATE TRIGGER `entries_fts_publication` AFTER UPDATE OF `published_rev` ON `entries`
21
+ BEGIN
22
+ DELETE FROM `entries_fts` WHERE `entry_id` = OLD.`id`;
23
+ INSERT INTO `entries_fts` (`title`, `tags`, `body`, `entry_id`, `site_id`, `collection`, `locale`)
24
+ SELECT
25
+ coalesce(json_extract(r.`frontmatter`, '$.title'), ''),
26
+ coalesce((SELECT group_concat(t.`tag`, ' ') FROM `revision_tags` t WHERE t.`revision_id` = r.`id`), ''),
27
+ coalesce((SELECT group_concat(n.`value`, ' ') FROM json_tree(r.`document`, '$.nodes') n
28
+ WHERE n.`type` = 'text' AND typeof(n.`key`) = 'integer' AND n.`key` >= 2), ''),
29
+ NEW.`id`, NEW.`site_id`, NEW.`collection`, NEW.`locale`
30
+ FROM `revisions` r
31
+ WHERE NEW.`published_rev` IS NOT NULL AND r.`id` = NEW.`published_rev`;
32
+ END;
33
+ --> statement-breakpoint
34
+ CREATE TRIGGER `entries_fts_removal` AFTER DELETE ON `entries`
35
+ BEGIN
36
+ DELETE FROM `entries_fts` WHERE `entry_id` = OLD.`id`;
37
+ END;
38
+ --> statement-breakpoint
39
+ -- What is published already.
40
+ INSERT INTO `entries_fts` (`title`, `tags`, `body`, `entry_id`, `site_id`, `collection`, `locale`)
41
+ SELECT
42
+ coalesce(json_extract(r.`frontmatter`, '$.title'), ''),
43
+ coalesce((SELECT group_concat(t.`tag`, ' ') FROM `revision_tags` t WHERE t.`revision_id` = r.`id`), ''),
44
+ coalesce((SELECT group_concat(n.`value`, ' ') FROM json_tree(r.`document`, '$.nodes') n
45
+ WHERE n.`type` = 'text' AND typeof(n.`key`) = 'integer' AND n.`key` >= 2), ''),
46
+ e.`id`, e.`site_id`, e.`collection`, e.`locale`
47
+ FROM `entries` e JOIN `revisions` r ON r.`id` = e.`published_rev`;
@@ -0,0 +1,19 @@
1
+ CREATE TABLE `entry_embeddings` (
2
+ `id` text PRIMARY KEY NOT NULL,
3
+ `site_id` text NOT NULL,
4
+ `entry_id` text NOT NULL,
5
+ `revision_id` text NOT NULL,
6
+ `model` text NOT NULL,
7
+ `chunker_version` text NOT NULL,
8
+ `pieces` integer NOT NULL,
9
+ `related` text DEFAULT '[]' NOT NULL,
10
+ `created_at` text NOT NULL,
11
+ `updated_at` text NOT NULL,
12
+ FOREIGN KEY (`site_id`) REFERENCES `sites`(`id`) ON UPDATE no action ON DELETE no action,
13
+ FOREIGN KEY (`entry_id`,`site_id`) REFERENCES `entries`(`id`,`site_id`) ON UPDATE no action ON DELETE cascade,
14
+ FOREIGN KEY (`revision_id`,`site_id`) REFERENCES `revisions`(`id`,`site_id`) ON UPDATE no action ON DELETE no action,
15
+ CONSTRAINT "entry_embeddings_pieces" CHECK("entry_embeddings"."pieces" >= 1)
16
+ );
17
+ --> statement-breakpoint
18
+ CREATE UNIQUE INDEX `entry_embeddings_id_site` ON `entry_embeddings` (`id`,`site_id`);--> statement-breakpoint
19
+ CREATE UNIQUE INDEX `entry_embeddings_entry` ON `entry_embeddings` (`site_id`,`entry_id`);
@@ -0,0 +1,17 @@
1
+ CREATE TABLE `review_comments` (
2
+ `id` text PRIMARY KEY NOT NULL,
3
+ `site_id` text NOT NULL,
4
+ `review_id` text NOT NULL,
5
+ `author_id` text NOT NULL,
6
+ `author_meta` text,
7
+ `body` text NOT NULL,
8
+ `created_at` text NOT NULL,
9
+ `updated_at` text NOT NULL,
10
+ FOREIGN KEY (`site_id`) REFERENCES `sites`(`id`) ON UPDATE no action ON DELETE no action,
11
+ FOREIGN KEY (`review_id`,`site_id`) REFERENCES `reviews`(`id`,`site_id`) ON UPDATE no action ON DELETE no action,
12
+ FOREIGN KEY (`author_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action,
13
+ CONSTRAINT "review_comments_body" CHECK(length("review_comments"."body") BETWEEN 1 AND 4000)
14
+ );
15
+ --> statement-breakpoint
16
+ CREATE INDEX `review_comments_review` ON `review_comments` (`site_id`,`review_id`,`created_at`);--> statement-breakpoint
17
+ CREATE UNIQUE INDEX `review_comments_id_site` ON `review_comments` (`id`,`site_id`);