@hiai-gg/docsmint 0.3.5 → 0.3.7

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.
Files changed (158) hide show
  1. package/README.md +0 -18
  2. package/backend/src/lib/lifecycle-service.ts +334 -219
  3. package/dist/backend/index.js +232 -18
  4. package/dist/backend-launcher.d.ts +15 -0
  5. package/dist/backend-launcher.js +22 -0
  6. package/dist/frontend/CategoryDialog--rIE7FrH.js +580 -0
  7. package/dist/frontend/ConfirmDialog-Cpzyb55f.js +212 -0
  8. package/dist/frontend/CreateSnapshotDialog-CmkV-n2A.js +216 -0
  9. package/dist/frontend/DatePicker-Byynca7V.js +257 -0
  10. package/dist/frontend/DeleteDialog-ap6nOywZ.js +174 -0
  11. package/dist/frontend/FolderDialog-DmN_trV0.js +247 -0
  12. package/dist/frontend/FolderNode-C4B8a2fd.js +317 -0
  13. package/dist/frontend/FolderTreeSelector-D6ijiZTA.js +100 -0
  14. package/dist/frontend/Icon-DtNo0N1U.js +64 -0
  15. package/dist/frontend/SettingsDialog-2kTaTXAs.js +992 -0
  16. package/dist/frontend/ShareDialog-BBDGNF5n.js +165 -0
  17. package/dist/frontend/TagCreateDialog-DiLZnJo4.js +258 -0
  18. package/dist/frontend/api/attachments.d.ts +6 -0
  19. package/dist/frontend/api/attachments.js +2 -0
  20. package/dist/frontend/api/categories.js +1 -1
  21. package/dist/frontend/api/documents.js +1 -1
  22. package/dist/frontend/api/folders.js +1 -1
  23. package/dist/frontend/api/settings.js +1 -1
  24. package/dist/frontend/api/share.d.ts +5 -0
  25. package/dist/frontend/api/share.js +2 -0
  26. package/dist/frontend/api/tags.js +1 -1
  27. package/dist/frontend/api-keys-CpkrA8rD.js +35 -0
  28. package/dist/frontend/attachments-BuILSs56.js +50 -0
  29. package/dist/frontend/{settings-PCrOtied.js → auth-client-Cygmp1I2.js} +205 -265
  30. package/dist/frontend/awareness-snQ3RD9f.js +3714 -0
  31. package/dist/frontend/bundle-mjs-CP6Owz-8.js +1723 -0
  32. package/dist/frontend/camera-B7dAOd4s.js +28 -0
  33. package/dist/frontend/{categories-BE6ZpZl8.js → categories-izQovv2w.js} +1 -1
  34. package/dist/frontend/check-CDwRlG9T.js +24 -0
  35. package/dist/frontend/chevron-down-B7ZrjUmT.js +24 -0
  36. package/dist/frontend/circle-check-CDqeMB2d.js +28 -0
  37. package/dist/frontend/{client-C6QQGb0e.js → client-Bc5Fzx40.js} +833 -814
  38. package/dist/frontend/{client-B9hzI0W8.js → client-GbtOH90i.js} +1 -1
  39. package/dist/frontend/clipboard-Cgzxc0_R.js +16 -0
  40. package/dist/frontend/clock-Ck5Sn-Zl.js +28 -0
  41. package/dist/frontend/clsx-nmtDPO_P.js +16 -0
  42. package/dist/frontend/collaboration.d.ts +6 -0
  43. package/dist/frontend/collaboration.js +182 -0
  44. package/dist/frontend/components/category-dialog.d.ts +2 -0
  45. package/dist/frontend/components/category-dialog.js +2 -0
  46. package/dist/frontend/components/create-snapshot-dialog.d.ts +2 -0
  47. package/dist/frontend/components/create-snapshot-dialog.js +2 -0
  48. package/dist/frontend/components/delete-dialog.d.ts +2 -0
  49. package/dist/frontend/components/delete-dialog.js +2 -0
  50. package/dist/frontend/components/editor/document-editor.d.ts +3 -0
  51. package/dist/frontend/components/editor/document-editor.js +3021 -0
  52. package/dist/frontend/components/editor/document-title.d.ts +2 -0
  53. package/dist/frontend/components/editor/document-title.js +31 -0
  54. package/dist/frontend/components/editor/docx-serializer.d.ts +2 -0
  55. package/dist/frontend/components/editor/docx-serializer.js +14600 -0
  56. package/dist/frontend/components/editor/extensions.d.ts +2 -0
  57. package/dist/frontend/components/editor/extensions.js +2 -0
  58. package/dist/frontend/components/editor/markdown-toggle.d.ts +2 -0
  59. package/dist/frontend/components/editor/markdown-toggle.js +72 -0
  60. package/dist/frontend/components/editor/markdown.d.ts +1 -0
  61. package/dist/frontend/components/editor/markdown.js +2 -0
  62. package/dist/frontend/components/folder-node.d.ts +3 -0
  63. package/dist/frontend/components/folder-node.js +2 -0
  64. package/dist/frontend/components/folder-tree-selector.d.ts +2 -0
  65. package/dist/frontend/components/folder-tree-selector.js +2 -0
  66. package/dist/frontend/components/save-as-dialog.d.ts +2 -0
  67. package/dist/frontend/components/save-as-dialog.js +254 -0
  68. package/dist/frontend/components/settings.js +1 -1
  69. package/dist/frontend/components/share-dialog.d.ts +2 -0
  70. package/dist/frontend/components/share-dialog.js +2 -0
  71. package/dist/frontend/components/sidebar.js +1206 -3762
  72. package/dist/frontend/components/tag-create-dialog.d.ts +2 -0
  73. package/dist/frontend/components/tag-create-dialog.js +2 -0
  74. package/dist/frontend/components/version-history.d.ts +2 -0
  75. package/dist/frontend/components/version-history.js +293 -0
  76. package/dist/frontend/{context-DbnW5yib.js → context-BMD6XTPe.js} +0 -3
  77. package/dist/frontend/copy-CMAA6GP0.js +31 -0
  78. package/dist/frontend/dashboard.js +1148 -1224
  79. package/dist/frontend/{SettingsDialog-D4pgFD0D.js → db-ByrlYfaI.js} +923 -2002
  80. package/dist/frontend/dialog-BDkiG42a.js +213 -0
  81. package/dist/frontend/disclose-version-5I57E3LD.js +3 -0
  82. package/dist/frontend/dndzone-BzsOINtP.js +1199 -0
  83. package/dist/frontend/doc-tabs.d.ts +7 -0
  84. package/dist/frontend/doc-tabs.js +14 -0
  85. package/dist/frontend/document-drop-coordinator-C4xHMjwC.js +68 -0
  86. package/dist/frontend/document-drop-coordinator.d.ts +2 -0
  87. package/dist/frontend/document-drop-coordinator.js +2 -0
  88. package/dist/frontend/{documents-DFVS9SsL.js → documents-BsDkvzCm.js} +1 -1
  89. package/dist/frontend/editorExtensions-BMHvUbdk.js +27021 -0
  90. package/dist/frontend/ellipsis-vertical-BBuPldbW.js +519 -0
  91. package/dist/frontend/extension.d.ts +2 -1
  92. package/dist/frontend/extension.js +6 -5
  93. package/dist/frontend/floating-layer-anchor-C51M4l6v.js +4513 -0
  94. package/dist/frontend/folder-TQjKY4yn.js +44 -0
  95. package/dist/frontend/folder-refresh.d.ts +8 -0
  96. package/dist/frontend/folder-refresh.js +2 -0
  97. package/dist/frontend/{folders-CGe9diAG.js → folders-CTuV_JtU.js} +1 -1
  98. package/dist/frontend/frontend.css +1 -1
  99. package/dist/frontend/i18n.js +1 -1
  100. package/dist/frontend/identity-DyWeNzu_.js +69 -0
  101. package/dist/frontend/index-client-jobkrWVL.js +86 -0
  102. package/dist/frontend/input-CBI6p4Xl.js +23 -0
  103. package/dist/frontend/keyboard.d.ts +14 -0
  104. package/dist/frontend/keyboard.js +2 -0
  105. package/dist/frontend/keyboard.svelte-D7H8DihC.js +133 -0
  106. package/dist/frontend/label-C-GkhEp4.js +30 -0
  107. package/dist/frontend/loader-circle-Bk-WFz9g.js +267 -0
  108. package/dist/frontend/markdown-BwnyjITU.js +11 -0
  109. package/dist/frontend/messages-BpYaLbzE.js +1091 -0
  110. package/dist/frontend/offline/identity.d.ts +6 -0
  111. package/dist/frontend/offline/identity.js +2 -0
  112. package/dist/frontend/resolve-BG4HgzpN.js +19 -0
  113. package/dist/frontend/rolldown-runtime-CWhphoD1.js +23 -0
  114. package/dist/frontend/rotate-ccw-CleZtJ5D.js +24 -0
  115. package/dist/frontend/search.js +637 -628
  116. package/dist/frontend/select-value-CvrWRkXW.js +1436 -0
  117. package/dist/frontend/settings-blyobazp.js +62 -0
  118. package/dist/frontend/share-Cu7SSJuJ.js +28 -0
  119. package/dist/frontend/shared-document.js +105 -69
  120. package/dist/frontend/subfolders-refresh-store.svelte-BsheoQma.js +65 -0
  121. package/dist/frontend/{tag-C5xrg-bb.js → tag-Dx3SmucJ.js} +7 -7
  122. package/dist/frontend/tag-store.svelte-CIaF4sdZ.js +29 -0
  123. package/dist/frontend/{tags-DVXTut0p.js → tags-CtCV0Djo.js} +1 -1
  124. package/dist/frontend/textarea-Dfp78XlL.js +23 -0
  125. package/dist/frontend/theme.js +1 -1
  126. package/dist/frontend/{theme.svelte-ei0QxXa8.js → theme.svelte-CROg1Hi5.js} +7 -7
  127. package/dist/frontend/types.d.ts +6 -0
  128. package/dist/frontend/types.js +0 -0
  129. package/dist/frontend/utils/clipboard.d.ts +1 -0
  130. package/dist/frontend/utils/clipboard.js +2 -0
  131. package/dist/frontend/utils/dndzone.d.ts +4 -0
  132. package/dist/frontend/utils/dndzone.js +2 -0
  133. package/dist/frontend/utils-C0OW2678.js +20 -0
  134. package/dist/frontend/utils-D8kmM3ub.js +8 -0
  135. package/dist/frontend/utils.d.ts +4 -0
  136. package/dist/frontend/utils.js +2 -0
  137. package/dist/frontend/x-DaTdeDXv.js +24 -0
  138. package/dist/lifecycle-persistent.d.ts +31 -0
  139. package/dist/lifecycle-persistent.js +33 -0
  140. package/dist/server-only-browser-entry.d.ts +0 -0
  141. package/dist/server-only-browser-entry.js +3 -0
  142. package/dist/storage-quota.d.ts +36 -0
  143. package/dist/storage-quota.js +14 -1
  144. package/dist/types.d.ts +7 -0
  145. package/dist/workspace.d.ts +4 -1
  146. package/dist/workspace.js +29 -8
  147. package/package.json +110 -3
  148. package/packages/cli/src/index.ts +1 -1
  149. package/packages/db/src/index.ts +11 -2
  150. package/packages/db/src/schema.ts +30 -2
  151. package/packages/db/src/with-tenant.ts +38 -1
  152. package/packages/mcp-server/src/index.ts +1 -1
  153. package/dist/frontend/DatePicker-BcMD49PV.js +0 -275
  154. package/dist/frontend/ShareDialog-Dzh7rV7J.js +0 -1317
  155. package/dist/frontend/input-ieWIt8I7.js +0 -257
  156. package/dist/frontend/messages-CTGAsqFu.js +0 -1106
  157. package/dist/frontend/resolve-Bl6xmJl_.js +0 -2068
  158. package/dist/frontend/tag-store.svelte-B3839FYq.js +0 -6152
