@omnitron-dev/titan-lumen 0.0.0-stage → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +250 -2
- package/dist/access/access-filter.service.d.ts +25 -0
- package/dist/access/access-filter.service.d.ts.map +1 -0
- package/dist/access/access-filter.service.js +61 -0
- package/dist/access/access-filter.service.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +3 -0
- package/dist/constants.js.map +1 -0
- package/dist/corpus-files.d.ts +12 -0
- package/dist/corpus-files.d.ts.map +1 -0
- package/dist/corpus-files.js +114 -0
- package/dist/corpus-files.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/live/live-edit.service.d.ts +47 -0
- package/dist/live/live-edit.service.d.ts.map +1 -0
- package/dist/live/live-edit.service.js +253 -0
- package/dist/live/live-edit.service.js.map +1 -0
- package/dist/live/migration.d.ts +4 -0
- package/dist/live/migration.d.ts.map +1 -0
- package/dist/live/migration.js +38 -0
- package/dist/live/migration.js.map +1 -0
- package/dist/live/schema.d.ts +19 -0
- package/dist/live/schema.d.ts.map +1 -0
- package/dist/live/schema.js +2 -0
- package/dist/live/schema.js.map +1 -0
- package/dist/lumen.module.d.ts +8 -0
- package/dist/lumen.module.d.ts.map +1 -0
- package/dist/lumen.module.js +58 -0
- package/dist/lumen.module.js.map +1 -0
- package/dist/lumen.service.d.ts +32 -0
- package/dist/lumen.service.d.ts.map +1 -0
- package/dist/lumen.service.js +710 -0
- package/dist/lumen.service.js.map +1 -0
- package/dist/mcp.d.ts +16 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +52 -0
- package/dist/mcp.js.map +1 -0
- package/dist/types.d.ts +119 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 LuxQuant
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,251 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @omnitron-dev/titan-lumen
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lumen's Titan integration provides document reads, search, theorem graphs and
|
|
4
|
+
controlled authoring through Netron. The AST compiler remains in
|
|
5
|
+
`@omnitron-dev/lumen-ast`; transport authentication and invocation isolation
|
|
6
|
+
belong to Titan's Netron core.
|
|
7
|
+
|
|
8
|
+
## Integrating with an existing Titan application
|
|
9
|
+
|
|
10
|
+
Register `LumenModule.forRoot({ docsDir: '/absolute/docs' })` in the application's
|
|
11
|
+
modules, or use `LumenModule.forRootAsync({ useFactory, inject })` for configuration
|
|
12
|
+
provided by DI. Resolve `LumenService` and expose it on the application's
|
|
13
|
+
Netron peer using `peer.exposeService(app.resolve(LumenService))`. The application owns its
|
|
14
|
+
listeners and authentication adapter; Lumen does not need a second HTTP auth
|
|
15
|
+
middleware.
|
|
16
|
+
|
|
17
|
+
The equivalent explicit composition is:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { LumenService } from '@omnitron-dev/titan-lumen';
|
|
21
|
+
|
|
22
|
+
const documents = new LumenService({
|
|
23
|
+
docsDir: '/absolute/docs',
|
|
24
|
+
enablePlayground: false,
|
|
25
|
+
});
|
|
26
|
+
await netron.peer.exposeService(documents);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Configure authentication on the host Netron before opening its listeners. Use the
|
|
30
|
+
same application authentication/session provider as other Titan services. Never
|
|
31
|
+
accept a role list or `valid: true` in a request as proof of identity.
|
|
32
|
+
|
|
33
|
+
## Published RPC surface
|
|
34
|
+
|
|
35
|
+
The service name is `Lumen`. `LumenServiceContract` describes its network methods:
|
|
36
|
+
|
|
37
|
+
| Method | Result | Authorization |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `search(query, options?)` | Ranked document hits | Public projection or verified caller's document access |
|
|
40
|
+
| `getDocument(path)` | Authorized Markdown or `null` | Document and node access |
|
|
41
|
+
| `getEditableDocument(path)` | Whole document, revision, hash and access or `null` | `lumen.documents.write` and access to every section |
|
|
42
|
+
| `getSymbolContract(symbol)` | Symbol contract or `null` | Configured source access, internal by default |
|
|
43
|
+
| `getTheoremGraph()` | Theorem nodes and edges | Authorized document projection |
|
|
44
|
+
| `executePlayground(code)` | Execution result or error | `lumen.playground.execute` |
|
|
45
|
+
| `saveLiveEdit(payload)` | Revision or conflict | `lumen.documents.write` |
|
|
46
|
+
|
|
47
|
+
Grant mutation permissions through the host application's role/permission system.
|
|
48
|
+
They are independent: an editor can receive document write access without code
|
|
49
|
+
execution access. The same requirements apply to HTTP and WebSocket; missing
|
|
50
|
+
identity returns 401 and insufficient permissions return 403.
|
|
51
|
+
|
|
52
|
+
Netron supplies the admitted identity through `getCurrentInvocationContext()`.
|
|
53
|
+
`session` is not part of the wire contract. Extra arguments cannot impersonate an
|
|
54
|
+
administrator. Search filters cannot override the verified caller's roles.
|
|
55
|
+
`getOptions()` and `getAccessFilterService()` are internal service APIs and are
|
|
56
|
+
not published RPC methods.
|
|
57
|
+
|
|
58
|
+
Direct in-process reads outside an admitted Netron invocation are anonymous.
|
|
59
|
+
Mutations require the corresponding admitted service method and a verified
|
|
60
|
+
identity. There is one service and one contract; there are no session arguments,
|
|
61
|
+
controller wrappers or alternate service tokens. Titan resolves the service and
|
|
62
|
+
its `AccessFilterService` and `LiveEditService` collaborators by their classes.
|
|
63
|
+
`LUMEN_OPTIONS_TOKEN` is the configuration token.
|
|
64
|
+
|
|
65
|
+
An empty corpus returns empty results. A missing configured corpus reports an
|
|
66
|
+
error on document, search and graph reads; it never substitutes demonstration
|
|
67
|
+
documents or symbol contracts.
|
|
68
|
+
|
|
69
|
+
## Durable authoring
|
|
70
|
+
|
|
71
|
+
Without `liveEdit`, Lumen reads the filesystem and refuses edits. Enable authoring
|
|
72
|
+
with `liveEdit: { corpusId: 'product-docs', connectionName: 'default' }` and the
|
|
73
|
+
application's `TitanDatabaseModule`. Both synchronous and asynchronous Lumen
|
|
74
|
+
module providers resolve that module's `DATABASE_MANAGER`; they do not create
|
|
75
|
+
another pool. The connection must use PostgreSQL or file-backed SQLite with its
|
|
76
|
+
native driver installed. Choose a stable `corpusId` shared by every instance that
|
|
77
|
+
serves the same corpus. Different corpus IDs have independent document identities.
|
|
78
|
+
|
|
79
|
+
Apply the exported migration through the application's explicit migration workflow
|
|
80
|
+
before enabling authoring. Runtime dependency injection never creates tables:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { MigrationRunner } from '@omnitron-dev/titan-database/migration';
|
|
84
|
+
import { lumenAuthoringMigration, type LumenAuthoringDatabase } from '@omnitron-dev/titan-lumen';
|
|
85
|
+
|
|
86
|
+
const db = await databaseManager.getConnection<LumenAuthoringDatabase>('default');
|
|
87
|
+
await new MigrationRunner(db, [lumenAuthoringMigration], {
|
|
88
|
+
useTransactions: true,
|
|
89
|
+
enforceChecksums: true,
|
|
90
|
+
}).up();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
An unavailable database or missing migration is an error for enabled authoring,
|
|
94
|
+
including document reads, search and graph operations. No in-memory store or
|
|
95
|
+
stale filesystem substitute is used.
|
|
96
|
+
|
|
97
|
+
Editors first call `getEditableDocument('guides/start.pdx')`, then send its exact
|
|
98
|
+
`revision` and SHA-256 `hash` as `baseRevision` and `baseHash` with each edit:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const document = await lumen.getEditableDocument('guides/start.pdx');
|
|
102
|
+
if (document) {
|
|
103
|
+
const saved = await lumen.saveLiveEdit({
|
|
104
|
+
docId: document.docId,
|
|
105
|
+
baseRevision: document.revision,
|
|
106
|
+
baseHash: document.hash,
|
|
107
|
+
mode: 'replace',
|
|
108
|
+
patch: updatedMarkdown,
|
|
109
|
+
});
|
|
110
|
+
if (saved.conflict) {
|
|
111
|
+
// Reload the current revision and let the editor reconcile its changes.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Paths include their `.pdx`, `.md` or `.mdx` extension and must be canonical relative
|
|
117
|
+
paths. A disk document starts at revision zero. Its first successful edit stores
|
|
118
|
+
that exact seed, provenance and hash as history revision zero and the edit as
|
|
119
|
+
revision one, in the same transaction. The database then owns that path: later
|
|
120
|
+
filesystem changes cannot replace it. A new document uses revision zero and the
|
|
121
|
+
SHA-256 of the empty string as its base. `append`, `replace` and one-based
|
|
122
|
+
`line-range` edits preserve submitted whitespace.
|
|
123
|
+
|
|
124
|
+
Current content and append-only revision history commit atomically. A database
|
|
125
|
+
compare-and-swap checks both revision and hash. Competing editors receive a
|
|
126
|
+
conflict; SQLite snapshot conflicts and PostgreSQL serialization/deadlock errors
|
|
127
|
+
use TitanDatabase's bounded whole-transaction retry. Connection failures are
|
|
128
|
+
reported. Success and `live-edit` notifications occur only after commit. The
|
|
129
|
+
author comes from the verified Netron invocation; client `author` and `access`
|
|
130
|
+
fields are rejected. Existing content is authorized before conflict details are
|
|
131
|
+
returned; every section of edited content is authorized before commit. Reads and
|
|
132
|
+
edits verify stored revision numbers, UTF-8 byte lengths and content hashes;
|
|
133
|
+
corruption is an explicit error.
|
|
134
|
+
|
|
135
|
+
Accepted documents are never evicted. Writes default to 256 KiB per patch and
|
|
136
|
+
2 MiB per document, configurable up to 8 MiB. `readCorpus` checks entry and byte
|
|
137
|
+
metadata in one consistent database snapshot before reading content, then the
|
|
138
|
+
service merges unowned filesystem documents within the remaining corpus budget.
|
|
139
|
+
Document reads, search and graph therefore share the same durable source.
|
|
140
|
+
Internal `getHistory` requires an authenticated actor and access to both the
|
|
141
|
+
current document and each returned revision; it defaults to 50 versions and
|
|
142
|
+
8 MiB per page, with hard bounds of 100 versions and 64 MiB. History is not an
|
|
143
|
+
additional public RPC.
|
|
144
|
+
|
|
145
|
+
## MCP on the existing Netron HTTP host
|
|
146
|
+
|
|
147
|
+
`LumenMcpAdapter` uses the official MCP SDK for protocol negotiation, schemas and
|
|
148
|
+
errors. It translates tool/resource calls into the host's native Netron HTTP
|
|
149
|
+
entry point in process. Authorization headers and the host-supplied connection
|
|
150
|
+
address enter the same authentication, permission and rate-limit path as RPC.
|
|
151
|
+
The adapter has no listener, authentication callback or mutable caller session.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { HttpNativeServer } from '@omnitron-dev/titan/netron/transport/http';
|
|
155
|
+
import { LumenMcpAdapter } from '@omnitron-dev/titan-lumen';
|
|
156
|
+
|
|
157
|
+
let mcp: LumenMcpAdapter;
|
|
158
|
+
const http = new HttpNativeServer({
|
|
159
|
+
host: '127.0.0.1',
|
|
160
|
+
port: 8081,
|
|
161
|
+
customRoutes: [async (request, { remoteAddress }) =>
|
|
162
|
+
new URL(request.url).pathname === '/mcp'
|
|
163
|
+
? mcp.handleRequest(request, remoteAddress)
|
|
164
|
+
: null],
|
|
165
|
+
});
|
|
166
|
+
http.setPeer(netron.peer);
|
|
167
|
+
mcp = new LumenMcpAdapter({
|
|
168
|
+
http,
|
|
169
|
+
resources: ['guides/start.pdx'],
|
|
170
|
+
allowedHostnames: ['localhost', '127.0.0.1', '[::1]'],
|
|
171
|
+
allowedOriginHostnames: ['localhost', '127.0.0.1', '[::1]'],
|
|
172
|
+
});
|
|
173
|
+
await http.listen();
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The adapter publishes `search_corpus`, `get_document`, `lumen_inspect_ast` and
|
|
177
|
+
`lumen://corpus/{path}` resources. Resource lists contain only documents readable
|
|
178
|
+
by the caller; every read re-enters the service, so no independent content store
|
|
179
|
+
can bypass document access. Tool schemas reject undeclared role/session fields.
|
|
180
|
+
|
|
181
|
+
The endpoint uses stateless Streamable HTTP with JSON responses. POST is the
|
|
182
|
+
only accepted method; it does not open persistent SSE sessions. The default
|
|
183
|
+
body limit is 1 MiB. Configure host/origin hostname allowlists for the actual
|
|
184
|
+
deployment; they default to loopback names. Set `rpcPath` when the host changes
|
|
185
|
+
its native invocation route. The host owns TLS, authentication and shutdown.
|
|
186
|
+
See the [official TypeScript MCP SDK](https://github.com/modelcontextprotocol/typescript-sdk).
|
|
187
|
+
|
|
188
|
+
## Qualification status
|
|
189
|
+
|
|
190
|
+
Server playground calls use the shared Lumen WASM interpreter in a fresh Node
|
|
191
|
+
Worker with an empty environment. Netron checks `lumen.playground.execute`
|
|
192
|
+
before admitting a call. At most four workers run per service; excess calls
|
|
193
|
+
receive 429, and capacity is released only after worker termination. The outer
|
|
194
|
+
five-second timer includes initialization. Execution, memory, source and result
|
|
195
|
+
limits are enforced by `@omnitron-dev/lumen-ast/execution`; no `node:vm` or host
|
|
196
|
+
JavaScript fallback is used. Keep `enablePlayground: false` when execution is
|
|
197
|
+
unnecessary; public read access never grants execution permission.
|
|
198
|
+
|
|
199
|
+
The 0.4.0 audit is in progress. Do not infer that a test with a PDF header,
|
|
200
|
+
hydration flag or mock request establishes production readiness. The repository's
|
|
201
|
+
`audit/LUMEN-0.4.0.md` records release qualification work. Durable authoring is
|
|
202
|
+
covered by real SQLite and PostgreSQL transactions, independent connection races, commit failure,
|
|
203
|
+
restart, native RPC and shared document/search/graph tests. The isolated execution engine has
|
|
204
|
+
local negative tests through actual Netron, including memory exhaustion,
|
|
205
|
+
constructor escapes and concurrent admission. Browser and final package checks
|
|
206
|
+
remain part of the release gate. Nothing in this package's audit
|
|
207
|
+
activates changes on running Omnitron controllers or applications.
|
|
208
|
+
|
|
209
|
+
## Corpus bounds and source visibility
|
|
210
|
+
|
|
211
|
+
Filesystem documents use canonical relative paths. Symlinks, hidden paths,
|
|
212
|
+
`node_modules` and generated `dist` trees are excluded. A missing document is
|
|
213
|
+
`null`; a missing configured directory, invalid UTF-8 or failed read is an error.
|
|
214
|
+
Search and theorem graphs consume the same complete, deterministic inventory.
|
|
215
|
+
The default inventory budget is 10,000 entries, 64 MiB and 32 directory levels;
|
|
216
|
+
`corpusLimits` configures these bounds. Documents are limited to 5 MiB and source
|
|
217
|
+
files to 2 MiB. Exceeding a bound fails the request rather than returning a
|
|
218
|
+
successful partial corpus. Search accepts limits from 0 to 100 and queries of
|
|
219
|
+
at most 4 KiB.
|
|
220
|
+
|
|
221
|
+
Each service admits at most four concurrent corpus reads, independently of its
|
|
222
|
+
four playground workers. Read admission covers the complete operation through
|
|
223
|
+
authorization and projection; excess calls receive 429 and can retry after the
|
|
224
|
+
current operations finish. An overload is an error, not an empty search result.
|
|
225
|
+
|
|
226
|
+
## PostgreSQL qualification
|
|
227
|
+
|
|
228
|
+
The dedicated `test:postgres` target exercises the built package on a real
|
|
229
|
+
PostgreSQL 16 container through TitanDatabase and Kysera. Build the SDK first,
|
|
230
|
+
then set `LUMEN_POSTGRES_DATA_ROOT` to an existing absolute directory reserved
|
|
231
|
+
for disposable test data. On this development Mac, use the mounted AGI workspace.
|
|
232
|
+
The regular unit target excludes this container suite; the explicit PostgreSQL
|
|
233
|
+
target fails if Docker, PostgreSQL or test storage is unavailable.
|
|
234
|
+
|
|
235
|
+
The suite creates its own Docker network, loopback port and data directory.
|
|
236
|
+
The existing DockerTestManager enforces 1 CPU, 512 MiB of memory and 128 processes
|
|
237
|
+
from container creation; tests inspect the actual Docker configuration. Cleanup
|
|
238
|
+
owns only these resources, and the global orphan reaper is disabled for this run.
|
|
239
|
+
No configured application database is used.
|
|
240
|
+
|
|
241
|
+
Qualification covers migration and checksum enforcement, races between eight
|
|
242
|
+
independent PostgreSQL connections, rollback when a deferred constraint rejects
|
|
243
|
+
COMMIT, canonical transaction retry after SQLSTATE `40001`, and history/source
|
|
244
|
+
hashes after restarting the isolated PostgreSQL server. PostgreSQL durability
|
|
245
|
+
settings remain enabled throughout the run.
|
|
246
|
+
|
|
247
|
+
`sourceAccess` and `knowledgeBaseAccess` default to `internal`, independently of
|
|
248
|
+
public Markdown. Set either to `public` only when that entire configured source
|
|
249
|
+
is intended for anonymous readers. Per-entry and section restrictions still
|
|
250
|
+
apply to knowledge-base search results. A failed KB query or source scan is
|
|
251
|
+
reported explicitly; it does not silently fall back to a different corpus.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type DocumentAccessOptions, type LumenDocumentAst } from '@omnitron-dev/lumen-ast';
|
|
2
|
+
export declare function extractDocumentMetadata(content: string): {
|
|
3
|
+
id?: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
access: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function extractDocumentAccess(content: string): string;
|
|
8
|
+
export declare class AccessFilterService {
|
|
9
|
+
private readonly options;
|
|
10
|
+
constructor(options?: DocumentAccessOptions);
|
|
11
|
+
isAllowed(userRoles: string[], accessLevel?: unknown): boolean;
|
|
12
|
+
filterSections<T extends {
|
|
13
|
+
access?: string;
|
|
14
|
+
level?: string;
|
|
15
|
+
}>(sections: T[], userRoles: string[]): T[];
|
|
16
|
+
projectContent(content: string, userRoles: string[], explicitAccess?: string): {
|
|
17
|
+
allowed: boolean;
|
|
18
|
+
ast: LumenDocumentAst;
|
|
19
|
+
strippedAny: boolean;
|
|
20
|
+
content: string | null;
|
|
21
|
+
};
|
|
22
|
+
filterRawMarkdownContent(content: string, userRoles: string[], explicitAccess?: string): string | null;
|
|
23
|
+
filterAst(ast: LumenDocumentAst, userRoles: string[]): LumenDocumentAst;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=access-filter.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-filter.service.d.ts","sourceRoot":"","sources":["../../src/access/access-filter.service.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAEjC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAUxG;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,qBACa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAApC,YAA6B,OAAO,GAAE,qBAA0B,EAAI;IAEpE,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAE7D;IAED,cAAc,CAAC,CAAC,SAAS;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAGrG;IAED,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM;;;;;MAc3E;IAED,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAErG;IAED,SAAS,CAAC,GAAG,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAEtE;CACF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Injectable } from '@omnitron-dev/titan';
|
|
11
|
+
import { createDocumentAccessPredicate, projectDocumentAccess, parseDocumentSync, parseSafeFrontmatter, toCleanMarkdown, } from '@omnitron-dev/lumen-ast';
|
|
12
|
+
export function extractDocumentMetadata(content) {
|
|
13
|
+
const parsed = parseSafeFrontmatter(content);
|
|
14
|
+
const { id, title, access } = parsed.frontmatter;
|
|
15
|
+
return {
|
|
16
|
+
id,
|
|
17
|
+
title,
|
|
18
|
+
access: parsed.diagnostics.some((diagnostic) => diagnostic.severity === 'error')
|
|
19
|
+
? 'restricted'
|
|
20
|
+
: (access ?? 'public'),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function extractDocumentAccess(content) {
|
|
24
|
+
return extractDocumentMetadata(content).access;
|
|
25
|
+
}
|
|
26
|
+
let AccessFilterService = class AccessFilterService {
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.options = options;
|
|
29
|
+
}
|
|
30
|
+
isAllowed(userRoles, accessLevel) {
|
|
31
|
+
return createDocumentAccessPredicate(userRoles, this.options)(accessLevel);
|
|
32
|
+
}
|
|
33
|
+
filterSections(sections, userRoles) {
|
|
34
|
+
const allows = createDocumentAccessPredicate(userRoles, this.options);
|
|
35
|
+
return sections.filter((section) => allows(Object.hasOwn(section, 'access') ? section.access : section.level));
|
|
36
|
+
}
|
|
37
|
+
projectContent(content, userRoles, explicitAccess) {
|
|
38
|
+
const allows = createDocumentAccessPredicate(userRoles, this.options);
|
|
39
|
+
const result = projectDocumentAccess(parseDocumentSync(content), (access) => allows(explicitAccess) && allows(access));
|
|
40
|
+
return {
|
|
41
|
+
...result,
|
|
42
|
+
content: result.allowed
|
|
43
|
+
? result.strippedAny
|
|
44
|
+
? toCleanMarkdown(result.ast, { includeFrontmatter: true })
|
|
45
|
+
: content
|
|
46
|
+
: null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
filterRawMarkdownContent(content, userRoles, explicitAccess) {
|
|
50
|
+
return this.projectContent(content, userRoles, explicitAccess).content;
|
|
51
|
+
}
|
|
52
|
+
filterAst(ast, userRoles) {
|
|
53
|
+
return projectDocumentAccess(ast, createDocumentAccessPredicate(userRoles, this.options)).ast;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
AccessFilterService = __decorate([
|
|
57
|
+
Injectable(),
|
|
58
|
+
__metadata("design:paramtypes", [Object])
|
|
59
|
+
], AccessFilterService);
|
|
60
|
+
export { AccessFilterService };
|
|
61
|
+
//# sourceMappingURL=access-filter.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-filter.service.js","sourceRoot":"","sources":["../../src/access/access-filter.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,GAGhB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC;IACjD,OAAO;QACL,EAAE;QACF,KAAK;QACL,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,KAAK,OAAO,CAAC;YAC9E,CAAC,CAAC,YAAY;YACd,CAAC,CAAC,CAAC,MAAM,IAAI,QAAQ,CAAC;KACzB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACnD,OAAO,uBAAuB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;AACjD,CAAC;AAGM,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAC9B,YAA6B,OAAO,GAA0B,EAAE;uBAAnC,OAAO;IAA+B,CAAC;IAEpE,SAAS,CAAC,SAAmB,EAAE,WAAqB;QAClD,OAAO,6BAA6B,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC;IAC7E,CAAC;IAED,cAAc,CAAgD,QAAa,EAAE,SAAmB;QAC9F,MAAM,MAAM,GAAG,6BAA6B,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACtE,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACjH,CAAC;IAED,cAAc,CAAC,OAAe,EAAE,SAAmB,EAAE,cAAuB;QAC1E,MAAM,MAAM,GAAG,6BAA6B,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,qBAAqB,CAClC,iBAAiB,CAAC,OAAO,CAAC,EAC1B,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CACrD,CAAC;QACF,OAAO;YACL,GAAG,MAAM;YACT,OAAO,EAAE,MAAM,CAAC,OAAO;gBACrB,CAAC,CAAC,MAAM,CAAC,WAAW;oBAClB,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC;oBAC3D,CAAC,CAAC,OAAO;gBACX,CAAC,CAAC,IAAI;SACT,CAAC;IACJ,CAAC;IAED,wBAAwB,CAAC,OAAe,EAAE,SAAmB,EAAE,cAAuB;QACpF,OAAO,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC,OAAO,CAAC;IACzE,CAAC;IAED,SAAS,CAAC,GAAqB,EAAE,SAAmB;QAClD,OAAO,qBAAqB,CAAC,GAAG,EAAE,6BAA6B,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAChG,CAAC;CACF,CAAA;AAnCY,mBAAmB;IAD/B,UAAU,EAAE;;GACA,mBAAmB,CAmC/B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,eAAoC,CAAC;AACrE,eAAO,MAAM,gBAAgB,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CorpusScanLimits {
|
|
2
|
+
maxEntries?: number;
|
|
3
|
+
maxBytes?: number;
|
|
4
|
+
maxDepth?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function isCanonicalDocumentPath(path: unknown, extensions: ReadonlySet<string>): path is string;
|
|
7
|
+
export declare function readCorpusFile(root: string, path: string, maxBytes: number): Promise<string | null>;
|
|
8
|
+
export declare function scanCorpusFiles(root: string, extensions: ReadonlySet<string>, maxFileBytes: number, options?: CorpusScanLimits, ownedPaths?: ReadonlySet<string>): Promise<Array<{
|
|
9
|
+
path: string;
|
|
10
|
+
content: string;
|
|
11
|
+
}>>;
|
|
12
|
+
//# sourceMappingURL=corpus-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corpus-files.d.ts","sourceRoot":"","sources":["../src/corpus-files.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAOD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,IAAI,MAAM,CAMtG;AAGD,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsCzG;AAGD,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,EACnG,UAAU,GAAE,WAAW,CAAC,MAAM,CAAa,GAC1C,OAAO,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAiCnD"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { lstat, open, opendir, realpath } from 'node:fs/promises';
|
|
3
|
+
import { extname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
4
|
+
function inside(root, target) {
|
|
5
|
+
const path = relative(root, target);
|
|
6
|
+
return path !== '' && path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path);
|
|
7
|
+
}
|
|
8
|
+
export function isCanonicalDocumentPath(path, extensions) {
|
|
9
|
+
if (typeof path !== 'string' || !path || Buffer.byteLength(path) > 1024 || /[\\%:?#]/.test(path) || isAbsolute(path))
|
|
10
|
+
return false;
|
|
11
|
+
if ([...path].some(char => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
|
|
12
|
+
return false;
|
|
13
|
+
if (path.split('/').some((part) => !part || part.startsWith('.') || ['~', 'node_modules', 'dist'].includes(part)))
|
|
14
|
+
return false;
|
|
15
|
+
const extension = extname(path).toLowerCase();
|
|
16
|
+
return !extension || extensions.has(extension);
|
|
17
|
+
}
|
|
18
|
+
export async function readCorpusFile(root, path, maxBytes) {
|
|
19
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)
|
|
20
|
+
throw new TypeError('Corpus byte budget must be a non-negative safe integer');
|
|
21
|
+
const target = resolve(root, path);
|
|
22
|
+
if (!inside(root, target))
|
|
23
|
+
return null;
|
|
24
|
+
let current = root;
|
|
25
|
+
for (const component of relative(root, target).split(sep)) {
|
|
26
|
+
current = resolve(current, component);
|
|
27
|
+
try {
|
|
28
|
+
if ((await lstat(current)).isSymbolicLink())
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error.code === 'ENOENT')
|
|
33
|
+
return null;
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const before = await lstat(target);
|
|
38
|
+
if (!before.isFile())
|
|
39
|
+
return null;
|
|
40
|
+
if (before.size > maxBytes)
|
|
41
|
+
throw new RangeError(`Corpus file exceeds ${maxBytes} bytes`);
|
|
42
|
+
const canonical = await realpath(target);
|
|
43
|
+
if (!inside(root, canonical))
|
|
44
|
+
return null;
|
|
45
|
+
const file = await open(canonical, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
46
|
+
try {
|
|
47
|
+
const opened = await file.stat();
|
|
48
|
+
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino)
|
|
49
|
+
throw new Error('Corpus file changed before reading');
|
|
50
|
+
const buffer = Buffer.alloc(Math.min(opened.size + 1, maxBytes + 1));
|
|
51
|
+
let offset = 0;
|
|
52
|
+
while (offset < buffer.length) {
|
|
53
|
+
const { bytesRead } = await file.read(buffer, offset, buffer.length - offset, offset);
|
|
54
|
+
if (!bytesRead)
|
|
55
|
+
break;
|
|
56
|
+
offset += bytesRead;
|
|
57
|
+
}
|
|
58
|
+
const after = await file.stat();
|
|
59
|
+
const currentPath = await lstat(target);
|
|
60
|
+
if (offset > maxBytes || after.size > maxBytes)
|
|
61
|
+
throw new RangeError(`Corpus file exceeds ${maxBytes} bytes`);
|
|
62
|
+
if (after.size !== offset || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs ||
|
|
63
|
+
currentPath.ino !== opened.ino || currentPath.dev !== opened.dev || currentPath.isSymbolicLink() ||
|
|
64
|
+
await realpath(target) !== canonical)
|
|
65
|
+
throw new Error('Corpus file changed while reading');
|
|
66
|
+
return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(buffer.subarray(0, offset));
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
await file.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export async function scanCorpusFiles(root, extensions, maxFileBytes, options = {}, ownedPaths = new Set()) {
|
|
73
|
+
const maxEntries = options.maxEntries ?? 10_000;
|
|
74
|
+
const maxBytes = options.maxBytes ?? 64 * 1024 * 1024;
|
|
75
|
+
const maxDepth = options.maxDepth ?? 32;
|
|
76
|
+
if (!Number.isSafeInteger(maxFileBytes) || maxFileBytes < 1)
|
|
77
|
+
throw new TypeError('Corpus file limit must be a positive safe integer');
|
|
78
|
+
if ([maxEntries, maxDepth].some((value) => !Number.isSafeInteger(value) || value < 1) ||
|
|
79
|
+
!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
80
|
+
throw new TypeError('Corpus entry/depth limits must be positive and byte budget non-negative safe integers');
|
|
81
|
+
}
|
|
82
|
+
const files = [];
|
|
83
|
+
const directories = [{ path: root, depth: 0 }];
|
|
84
|
+
let entries = 0;
|
|
85
|
+
let bytes = 0;
|
|
86
|
+
while (directories.length) {
|
|
87
|
+
const directory = directories.pop();
|
|
88
|
+
if (directory.depth > maxDepth)
|
|
89
|
+
throw new RangeError(`Corpus directory depth exceeds ${maxDepth}`);
|
|
90
|
+
if (await realpath(directory.path) !== directory.path)
|
|
91
|
+
throw new Error('Corpus directory changed during scan');
|
|
92
|
+
for await (const entry of await opendir(directory.path)) {
|
|
93
|
+
if (++entries > maxEntries)
|
|
94
|
+
throw new RangeError(`Corpus entries exceed ${maxEntries}`);
|
|
95
|
+
if (entry.name.startsWith('.') || ['node_modules', 'dist'].includes(entry.name) || entry.isSymbolicLink())
|
|
96
|
+
continue;
|
|
97
|
+
const target = resolve(directory.path, entry.name);
|
|
98
|
+
if (entry.isDirectory())
|
|
99
|
+
directories.push({ path: target, depth: directory.depth + 1 });
|
|
100
|
+
else if (entry.isFile() && extensions.has(extname(entry.name).toLowerCase())) {
|
|
101
|
+
const path = relative(root, target).split(sep).join('/');
|
|
102
|
+
if (ownedPaths.has(path))
|
|
103
|
+
continue;
|
|
104
|
+
const content = await readCorpusFile(root, path, Math.min(maxFileBytes, maxBytes - bytes));
|
|
105
|
+
if (content === null)
|
|
106
|
+
throw new Error('Corpus file changed during scan');
|
|
107
|
+
bytes += Buffer.byteLength(content);
|
|
108
|
+
files.push({ path, content });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=corpus-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corpus-files.js","sourceRoot":"","sources":["../src/corpus-files.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { LumenModule } from './lumen.module.js';
|
|
2
|
+
export { LumenService } from './lumen.service.js';
|
|
3
|
+
export { LUMEN_OPTIONS_TOKEN, DEFAULT_DOCS_DIR } from './constants.js';
|
|
4
|
+
export { AccessFilterService, extractDocumentAccess, extractDocumentMetadata, } from './access/access-filter.service.js';
|
|
5
|
+
export { LiveEditService, DEFAULT_MAX_PATCH_BYTES, DEFAULT_MAX_DOCUMENT_BYTES, type DocumentState, type DocumentRevision, } from './live/live-edit.service.js';
|
|
6
|
+
export { lumenAuthoringMigration } from './live/migration.js';
|
|
7
|
+
export type { LumenAuthoringDatabase } from './live/schema.js';
|
|
8
|
+
export { LumenMcpAdapter, type LumenMcpOptions } from './mcp.js';
|
|
9
|
+
export type * from './types.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,0BAA0B,EAC1B,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,YAAY,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AACjE,mBAAmB,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { LumenModule } from './lumen.module.js';
|
|
2
|
+
export { LumenService } from './lumen.service.js';
|
|
3
|
+
export { LUMEN_OPTIONS_TOKEN, DEFAULT_DOCS_DIR } from './constants.js';
|
|
4
|
+
export { AccessFilterService, extractDocumentAccess, extractDocumentMetadata, } from './access/access-filter.service.js';
|
|
5
|
+
export { LiveEditService, DEFAULT_MAX_PATCH_BYTES, DEFAULT_MAX_DOCUMENT_BYTES, } from './live/live-edit.service.js';
|
|
6
|
+
export { lumenAuthoringMigration } from './live/migration.js';
|
|
7
|
+
export { LumenMcpAdapter } from './mcp.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,0BAA0B,GAG3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,OAAO,EAAE,eAAe,EAAwB,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { type DatabaseManager } from '@omnitron-dev/titan-database';
|
|
3
|
+
import { AccessFilterService } from '../access/access-filter.service.js';
|
|
4
|
+
import type { LiveEditActor, LiveEditPayload, LiveEditResult, LiveEditServiceOptions } from '../types.js';
|
|
5
|
+
export declare const DEFAULT_MAX_PATCH_BYTES = 262144;
|
|
6
|
+
export declare const DEFAULT_MAX_DOCUMENT_BYTES = 2097152;
|
|
7
|
+
export interface DocumentState {
|
|
8
|
+
docId: string;
|
|
9
|
+
content: string;
|
|
10
|
+
revision: number;
|
|
11
|
+
hash: string;
|
|
12
|
+
updatedAt: number;
|
|
13
|
+
access: string;
|
|
14
|
+
byteLength: number;
|
|
15
|
+
author: string;
|
|
16
|
+
origin: 'disk' | 'created';
|
|
17
|
+
seedHash: string;
|
|
18
|
+
}
|
|
19
|
+
export type DocumentRevision = Omit<DocumentState, 'origin' | 'seedHash'>;
|
|
20
|
+
export declare class LiveEditService extends EventEmitter {
|
|
21
|
+
private readonly database?;
|
|
22
|
+
private readonly options?;
|
|
23
|
+
private readonly maxPatchBytes;
|
|
24
|
+
private readonly maxDocumentBytes;
|
|
25
|
+
private readonly accessFilter;
|
|
26
|
+
constructor(options?: LiveEditServiceOptions, database?: DatabaseManager | undefined, accessFilter?: AccessFilterService);
|
|
27
|
+
isEnabled(): boolean;
|
|
28
|
+
private connection;
|
|
29
|
+
validatePayload(payload: unknown): {
|
|
30
|
+
valid: boolean;
|
|
31
|
+
error?: string;
|
|
32
|
+
};
|
|
33
|
+
private authorize;
|
|
34
|
+
getDocument(docId: string): Promise<DocumentState | undefined>;
|
|
35
|
+
readCorpus(limits?: {
|
|
36
|
+
maxEntries?: number;
|
|
37
|
+
maxBytes?: number;
|
|
38
|
+
}): Promise<DocumentState[]>;
|
|
39
|
+
getHistory(docId: string, actor: LiveEditActor, options?: {
|
|
40
|
+
beforeRevision?: number;
|
|
41
|
+
limit?: number;
|
|
42
|
+
maxBytes?: number;
|
|
43
|
+
}): Promise<DocumentRevision[]>;
|
|
44
|
+
private conflict;
|
|
45
|
+
applyEdit(payload: LiveEditPayload, actor: LiveEditActor, initialContent?: string): Promise<LiveEditResult>;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=live-edit.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-edit.service.d.ts","sourceRoot":"","sources":["../../src/live/live-edit.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,OAAO,EAAwB,KAAK,eAAe,EAAiC,MAAM,8BAA8B,CAAC;AACzH,OAAO,EAAE,mBAAmB,EAAyB,MAAM,oCAAoC,CAAC;AAChG,OAAO,KAAK,EAAE,aAAa,EAA+B,eAAe,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAIvI,eAAO,MAAM,uBAAuB,SAAU,CAAC;AAC/C,eAAO,MAAM,0BAA0B,UAAY,CAAC;AAQpD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC;AA2B1E,qBACa,eAAgB,SAAQ,YAAY;IAMD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IALxE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IAEnD,YAAY,OAAO,CAAC,EAAE,sBAAsB,EAAmB,QAAQ,CAAC,EAAE,eAAe,YAAA,EAAE,YAAY,CAAC,EAAE,mBAAmB,EAQ5H;IAED,SAAS,IAAI,OAAO,CAAuC;YAE7C,UAAU;IAYxB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAgBpE;IAED,OAAO,CAAC,SAAS;IAWX,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAMnE;IAGK,UAAU,CAAC,MAAM,GAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAgBlG;IAEK,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA4B/J;YAEa,QAAQ;IAOhB,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,aAAa,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA0DhH;CACF"}
|