@hiai-gg/docsmint 0.3.2 → 0.3.3

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.
@@ -0,0 +1,574 @@
1
+ import {
2
+ attachments,
3
+ auditLog,
4
+ db,
5
+ documentEmbeddings,
6
+ documents,
7
+ lifecycleOperations,
8
+ shareLinks,
9
+ versions,
10
+ } from "@hiai-docs/db";
11
+ import type {
12
+ AssertPurgeAllowed,
13
+ ExportUserDataContext,
14
+ LifecycleHostStep,
15
+ PurgeUserDataContext,
16
+ PurgeUserDataResult,
17
+ UserDataExportRecord,
18
+ } from "@hiai-docs/sdk";
19
+ import { and, eq, inArray, lt, or, sql } from "drizzle-orm";
20
+
21
+ const LEASE_MS = 60_000;
22
+ const SAFE_ERROR_CODES = new Set([
23
+ "aborted",
24
+ "fence_rejected",
25
+ "lease_lost",
26
+ "object_storage_failed",
27
+ "queue_failed",
28
+ "redis_failed",
29
+ "graph_failed",
30
+ "host_step_failed",
31
+ "persistence_failed",
32
+ ]);
33
+
34
+ type PersistentOperation = typeof lifecycleOperations.$inferSelect;
35
+
36
+ export type LifecycleRuntimeAdapters = Readonly<{
37
+ /** Checks the host-owned fence immediately before the first mutation. */
38
+ verifyPurgeFence: (
39
+ ctx: PurgeUserDataContext,
40
+ fenceToken: string,
41
+ ) => Promise<void>;
42
+ deleteObjects: (
43
+ keys: readonly string[],
44
+ signal?: AbortSignal,
45
+ ) => Promise<number>;
46
+ cancelAccountJobs: (
47
+ actorUserId: string,
48
+ signal?: AbortSignal,
49
+ ) => Promise<number>;
50
+ clearAccountRedisState: (
51
+ actorUserId: string,
52
+ signal?: AbortSignal,
53
+ ) => Promise<number>;
54
+ removeCollaborationState: (
55
+ actorUserId: string,
56
+ signal?: AbortSignal,
57
+ ) => Promise<number>;
58
+ removeGraphState: (
59
+ documentIds: readonly string[],
60
+ signal?: AbortSignal,
61
+ ) => Promise<number>;
62
+ }>;
63
+
64
+ export type PersistentLifecycleService = Readonly<{
65
+ exportUserData(
66
+ ctx: ExportUserDataContext,
67
+ ): AsyncIterable<UserDataExportRecord>;
68
+ purgeUserData(
69
+ ctx: PurgeUserDataContext,
70
+ gate: Readonly<{ fenceToken: string }>,
71
+ ): Promise<PurgeUserDataResult>;
72
+ }>;
73
+
74
+ function throwIfAborted(signal: AbortSignal | undefined): void {
75
+ if (signal?.aborted)
76
+ throw signal.reason ?? new DOMException("Aborted", "AbortError");
77
+ }
78
+
79
+ function safeErrorCode(error: unknown): string {
80
+ if (error instanceof DOMException && error.name === "AbortError")
81
+ return "aborted";
82
+ if (error instanceof Error && SAFE_ERROR_CODES.has(error.message))
83
+ return error.message;
84
+ return "persistence_failed";
85
+ }
86
+
87
+ function tokenHash(token: string): string {
88
+ return new Bun.CryptoHasher("sha256").update(token).digest("hex");
89
+ }
90
+
91
+ function checksumLine(
92
+ hash: Bun.CryptoHasher,
93
+ record: UserDataExportRecord,
94
+ ): void {
95
+ hash.update(`${JSON.stringify(record)}\n`, "utf8");
96
+ }
97
+
98
+ function counts(value: unknown): Record<string, number> {
99
+ return typeof value === "object" && value !== null
100
+ ? (value as Record<string, number>)
101
+ : {};
102
+ }
103
+
104
+ function operationCounts(
105
+ operation: PersistentOperation,
106
+ ): Record<string, number> {
107
+ const result = operation.terminalResult as {
108
+ deletedByDomain?: unknown;
109
+ } | null;
110
+ return counts(result?.deletedByDomain);
111
+ }
112
+
113
+ /**
114
+ * Durable OSS-owned lifecycle saga. It does not know workspace membership or
115
+ * billing policy: the caller supplies a host fence and optional host steps.
116
+ */
117
+ export function createPersistentLifecycleService(
118
+ runtime: LifecycleRuntimeAdapters,
119
+ hostSteps: readonly LifecycleHostStep[] = [],
120
+ ): PersistentLifecycleService {
121
+ const orderedSteps = [...hostSteps].sort(
122
+ (a, b) => a.order - b.order || a.id.localeCompare(b.id),
123
+ );
124
+ if (
125
+ new Set(orderedSteps.map((step) => step.id)).size !== orderedSteps.length
126
+ ) {
127
+ throw new Error("Lifecycle host step IDs must be globally unique");
128
+ }
129
+
130
+ async function getOrCreateOperation(
131
+ ctx: PurgeUserDataContext,
132
+ ): Promise<PersistentOperation> {
133
+ await db
134
+ .insert(lifecycleOperations)
135
+ .values({
136
+ actorUserId: ctx.actorUserId,
137
+ idempotencyKey: ctx.idempotencyKey,
138
+ operationKind: "purge",
139
+ status: "pending",
140
+ })
141
+ .onConflictDoNothing();
142
+ const operation = await db.query.lifecycleOperations.findFirst({
143
+ where: and(
144
+ eq(lifecycleOperations.actorUserId, ctx.actorUserId),
145
+ eq(lifecycleOperations.idempotencyKey, ctx.idempotencyKey),
146
+ ),
147
+ });
148
+ if (!operation) throw new Error("persistence_failed");
149
+ if (operation.operationKind !== "purge")
150
+ throw new Error("persistence_failed");
151
+ return operation;
152
+ }
153
+
154
+ async function acquireLease(
155
+ operation: PersistentOperation,
156
+ owner: string,
157
+ ): Promise<boolean> {
158
+ const now = new Date();
159
+ const expiry = new Date(now.getTime() + LEASE_MS);
160
+ const updated = await db
161
+ .update(lifecycleOperations)
162
+ .set({
163
+ status: "running",
164
+ leaseOwner: owner,
165
+ leaseExpiresAt: expiry,
166
+ attemptCount: sql`${lifecycleOperations.attemptCount} + 1`,
167
+ updatedAt: now,
168
+ })
169
+ .where(
170
+ and(
171
+ eq(lifecycleOperations.id, operation.id),
172
+ or(
173
+ eq(lifecycleOperations.status, "pending"),
174
+ eq(lifecycleOperations.status, "retryable"),
175
+ and(
176
+ eq(lifecycleOperations.status, "running"),
177
+ lt(lifecycleOperations.leaseExpiresAt, now),
178
+ ),
179
+ ),
180
+ ),
181
+ )
182
+ .returning({ id: lifecycleOperations.id });
183
+ return updated.length === 1;
184
+ }
185
+
186
+ async function persistStep(
187
+ operationId: string,
188
+ leaseOwner: string,
189
+ step: string,
190
+ deletedByDomain: Record<string, number>,
191
+ ): Promise<void> {
192
+ const current = await db.query.lifecycleOperations.findFirst({
193
+ where: and(
194
+ eq(lifecycleOperations.id, operationId),
195
+ eq(lifecycleOperations.leaseOwner, leaseOwner),
196
+ ),
197
+ });
198
+ if (!current) throw new Error("lease_lost");
199
+ if (
200
+ current.status !== "running" ||
201
+ (current.leaseExpiresAt && current.leaseExpiresAt < new Date())
202
+ ) {
203
+ throw new Error("lease_lost");
204
+ }
205
+ const completed = Array.isArray(current.completedSteps)
206
+ ? current.completedSteps.filter(
207
+ (value): value is string => typeof value === "string",
208
+ )
209
+ : [];
210
+ if (!completed.includes(step)) completed.push(step);
211
+ await db
212
+ .update(lifecycleOperations)
213
+ .set({
214
+ completedSteps: completed,
215
+ terminalResult: { deletedByDomain },
216
+ updatedAt: new Date(),
217
+ })
218
+ .where(
219
+ and(
220
+ eq(lifecycleOperations.id, operationId),
221
+ eq(lifecycleOperations.leaseOwner, leaseOwner),
222
+ ),
223
+ );
224
+ }
225
+
226
+ async function runStep(
227
+ operation: PersistentOperation,
228
+ leaseOwner: string,
229
+ step: string,
230
+ deletedByDomain: Record<string, number>,
231
+ action: () => Promise<number>,
232
+ ): Promise<void> {
233
+ const completed = Array.isArray(operation.completedSteps)
234
+ ? operation.completedSteps
235
+ : [];
236
+ if (completed.includes(step)) return;
237
+ const count = await action();
238
+ deletedByDomain[step] = count;
239
+ await persistStep(operation.id, leaseOwner, step, deletedByDomain);
240
+ operation.completedSteps = [...completed, step];
241
+ }
242
+
243
+ return {
244
+ async *exportUserData(ctx) {
245
+ throwIfAborted(ctx.signal);
246
+ const exportId = crypto.randomUUID();
247
+ const hash = new Bun.CryptoHasher("sha256");
248
+ let recordCount = 0;
249
+ const manifest: UserDataExportRecord = {
250
+ type: "manifest",
251
+ schemaVersion: 1,
252
+ exportId,
253
+ actorUserId: ctx.actorUserId,
254
+ generatedAt: new Date().toISOString(),
255
+ };
256
+ checksumLine(hash, manifest);
257
+ recordCount += 1;
258
+ yield manifest;
259
+ const ownedDocuments = await db
260
+ .select()
261
+ .from(documents)
262
+ .where(eq(documents.ownerId, ctx.actorUserId));
263
+ for (const document of ownedDocuments) {
264
+ throwIfAborted(ctx.signal);
265
+ const record: UserDataExportRecord = {
266
+ type: "data",
267
+ domain: "documents",
268
+ resourceType: "document",
269
+ resourceId: document.id,
270
+ workspaceId: document.workspaceId,
271
+ payload: {
272
+ title: document.title,
273
+ content: document.content,
274
+ contentJson: document.contentJson,
275
+ metadata: document.metadata,
276
+ visibility: document.visibility,
277
+ createdAt: document.createdAt,
278
+ updatedAt: document.updatedAt,
279
+ },
280
+ };
281
+ checksumLine(hash, record);
282
+ recordCount += 1;
283
+ yield record;
284
+ }
285
+ const ownedAttachments = await db
286
+ .select({
287
+ id: attachments.id,
288
+ workspaceId: attachments.workspaceId,
289
+ filename: attachments.filename,
290
+ mimeType: attachments.mimeType,
291
+ size: attachments.size,
292
+ })
293
+ .from(attachments)
294
+ .innerJoin(documents, eq(attachments.documentId, documents.id))
295
+ .where(eq(documents.ownerId, ctx.actorUserId));
296
+ for (const attachment of ownedAttachments) {
297
+ throwIfAborted(ctx.signal);
298
+ const record: UserDataExportRecord = {
299
+ type: "attachment",
300
+ attachmentId: attachment.id,
301
+ workspaceId: attachment.workspaceId,
302
+ filename: attachment.filename,
303
+ contentType: attachment.mimeType,
304
+ size: attachment.size,
305
+ sha256: null,
306
+ };
307
+ checksumLine(hash, record);
308
+ recordCount += 1;
309
+ yield record;
310
+ }
311
+ for (const step of orderedSteps) {
312
+ if (!step.export) continue;
313
+ for await (const record of step.export(ctx)) {
314
+ throwIfAborted(ctx.signal);
315
+ if (record.type === "manifest" || record.type === "complete")
316
+ throw new Error("Host export step emitted a reserved record type");
317
+ checksumLine(hash, record);
318
+ recordCount += 1;
319
+ yield record;
320
+ }
321
+ }
322
+ yield { type: "complete", recordCount, checksum: hash.digest("hex") };
323
+ },
324
+
325
+ async purgeUserData(ctx, gate) {
326
+ throwIfAborted(ctx.signal);
327
+ let operation = await getOrCreateOperation(ctx);
328
+ if (operation.status === "completed") {
329
+ return {
330
+ status: "already_completed",
331
+ operationId: operation.id,
332
+ deletedByDomain: operationCounts(operation),
333
+ };
334
+ }
335
+ const leaseOwner = crypto.randomUUID();
336
+ if (!(await acquireLease(operation, leaseOwner)))
337
+ throw new Error("lease_lost");
338
+ operation =
339
+ (await db.query.lifecycleOperations.findFirst({
340
+ where: eq(lifecycleOperations.id, operation.id),
341
+ })) ?? operation;
342
+ const deletedByDomain = operationCounts(operation);
343
+ try {
344
+ throwIfAborted(ctx.signal);
345
+ await db
346
+ .update(lifecycleOperations)
347
+ .set({
348
+ fenceTokenHash: tokenHash(gate.fenceToken),
349
+ updatedAt: new Date(),
350
+ })
351
+ .where(
352
+ and(
353
+ eq(lifecycleOperations.id, operation.id),
354
+ eq(lifecycleOperations.leaseOwner, leaseOwner),
355
+ ),
356
+ );
357
+ await runtime.verifyPurgeFence(ctx, gate.fenceToken);
358
+
359
+ await runStep(
360
+ operation,
361
+ leaseOwner,
362
+ "cancel_account_jobs",
363
+ deletedByDomain,
364
+ () => runtime.cancelAccountJobs(ctx.actorUserId, ctx.signal),
365
+ );
366
+ await runStep(
367
+ operation,
368
+ leaseOwner,
369
+ "remove_collaboration_state",
370
+ deletedByDomain,
371
+ () => runtime.removeCollaborationState(ctx.actorUserId, ctx.signal),
372
+ );
373
+ await runStep(
374
+ operation,
375
+ leaseOwner,
376
+ "remove_subject_created_shares",
377
+ deletedByDomain,
378
+ async () =>
379
+ (
380
+ await db
381
+ .delete(shareLinks)
382
+ .where(eq(shareLinks.createdBy, ctx.actorUserId))
383
+ .returning({ id: shareLinks.id })
384
+ ).length,
385
+ );
386
+ const owned = await db
387
+ .select({ id: documents.id })
388
+ .from(documents)
389
+ .where(eq(documents.ownerId, ctx.actorUserId));
390
+ const documentIds = owned.map((row) => row.id);
391
+ await runStep(
392
+ operation,
393
+ leaseOwner,
394
+ "remove_document_versions",
395
+ deletedByDomain,
396
+ async () =>
397
+ documentIds.length
398
+ ? (
399
+ await db
400
+ .delete(versions)
401
+ .where(inArray(versions.documentId, documentIds))
402
+ .returning({ id: versions.id })
403
+ ).length
404
+ : 0,
405
+ );
406
+ await runStep(
407
+ operation,
408
+ leaseOwner,
409
+ "remove_chunks_and_embeddings",
410
+ deletedByDomain,
411
+ async () =>
412
+ documentIds.length
413
+ ? (
414
+ await db
415
+ .delete(documentEmbeddings)
416
+ .where(inArray(documentEmbeddings.documentId, documentIds))
417
+ .returning({ id: documentEmbeddings.id })
418
+ ).length
419
+ : 0,
420
+ );
421
+ await runStep(
422
+ operation,
423
+ leaseOwner,
424
+ "remove_graph_state",
425
+ deletedByDomain,
426
+ () => runtime.removeGraphState(documentIds, ctx.signal),
427
+ );
428
+ const objectRows = documentIds.length
429
+ ? await db
430
+ .select({
431
+ id: attachments.id,
432
+ storageKey: attachments.storageKey,
433
+ })
434
+ .from(attachments)
435
+ .where(inArray(attachments.documentId, documentIds))
436
+ : [];
437
+ await runStep(
438
+ operation,
439
+ leaseOwner,
440
+ "delete_attachment_objects",
441
+ deletedByDomain,
442
+ () =>
443
+ runtime.deleteObjects(
444
+ objectRows.map((row) => row.storageKey),
445
+ ctx.signal,
446
+ ),
447
+ );
448
+ await runStep(
449
+ operation,
450
+ leaseOwner,
451
+ "remove_attachment_rows",
452
+ deletedByDomain,
453
+ async () =>
454
+ objectRows.length
455
+ ? (
456
+ await db
457
+ .delete(attachments)
458
+ .where(
459
+ inArray(
460
+ attachments.id,
461
+ objectRows.map((row) => row.id),
462
+ ),
463
+ )
464
+ .returning({ id: attachments.id })
465
+ ).length
466
+ : 0,
467
+ );
468
+ await runStep(
469
+ operation,
470
+ leaseOwner,
471
+ "remove_subject_documents",
472
+ deletedByDomain,
473
+ async () =>
474
+ (
475
+ await db
476
+ .delete(documents)
477
+ .where(eq(documents.ownerId, ctx.actorUserId))
478
+ .returning({ id: documents.id })
479
+ ).length,
480
+ );
481
+ await runStep(
482
+ operation,
483
+ leaseOwner,
484
+ "clear_redis_state",
485
+ deletedByDomain,
486
+ () => runtime.clearAccountRedisState(ctx.actorUserId, ctx.signal),
487
+ );
488
+ for (const step of orderedSteps) {
489
+ if (!step.purge) continue;
490
+ await runStep(
491
+ operation,
492
+ leaseOwner,
493
+ `host:${step.id}`,
494
+ deletedByDomain,
495
+ async () => (await step.purge?.(ctx))?.deletedCount ?? 0,
496
+ );
497
+ }
498
+ await runStep(
499
+ operation,
500
+ leaseOwner,
501
+ "write_deletion_audit",
502
+ deletedByDomain,
503
+ async () => {
504
+ await db.insert(auditLog).values({
505
+ actorId: ctx.actorUserId,
506
+ action: "account_data_purged",
507
+ resourceType: "lifecycle_operation",
508
+ details: {
509
+ operationId: operation.id,
510
+ outcome: "completed",
511
+ deletedByDomain,
512
+ },
513
+ });
514
+ return 1;
515
+ },
516
+ );
517
+ await db
518
+ .update(lifecycleOperations)
519
+ .set({
520
+ status: "completed",
521
+ terminalResult: { deletedByDomain },
522
+ completedAt: new Date(),
523
+ leaseOwner: null,
524
+ leaseExpiresAt: null,
525
+ updatedAt: new Date(),
526
+ })
527
+ .where(
528
+ and(
529
+ eq(lifecycleOperations.id, operation.id),
530
+ eq(lifecycleOperations.leaseOwner, leaseOwner),
531
+ ),
532
+ );
533
+ return {
534
+ status: "completed",
535
+ operationId: operation.id,
536
+ deletedByDomain,
537
+ };
538
+ } catch (error) {
539
+ const code = safeErrorCode(error);
540
+ await db
541
+ .update(lifecycleOperations)
542
+ .set({
543
+ status: code === "fence_rejected" ? "rejected" : "retryable",
544
+ safeErrorCode: code,
545
+ leaseOwner: null,
546
+ leaseExpiresAt: null,
547
+ completedAt: code === "fence_rejected" ? new Date() : null,
548
+ updatedAt: new Date(),
549
+ })
550
+ .where(
551
+ and(
552
+ eq(lifecycleOperations.id, operation.id),
553
+ eq(lifecycleOperations.leaseOwner, leaseOwner),
554
+ ),
555
+ );
556
+ throw error;
557
+ }
558
+ },
559
+ };
560
+ }
561
+
562
+ /** Builds the public facade without allowing the OSS service to own membership policy. */
563
+ export function bindPersistentLifecycle(
564
+ service: PersistentLifecycleService,
565
+ assertPurgeAllowed: AssertPurgeAllowed,
566
+ ) {
567
+ return {
568
+ exportUserData: service.exportUserData,
569
+ async purgeUserData(ctx: PurgeUserDataContext) {
570
+ const gate = await assertPurgeAllowed(ctx);
571
+ return service.purgeUserData(ctx, gate);
572
+ },
573
+ };
574
+ }
@@ -1,10 +1,14 @@
1
1
 
