@hiai-gg/docsmint 0.3.3 → 0.3.5

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,56 @@
1
+ /** Server-only, persistence-agnostic storage quota contract. */
2
+ export type StorageQuotaContext = Readonly<{
3
+ actorUserId: string;
4
+ requestId: string;
5
+ idempotencyKey: string;
6
+ signal?: AbortSignal;
7
+ }>;
8
+ export type StorageQuotaReservationRequest = StorageQuotaContext & Readonly<{
9
+ bytes: number;
10
+ }>;
11
+ export type StorageQuotaReservation = Readonly<{
12
+ status: "reserved" | "already_reserved";
13
+ reservationId: string;
14
+ reservedBytes: number;
15
+ usageBytes: number;
16
+ limitBytes: number;
17
+ expiresAt: string;
18
+ }>;
19
+ export type StorageQuotaRejection = Readonly<{
20
+ status: "rejected";
21
+ usageBytes: number;
22
+ limitBytes: number;
23
+ requestedBytes: number;
24
+ }>;
25
+ export type StorageQuotaCommitRequest = StorageQuotaContext & Readonly<{
26
+ reservationId: string;
27
+ actualBytes: number;
28
+ }>;
29
+ export type StorageQuotaReleaseRequest = StorageQuotaContext & Readonly<{
30
+ reservationId: string;
31
+ }>;
32
+ export type StorageQuotaCommitResult = Readonly<{
33
+ status: "committed" | "already_committed";
34
+ }>;
35
+ export type StorageQuotaReleaseResult = Readonly<{
36
+ status: "released" | "already_released" | "not_found";
37
+ }>;
38
+ export type StorageQuotaAdapter = Readonly<{
39
+ /** Must atomically check usage and create an idempotent reservation. */
40
+ reserve(request: StorageQuotaReservationRequest): Promise<StorageQuotaReservation | StorageQuotaRejection>;
41
+ commit(request: StorageQuotaCommitRequest): Promise<StorageQuotaCommitResult>;
42
+ release(request: StorageQuotaReleaseRequest): Promise<StorageQuotaReleaseResult>;
43
+ }>;
44
+ export type StorageQuotaService = Readonly<{
45
+ reserve(request: StorageQuotaReservationRequest): Promise<StorageQuotaReservation>;
46
+ commit(request: StorageQuotaCommitRequest): Promise<StorageQuotaCommitResult>;
47
+ release(request: StorageQuotaReleaseRequest): Promise<StorageQuotaReleaseResult>;
48
+ }>;
49
+ export declare class StorageQuotaExceededError extends Error {
50
+ readonly code: "STORAGE_QUOTA_EXCEEDED";
51
+ readonly usageBytes: number;
52
+ readonly limitBytes: number;
53
+ readonly requestedBytes: number;
54
+ constructor(rejection: StorageQuotaRejection);
55
+ }
56
+ export declare function createStorageQuotaService(adapter: StorageQuotaAdapter): StorageQuotaService;
@@ -0,0 +1,91 @@
1
+ /** Server-only, persistence-agnostic storage quota contract. */
2
+ export class StorageQuotaExceededError extends Error {
3
+ code = "STORAGE_QUOTA_EXCEEDED";
4
+ usageBytes;
5
+ limitBytes;
6
+ requestedBytes;
7
+ constructor(rejection) {
8
+ super("Storage quota exceeded");
9
+ this.name = "StorageQuotaExceededError";
10
+ this.usageBytes = rejection.usageBytes;
11
+ this.limitBytes = rejection.limitBytes;
12
+ this.requestedBytes = rejection.requestedBytes;
13
+ }
14
+ }
15
+ function assertContext(context) {
16
+ for (const [name, value] of [
17
+ ["actorUserId", context.actorUserId],
18
+ ["requestId", context.requestId],
19
+ ["idempotencyKey", context.idempotencyKey],
20
+ ]) {
21
+ if (!value.trim())
22
+ throw new TypeError(`${name} must not be empty`);
23
+ }
24
+ if (context.signal?.aborted) {
25
+ throw new DOMException("Storage quota operation aborted", "AbortError");
26
+ }
27
+ }
28
+ function assertPositiveBytes(bytes, name) {
29
+ if (!Number.isSafeInteger(bytes) || bytes <= 0) {
30
+ throw new TypeError(`${name} must be a positive safe integer`);
31
+ }
32
+ }
33
+ function assertNonnegativeBytes(bytes, name) {
34
+ if (!Number.isSafeInteger(bytes) || bytes < 0) {
35
+ throw new TypeError(`${name} must be a non-negative safe integer`);
36
+ }
37
+ }
38
+ function assertReservation(result) {
39
+ assertReservationId(result.reservationId);
40
+ assertPositiveBytes(result.reservedBytes, "reservedBytes");
41
+ assertNonnegativeBytes(result.usageBytes, "usageBytes");
42
+ assertNonnegativeBytes(result.limitBytes, "limitBytes");
43
+ if (Number.isNaN(Date.parse(result.expiresAt))) {
44
+ throw new TypeError("expiresAt must be an ISO-compatible timestamp");
45
+ }
46
+ }
47
+ function assertRejection(result) {
48
+ assertNonnegativeBytes(result.usageBytes, "usageBytes");
49
+ assertNonnegativeBytes(result.limitBytes, "limitBytes");
50
+ assertPositiveBytes(result.requestedBytes, "requestedBytes");
51
+ }
52
+ function assertReservationId(value) {
53
+ if (!value.trim())
54
+ throw new TypeError("reservationId must not be empty");
55
+ }
56
+ export function createStorageQuotaService(adapter) {
57
+ return Object.freeze({
58
+ async reserve(request) {
59
+ assertContext(request);
60
+ assertPositiveBytes(request.bytes, "bytes");
61
+ const result = await adapter.reserve(Object.freeze({ ...request }));
62
+ if (result.status === "rejected") {
63
+ assertRejection(result);
64
+ throw new StorageQuotaExceededError(result);
65
+ }
66
+ assertReservation(result);
67
+ return Object.freeze({ ...result });
68
+ },
69
+ async commit(request) {
70
+ assertContext(request);
71
+ assertReservationId(request.reservationId);
72
+ assertPositiveBytes(request.actualBytes, "actualBytes");
73
+ const result = await adapter.commit(Object.freeze({ ...request }));
74
+ if (result.status !== "committed" && result.status !== "already_committed") {
75
+ throw new TypeError("Storage quota adapter returned an invalid commit status");
76
+ }
77
+ return Object.freeze({ ...result });
78
+ },
79
+ async release(request) {
80
+ assertContext(request);
81
+ assertReservationId(request.reservationId);
82
+ const result = await adapter.release(Object.freeze({ ...request }));
83
+ if (result.status !== "released" &&
84
+ result.status !== "already_released" &&
85
+ result.status !== "not_found") {
86
+ throw new TypeError("Storage quota adapter returned an invalid release status");
87
+ }
88
+ return Object.freeze({ ...result });
89
+ },
90
+ });
91
+ }
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "@hiai-gg/docsmint",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "browser": {
6
+ "./dist/backend-launcher.js": false,
6
7
  "./dist/lifecycle.js": false,
8
+ "./dist/storage-quota.js": false,
7
9
  "./dist/workspace.js": false
8
10
  },
9
11
  "license": "Apache-2.0",
@@ -61,6 +63,16 @@
61
63
  "import": "./dist/workspace.js",
62
64
  "types": "./dist/workspace.d.ts"
63
65
  },
66
+ "./backend/launcher": {
67
+ "browser": "./dist/server-only-browser-entry.js",
68
+ "import": "./dist/backend-launcher.js",
69
+ "types": "./dist/backend-launcher.d.ts"
70
+ },
71
+ "./storage-quota": {
72
+ "browser": "./dist/server-only-browser-entry.js",
73
+ "import": "./dist/storage-quota.js",
74
+ "types": "./dist/storage-quota.d.ts"
75
+ },
64
76
  "./frontend/dashboard": {
65
77
  "import": "./dist/frontend/dashboard.js",
66
78
  "types": "./dist/frontend/dashboard.d.ts"
@@ -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.3";
27
+ const VERSION = "0.3.5";
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.3",
38
+ version: "0.3.5",
39
39
  });
40
40
 
41
41
  /**