@agentdocstore/core 0.2.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.
- package/README.md +55 -0
- package/dist/authz.d.ts +76 -0
- package/dist/authz.js +106 -0
- package/dist/diff.d.ts +31 -0
- package/dist/diff.js +57 -0
- package/dist/errors.d.ts +54 -0
- package/dist/errors.js +75 -0
- package/dist/id.d.ts +18 -0
- package/dist/id.js +26 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/model/comment.d.ts +18 -0
- package/dist/model/comment.js +2 -0
- package/dist/model/document.d.ts +41 -0
- package/dist/model/document.js +32 -0
- package/dist/model/edit-message.d.ts +8 -0
- package/dist/model/edit-message.js +20 -0
- package/dist/model/limits.d.ts +42 -0
- package/dist/model/limits.js +43 -0
- package/dist/model/title.d.ts +8 -0
- package/dist/model/title.js +17 -0
- package/dist/model/version.d.ts +20 -0
- package/dist/model/version.js +2 -0
- package/dist/offline.d.ts +57 -0
- package/dist/offline.js +58 -0
- package/dist/scanner.d.ts +47 -0
- package/dist/scanner.js +294 -0
- package/dist/search/CoreSearchIndex.d.ts +21 -0
- package/dist/search/CoreSearchIndex.js +87 -0
- package/dist/spi/capabilities.d.ts +28 -0
- package/dist/spi/capabilities.js +2 -0
- package/dist/spi/comments.d.ts +14 -0
- package/dist/spi/comments.js +2 -0
- package/dist/spi/identity.d.ts +13 -0
- package/dist/spi/identity.js +2 -0
- package/dist/spi/index.d.ts +8 -0
- package/dist/spi/index.js +8 -0
- package/dist/spi/module.d.ts +54 -0
- package/dist/spi/module.js +57 -0
- package/dist/spi/provider.d.ts +33 -0
- package/dist/spi/provider.js +2 -0
- package/dist/spi/repository.d.ts +64 -0
- package/dist/spi/repository.js +2 -0
- package/dist/spi/search.d.ts +53 -0
- package/dist/spi/search.js +2 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# `@agentdocstore/core`
|
|
2
|
+
|
|
3
|
+
Domain model and extension contracts for
|
|
4
|
+
[AgentDocStore](https://github.com/koushikginjupally/agentdocstore), an offline-first,
|
|
5
|
+
self-hostable artifact-sharing service.
|
|
6
|
+
|
|
7
|
+
This package is the stable surface for storage-provider authors. It exports:
|
|
8
|
+
|
|
9
|
+
- `Provider`, `ProviderModule`, `DocumentRepository`, `CommentStore`, `SearchIndex`
|
|
10
|
+
and `Capabilities`.
|
|
11
|
+
- Document, version, comment, page and search types.
|
|
12
|
+
- Typed errors including `VersionConflictError`, `NotFoundError`,
|
|
13
|
+
`ValidationError` and `ContentTooLargeError`.
|
|
14
|
+
- Authorization helpers, id generation/validation, the credential scanner,
|
|
15
|
+
redaction, the diff engine and the MiniSearch-backed fallback index.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @agentdocstore/core
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Provider module shape
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import type { ProviderModule } from '@agentdocstore/core';
|
|
27
|
+
|
|
28
|
+
export const providerName = 'example';
|
|
29
|
+
export const optionsSchema = {
|
|
30
|
+
parse(value: unknown) {
|
|
31
|
+
// Validate and return typed options, or throw a helpful error.
|
|
32
|
+
return value;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const createProvider: ProviderModule['createProvider'] = async (options) => {
|
|
37
|
+
return makeYourProvider(options);
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
A provider must declare whether it needs a network, implement compare-and-set
|
|
42
|
+
version appends, keep versions immutable, hide private search results, and make
|
|
43
|
+
delete cascade through versions/comments/search. Do not rely on this summary as
|
|
44
|
+
the contract: read the complete [provider SPI guide](../../docs/PROVIDERS.md)
|
|
45
|
+
and prove the implementation with `@agentdocstore/provider-tests`.
|
|
46
|
+
|
|
47
|
+
## Compatibility
|
|
48
|
+
|
|
49
|
+
AgentDocStore is pre-1.0. The package follows semantic versioning, but a minor
|
|
50
|
+
release may change the SPI; such changes are called out in the root
|
|
51
|
+
[CHANGELOG](../../CHANGELOG.md).
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
Apache-2.0.
|
package/dist/authz.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization helper.
|
|
3
|
+
*
|
|
4
|
+
* Enforces the single access rule that governs every doc-scoped surface
|
|
5
|
+
* (read, raw, update/write, delete, comments):
|
|
6
|
+
*
|
|
7
|
+
* - PUBLIC documents are readable / commentable by anyone (including anonymous).
|
|
8
|
+
* - PRIVATE documents are owner-only for ALL surfaces.
|
|
9
|
+
* - Mutation (write / delete) is owner-only regardless of visibility.
|
|
10
|
+
*
|
|
11
|
+
* Denials throw {@link NotFoundError}, never a "forbidden" error: a non-owner
|
|
12
|
+
* must not be able to distinguish "exists but you can't touch it" from "does
|
|
13
|
+
* not exist", so a PRIVATE doc's existence is never disclosed. (There is also
|
|
14
|
+
* no dedicated Forbidden error in the core taxonomy by design.) The denial
|
|
15
|
+
* message never contains the owner's identity.
|
|
16
|
+
*/
|
|
17
|
+
import type { Visibility } from './model/document.js';
|
|
18
|
+
/** The caller's identity. `null`/`undefined`/`''` == unauthenticated / anonymous. */
|
|
19
|
+
export type Viewer = string | null | undefined;
|
|
20
|
+
/** The minimal doc projection authorization decisions depend on. */
|
|
21
|
+
export interface AccessTarget {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly visibility: Visibility;
|
|
24
|
+
readonly createdBy: string;
|
|
25
|
+
}
|
|
26
|
+
/** True when `viewer` is the authenticated creator of `doc`. */
|
|
27
|
+
export declare function isOwner(doc: AccessTarget, viewer: Viewer): boolean;
|
|
28
|
+
/** True when `viewer` may view `doc` (and its raw content / comments). */
|
|
29
|
+
export declare function canRead(doc: AccessTarget, viewer: Viewer): boolean;
|
|
30
|
+
/** True when `viewer` may update `doc` (owner-only, any visibility). */
|
|
31
|
+
export declare function canWrite(doc: AccessTarget, viewer: Viewer): boolean;
|
|
32
|
+
/** True when `viewer` may delete `doc` (owner-only, any visibility). */
|
|
33
|
+
export declare function canDelete(doc: AccessTarget, viewer: Viewer): boolean;
|
|
34
|
+
/** True when `viewer` may post/read comments on `doc` (same gate as read). */
|
|
35
|
+
export declare function canComment(doc: AccessTarget, viewer: Viewer): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* True when `viewer` may delete a comment on `doc`: its author or the document
|
|
38
|
+
* owner, and only while they can still read the document. Anyone who can read
|
|
39
|
+
* a PUBLIC document may comment on it, so without this rule any reader could
|
|
40
|
+
* erase anyone else's feedback.
|
|
41
|
+
*/
|
|
42
|
+
export declare function canDeleteComment(doc: AccessTarget, comment: {
|
|
43
|
+
readonly author: string;
|
|
44
|
+
}, viewer: Viewer): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* True when `doc` has an `expiresAt` at or before `now`.
|
|
47
|
+
*
|
|
48
|
+
* Expiry is enforced at read time with this check: the background sweep (or a
|
|
49
|
+
* store's native TTL) only reclaims storage, and may run minutes later — or,
|
|
50
|
+
* for the stdio MCP server, not at all. An expired document must behave as
|
|
51
|
+
* deleted from the moment it expires.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isExpired(doc: {
|
|
54
|
+
readonly expiresAt?: string;
|
|
55
|
+
}, now?: Date): boolean;
|
|
56
|
+
/** Assert `viewer` may read `doc`, else throw {@link NotFoundError}. */
|
|
57
|
+
export declare function assertCanRead(doc: AccessTarget, viewer: Viewer): void;
|
|
58
|
+
/** Assert `viewer` may read `doc`'s raw content (`/raw` is read-gated). */
|
|
59
|
+
export declare function assertCanReadRaw(doc: AccessTarget, viewer: Viewer): void;
|
|
60
|
+
/** Assert `viewer` may update `doc` (owner-only), else throw {@link NotFoundError}. */
|
|
61
|
+
export declare function assertCanWrite(doc: AccessTarget, viewer: Viewer): void;
|
|
62
|
+
/** Assert `viewer` may delete `doc` (owner-only), else throw {@link NotFoundError}. */
|
|
63
|
+
export declare function assertCanDelete(doc: AccessTarget, viewer: Viewer): void;
|
|
64
|
+
/** Assert `viewer` may comment on `doc` (same gate as read), else throw {@link NotFoundError}. */
|
|
65
|
+
export declare function assertCanComment(doc: AccessTarget, viewer: Viewer): void;
|
|
66
|
+
/**
|
|
67
|
+
* Assert `viewer` may delete `comment` on `doc`. A viewer who cannot read the
|
|
68
|
+
* document gets the document denial; one who can read it but neither wrote the
|
|
69
|
+
* comment nor owns the document gets "comment not found", matching how every
|
|
70
|
+
* other denial is shaped.
|
|
71
|
+
*/
|
|
72
|
+
export declare function assertCanDeleteComment(doc: AccessTarget, comment: {
|
|
73
|
+
readonly id: string;
|
|
74
|
+
readonly author: string;
|
|
75
|
+
}, viewer: Viewer): void;
|
|
76
|
+
//# sourceMappingURL=authz.d.ts.map
|
package/dist/authz.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization helper.
|
|
3
|
+
*
|
|
4
|
+
* Enforces the single access rule that governs every doc-scoped surface
|
|
5
|
+
* (read, raw, update/write, delete, comments):
|
|
6
|
+
*
|
|
7
|
+
* - PUBLIC documents are readable / commentable by anyone (including anonymous).
|
|
8
|
+
* - PRIVATE documents are owner-only for ALL surfaces.
|
|
9
|
+
* - Mutation (write / delete) is owner-only regardless of visibility.
|
|
10
|
+
*
|
|
11
|
+
* Denials throw {@link NotFoundError}, never a "forbidden" error: a non-owner
|
|
12
|
+
* must not be able to distinguish "exists but you can't touch it" from "does
|
|
13
|
+
* not exist", so a PRIVATE doc's existence is never disclosed. (There is also
|
|
14
|
+
* no dedicated Forbidden error in the core taxonomy by design.) The denial
|
|
15
|
+
* message never contains the owner's identity.
|
|
16
|
+
*/
|
|
17
|
+
import { NotFoundError } from './errors.js';
|
|
18
|
+
/** True when `viewer` is the authenticated creator of `doc`. */
|
|
19
|
+
export function isOwner(doc, viewer) {
|
|
20
|
+
return typeof viewer === 'string' && viewer.length > 0 && doc.createdBy === viewer;
|
|
21
|
+
}
|
|
22
|
+
/** True when `viewer` may view `doc` (and its raw content / comments). */
|
|
23
|
+
export function canRead(doc, viewer) {
|
|
24
|
+
return doc.visibility === 'PUBLIC' || isOwner(doc, viewer);
|
|
25
|
+
}
|
|
26
|
+
/** True when `viewer` may update `doc` (owner-only, any visibility). */
|
|
27
|
+
export function canWrite(doc, viewer) {
|
|
28
|
+
return isOwner(doc, viewer);
|
|
29
|
+
}
|
|
30
|
+
/** True when `viewer` may delete `doc` (owner-only, any visibility). */
|
|
31
|
+
export function canDelete(doc, viewer) {
|
|
32
|
+
return isOwner(doc, viewer);
|
|
33
|
+
}
|
|
34
|
+
/** True when `viewer` may post/read comments on `doc` (same gate as read). */
|
|
35
|
+
export function canComment(doc, viewer) {
|
|
36
|
+
return canRead(doc, viewer);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* True when `viewer` may delete a comment on `doc`: its author or the document
|
|
40
|
+
* owner, and only while they can still read the document. Anyone who can read
|
|
41
|
+
* a PUBLIC document may comment on it, so without this rule any reader could
|
|
42
|
+
* erase anyone else's feedback.
|
|
43
|
+
*/
|
|
44
|
+
export function canDeleteComment(doc, comment, viewer) {
|
|
45
|
+
if (!canComment(doc, viewer))
|
|
46
|
+
return false;
|
|
47
|
+
if (isOwner(doc, viewer))
|
|
48
|
+
return true;
|
|
49
|
+
return typeof viewer === 'string' && viewer.length > 0 && comment.author === viewer;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* True when `doc` has an `expiresAt` at or before `now`.
|
|
53
|
+
*
|
|
54
|
+
* Expiry is enforced at read time with this check: the background sweep (or a
|
|
55
|
+
* store's native TTL) only reclaims storage, and may run minutes later — or,
|
|
56
|
+
* for the stdio MCP server, not at all. An expired document must behave as
|
|
57
|
+
* deleted from the moment it expires.
|
|
58
|
+
*/
|
|
59
|
+
export function isExpired(doc, now = new Date()) {
|
|
60
|
+
return doc.expiresAt !== undefined && Date.parse(doc.expiresAt) <= now.getTime();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Uniform denial: throw NotFound with a message that reveals neither the
|
|
64
|
+
* owner nor whether the doc merely exists.
|
|
65
|
+
*/
|
|
66
|
+
function deny(doc) {
|
|
67
|
+
throw new NotFoundError(`Document '${doc.id}' not found`);
|
|
68
|
+
}
|
|
69
|
+
/** Assert `viewer` may read `doc`, else throw {@link NotFoundError}. */
|
|
70
|
+
export function assertCanRead(doc, viewer) {
|
|
71
|
+
if (!canRead(doc, viewer))
|
|
72
|
+
deny(doc);
|
|
73
|
+
}
|
|
74
|
+
/** Assert `viewer` may read `doc`'s raw content (`/raw` is read-gated). */
|
|
75
|
+
export function assertCanReadRaw(doc, viewer) {
|
|
76
|
+
if (!canRead(doc, viewer))
|
|
77
|
+
deny(doc);
|
|
78
|
+
}
|
|
79
|
+
/** Assert `viewer` may update `doc` (owner-only), else throw {@link NotFoundError}. */
|
|
80
|
+
export function assertCanWrite(doc, viewer) {
|
|
81
|
+
if (!canWrite(doc, viewer))
|
|
82
|
+
deny(doc);
|
|
83
|
+
}
|
|
84
|
+
/** Assert `viewer` may delete `doc` (owner-only), else throw {@link NotFoundError}. */
|
|
85
|
+
export function assertCanDelete(doc, viewer) {
|
|
86
|
+
if (!canDelete(doc, viewer))
|
|
87
|
+
deny(doc);
|
|
88
|
+
}
|
|
89
|
+
/** Assert `viewer` may comment on `doc` (same gate as read), else throw {@link NotFoundError}. */
|
|
90
|
+
export function assertCanComment(doc, viewer) {
|
|
91
|
+
if (!canComment(doc, viewer))
|
|
92
|
+
deny(doc);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Assert `viewer` may delete `comment` on `doc`. A viewer who cannot read the
|
|
96
|
+
* document gets the document denial; one who can read it but neither wrote the
|
|
97
|
+
* comment nor owns the document gets "comment not found", matching how every
|
|
98
|
+
* other denial is shaped.
|
|
99
|
+
*/
|
|
100
|
+
export function assertCanDeleteComment(doc, comment, viewer) {
|
|
101
|
+
assertCanComment(doc, viewer);
|
|
102
|
+
if (!canDeleteComment(doc, comment, viewer)) {
|
|
103
|
+
throw new NotFoundError(`Comment '${comment.id}' not found`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=authz.js.map
|
package/dist/diff.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified-diff generation backed by the `diff` npm package.
|
|
3
|
+
*
|
|
4
|
+
* Provides a single function {@link unifiedDiff} that produces a standard
|
|
5
|
+
* unified diff string from two content strings, with a configurable size cap
|
|
6
|
+
* to prevent runaway memory use on very large inputs.
|
|
7
|
+
*/
|
|
8
|
+
/** Options for {@link unifiedDiff}. */
|
|
9
|
+
export interface UnifiedDiffOptions {
|
|
10
|
+
/** Label for the old file in the diff header. */
|
|
11
|
+
readonly oldLabel?: string;
|
|
12
|
+
/** Label for the new file in the diff header. */
|
|
13
|
+
readonly newLabel?: string;
|
|
14
|
+
/** Number of unchanged context lines around each hunk. */
|
|
15
|
+
readonly context?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Produce a unified diff between two content strings.
|
|
19
|
+
*
|
|
20
|
+
* Returns an empty string when `oldContent` and `newContent` are identical
|
|
21
|
+
* (byte-equal). Throws {@link ContentTooLargeError} if either input exceeds
|
|
22
|
+
* {@link LIMITS.MAX_DIFF_INPUT_BYTES}, or if the diff would add or remove
|
|
23
|
+
* more than {@link LIMITS.MAX_DIFF_CHANGED_LINES} lines.
|
|
24
|
+
*
|
|
25
|
+
* @param oldContent - The original content.
|
|
26
|
+
* @param newContent - The updated content.
|
|
27
|
+
* @param opts - Optional labels and context lines.
|
|
28
|
+
* @returns A unified diff string, or `''` when inputs are identical.
|
|
29
|
+
*/
|
|
30
|
+
export declare function unifiedDiff(oldContent: string, newContent: string, opts?: UnifiedDiffOptions): string;
|
|
31
|
+
//# sourceMappingURL=diff.d.ts.map
|
package/dist/diff.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified-diff generation backed by the `diff` npm package.
|
|
3
|
+
*
|
|
4
|
+
* Provides a single function {@link unifiedDiff} that produces a standard
|
|
5
|
+
* unified diff string from two content strings, with a configurable size cap
|
|
6
|
+
* to prevent runaway memory use on very large inputs.
|
|
7
|
+
*/
|
|
8
|
+
import { createTwoFilesPatch } from 'diff';
|
|
9
|
+
import { ContentTooLargeError } from './errors.js';
|
|
10
|
+
import { LIMITS } from './model/limits.js';
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Implementation
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
/**
|
|
15
|
+
* Produce a unified diff between two content strings.
|
|
16
|
+
*
|
|
17
|
+
* Returns an empty string when `oldContent` and `newContent` are identical
|
|
18
|
+
* (byte-equal). Throws {@link ContentTooLargeError} if either input exceeds
|
|
19
|
+
* {@link LIMITS.MAX_DIFF_INPUT_BYTES}, or if the diff would add or remove
|
|
20
|
+
* more than {@link LIMITS.MAX_DIFF_CHANGED_LINES} lines.
|
|
21
|
+
*
|
|
22
|
+
* @param oldContent - The original content.
|
|
23
|
+
* @param newContent - The updated content.
|
|
24
|
+
* @param opts - Optional labels and context lines.
|
|
25
|
+
* @returns A unified diff string, or `''` when inputs are identical.
|
|
26
|
+
*/
|
|
27
|
+
export function unifiedDiff(oldContent, newContent, opts) {
|
|
28
|
+
// Size guard — measure byte length, not character count.
|
|
29
|
+
const oldBytes = Buffer.byteLength(oldContent, 'utf8');
|
|
30
|
+
const newBytes = Buffer.byteLength(newContent, 'utf8');
|
|
31
|
+
const limit = LIMITS.MAX_DIFF_INPUT_BYTES;
|
|
32
|
+
if (oldBytes > limit) {
|
|
33
|
+
throw new ContentTooLargeError(`Old content exceeds diff size limit (${oldBytes} > ${limit} bytes)`, limit, oldBytes);
|
|
34
|
+
}
|
|
35
|
+
if (newBytes > limit) {
|
|
36
|
+
throw new ContentTooLargeError(`New content exceeds diff size limit (${newBytes} > ${limit} bytes)`, limit, newBytes);
|
|
37
|
+
}
|
|
38
|
+
// Identical inputs produce no diff.
|
|
39
|
+
if (oldContent === newContent)
|
|
40
|
+
return '';
|
|
41
|
+
const oldLabel = opts?.oldLabel ?? 'a';
|
|
42
|
+
const newLabel = opts?.newLabel ?? 'b';
|
|
43
|
+
const context = opts?.context ?? 3;
|
|
44
|
+
// The search grows with the square of the lines that differ and runs on
|
|
45
|
+
// the caller's thread, so stop it at the cap rather than let two versions
|
|
46
|
+
// that share few lines hold the server for minutes.
|
|
47
|
+
const maxEditLength = LIMITS.MAX_DIFF_CHANGED_LINES;
|
|
48
|
+
const patch = createTwoFilesPatch(oldLabel, newLabel, oldContent, newContent, '', '', {
|
|
49
|
+
context,
|
|
50
|
+
maxEditLength,
|
|
51
|
+
});
|
|
52
|
+
if (patch === undefined) {
|
|
53
|
+
throw new ContentTooLargeError(`Too many changes to diff: more than ${maxEditLength} lines added or removed`, maxEditLength);
|
|
54
|
+
}
|
|
55
|
+
return patch;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=diff.js.map
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed core errors. Each carries a discriminant `name` (for `switch (e.name)`)
|
|
3
|
+
* and a stable machine `code`. `Object.setPrototypeOf` keeps `instanceof`
|
|
4
|
+
* reliable across the compiled ESM output.
|
|
5
|
+
*/
|
|
6
|
+
export type ErrorCode = 'VERSION_CONFLICT' | 'NOT_FOUND' | 'VALIDATION' | 'CONTENT_TOO_LARGE' | 'OFFLINE_VIOLATION';
|
|
7
|
+
/** Raised by CAS `appendVersion` when `expect.latestVersion` is stale. */
|
|
8
|
+
export declare class VersionConflictError extends Error {
|
|
9
|
+
readonly expected?: number | undefined;
|
|
10
|
+
readonly actual?: number | undefined;
|
|
11
|
+
readonly code: "VERSION_CONFLICT";
|
|
12
|
+
constructor(message?: string, expected?: number | undefined, actual?: number | undefined);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Raised when a resource does not exist OR the caller is not permitted to see
|
|
16
|
+
* it. Authorization deliberately surfaces as NotFound (not Forbidden) so a
|
|
17
|
+
* PRIVATE doc's existence is never disclosed to a non-owner.
|
|
18
|
+
*/
|
|
19
|
+
export declare class NotFoundError extends Error {
|
|
20
|
+
readonly code: "NOT_FOUND";
|
|
21
|
+
constructor(message?: string);
|
|
22
|
+
}
|
|
23
|
+
/** Raised on invalid input (bad language, empty title, oversized field, ...). */
|
|
24
|
+
export declare class ValidationError extends Error {
|
|
25
|
+
readonly code: "VALIDATION";
|
|
26
|
+
constructor(message?: string);
|
|
27
|
+
}
|
|
28
|
+
/** Raised when content exceeds a configured size cap (write-time or diff-input). */
|
|
29
|
+
export declare class ContentTooLargeError extends Error {
|
|
30
|
+
readonly limit?: number | undefined;
|
|
31
|
+
readonly actual?: number | undefined;
|
|
32
|
+
readonly code: "CONTENT_TOO_LARGE";
|
|
33
|
+
constructor(message?: string, limit?: number | undefined, actual?: number | undefined);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Raised when the configured runtime mode forbids what was asked for — an
|
|
37
|
+
* offline instance pointed at a networked provider, bound to a non-loopback
|
|
38
|
+
* address, or a dependency attempting outbound egress through the fuse.
|
|
39
|
+
*/
|
|
40
|
+
export declare class OfflineViolationError extends Error {
|
|
41
|
+
/** What was attempted, e.g. `provider`, `host`, `connect`, `fetch`. */
|
|
42
|
+
readonly subject?: string | undefined;
|
|
43
|
+
/** How the operator can proceed deliberately. */
|
|
44
|
+
readonly remedy?: string | undefined;
|
|
45
|
+
readonly code: "OFFLINE_VIOLATION";
|
|
46
|
+
constructor(message?: string,
|
|
47
|
+
/** What was attempted, e.g. `provider`, `host`, `connect`, `fetch`. */
|
|
48
|
+
subject?: string | undefined,
|
|
49
|
+
/** How the operator can proceed deliberately. */
|
|
50
|
+
remedy?: string | undefined);
|
|
51
|
+
}
|
|
52
|
+
/** Any core error carrying a known {@link ErrorCode}. */
|
|
53
|
+
export type AgentDocStoreError = VersionConflictError | NotFoundError | ValidationError | ContentTooLargeError | OfflineViolationError;
|
|
54
|
+
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed core errors. Each carries a discriminant `name` (for `switch (e.name)`)
|
|
3
|
+
* and a stable machine `code`. `Object.setPrototypeOf` keeps `instanceof`
|
|
4
|
+
* reliable across the compiled ESM output.
|
|
5
|
+
*/
|
|
6
|
+
/** Raised by CAS `appendVersion` when `expect.latestVersion` is stale. */
|
|
7
|
+
export class VersionConflictError extends Error {
|
|
8
|
+
expected;
|
|
9
|
+
actual;
|
|
10
|
+
code = 'VERSION_CONFLICT';
|
|
11
|
+
constructor(message = 'Version conflict', expected, actual) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.expected = expected;
|
|
14
|
+
this.actual = actual;
|
|
15
|
+
this.name = 'VersionConflictError';
|
|
16
|
+
Object.setPrototypeOf(this, VersionConflictError.prototype);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Raised when a resource does not exist OR the caller is not permitted to see
|
|
21
|
+
* it. Authorization deliberately surfaces as NotFound (not Forbidden) so a
|
|
22
|
+
* PRIVATE doc's existence is never disclosed to a non-owner.
|
|
23
|
+
*/
|
|
24
|
+
export class NotFoundError extends Error {
|
|
25
|
+
code = 'NOT_FOUND';
|
|
26
|
+
constructor(message = 'Not found') {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'NotFoundError';
|
|
29
|
+
Object.setPrototypeOf(this, NotFoundError.prototype);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Raised on invalid input (bad language, empty title, oversized field, ...). */
|
|
33
|
+
export class ValidationError extends Error {
|
|
34
|
+
code = 'VALIDATION';
|
|
35
|
+
constructor(message = 'Validation failed') {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'ValidationError';
|
|
38
|
+
Object.setPrototypeOf(this, ValidationError.prototype);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Raised when content exceeds a configured size cap (write-time or diff-input). */
|
|
42
|
+
export class ContentTooLargeError extends Error {
|
|
43
|
+
limit;
|
|
44
|
+
actual;
|
|
45
|
+
code = 'CONTENT_TOO_LARGE';
|
|
46
|
+
constructor(message = 'Content too large', limit, actual) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.limit = limit;
|
|
49
|
+
this.actual = actual;
|
|
50
|
+
this.name = 'ContentTooLargeError';
|
|
51
|
+
Object.setPrototypeOf(this, ContentTooLargeError.prototype);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Raised when the configured runtime mode forbids what was asked for — an
|
|
56
|
+
* offline instance pointed at a networked provider, bound to a non-loopback
|
|
57
|
+
* address, or a dependency attempting outbound egress through the fuse.
|
|
58
|
+
*/
|
|
59
|
+
export class OfflineViolationError extends Error {
|
|
60
|
+
subject;
|
|
61
|
+
remedy;
|
|
62
|
+
code = 'OFFLINE_VIOLATION';
|
|
63
|
+
constructor(message = 'Offline mode violation',
|
|
64
|
+
/** What was attempted, e.g. `provider`, `host`, `connect`, `fetch`. */
|
|
65
|
+
subject,
|
|
66
|
+
/** How the operator can proceed deliberately. */
|
|
67
|
+
remedy) {
|
|
68
|
+
super(message);
|
|
69
|
+
this.subject = subject;
|
|
70
|
+
this.remedy = remedy;
|
|
71
|
+
this.name = 'OfflineViolationError';
|
|
72
|
+
Object.setPrototypeOf(this, OfflineViolationError.prototype);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=errors.js.map
|
package/dist/id.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL-safe id alphabet. Deliberately excludes `/`, `.`, and whitespace so a
|
|
3
|
+
* valid id can never encode a path segment or traversal sequence — this is the
|
|
4
|
+
* primary path-traversal defense used by filesystem-backed providers.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ID_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
7
|
+
/** Fixed id length. */
|
|
8
|
+
export declare const ID_LENGTH = 10;
|
|
9
|
+
/** Generate a fresh URL-safe id. */
|
|
10
|
+
export declare function newId(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Type guard: `true` only for a string that is exactly a well-formed id.
|
|
13
|
+
* Providers MUST call this before constructing any path from a caller-supplied
|
|
14
|
+
* id, so values like `..`, `../x`, or `a/b` are rejected before touching the
|
|
15
|
+
* filesystem.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isValidId(id: unknown): id is string;
|
|
18
|
+
//# sourceMappingURL=id.d.ts.map
|
package/dist/id.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { customAlphabet } from 'nanoid';
|
|
2
|
+
/**
|
|
3
|
+
* URL-safe id alphabet. Deliberately excludes `/`, `.`, and whitespace so a
|
|
4
|
+
* valid id can never encode a path segment or traversal sequence — this is the
|
|
5
|
+
* primary path-traversal defense used by filesystem-backed providers.
|
|
6
|
+
*/
|
|
7
|
+
export const ID_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-';
|
|
8
|
+
/** Fixed id length. */
|
|
9
|
+
export const ID_LENGTH = 10;
|
|
10
|
+
const generate = customAlphabet(ID_ALPHABET, ID_LENGTH);
|
|
11
|
+
/** Generate a fresh URL-safe id. */
|
|
12
|
+
export function newId() {
|
|
13
|
+
return generate();
|
|
14
|
+
}
|
|
15
|
+
/** Matches exactly {@link ID_LENGTH} chars from {@link ID_ALPHABET}. */
|
|
16
|
+
const ID_PATTERN = /^[A-Za-z0-9_-]{10}$/;
|
|
17
|
+
/**
|
|
18
|
+
* Type guard: `true` only for a string that is exactly a well-formed id.
|
|
19
|
+
* Providers MUST call this before constructing any path from a caller-supplied
|
|
20
|
+
* id, so values like `..`, `../x`, or `a/b` are rejected before touching the
|
|
21
|
+
* filesystem.
|
|
22
|
+
*/
|
|
23
|
+
export function isValidId(id) {
|
|
24
|
+
return typeof id === 'string' && ID_PATTERN.test(id);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=id.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './model/document.js';
|
|
2
|
+
export * from './model/version.js';
|
|
3
|
+
export * from './model/comment.js';
|
|
4
|
+
export * from './model/limits.js';
|
|
5
|
+
export * from './model/edit-message.js';
|
|
6
|
+
export * from './model/title.js';
|
|
7
|
+
export * from './errors.js';
|
|
8
|
+
export * from './authz.js';
|
|
9
|
+
export * from './offline.js';
|
|
10
|
+
export * from './id.js';
|
|
11
|
+
export * from './spi/index.js';
|
|
12
|
+
export * from './search/CoreSearchIndex.js';
|
|
13
|
+
export * from './scanner.js';
|
|
14
|
+
export * from './diff.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './model/document.js';
|
|
2
|
+
export * from './model/version.js';
|
|
3
|
+
export * from './model/comment.js';
|
|
4
|
+
export * from './model/limits.js';
|
|
5
|
+
export * from './model/edit-message.js';
|
|
6
|
+
export * from './model/title.js';
|
|
7
|
+
export * from './errors.js';
|
|
8
|
+
export * from './authz.js';
|
|
9
|
+
export * from './offline.js';
|
|
10
|
+
export * from './id.js';
|
|
11
|
+
export * from './spi/index.js';
|
|
12
|
+
export * from './search/CoreSearchIndex.js';
|
|
13
|
+
export * from './scanner.js';
|
|
14
|
+
export * from './diff.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** A comment attached to a {@link Document}. */
|
|
2
|
+
export interface Comment {
|
|
3
|
+
/** URL-safe comment id. */
|
|
4
|
+
readonly id: string;
|
|
5
|
+
/** Owning doc id. */
|
|
6
|
+
readonly documentId: string;
|
|
7
|
+
/** Identity that authored the comment. */
|
|
8
|
+
readonly author: string;
|
|
9
|
+
/** Comment body text. */
|
|
10
|
+
body: string;
|
|
11
|
+
/** Whether the comment has been marked resolved. */
|
|
12
|
+
resolved: boolean;
|
|
13
|
+
/** ISO-8601 creation timestamp. */
|
|
14
|
+
readonly createdAt: string;
|
|
15
|
+
/** ISO-8601 last-update timestamp. */
|
|
16
|
+
updatedAt: string;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=comment.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core domain model for AgentDocStore.
|
|
3
|
+
*
|
|
4
|
+
* Only the shapes required by the authorization helper are guaranteed stable
|
|
5
|
+
* here; the full model (limits, comment, version) is layered in by sibling
|
|
6
|
+
* modules. `visibility` + `createdBy` are the fields authorization decisions
|
|
7
|
+
* depend on.
|
|
8
|
+
*/
|
|
9
|
+
/** Per-doc visibility. PUBLIC is the default; PRIVATE is owner-only. */
|
|
10
|
+
export type Visibility = 'PUBLIC' | 'PRIVATE';
|
|
11
|
+
/** Supported content languages / render modes (18). */
|
|
12
|
+
export type Language = 'markdown' | 'mermaid' | 'plaintext' | 'text' | 'javascript' | 'typescript' | 'python' | 'java' | 'go' | 'rust' | 'json' | 'yaml' | 'xml' | 'html' | 'css' | 'sql' | 'bash' | 'dockerfile';
|
|
13
|
+
/** All valid {@link Language} values, for validation and UI enumeration. */
|
|
14
|
+
export declare const LANGUAGES: readonly Language[];
|
|
15
|
+
/** The default visibility applied when a caller does not specify one. */
|
|
16
|
+
export declare const DEFAULT_VISIBILITY: Visibility;
|
|
17
|
+
/**
|
|
18
|
+
* A stored artifact. Content lives in immutable versions addressed by
|
|
19
|
+
* {@link Document.latestVersion}; the metadata pointer is what this type describes.
|
|
20
|
+
*/
|
|
21
|
+
export interface Document {
|
|
22
|
+
/** URL-safe identifier (see id.ts). */
|
|
23
|
+
readonly id: string;
|
|
24
|
+
/** Human-readable title. */
|
|
25
|
+
title: string;
|
|
26
|
+
/** Content language / render mode. */
|
|
27
|
+
language: Language;
|
|
28
|
+
/** Access visibility. */
|
|
29
|
+
visibility: Visibility;
|
|
30
|
+
/** Identity of the creator; authoritative owner for authorization. */
|
|
31
|
+
readonly createdBy: string;
|
|
32
|
+
/** ISO-8601 creation timestamp. */
|
|
33
|
+
readonly createdAt: string;
|
|
34
|
+
/** ISO-8601 last-update timestamp. */
|
|
35
|
+
updatedAt: string;
|
|
36
|
+
/** Monotonic latest version number (1-based). */
|
|
37
|
+
latestVersion: number;
|
|
38
|
+
/** Optional ISO-8601 expiry (TTL sweep target when the provider lacks native TTL). */
|
|
39
|
+
expiresAt?: string;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core domain model for AgentDocStore.
|
|
3
|
+
*
|
|
4
|
+
* Only the shapes required by the authorization helper are guaranteed stable
|
|
5
|
+
* here; the full model (limits, comment, version) is layered in by sibling
|
|
6
|
+
* modules. `visibility` + `createdBy` are the fields authorization decisions
|
|
7
|
+
* depend on.
|
|
8
|
+
*/
|
|
9
|
+
/** All valid {@link Language} values, for validation and UI enumeration. */
|
|
10
|
+
export const LANGUAGES = [
|
|
11
|
+
'markdown',
|
|
12
|
+
'mermaid',
|
|
13
|
+
'plaintext',
|
|
14
|
+
'text',
|
|
15
|
+
'javascript',
|
|
16
|
+
'typescript',
|
|
17
|
+
'python',
|
|
18
|
+
'java',
|
|
19
|
+
'go',
|
|
20
|
+
'rust',
|
|
21
|
+
'json',
|
|
22
|
+
'yaml',
|
|
23
|
+
'xml',
|
|
24
|
+
'html',
|
|
25
|
+
'css',
|
|
26
|
+
'sql',
|
|
27
|
+
'bash',
|
|
28
|
+
'dockerfile',
|
|
29
|
+
];
|
|
30
|
+
/** The default visibility applied when a caller does not specify one. */
|
|
31
|
+
export const DEFAULT_VISIBILITY = 'PUBLIC';
|
|
32
|
+
//# sourceMappingURL=document.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize an editor's note before it is stored on a version: trim it, treat
|
|
3
|
+
* a blank note as no note, and reject one longer than
|
|
4
|
+
* {@link LIMITS.MAX_EDIT_MESSAGE_CHARS}. REST and MCP both call this so the
|
|
5
|
+
* two surfaces store the same thing.
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizeEditMessage(raw: string | undefined): string | undefined;
|
|
8
|
+
//# sourceMappingURL=edit-message.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ValidationError } from '../errors.js';
|
|
2
|
+
import { LIMITS } from './limits.js';
|
|
3
|
+
/**
|
|
4
|
+
* Normalize an editor's note before it is stored on a version: trim it, treat
|
|
5
|
+
* a blank note as no note, and reject one longer than
|
|
6
|
+
* {@link LIMITS.MAX_EDIT_MESSAGE_CHARS}. REST and MCP both call this so the
|
|
7
|
+
* two surfaces store the same thing.
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeEditMessage(raw) {
|
|
10
|
+
if (raw === undefined)
|
|
11
|
+
return undefined;
|
|
12
|
+
const message = raw.trim();
|
|
13
|
+
if (message.length === 0)
|
|
14
|
+
return undefined;
|
|
15
|
+
if (message.length > LIMITS.MAX_EDIT_MESSAGE_CHARS) {
|
|
16
|
+
throw new ValidationError(`Edit message must be at most ${LIMITS.MAX_EDIT_MESSAGE_CHARS} characters`);
|
|
17
|
+
}
|
|
18
|
+
return message;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=edit-message.js.map
|