2
- export type CategoryDto = Readonly<Record<string, unknown>>;
3
- export declare const createCategoryInputSchema: unknown;
4
- export declare const updateCategoryInputSchema: unknown;
5
- export declare function listCategories(...args: readonly unknown[]): Promise<readonly CategoryDto[]>;
6
- export declare function createCategory(...args: readonly unknown[]): Promise<CategoryDto>;
7
- export declare function updateCategory(...args: readonly unknown[]): Promise<CategoryDto>;
8
- export declare function deleteCategory(...args: readonly unknown[]): Promise<unknown>;
9
- export declare function setDocumentCategory(...args: readonly unknown[]): Promise<unknown>;
10
- export declare function setFolderCategory(...args: readonly unknown[]): Promise<unknown>;
2
+ import type { z } from "zod";
3
+ export interface CategoryDto { id: string; name: string; order: number; apiMode?: "unavailable" | "global" | "general" | "category" | null; apiPermissionRead?: boolean | null; apiPermissionEdit?: boolean | null; apiPermissionWrite?: boolean | null; createdAt: string; updatedAt: string; documentCount?: number; folderCount?: number; }
4
+ export type Category = CategoryDto;
5
+ export interface CreateCategoryInput { name: string; apiMode?: "unavailable" | "global" | "general" | "category"; apiPermissionRead?: boolean; apiPermissionEdit?: boolean; apiPermissionWrite?: boolean; }
6
+ export interface UpdateCategoryInput { name?: string; order?: number; apiMode?: "unavailable" | "global" | "general" | "category"; apiPermissionRead?: boolean; apiPermissionEdit?: boolean; apiPermissionWrite?: boolean; }
7
+ export declare const createCategoryInputSchema: z.ZodType<CreateCategoryInput>;
8
+ export declare const updateCategoryInputSchema: z.ZodType<UpdateCategoryInput>;
9
+ export declare function listCategories(fetcher?: typeof fetch): Promise<CategoryDto[]>;
10
+ export declare function createCategory(inputOrName: string | CreateCategoryInput): Promise<CategoryDto>;
11
+ export declare function updateCategory(id: string, data: UpdateCategoryInput): Promise<CategoryDto>;
12
+ export declare function deleteCategory(id: string): Promise<void>;
13
+ export declare function setDocumentCategory(documentId: string, categoryId: string | null): Promise<void>;
14
+ export declare function setFolderCategory(folderId: string, categoryId: string | null): Promise<void>;
@@ -1,10 +1,16 @@
1
1
 
