@hiai-gg/docsmint 0.4.5 → 0.4.6

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,10 +1,16 @@
1
1
  import {
2
+ accounts,
2
3
  attachments,
3
4
  auditLog,
5
+ categories,
4
6
  documentEmbeddings,
5
7
  documents,
8
+ folders,
6
9
  lifecycleOperations,
10
+ sessions,
7
11
  shareLinks,
12
+ tags,
13
+ users,
8
14
  versions,
9
15
  } from "@hiai-docs/db/schema";
10
16
  import type { TenantTransaction } from "@hiai-docs/db/with-tenant";
@@ -121,6 +127,14 @@ function subjectHash(actorUserId: string): string {
121
127
  return new Bun.CryptoHasher("sha256").update(actorUserId).digest("hex");
122
128
  }
123
129
 
130
+ export function lifecycleTombstoneEmail(actorUserId: string): string {
131
+ const hash = new Bun.CryptoHasher("sha256")
132
+ .update("docsmint:privacy-tombstone:v1\0")
133
+ .update(actorUserId)
134
+ .digest("hex");
135
+ return `deleted-${hash}@invalid.local`;
136
+ }
137
+
124
138
  function checksumLine(
125
139
  hash: Bun.CryptoHasher,
126
140
  record: UserDataExportRecord,
@@ -317,6 +331,92 @@ export function createPersistentLifecycleService(
317
331
  checksumLine(hash, manifest);
318
332
  recordCount += 1;
319
333
  yield manifest;
334
+ const actor = await withActor(ctx.actorUserId, (tx) =>
335
+ tx
336
+ .select({
337
+ id: users.id,
338
+ email: users.email,
339
+ name: users.name,
340
+ emailVerified: users.emailVerified,
341
+ image: users.image,
342
+ createdAt: users.createdAt,
343
+ updatedAt: users.updatedAt,
344
+ })
345
+ .from(users)
346
+ .where(eq(users.id, ctx.actorUserId))
347
+ .limit(1),
348
+ );
349
+ for (const profile of actor) {
350
+ const record: UserDataExportRecord = {
351
+ type: "data",
352
+ domain: "account",
353
+ resourceType: "user",
354
+ resourceId: profile.id,
355
+ workspaceId: null,
356
+ payload: {
357
+ email: profile.email,
358
+ name: profile.name,
359
+ emailVerified: profile.emailVerified,
360
+ image: profile.image,
361
+ createdAt: profile.createdAt,
362
+ updatedAt: profile.updatedAt,
363
+ },
364
+ };
365
+ checksumLine(hash, record);
366
+ recordCount += 1;
367
+ yield record;
368
+ }
369
+ const actorFolders = await withActor(ctx.actorUserId, (tx) =>
370
+ tx.select().from(folders).where(eq(folders.ownerId, ctx.actorUserId)),
371
+ );
372
+ for (const folder of actorFolders) {
373
+ const record: UserDataExportRecord = {
374
+ type: "data",
375
+ domain: "folders",
376
+ resourceType: "folder",
377
+ resourceId: folder.id,
378
+ workspaceId: folder.workspaceId,
379
+ payload: folder,
380
+ };
381
+ checksumLine(hash, record);
382
+ recordCount += 1;
383
+ yield record;
384
+ }
385
+ const actorTags = await withActor(ctx.actorUserId, (tx) =>
386
+ tx.select().from(tags).where(eq(tags.ownerId, ctx.actorUserId)),
387
+ );
388
+ for (const tag of actorTags) {
389
+ const record: UserDataExportRecord = {
390
+ type: "data",
391
+ domain: "tags",
392
+ resourceType: "tag",
393
+ resourceId: tag.id,
394
+ workspaceId: tag.workspaceId,
395
+ payload: tag,
396
+ };
397
+ checksumLine(hash, record);
398
+ recordCount += 1;
399
+ yield record;
400
+ }
401
+ const actorCategories = await withActor(ctx.actorUserId, (tx) =>
402
+ tx
403
+ .select()
404
+ .from(categories)
405
+ .where(eq(categories.ownerId, ctx.actorUserId)),
406
+ );
407
+ for (const category of actorCategories) {
408
+ const record: UserDataExportRecord = {
409
+ type: "data",
410
+ domain: "categories",
411
+ resourceType: "category",
412
+ resourceId: category.id,
413
+ workspaceId: category.workspaceId,
414
+ payload: category,
415
+ };
416
+ checksumLine(hash, record);
417
+ recordCount += 1;
418
+ yield record;
419
+ }
320
420
  const ownedDocuments = await withActor(ctx.actorUserId, (tx) =>
321
421
  tx
322
422
  .select()
@@ -529,6 +629,48 @@ export function createPersistentLifecycleService(
529
629
  ctx.signal,
530
630
  ),
531
631
  );
632
+ await runStep(
633
+ operation,
634
+ ctx.actorUserId,
635
+ leaseOwner,
636
+ "remove_subject_folders",
637
+ deletedByDomain,
638
+ () =>
639
+ dbDelete((tx) =>
640
+ tx
641
+ .delete(folders)
642
+ .where(eq(folders.ownerId, ctx.actorUserId))
643
+ .returning({ id: folders.id }),
644
+ ),
645
+ );
646
+ await runStep(
647
+ operation,
648
+ ctx.actorUserId,
649
+ leaseOwner,
650
+ "remove_subject_tags",
651
+ deletedByDomain,
652
+ () =>
653
+ dbDelete((tx) =>
654
+ tx
655
+ .delete(tags)
656
+ .where(eq(tags.ownerId, ctx.actorUserId))
657
+ .returning({ id: tags.id }),
658
+ ),
659
+ );
660
+ await runStep(
661
+ operation,
662
+ ctx.actorUserId,
663
+ leaseOwner,
664
+ "remove_subject_categories",
665
+ deletedByDomain,
666
+ () =>
667
+ dbDelete((tx) =>
668
+ tx
669
+ .delete(categories)
670
+ .where(eq(categories.ownerId, ctx.actorUserId))
671
+ .returning({ id: categories.id }),
672
+ ),
673
+ );
532
674
  await runStep(
533
675
  operation,
534
676
  ctx.actorUserId,
@@ -582,6 +724,55 @@ export function createPersistentLifecycleService(
582
724
  deletedByDomain,
583
725
  async () => (await step.purge?.(ctx))?.deletedCount ?? 0,
584
726
  );
727
+ await runStep(
728
+ operation,
729
+ ctx.actorUserId,
730
+ leaseOwner,
731
+ "remove_auth_sessions",
732
+ deletedByDomain,
733
+ () =>
734
+ dbDelete((tx) =>
735
+ tx
736
+ .delete(sessions)
737
+ .where(eq(sessions.userId, ctx.actorUserId))
738
+ .returning({ id: sessions.id }),
739
+ ),
740
+ );
741
+ await runStep(
742
+ operation,
743
+ ctx.actorUserId,
744
+ leaseOwner,
745
+ "remove_auth_accounts",
746
+ deletedByDomain,
747
+ () =>
748
+ dbDelete((tx) =>
749
+ tx
750
+ .delete(accounts)
751
+ .where(eq(accounts.userId, ctx.actorUserId))
752
+ .returning({ id: accounts.id }),
753
+ ),
754
+ );
755
+ await runStep(
756
+ operation,
757
+ ctx.actorUserId,
758
+ leaseOwner,
759
+ "tombstone_subject_user",
760
+ deletedByDomain,
761
+ () =>
762
+ dbDelete((tx) =>
763
+ tx
764
+ .update(users)
765
+ .set({
766
+ email: lifecycleTombstoneEmail(ctx.actorUserId),
767
+ name: null,
768
+ image: null,
769
+ emailVerified: false,
770
+ updatedAt: new Date(),
771
+ })
772
+ .where(eq(users.id, ctx.actorUserId))
773
+ .returning({ id: users.id }),
774
+ ),
775
+ );
585
776
  await runStep(
586
777
  operation,
587
778
  ctx.actorUserId,
@@ -214361,7 +214361,7 @@ var swaggerConfig = {
214361
214361
  documentation: {
214362
214362
  info: {
214363
214363
  title: "DocsMint API",
214364
- version: "0.4.5",
214364
+ version: "0.4.6",
214365
214365
  description: "Self-hosted AI-first documentation platform. Full-text + semantic search, version history, sharing, and folder organization.",
214366
214366
  contact: { name: "HiAi-gg", url: "https://github.com/HiAi-gg/docsmint" },
214367
214367
  license: {
@@ -538,6 +538,10 @@ function tokenHash(token) {
538
538
  function subjectHash(actorUserId) {
539
539
  return new Bun.CryptoHasher("sha256").update(actorUserId).digest("hex");
540
540
  }
541
+ function lifecycleTombstoneEmail(actorUserId) {
542
+ const hash = new Bun.CryptoHasher("sha256").update("docsmint:privacy-tombstone:v1\x00").update(actorUserId).digest("hex");
543
+ return `deleted-${hash}@invalid.local`;
544
+ }
541
545
  function checksumLine(hash, record) {
542
546
  hash.update(`${JSON.stringify(record)}
543
547
  `, "utf8");
@@ -634,6 +638,77 @@ function createPersistentLifecycleService(runtime, database, hostSteps = []) {
634
638
  checksumLine(hash, manifest);
635
639
  recordCount += 1;
636
640
  yield manifest;
641
+ const actor = await withActor(ctx.actorUserId, (tx) => tx.select({
642
+ id: users.id,
643
+ email: users.email,
644
+ name: users.name,
645
+ emailVerified: users.emailVerified,
646
+ image: users.image,
647
+ createdAt: users.createdAt,
648
+ updatedAt: users.updatedAt
649
+ }).from(users).where(eq(users.id, ctx.actorUserId)).limit(1));
650
+ for (const profile of actor) {
651
+ const record = {
652
+ type: "data",
653
+ domain: "account",
654
+ resourceType: "user",
655
+ resourceId: profile.id,
656
+ workspaceId: null,
657
+ payload: {
658
+ email: profile.email,
659
+ name: profile.name,
660
+ emailVerified: profile.emailVerified,
661
+ image: profile.image,
662
+ createdAt: profile.createdAt,
663
+ updatedAt: profile.updatedAt
664
+ }
665
+ };
666
+ checksumLine(hash, record);
667
+ recordCount += 1;
668
+ yield record;
669
+ }
670
+ const actorFolders = await withActor(ctx.actorUserId, (tx) => tx.select().from(folders).where(eq(folders.ownerId, ctx.actorUserId)));
671
+ for (const folder of actorFolders) {
672
+ const record = {
673
+ type: "data",
674
+ domain: "folders",
675
+ resourceType: "folder",
676
+ resourceId: folder.id,
677
+ workspaceId: folder.workspaceId,
678
+ payload: folder
679
+ };
680
+ checksumLine(hash, record);
681
+ recordCount += 1;
682
+ yield record;
683
+ }
684
+ const actorTags = await withActor(ctx.actorUserId, (tx) => tx.select().from(tags).where(eq(tags.ownerId, ctx.actorUserId)));
685
+ for (const tag of actorTags) {
686
+ const record = {
687
+ type: "data",
688
+ domain: "tags",
689
+ resourceType: "tag",
690
+ resourceId: tag.id,
691
+ workspaceId: tag.workspaceId,
692
+ payload: tag
693
+ };
694
+ checksumLine(hash, record);
695
+ recordCount += 1;
696
+ yield record;
697
+ }
698
+ const actorCategories = await withActor(ctx.actorUserId, (tx) => tx.select().from(categories).where(eq(categories.ownerId, ctx.actorUserId)));
699
+ for (const category of actorCategories) {
700
+ const record = {
701
+ type: "data",
702
+ domain: "categories",
703
+ resourceType: "category",
704
+ resourceId: category.id,
705
+ workspaceId: category.workspaceId,
706
+ payload: category
707
+ };
708
+ checksumLine(hash, record);
709
+ recordCount += 1;
710
+ yield record;
711
+ }
637
712
  const ownedDocuments = await withActor(ctx.actorUserId, (tx) => tx.select().from(documents).where(eq(documents.ownerId, ctx.actorUserId)));
638
713
  for (const document of ownedDocuments) {
639
714
  throwIfAborted(ctx.signal);
@@ -733,12 +808,24 @@ function createPersistentLifecycleService(runtime, database, hostSteps = []) {
733
808
  storageKey: attachments.storageKey
734
809
  }).from(attachments).where(inArray(attachments.documentId, documentIds))) : [];
735
810
  await runStep(operation, ctx.actorUserId, leaseOwner, "delete_attachment_objects", deletedByDomain, () => runtime.deleteObjects(objectRows.map((row) => row.storageKey), ctx.signal));
811
+ await runStep(operation, ctx.actorUserId, leaseOwner, "remove_subject_folders", deletedByDomain, () => dbDelete((tx) => tx.delete(folders).where(eq(folders.ownerId, ctx.actorUserId)).returning({ id: folders.id })));
812
+ await runStep(operation, ctx.actorUserId, leaseOwner, "remove_subject_tags", deletedByDomain, () => dbDelete((tx) => tx.delete(tags).where(eq(tags.ownerId, ctx.actorUserId)).returning({ id: tags.id })));
813
+ await runStep(operation, ctx.actorUserId, leaseOwner, "remove_subject_categories", deletedByDomain, () => dbDelete((tx) => tx.delete(categories).where(eq(categories.ownerId, ctx.actorUserId)).returning({ id: categories.id })));
736
814
  await runStep(operation, ctx.actorUserId, leaseOwner, "remove_attachment_rows", deletedByDomain, () => objectRows.length ? dbDelete((tx) => tx.delete(attachments).where(inArray(attachments.id, objectRows.map((row) => row.id))).returning({ id: attachments.id })) : Promise.resolve(0));
737
815
  await runStep(operation, ctx.actorUserId, leaseOwner, "remove_subject_documents", deletedByDomain, () => dbDelete((tx) => tx.delete(documents).where(eq(documents.ownerId, ctx.actorUserId)).returning({ id: documents.id })));
738
816
  await runStep(operation, ctx.actorUserId, leaseOwner, "clear_redis_state", deletedByDomain, () => runtime.clearAccountRedisState(ctx.actorUserId, ctx.signal));
739
817
  for (const step of orderedSteps)
740
818
  if (step.purge)
741
819
  await runStep(operation, ctx.actorUserId, leaseOwner, `host:${step.id}`, deletedByDomain, async () => (await step.purge?.(ctx))?.deletedCount ?? 0);
820
+ await runStep(operation, ctx.actorUserId, leaseOwner, "remove_auth_sessions", deletedByDomain, () => dbDelete((tx) => tx.delete(sessions).where(eq(sessions.userId, ctx.actorUserId)).returning({ id: sessions.id })));
821
+ await runStep(operation, ctx.actorUserId, leaseOwner, "remove_auth_accounts", deletedByDomain, () => dbDelete((tx) => tx.delete(accounts).where(eq(accounts.userId, ctx.actorUserId)).returning({ id: accounts.id })));
822
+ await runStep(operation, ctx.actorUserId, leaseOwner, "tombstone_subject_user", deletedByDomain, () => dbDelete((tx) => tx.update(users).set({
823
+ email: lifecycleTombstoneEmail(ctx.actorUserId),
824
+ name: null,
825
+ image: null,
826
+ emailVerified: false,
827
+ updatedAt: new Date
828
+ }).where(eq(users.id, ctx.actorUserId)).returning({ id: users.id })));
742
829
  await runStep(operation, ctx.actorUserId, leaseOwner, "write_deletion_audit", deletedByDomain, async () => {
743
830
  await withActor(ctx.actorUserId, (tx) => tx.insert(auditLog).values({
744
831
  actorId: ctx.actorUserId,
@@ -803,6 +890,7 @@ function createPersistentLifecycleRuntime(options) {
803
890
  }
804
891
  export {
805
892
  requireLeaseWrite,
893
+ lifecycleTombstoneEmail,
806
894
  createPersistentLifecycleService,
807
895
  createPersistentLifecycleRuntime,
808
896
  bindPersistentLifecycle,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hiai-gg/docsmint",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "type": "module",
5
5
  "browser": {
6
6
  "./dist/backend-launcher.js": false,
@@ -24,7 +24,7 @@ import { registerSearch } from "./commands/search.js";
24
24
  import { registerSnapshot } from "./commands/snapshot.js";
25
25
  import { registerUpdate } from "./commands/update.js";
26
26
 
27
- const VERSION = "0.4.5";
27
+ const VERSION = "0.4.6";
28
28
 
29
29
  const program = new Command();
30
30
  program
@@ -35,7 +35,7 @@ interface McpToolResult {
35
35
 
36
36
  const server = new McpServer({
37
37
  name: "hiai-docs",
38
- version: "0.4.5",
38
+ version: "0.4.6",
39
39
  });
40
40
 
41
41
  /**