@@ -0,0 +1,31 @@
1
+ import { type AssertPurgeAllowed, type LifecycleHostStep, type UserDataLifecycle, type UserDataLifecycleAdapter } from "./lifecycle";
2
+ /**
3
+ * Minimal transaction boundary required by the durable lifecycle runtime.
4
+ * Hosts supply a request/RLS-scoped executor; SDK code never imports a global
5
+ * database singleton or bypasses host tenant policy.
6
+ */
7
+ export type LifecycleScopedDatabaseExecutor = <T>(context: Readonly<{
8
+ actorUserId: string;
9
+ requestId: string;
10
+ signal?: AbortSignal;
11
+ }>, operation: () => Promise<T>) => Promise<T>;
12
+ /**
13
+ * Public persistent lifecycle composition contract. The OSS persistence
14
+ * saga is supplied by the backend runtime; SaaS hosts attach their own steps
15
+ * and RLS executor without private imports.
16
+ */
17
+ export type LifecycleRuntimeAdapters = Readonly<{
18
+ database: LifecycleScopedDatabaseExecutor;
19
+ adapter: UserDataLifecycleAdapter;
20
+ }>;
21
+ export type PersistentLifecycleRuntimeOptions = Readonly<{
22
+ runtime: LifecycleRuntimeAdapters;
23
+ assertPurgeAllowed: AssertPurgeAllowed;
24
+ hostSteps?: readonly LifecycleHostStep[];
25
+ }>;
26
+ /**
27
+ * Creates an immutable, transaction-scoped public lifecycle facade.
28
+ * The database executor is invoked for every OSS adapter operation so hosts
29
+ * can set RLS GUCs and reject a missing scope before any mutation occurs.
30
+ */
31
+ export declare function createPersistentLifecycleRuntime(options: PersistentLifecycleRuntimeOptions): UserDataLifecycle;
@@ -0,0 +1,33 @@
1
+ import { createUserDataLifecycle, orderLifecycleHostSteps, } from "./lifecycle";
2
+ function immutableContext(context) {
3
+ return Object.freeze({ ...context });
4
+ }
5
+ /**
6
+ * Creates an immutable, transaction-scoped public lifecycle facade.
7
+ * The database executor is invoked for every OSS adapter operation so hosts
8
+ * can set RLS GUCs and reject a missing scope before any mutation occurs.
9
+ */
10
+ export function createPersistentLifecycleRuntime(options) {
11
+ const hostSteps = orderLifecycleHostSteps(options.hostSteps ?? []);
12
+ const lifecycle = createUserDataLifecycle({
13
+ async *exportUserData(context) {
14
+ const immutable = immutableContext(context);
15
+ const records = await options.runtime.database(immutable, async () => {
16
+ const result = [];
17
+ for await (const record of options.runtime.adapter.exportUserData(immutable))
18
+ result.push(record);
19
+ return result;
20
+ });
21
+ for (const record of records)
22
+ yield record;
23
+ },
24
+ async purgeUserData(context, gate) {
25
+ const immutable = immutableContext(context);
26
+ return options.runtime.database(immutable, () => options.runtime.adapter.purgeUserData(immutable, gate));
27
+ },
28
+ }, async (context) => options.assertPurgeAllowed(immutableContext(context)));
29
+ // Validate host-step ordering eagerly. The OSS adapter owns invocation; the
30
+ // public factory records the accepted contract without inventing SaaS data.
31
+ void hostSteps;
32
+ return lifecycle;
33
+ }
File without changes
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ /** Browser-condition guard for every server-only public subpath. */
3
+ throw new Error("This @hiai-gg/docsmint entrypoint is server-only");
@@ -46,6 +46,42 @@ export type StorageQuotaService = Readonly<{
46
46
  commit(request: StorageQuotaCommitRequest): Promise<StorageQuotaCommitResult>;
47
47
  release(request: StorageQuotaReleaseRequest): Promise<StorageQuotaReleaseResult>;
48
48
  }>;