2
- export type DocumentDto = Readonly<Record<string, unknown>>;
3
- export declare function listDocuments(...args: readonly unknown[]): Promise<readonly DocumentDto[]>;
4
- export declare function getDocument(...args: readonly unknown[]): Promise<DocumentDto>;
5
- export declare function createDocument(...args: readonly unknown[]): Promise<DocumentDto>;
6
- export declare function updateDocument(...args: readonly unknown[]): Promise<DocumentDto>;
7
- export declare function deleteDocument(...args: readonly unknown[]): Promise<unknown>;
8
- export declare function importDocument(...args: readonly unknown[]): Promise<DocumentDto>;
9
- export declare function importDocuments(...args: readonly unknown[]): Promise<readonly DocumentDto[]>;
2
+ export interface DocumentTagDto { id: string; name: string; color: string; }
3
+ export interface DocumentDto { id: string; title: string; content: string; contentJson?: unknown; folderId?: string | null; folderName?: string; categoryId?: string | null; tags?: DocumentTagDto[]; excerpt?: string; createdAt: string; updatedAt: string; }
4
+ export type Document = DocumentDto;
5
+ export interface DocumentListResponse { items: DocumentDto[]; total: number; page: number; limit: number; }
6
+ export interface UpdateDocumentInput { title?: string; content?: string; folderId?: string | null; categoryId?: string | null; contentJson?: unknown; expectedUpdatedAt?: string; }
7
+ export interface ImportResult { filename: string; status: "ok" | "error"; document?: DocumentDto; error?: string; }
8
+ export interface ImportResponse { items: ImportResult[]; imported: number; failed: number; }
9
+ export declare function listDocuments(params?: { folderId?: string; tag?: string; page?: number; limit?: number }, fetcher?: typeof fetch): Promise<DocumentListResponse>;
10
+ export declare function getDocument(id: string, fetcher?: typeof fetch): Promise<DocumentDto>;
11
+ export declare function createDocument(data: { title: string; content?: string; folderId?: string; categoryId?: string }, fetcher?: typeof fetch): Promise<DocumentDto>;
12
+ export declare function updateDocument(id: string, data: UpdateDocumentInput): Promise<DocumentDto>;
13
+ export declare function deleteDocument(id: string): Promise<void>;
14
+ export declare function importDocument(file: File, folderId?: string): Promise<DocumentDto>;
15
+ export declare function importDocuments(files: File[], folderId?: string): Promise<ImportResponse>;
10
16
  export declare function clearDocumentsCache(...args: readonly unknown[]): void;
@@ -1,10 +1,14 @@
1
1
 
2
- export type FolderDto = Readonly<Record<string, unknown>>;
3
- export declare function listFolders(...args: readonly unknown[]): Promise<readonly FolderDto[]>;
4
- export declare function getFolder(...args: readonly unknown[]): Promise<FolderDto>;
5
- export declare function getFolderPath(...args: readonly unknown[]): Promise<readonly FolderDto[]>;
6
- export declare function createFolder(...args: readonly unknown[]): Promise<FolderDto>;
7
- export declare function updateFolder(...args: readonly unknown[]): Promise<FolderDto>;
8
- export declare function deleteFolder(...args: readonly unknown[]): Promise<unknown>;
9
- export declare function duplicateDocument(...args: readonly unknown[]): Promise<unknown>;
10
- export declare function deleteDocument(...args: readonly unknown[]): Promise<unknown>;
2
+ export interface FolderDocumentDto { id: string; title: string; content?: string; folderId: string | null; folderName: string; categoryId?: string | null; tags: string[]; createdAt: string; updatedAt: string; excerpt: string; }
3
+ export interface FolderDto { id: string; name: string; parentId: string | null; categoryId?: string | null; order: number; documentCount: number; subfolderCount: number; children: FolderDto[]; documents: FolderDocumentDto[]; createdAt: string; updatedAt: string; }
4
+ export type Folder = FolderDto;
5
+ export interface CreateFolderData { name: string; parentId?: string | null; categoryId?: string | null; }
6
+ export interface UpdateFolderData { name?: string; parentId?: string | null; categoryId?: string | null; order?: number; }
7
+ export declare function listFolders(parentId?: string | null, all?: boolean, fetcher?: typeof fetch): Promise<FolderDto[]>;
8
+ export declare function getFolder(id: string, fetcher?: typeof fetch): Promise<FolderDto>;
9
+ export declare function getFolderPath(folderId: string, fetcher?: typeof fetch): Promise<Array<{ id: string; name: string }>>;
10
+ export declare function createFolder(data: CreateFolderData): Promise<FolderDto>;
11
+ export declare function updateFolder(id: string, data: UpdateFolderData): Promise<FolderDto>;
12
+ export declare function deleteFolder(id: string): Promise<void>;
13
+ export declare function duplicateDocument(documentId: string): Promise<FolderDocumentDto>;
14
+ export declare function deleteDocument(documentId: string): Promise<void>;
@@ -1,8 +1,10 @@
1
1
 