49
+ /**
50
+ * Verified server-side identity for an attachment write. It is intentionally
51
+ * richer than the generic quota adapter: attachment storage must be charged
52
+ * to one workspace, actor and immutable object key before a presign is issued.
53
+ */
54
+ export type AttachmentStorageQuotaContext = Readonly<{
55
+ workspaceId: string;
56
+ actorUserId: string;
57
+ documentId: string;
58
+ storageKey: string;
59
+ proposedSize: number;
60
+ requestId: string;
61
+ idempotencyKey: string;
62
+ signal?: AbortSignal;
63
+ }>;
64
+ export type AttachmentStorageQuotaFinalization = Readonly<{
65
+ reservationId: string;
66
+ actualSize: number;
67
+ }>;
68
+ /**
69
+ * OSS invokes this only after it has verified tenant context and document
70
+ * access. A host provider is never exposed to HTTP or browser code.
71
+ */
72
+ export type AttachmentStorageQuotaAdmission = Readonly<{
73
+ reserve(context: AttachmentStorageQuotaContext): Promise<Readonly<{
74
+ id: string;
75
+ }>>;
76
+ finalize(context: AttachmentStorageQuotaContext, finalization: AttachmentStorageQuotaFinalization): Promise<void>;
77
+ releaseReservation(context: AttachmentStorageQuotaContext, reservationId: string): Promise<void>;
78
+ releaseCommitted(context: AttachmentStorageQuotaContext): Promise<void>;
79
+ }>;
80
+ export declare class MissingAttachmentStorageQuotaAdmissionError extends Error {
81
+ readonly code: "ATTACHMENT_STORAGE_QUOTA_ADMISSION_MISSING";
82
+ constructor();
83
+ }
84
+ export declare function requireAttachmentStorageQuotaAdmission(admission: AttachmentStorageQuotaAdmission | undefined): AttachmentStorageQuotaAdmission;
49
85
  export declare class StorageQuotaExceededError extends Error {
50
86
  readonly code: "STORAGE_QUOTA_EXCEEDED";
51
87
  readonly usageBytes: number;
@@ -1,4 +1,16 @@
1
1
  /** Server-only, persistence-agnostic storage quota contract. */
2
+ export class MissingAttachmentStorageQuotaAdmissionError extends Error {
3
+ code = "ATTACHMENT_STORAGE_QUOTA_ADMISSION_MISSING";
4
+ constructor() {
5
+ super("Attachment storage quota admission is required when workspace tenancy is enabled");
6
+ this.name = "MissingAttachmentStorageQuotaAdmissionError";
7
+ }
8
+ }
9
+ export function requireAttachmentStorageQuotaAdmission(admission) {
10
+ if (!admission)
11
+ throw new MissingAttachmentStorageQuotaAdmissionError();
12
+ return admission;
13
+ }
2
14
  export class StorageQuotaExceededError extends Error {
3
15
  code = "STORAGE_QUOTA_EXCEEDED";
4
16
  usageBytes;
@@ -71,7 +83,8 @@ export function createStorageQuotaService(adapter) {
71
83
  assertReservationId(request.reservationId);
72
84
  assertPositiveBytes(request.actualBytes, "actualBytes");
73
85
  const result = await adapter.commit(Object.freeze({ ...request }));
74
- if (result.status !== "committed" && result.status !== "already_committed") {
86
+ if (result.status !== "committed" &&
87
+ result.status !== "already_committed") {
75
88
  throw new TypeError("Storage quota adapter returned an invalid commit status");
76
89
  }
77
90
  return Object.freeze({ ...result });
package/dist/types.d.ts CHANGED
@@ -294,11 +294,18 @@ export interface DocsAttachmentPresignInput {
294
294
  export interface DocsAttachmentPresignResponse {
295
295
  url: string;
296
296
  key: string;
297
+ /**
298
+ * Opaque server-issued reservation. Present only when workspace storage
299
+ * enforcement is enabled and required verbatim by confirmAttachment.
300
+ */
301
+ quotaReservationId?: string;
297
302
  maxSize: number;
298
303
  expiresIn: number;
299
304
  }
300
305
  export interface DocsAttachmentConfirmInput extends DocsAttachmentPresignInput {
301
306
  key: string;
307
+ /** The opaque reservation returned by presignAttachment when present. */
308
+ quotaReservationId?: string;
302
309
  }
303
310
  export type DocsApiKeyScope = "global" | `category:${string}:${"read" | "edit" | "write"}`;
304
311
  export interface DocsApiKeyCreated {
@@ -15,7 +15,10 @@ export type WorkspaceAssertionOptions = Readonly<{
15
15
  issuer: string;
16
16
  nowSeconds?: number;
17
17
  clockSkewSeconds?: number;
18
- maxTtlSeconds?: number;
18
+ /** @deprecated Assertion lifetime is fixed at 60 seconds. */
19
+ maxTtlSeconds?: never;
19
20
  }>;
21
+ export declare const DOCSMINT_WORKSPACE_ASSERTION_TTL_SECONDS = 60;
22
+ export declare const DOCSMINT_WORKSPACE_ASSERTION_CLOCK_SKEW_SECONDS = 5;
20
23
  export declare function createDocsmintWorkspaceAssertion(context: DocsmintWorkspaceContext, secret: string): Promise<string>;
21
24
  export declare function verifyDocsmintWorkspaceAssertion(assertion: string, options: WorkspaceAssertionOptions): Promise<DocsmintWorkspaceContext>;
package/dist/workspace.js CHANGED
@@ -4,17 +4,22 @@ export const DOCSMINT_WORKSPACE_CONTEXT_HEADER = "x-docsmint-workspace-context";
4
4
  export const EXTERNAL_TENANT_CONTEXT_HEADER = "x-hiai-tenant-context";
5
5
  const encoder = new TextEncoder();
6
6
  const decoder = new TextDecoder();
7
- const MAX_TTL_SECONDS = 300;
7
+ export const DOCSMINT_WORKSPACE_ASSERTION_TTL_SECONDS = 60;
8
+ export const DOCSMINT_WORKSPACE_ASSERTION_CLOCK_SKEW_SECONDS = 5;
8
9
  function toBase64Url(bytes) {
9
10
  let binary = "";
10
11
  for (const byte of bytes)
11
12
  binary += String.fromCharCode(byte);
12
- return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
13
+ return btoa(binary)
14
+ .replaceAll("+", "-")
15
+ .replaceAll("/", "_")
16
+ .replace(/=+$/, "");
13
17
  }
14
18
  function fromBase64Url(value) {
15
19
  if (!/^[A-Za-z0-9_-]+$/.test(value))
16
20
  throw new Error("Invalid base64url");
17
- const padded = value.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - (value.length % 4)) % 4);
21
+ const padded = value.replaceAll("-", "+").replaceAll("_", "/") +
22
+ "=".repeat((4 - (value.length % 4)) % 4);
18
23
  const binary = atob(padded);
19
24
  return Uint8Array.from(binary, (character) => character.charCodeAt(0));
20
25
  }
@@ -26,13 +31,20 @@ function assertContext(value) {
26
31
  if (!value || typeof value !== "object")
27
32
  throw new Error("Invalid workspace assertion payload");
28
33
  const context = value;
29
- if (typeof context.actorUserId !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(context.actorUserId))
34
+ if (typeof context.actorUserId !== "string" ||
35
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(context.actorUserId))
30
36
  throw new Error("Invalid actorUserId");
31
- if (typeof context.workspaceId !== "string" || !context.workspaceId.trim() || context.workspaceId.trim() !== context.workspaceId || context.workspaceId.length > 128)
37
+ if (typeof context.workspaceId !== "string" ||
38
+ !context.workspaceId.trim() ||
39
+ context.workspaceId.trim() !== context.workspaceId ||
40
+ context.workspaceId.length > 128)
32
41
  throw new Error("Invalid workspaceId");
33
42
  if (!["owner", "admin", "editor", "viewer"].includes(context.actorRole))
34
43
  throw new Error("Invalid actorRole");
35
- if (!Number.isFinite(context.issuedAt) || !Number.isFinite(context.expiresAt) || typeof context.issuer !== "string" || !context.issuer)
44
+ if (!Number.isFinite(context.issuedAt) ||
45
+ !Number.isFinite(context.expiresAt) ||
46
+ typeof context.issuer !== "string" ||
47
+ !context.issuer)
36
48
  throw new Error("Invalid workspace assertion timestamps or issuer");
37
49
  }
38
50
  export async function createDocsmintWorkspaceAssertion(context, secret) {
@@ -59,8 +71,17 @@ export async function verifyDocsmintWorkspaceAssertion(assertion, options) {
59
71
  if (context.issuer !== options.issuer)
60
72
  throw new Error("Invalid workspace assertion issuer");
61
73
  const now = options.nowSeconds ?? Math.floor(Date.now() / 1000);
62
- const skew = options.clockSkewSeconds ?? 5;
63
- if (context.issuedAt > now + skew || context.expiresAt <= now - skew || context.expiresAt <= context.issuedAt || context.expiresAt - context.issuedAt > (options.maxTtlSeconds ?? MAX_TTL_SECONDS))
74
+ const skew = options.clockSkewSeconds ?? DOCSMINT_WORKSPACE_ASSERTION_CLOCK_SKEW_SECONDS;
75
+ if (!Number.isSafeInteger(skew) ||
76
+ skew < 0 ||
77
+ skew > DOCSMINT_WORKSPACE_ASSERTION_CLOCK_SKEW_SECONDS) {
78
+ throw new Error("Invalid workspace assertion clock skew");
79
+ }
80
+ if (context.issuedAt > now + skew ||
81
+ context.expiresAt <= now - skew ||
82
+ context.expiresAt <= context.issuedAt ||
83
+ context.expiresAt - context.issuedAt >
84
+ DOCSMINT_WORKSPACE_ASSERTION_TTL_SECONDS)
64
85
  throw new Error("Invalid workspace assertion lifetime");
65
86
  return Object.freeze({ ...context });
66
87
  }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@hiai-gg/docsmint",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "type": "module",
5
5
  "browser": {
6
6
  "./dist/backend-launcher.js": false,
7
7
  "./dist/lifecycle.js": false,
8
+ "./dist/lifecycle-persistent.js": false,
8
9
  "./dist/storage-quota.js": false,
9
10
  "./dist/workspace.js": false
10
11
  },
@@ -109,6 +110,86 @@
109
110
  "import": "./dist/frontend/api/settings.js",
110
111
  "types": "./dist/frontend/api/settings.d.ts"
111
112
  },
113
+ "./frontend/api/attachments": {
114
+ "import": "./dist/frontend/api/attachments.js",
115
+ "types": "./dist/frontend/api/attachments.d.ts"
116
+ },
117
+ "./frontend/collaboration": {
118
+ "import": "./dist/frontend/collaboration.js",
119
+ "types": "./dist/frontend/collaboration.d.ts"
120
+ },
121
+ "./frontend/components/create-snapshot-dialog": {
122
+ "import": "./dist/frontend/components/create-snapshot-dialog.js",
123
+ "types": "./dist/frontend/components/create-snapshot-dialog.d.ts"
124
+ },
125
+ "./frontend/components/delete-dialog": {
126
+ "import": "./dist/frontend/components/delete-dialog.js",
127
+ "types": "./dist/frontend/components/delete-dialog.d.ts"
128
+ },
129
+ "./frontend/components/category-dialog": {
130
+ "import": "./dist/frontend/components/category-dialog.js",
131
+ "types": "./dist/frontend/components/category-dialog.d.ts"
132
+ },
133
+ "./frontend/components/folder-node": {
134
+ "import": "./dist/frontend/components/folder-node.js",
135
+ "types": "./dist/frontend/components/folder-node.d.ts"
136
+ },
137
+ "./frontend/document-drop-coordinator": {
138
+ "import": "./dist/frontend/document-drop-coordinator.js",
139
+ "types": "./dist/frontend/document-drop-coordinator.d.ts"
140
+ },
141
+ "./frontend/offline/identity": {
142
+ "import": "./dist/frontend/offline/identity.js",
143
+ "types": "./dist/frontend/offline/identity.d.ts"
144
+ },
145
+ "./frontend/doc-tabs": {
146
+ "import": "./dist/frontend/doc-tabs.js",
147
+ "types": "./dist/frontend/doc-tabs.d.ts"
148
+ },
149
+ "./frontend/components/editor/document-editor": {
150
+ "import": "./dist/frontend/components/editor/document-editor.js",
151
+ "types": "./dist/frontend/components/editor/document-editor.d.ts"
152
+ },
153
+ "./frontend/components/folder-tree-selector": {
154
+ "import": "./dist/frontend/components/folder-tree-selector.js",
155
+ "types": "./dist/frontend/components/folder-tree-selector.d.ts"
156
+ },
157
+ "./frontend/components/save-as-dialog": {
158
+ "import": "./dist/frontend/components/save-as-dialog.js",
159
+ "types": "./dist/frontend/components/save-as-dialog.d.ts"
160
+ },
161
+ "./frontend/components/share-dialog": {
162
+ "import": "./dist/frontend/components/share-dialog.js",
163
+ "types": "./dist/frontend/components/share-dialog.d.ts"
164
+ },
165
+ "./frontend/components/tag-create-dialog": {
166
+ "import": "./dist/frontend/components/tag-create-dialog.js",
167
+ "types": "./dist/frontend/components/tag-create-dialog.d.ts"
168
+ },
169
+ "./frontend/components/version-history": {
170
+ "import": "./dist/frontend/components/version-history.js",
171
+ "types": "./dist/frontend/components/version-history.d.ts"
172
+ },
173
+ "./frontend/components/editor/document-title": {
174
+ "import": "./dist/frontend/components/editor/document-title.js",
175
+ "types": "./dist/frontend/components/editor/document-title.d.ts"
176
+ },
177
+ "./frontend/components/editor/markdown-toggle": {
178
+ "import": "./dist/frontend/components/editor/markdown-toggle.js",
179
+ "types": "./dist/frontend/components/editor/markdown-toggle.d.ts"
180
+ },
181
+ "./frontend/components/editor/extensions": {
182
+ "import": "./dist/frontend/components/editor/extensions.js",
183
+ "types": "./dist/frontend/components/editor/extensions.d.ts"
184
+ },
185
+ "./frontend/components/editor/markdown": {
186
+ "import": "./dist/frontend/components/editor/markdown.js",
187
+ "types": "./dist/frontend/components/editor/markdown.d.ts"
188
+ },
189
+ "./frontend/components/editor/docx-serializer": {
190
+ "import": "./dist/frontend/components/editor/docx-serializer.js",
191
+ "types": "./dist/frontend/components/editor/docx-serializer.d.ts"
192
+ },
112
193
  "./frontend/components/sidebar": {
113
194
  "import": "./dist/frontend/components/sidebar.js",
114
195
  "types": "./dist/frontend/components/sidebar.d.ts"
@@ -117,6 +198,34 @@
117
198
  "import": "./dist/frontend/components/settings.js",
118
199
  "types": "./dist/frontend/components/settings.d.ts"
119
200
  },
201
+ "./frontend/types": {
202
+ "import": "./dist/frontend/types.js",
203
+ "types": "./dist/frontend/types.d.ts"
204
+ },
205
+ "./frontend/keyboard": {
206
+ "import": "./dist/frontend/keyboard.js",
207
+ "types": "./dist/frontend/keyboard.d.ts"
208
+ },
209
+ "./frontend/folder-refresh": {
210
+ "import": "./dist/frontend/folder-refresh.js",
211
+ "types": "./dist/frontend/folder-refresh.d.ts"
212
+ },
213
+ "./frontend/utils": {
214
+ "import": "./dist/frontend/utils.js",
215
+ "types": "./dist/frontend/utils.d.ts"
216
+ },
217
+ "./frontend/utils/clipboard": {
218
+ "import": "./dist/frontend/utils/clipboard.js",
219
+ "types": "./dist/frontend/utils/clipboard.d.ts"
220
+ },
221
+ "./frontend/utils/dndzone": {
222
+ "import": "./dist/frontend/utils/dndzone.js",
223
+ "types": "./dist/frontend/utils/dndzone.d.ts"
224
+ },
225
+ "./frontend/api/share": {
226
+ "import": "./dist/frontend/api/share.js",
227
+ "types": "./dist/frontend/api/share.d.ts"
228
+ },
120
229
  "./frontend/theme": {
121
230
  "import": "./dist/frontend/theme.js",
122
231
  "types": "./dist/frontend/theme.d.ts"
@@ -166,12 +275,10 @@
166
275
  "packages/db/src/client.ts",
167
276
  "packages/db/src/schema.ts",
168
277
  "packages/db/src/with-tenant.ts",
169
- "packages/cli/src/index.ts",
170
278
  "packages/cli/src/client.ts",
171
279
  "packages/cli/src/config.ts",
172
280
  "packages/cli/src/format.ts",
173
281
  "packages/cli/src/commands",
174
- "packages/mcp-server/src/index.ts",
175
282
  "packages/mcp-server/src/client.ts",
176
283
  "packages/mcp-server/src/types.ts",
177
284
  "packages/mcp-server/src/tools",
@@ -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.3.5";
27
+ const VERSION = "0.3.7";
28
28
 
29
29
  const program = new Command();
30
30
  program
@@ -1,5 +1,14 @@
1
1
  export * from "./schema";
2
2
  export { db, client } from "./client";
3
3
  export type { Database } from "./client";
4
- export { withTenant, adminTenantContext, shareGuestTenantContext } from "./with-tenant";
5
- export type { TenantContext } from "./with-tenant";
4
+ export {
5
+ withTenant,
6
+ adminTenantContext,
7
+ shareGuestTenantContext,
8
+ createActorScopedTransactionExecutor,
9
+ } from "./with-tenant";
10
+ export type {
11
+ ActorScopedTransactionExecutor,
12
+ TenantContext,
13
+ TenantTransaction,
14
+ } from "./with-tenant";
@@ -698,7 +698,8 @@ export const lifecycleOperations = pgTable(
698
698
  "lifecycle_operations",
699
699
  {
700
700
  id: uuid("id").primaryKey().defaultRandom(),
701
- actorUserId: uuid("actor_user_id").notNull().references(() => users.id, { onDelete: "restrict" }),
701
+ actorUserId: uuid("actor_user_id").references(() => users.id, { onDelete: "set null" }),
702
+ actorSubjectHash: text("actor_subject_hash").notNull(),
702
703
  idempotencyKey: text("idempotency_key").notNull(),
703
704
  operationKind: lifecycleOperationKindEnum("operation_kind").notNull(),
704
705
  status: lifecycleOperationStatusEnum("status").notNull().default("pending"),
@@ -717,7 +718,34 @@ export const lifecycleOperations = pgTable(
717
718
  uniqueIndex("lifecycle_operations_actor_idempotency_idx").on(table.actorUserId, table.idempotencyKey),
718
719
  index("lifecycle_operations_status_lease_idx").on(table.status, table.leaseExpiresAt),
719
720
  index("lifecycle_operations_actor_idx").on(table.actorUserId),
720
- index("lifecycle_operations_retryable_idx").on(table.status).where(sql`${table.status} = 'retryable'`),
721
+ index("lifecycle_operations_retryable_idx").on(table.status).where(sql`${table.status} = 'retryable'`),
722
+ check("lifecycle_operations_actor_subject_hash", sql`${table.actorSubjectHash} ~ '^[a-f0-9]{64}$'`),
723
+ ],
724
+ );
725
+
726
+ // Atomically committed alongside a document and its initial version when an
727
+ // authenticated client supplies Idempotency-Key on POST /api/documents.
728
+ export const documentCreateOperations = pgTable(
729
+ "document_create_operations",
730
+ {
731
+ id: uuid("id").primaryKey().defaultRandom(),
732
+ workspaceId: text("workspace_id").notNull(),
733
+ actorUserId: uuid("actor_user_id")
734
+ .notNull()
735
+ .references(() => users.id, { onDelete: "cascade" }),
736
+ idempotencyKey: text("idempotency_key").notNull(),
737
+ documentId: uuid("document_id")
738
+ .notNull()
739
+ .references(() => documents.id, { onDelete: "cascade" }),
740
+ createdAt: timestamp("created_at").defaultNow().notNull(),
741
+ },
742
+ (table) => [
743
+ uniqueIndex("document_create_operations_workspace_actor_key_idx").on(
744
+ table.workspaceId,
745
+ table.actorUserId,
746
+ table.idempotencyKey,
747
+ ),
748
+ uniqueIndex("document_create_operations_document_idx").on(table.documentId),
721
749
  ],
722
750
  );
723
751
 
@@ -1,5 +1,42 @@
1
1
  import { sql } from "drizzle-orm";
2
- import { db } from "./client";
2
+ import { db, type Database } from "./client";
3
+
4
+ /** A transaction carrying the same query surface as the tenant-scoped DB. */
5
+ export type TenantTransaction = Parameters<
6
+ Parameters<typeof db.transaction>[0]
7
+ >[0];
8
+
9
+ /**
10
+ * Server-side adapter for packages that must run short actor-scoped database
11
+ * transactions without importing this module's process-global client.
12
+ */
13
+ export interface ActorScopedTransactionExecutor {
14
+ withActorTransaction<T>(
15
+ actorUserId: string,
16
+ operation: (tx: TenantTransaction) => Promise<T>,
17
+ ): Promise<T>;
18
+ }
19
+
20
+ export function createActorScopedTransactionExecutor(
21
+ database: Database,
22
+ ): ActorScopedTransactionExecutor {
23
+ return {
24
+ withActorTransaction(actorUserId, operation) {
25
+ return database.transaction(async (tx) => {
26
+ await tx.execute(
27
+ sql`SELECT set_config('app.current_user_id', ${actorUserId}, true)`,
28
+ );
29
+ await tx.execute(
30
+ sql`SELECT set_config('app.current_user_role', 'user', true)`,
31
+ );
32
+ await tx.execute(
33
+ sql`SELECT set_config('app.current_workspace_id', '', true)`,
34
+ );
35
+ return operation(tx);
36
+ });
37
+ },
38
+ };
39
+ }
3
40
 
4
41
  export interface TenantContext {
5
42
  userId: string;
@@ -35,7 +35,7 @@ interface McpToolResult {
35
35
 
36
36
  const server = new McpServer({
37
37
  name: "hiai-docs",
38
- version: "0.3.5",
38
+ version: "0.3.7",
39
39
  });
40
40
 
41
41
  /**