2
- export type ProfileDto = Readonly<Record<string, unknown>>;
3
- export type EmbeddingConfigDto = Readonly<Record<string, unknown>>;
4
- export declare function getProfile(...args: readonly unknown[]): Promise<ProfileDto>;
5
- export declare function updateProfile(...args: readonly unknown[]): Promise<ProfileDto>;
6
- export declare function getEmbeddingConfig(...args: readonly unknown[]): Promise<EmbeddingConfigDto>;
7
- export declare function updateEmbeddingConfig(...args: readonly unknown[]): Promise<EmbeddingConfigDto>;
8
- export declare function deleteAccount(...args: readonly unknown[]): Promise<unknown>;
2
+ export interface ProfileDto { id: string; name: string; email: string; avatar: string | null; }
3
+ export type UserProfile = ProfileDto;
4
+ export interface EmbeddingConfigDto { baseUrl: string; apiKey: string; model: string; fallbackBaseUrl: string | null; fallbackApiKey: string | null; fallbackModel: string | null; }
5
+ export type EmbeddingConfig = EmbeddingConfigDto;
6
+ export declare function getProfile(): Promise<ProfileDto>;
7
+ export declare function updateProfile(data: { name?: string }): Promise<ProfileDto>;
8
+ export declare function getEmbeddingConfig(): EmbeddingConfigDto;
9
+ export declare function updateEmbeddingConfig(data: Partial<EmbeddingConfigDto>): EmbeddingConfigDto;
10
+ export declare function deleteAccount(): Promise<void>;
@@ -1,11 +1,15 @@
1
1
 
2
- export type TagDto = Readonly<Record<string, unknown>>;
3
- export declare const createTagInputSchema: unknown;
4
- export declare const updateTagInputSchema: unknown;
5
- export declare function listTags(...args: readonly unknown[]): Promise<readonly TagDto[]>;
6
- export declare function getTag(...args: readonly unknown[]): Promise<TagDto>;
7
- export declare function createTag(...args: readonly unknown[]): Promise<TagDto>;
8
- export declare function updateTag(...args: readonly unknown[]): Promise<TagDto>;
9
- export declare function deleteTag(...args: readonly unknown[]): Promise<unknown>;
10
- export declare function addTagToDocument(...args: readonly unknown[]): Promise<unknown>;
11
- export declare function removeTagFromDocument(...args: readonly unknown[]): Promise<unknown>;
2
+ import type { z } from "zod";
3
+ export interface TagDto { id: string; name: string; color: string | null; createdAt: string; documentCount?: number; }
4
+ export type Tag = TagDto;
5
+ export interface CreateTagInput { name: string; color?: string; }
6
+ export interface UpdateTagInput { name?: string; color?: string; }
7
+ export declare const createTagInputSchema: z.ZodType<CreateTagInput>;
8
+ export declare const updateTagInputSchema: z.ZodType<UpdateTagInput>;
9
+ export declare function listTags(fetcher?: typeof fetch): Promise<TagDto[]>;
10
+ export declare function getTag(id: string): Promise<TagDto>;
11
+ export declare function createTag(name: string, color?: string): Promise<TagDto>;
12
+ export declare function updateTag(id: string, data: UpdateTagInput): Promise<TagDto>;
13
+ export declare function deleteTag(id: string): Promise<void>;
14
+ export declare function addTagToDocument(documentId: string, tagId: string): Promise<void>;
15
+ export declare function removeTagFromDocument(documentId: string, tagId: string): Promise<void>;
@@ -1,2 +1,35 @@
1
- import type { Component } from "svelte";
2
- export declare const DocsmintExtensionProvider: Component;
1
+ import type { Component, ComponentType, Snippet, SvelteComponent } from "svelte";
2
+ export interface ExtensionVisibilityContext {
3
+ userId?: string;
4
+ pathname?: string;
5
+ capabilities?: Readonly<Record<string, boolean>>;
6
+ permissions?: Readonly<Record<string, boolean>>;
7
+ }
8
+ export type ExtensionVisibility = (context: ExtensionVisibilityContext) => boolean;
9
+ export type ExtensionIcon = ComponentType<SvelteComponent>;
10
+ export interface DocTabPanelProps { documentId: string; content: string; contentJson: object | undefined; }
11
+ export interface DocTabDefinition { id: string; label: string; component: Component<DocTabPanelProps>; order?: number; icon?: ExtensionIcon; disabled?: boolean; }
12
+ export interface NavigationExtension { id: string; label: string; href?: string; icon?: ExtensionIcon; order?: number; badge?: string | number; disabled?: boolean; visible?: ExtensionVisibility; }
13
+ export interface DashboardWidgetProps { userId?: string; }
14
+ export interface DashboardWidgetExtension { id: string; title?: string; component: Component<DashboardWidgetProps>; order?: number; colSpan?: 1 | 2 | 3 | 4 | 6 | 12; visible?: ExtensionVisibility; }
15
+ export interface SearchWidgetProps { query: string; loading: boolean; total?: number; }
16
+ export interface SearchWidgetExtension { id: string; title?: string; component: Component<SearchWidgetProps>; order?: number; visible?: ExtensionVisibility; }
17
+ export interface EditorActionContext { documentId: string; content: string; contentJson: object | undefined; selection?: unknown; commands?: Readonly<Record<string, (...args: unknown[]) => unknown>>; }
18
+ export type ExtensionAction = (context: EditorActionContext) => void | Promise<void>;
19
+ export interface EditorActionExtension { id: string; label: string; icon?: ExtensionIcon; order?: number; disabled?: boolean | ((context: EditorActionContext) => boolean); visible?: ExtensionVisibility; run: ExtensionAction; }
20
+ export interface DocumentMenuActionContext extends EditorActionContext { title?: string; }
21
+ export type DocumentMenuAction = (context: DocumentMenuActionContext) => void | Promise<void>;
22
+ export interface DocumentMenuActionExtension { id: string; label: string; icon?: ExtensionIcon; order?: number; destructive?: boolean; disabled?: boolean | ((context: DocumentMenuActionContext) => boolean); visible?: ExtensionVisibility; run: DocumentMenuAction; }
23
+ export interface SettingsSectionProps { userId?: string; }
24
+ export interface SettingsSectionExtension { id: string; label: string; component: Component<SettingsSectionProps>; order?: number; description?: string; visible?: ExtensionVisibility; }
25
+ export interface CommandPaletteActionContext { query?: string; }
26
+ export type CommandPaletteAction = (context: CommandPaletteActionContext) => void | Promise<void>;
27
+ export interface CommandPaletteActionExtension { id: string; label: string; keywords?: readonly string[]; group?: string; shortcut?: string; icon?: ExtensionIcon; order?: number; disabled?: boolean; visible?: ExtensionVisibility; run: CommandPaletteAction; }
28
+ export interface SharedDocumentExtensionContext { shareToken: string; documentId: string; title: string; content: string; contentJson?: object; role: "viewer" | "commenter" | "editor"; permissions: { read: true; annotate: boolean; edit: boolean; export: boolean; }; }
29
+ export interface SharedDocumentExtension { id: string; label: string; icon?: ExtensionIcon; order?: number; permission: "annotate" | "edit"; visible?: (context: SharedDocumentExtensionContext) => boolean; component: Component<{ context: SharedDocumentExtensionContext }>; }
30
+ export interface DocsmintFrontendExtensions { navigation: readonly NavigationExtension[]; dashboardWidgets: readonly DashboardWidgetExtension[]; searchWidgets: readonly SearchWidgetExtension[]; documentTabs: readonly DocTabDefinition[]; editorActions: readonly EditorActionExtension[]; documentMenuActions: readonly DocumentMenuActionExtension[]; settingsSections: readonly SettingsSectionExtension[]; commandPaletteActions: readonly CommandPaletteActionExtension[]; sharedDocumentHeaderActions: readonly SharedDocumentExtension[]; sharedDocumentTabs: readonly SharedDocumentExtension[]; sharedDocumentNotesModes: readonly SharedDocumentExtension[]; sharedDocumentEditorModes: readonly SharedDocumentExtension[]; }
31
+ /** @deprecated Use DocsmintFrontendExtensions. */
32
+ export type HiaiDocsFrontendExtensions = DocsmintFrontendExtensions;
33
+ export type FrontendExtensions = DocsmintFrontendExtensions;
34
+ export interface DocsmintExtensionProviderProps { extensions?: Partial<DocsmintFrontendExtensions>; children: Snippet; }
35
+ export declare const DocsmintExtensionProvider: Component<DocsmintExtensionProviderProps>;
@@ -1,3 +1,16 @@
1
1
  import type { Component } from "svelte";
2
2
  export declare const DocsmintSharedDocumentHost: Component;
3
- export declare function renderSharedDocument(...args: readonly unknown[]): unknown;
3
+ export interface ProseMirrorNode {
4
+ type: string;
5
+ text?: string;
6
+ content?: ProseMirrorNode[];
7
+ attrs?: Record<string, unknown>;
8
+ marks?: Array<{ type: string; attrs?: Record<string, unknown> }>;
9
+ }
10
+ export type ProseMirrorDoc = ProseMirrorNode & { content?: ProseMirrorNode[] };
11
+ export type SharedAttachmentObjectUrls = string[];
12
+ export declare function renderSharedDocument(doc: ProseMirrorDoc): string;
13
+ export declare function markMarkdownTaskItems(html: string): string;
14
+ export declare function sharedAttachmentHeaders(token: string, password?: string): HeadersInit;
15
+ export declare function hydrateSharedAttachmentImages(root: ParentNode, token: string, password?: string): Promise<SharedAttachmentObjectUrls>;
16
+ export declare function waitForSharedDocumentImages(root: ParentNode): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hiai-gg/docsmint",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "browser": {
6
6
  "./dist/lifecycle.js": false,
@@ -51,6 +51,11 @@
51
51
  "import": "./dist/lifecycle.js",
52
52
  "types": "./dist/lifecycle.d.ts"
53
53
  },
54
+ "./lifecycle/persistent": {
55
+ "browser": "./dist/server-only-browser-entry.js",
56
+ "import": "./dist/lifecycle-persistent.js",
57
+ "types": "./dist/lifecycle-persistent.d.ts"
58
+ },
54
59
  "./workspace": {
55
60
  "browser": "./dist/server-only-browser-entry.js",
56
61
  "import": "./dist/workspace.js",
@@ -161,6 +166,7 @@
161
166
  "backend/src/lib/redis-factory.ts",
162
167
  "backend/src/lib/storage-factory.ts",
163
168
  "backend/src/lib/logger.ts",
169
+ "backend/src/lib/lifecycle-service.ts",
164
170
  "README.md",
165
171
  "LICENSE"
166
172
  ],
@@ -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.2";
27
+ const VERSION = "0.3.3";
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.3.2",
38
+ version: "0.3.3",
39
39
  });
40
40
 
41
41
  /**