@okfit/engine 0.7.4 → 0.7.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.
- package/errors.js +33 -1
- package/external/ExternalReferences.js +41 -0
- package/index.d.ts +240 -2
- package/index.js +8 -3
- package/overlay/documents.js +61 -0
- package/overlay/layer.js +109 -0
- package/package.json +16 -16
- package/session/BundleSession.js +67 -0
- package/session/diff.js +44 -0
- package/session/range.js +39 -0
- package/version.js +1 -1
package/errors.js
CHANGED
|
@@ -138,6 +138,38 @@ var VerifySelectionError = class extends Schema.TaggedError()("VerifySelectionEr
|
|
|
138
138
|
return `type "${this.detail ?? ""}" is not declared in the config`;
|
|
139
139
|
}
|
|
140
140
|
};
|
|
141
|
+
const DOCUMENT_PATH_REASON_TEXT = {
|
|
142
|
+
"not-relative": "must be a bundle-relative posix path",
|
|
143
|
+
"escapes-bundle": "resolves outside the bundle root",
|
|
144
|
+
"not-markdown": "must name a .md file",
|
|
145
|
+
duplicate: "was given more than once",
|
|
146
|
+
"no-directory": "names a directory that does not exist in the bundle"
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* An unsaved-document input named a path the overlay cannot honestly shadow:
|
|
150
|
+
* absolute or backslashed (`not-relative`), outside the bundle
|
|
151
|
+
* (`escapes-bundle`), not a markdown file (`not-markdown`), the same file as
|
|
152
|
+
* an earlier input after normalization (`duplicate`), or under a directory that
|
|
153
|
+
* does not exist on disk, so the bundle walk would never reach it
|
|
154
|
+
* (`no-directory`). Exit 64: a usage error.
|
|
155
|
+
*
|
|
156
|
+
* @public
|
|
157
|
+
*/
|
|
158
|
+
var DocumentPathError = class extends Schema.TaggedError()("DocumentPathError", {
|
|
159
|
+
path: Schema.String,
|
|
160
|
+
reason: Schema.Literals([
|
|
161
|
+
"not-relative",
|
|
162
|
+
"escapes-bundle",
|
|
163
|
+
"not-markdown",
|
|
164
|
+
"duplicate",
|
|
165
|
+
"no-directory"
|
|
166
|
+
])
|
|
167
|
+
}) {
|
|
168
|
+
[Runtime.errorExitCode] = 64;
|
|
169
|
+
get message() {
|
|
170
|
+
return `document path "${this.path}" ${DOCUMENT_PATH_REASON_TEXT[this.reason]}`;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
141
173
|
|
|
142
174
|
//#endregion
|
|
143
|
-
export { ConfigMalformedError, ConfigPathNotFoundError, InitOverwriteError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError };
|
|
175
|
+
export { ConfigMalformedError, ConfigPathNotFoundError, DocumentPathError, InitOverwriteError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Option, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/external/ExternalReferences.ts
|
|
4
|
+
/**
|
|
5
|
+
* Reachability of an external reference: `unknown` means never checked.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
const ReferenceState = Schema.Literals([
|
|
10
|
+
"ok",
|
|
11
|
+
"unreachable",
|
|
12
|
+
"unknown"
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* One reachability answer. `checkedAt` is `None` when nothing was checked: the
|
|
16
|
+
* engine never reads a Clock to invent a timestamp.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
const ReferenceCheck = Schema.Struct({
|
|
21
|
+
url: Schema.String,
|
|
22
|
+
state: ReferenceState,
|
|
23
|
+
checkedAt: Schema.Option(Schema.DateTimeUtc)
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* External-reference reachability (spec 4.4). Phase 2 ships only `layerNoop`;
|
|
27
|
+
* phase 7 adds an HTTP layer over a store-backed cache.
|
|
28
|
+
*
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
var ExternalReferences = class ExternalReferences extends Context.Service()("@okfit/engine/ExternalReferences") {
|
|
32
|
+
/** Answers `unknown`, never checked, for every url. */
|
|
33
|
+
static layerNoop = Layer.succeed(ExternalReferences, { check: (url) => Effect.succeed(ReferenceCheck.make({
|
|
34
|
+
url,
|
|
35
|
+
state: "unknown",
|
|
36
|
+
checkedAt: Option.none()
|
|
37
|
+
})) });
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { ExternalReferences, ReferenceCheck, ReferenceState };
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Actor, BundleLoadError, ConceptId, Diagnostic, DiagnosticRange, DiagnosticSeverity, GraphLink, GraphNode, LinkGraph, LoadedBundle, OkfitConfig, OkfitConfigFile, StaleConcept, ValidationReport } from "@okfit/core";
|
|
2
|
-
import { Context, Crypto, DateTime, Effect, FileSystem, Layer, Option, Path, PlatformError, Runtime, Schema } from "effect";
|
|
2
|
+
import { Context, Crypto, DateTime, Effect, FileSystem, Layer, Option, Path, PlatformError, Result, Runtime, Schema } from "effect";
|
|
3
3
|
import { AppDirs, Xdg } from "@effected/xdg";
|
|
4
4
|
import { ConfigReadError } from "@effected/config-file";
|
|
5
5
|
import { GeneratedAtError, GitHistory, GitHistoryError, Layout, Profile, ProfileDiagnostic } from "@okfit/profiles";
|
|
@@ -182,6 +182,24 @@ export declare class VerifySelectionError extends VerifySelectionError_base {
|
|
|
182
182
|
readonly [Runtime.errorExitCode] = 64;
|
|
183
183
|
get message(): string;
|
|
184
184
|
}
|
|
185
|
+
declare const DocumentPathError_base: Schema.Class<DocumentPathError, Schema.TaggedStruct<"DocumentPathError", {
|
|
186
|
+
readonly path: Schema.String;
|
|
187
|
+
readonly reason: Schema.Literals<readonly ["not-relative", "escapes-bundle", "not-markdown", "duplicate", "no-directory"]>;
|
|
188
|
+
}>, import("effect/Cause").YieldableError>;
|
|
189
|
+
/**
|
|
190
|
+
* An unsaved-document input named a path the overlay cannot honestly shadow:
|
|
191
|
+
* absolute or backslashed (`not-relative`), outside the bundle
|
|
192
|
+
* (`escapes-bundle`), not a markdown file (`not-markdown`), the same file as
|
|
193
|
+
* an earlier input after normalization (`duplicate`), or under a directory that
|
|
194
|
+
* does not exist on disk, so the bundle walk would never reach it
|
|
195
|
+
* (`no-directory`). Exit 64: a usage error.
|
|
196
|
+
*
|
|
197
|
+
* @public
|
|
198
|
+
*/
|
|
199
|
+
export declare class DocumentPathError extends DocumentPathError_base {
|
|
200
|
+
readonly [Runtime.errorExitCode] = 64;
|
|
201
|
+
get message(): string;
|
|
202
|
+
}
|
|
185
203
|
//#endregion
|
|
186
204
|
//#region src/config/layer.d.ts
|
|
187
205
|
/**
|
|
@@ -357,6 +375,42 @@ interface ContextResult {
|
|
|
357
375
|
*/
|
|
358
376
|
export declare const runContext: (options: ContextRunOptions) => Effect.Effect<ContextResult, never, FileSystem.FileSystem | Path.Path>;
|
|
359
377
|
//#endregion
|
|
378
|
+
//#region src/external/ExternalReferences.d.ts
|
|
379
|
+
/**
|
|
380
|
+
* Reachability of an external reference: `unknown` means never checked.
|
|
381
|
+
*
|
|
382
|
+
* @public
|
|
383
|
+
*/
|
|
384
|
+
export declare const ReferenceState: Schema.Literals<readonly ["ok", "unreachable", "unknown"]>;
|
|
385
|
+
/**
|
|
386
|
+
* One reachability answer. `checkedAt` is `None` when nothing was checked: the
|
|
387
|
+
* engine never reads a Clock to invent a timestamp.
|
|
388
|
+
*
|
|
389
|
+
* @public
|
|
390
|
+
*/
|
|
391
|
+
export declare const ReferenceCheck: Schema.Struct<{
|
|
392
|
+
readonly url: Schema.String;
|
|
393
|
+
readonly state: Schema.Literals<readonly ["ok", "unreachable", "unknown"]>;
|
|
394
|
+
readonly checkedAt: Schema.Option<Schema.DateTimeUtc>;
|
|
395
|
+
}>;
|
|
396
|
+
/** @public */
|
|
397
|
+
export type ReferenceCheck = typeof ReferenceCheck.Type;
|
|
398
|
+
/** @public */
|
|
399
|
+
interface ExternalReferencesShape {
|
|
400
|
+
readonly check: (url: string) => Effect.Effect<ReferenceCheck>;
|
|
401
|
+
}
|
|
402
|
+
declare const ExternalReferences_base: Context.ServiceClass<ExternalReferences, "@okfit/engine/ExternalReferences", ExternalReferencesShape>;
|
|
403
|
+
/**
|
|
404
|
+
* External-reference reachability (spec 4.4). Phase 2 ships only `layerNoop`;
|
|
405
|
+
* phase 7 adds an HTTP layer over a store-backed cache.
|
|
406
|
+
*
|
|
407
|
+
* @public
|
|
408
|
+
*/
|
|
409
|
+
export declare class ExternalReferences extends ExternalReferences_base {
|
|
410
|
+
/** Answers `unknown`, never checked, for every url. */
|
|
411
|
+
static readonly layerNoop: Layer.Layer<ExternalReferences>;
|
|
412
|
+
}
|
|
413
|
+
//#endregion
|
|
360
414
|
//#region src/graph/run.d.ts
|
|
361
415
|
/** @public */
|
|
362
416
|
interface GraphRunOptions {
|
|
@@ -442,6 +496,95 @@ export declare const targetPaths: (options: ScaffoldOptions) => ReadonlyArray<st
|
|
|
442
496
|
*/
|
|
443
497
|
export declare const files: (options: ScaffoldOptions) => Effect.Effect<ReadonlyArray<ScaffoldFile>, MarkdownParseError | FrontmatterWriteError>;
|
|
444
498
|
//#endregion
|
|
499
|
+
//#region src/overlay/documents.d.ts
|
|
500
|
+
/**
|
|
501
|
+
* One unsaved document: `path` is bundle-relative posix (`metrics/churn.md`),
|
|
502
|
+
* `text` the full file contents.
|
|
503
|
+
*
|
|
504
|
+
* @public
|
|
505
|
+
*/
|
|
506
|
+
interface DocumentInput {
|
|
507
|
+
readonly path: string;
|
|
508
|
+
readonly text: string;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Resolve a bundle-relative posix path to the absolute key `Bundle.load` will
|
|
512
|
+
* read (`path.join(root, relative)`, which also normalizes `.`/`..`), or say
|
|
513
|
+
* why it cannot be.
|
|
514
|
+
*
|
|
515
|
+
* @public
|
|
516
|
+
*/
|
|
517
|
+
export declare const resolveDocumentPath: (path: Path.Path, root: string, relative: string) => Result.Result<string, DocumentPathError>;
|
|
518
|
+
/**
|
|
519
|
+
* Run `self` with `documents` shadowing the ambient `FileSystem` under `root`
|
|
520
|
+
* (spec 4.5). Every path is validated before `self` starts; the first bad one
|
|
521
|
+
* fails `DocumentPathError` and `self` never runs. Beyond `resolveDocumentPath`'s
|
|
522
|
+
* lexical rules, each document's parent directory must exist on the underlying
|
|
523
|
+
* `FileSystem` (`no-directory`): the bundle walk lists one existing directory at a
|
|
524
|
+
* time, so a draft under a missing directory would otherwise never be read while
|
|
525
|
+
* validation silently checked disk. An empty list runs `self` unchanged. The
|
|
526
|
+
* overlay is private to this call.
|
|
527
|
+
*
|
|
528
|
+
* @public
|
|
529
|
+
*/
|
|
530
|
+
export declare const provideDocuments: (root: string, documents: ReadonlyArray<DocumentInput>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | DocumentPathError, R | FileSystem.FileSystem | Path.Path>;
|
|
531
|
+
//#endregion
|
|
532
|
+
//#region src/overlay/layer.d.ts
|
|
533
|
+
/**
|
|
534
|
+
* One open, possibly unsaved, document: its full text and the editor's version.
|
|
535
|
+
*
|
|
536
|
+
* @public
|
|
537
|
+
*/
|
|
538
|
+
interface OverlayDocument {
|
|
539
|
+
readonly text: string;
|
|
540
|
+
readonly version: number;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* The open-document map. Keys are absolute, normalized paths, the same strings
|
|
544
|
+
* `Bundle.load` builds with `path.join(root, file)`. Callers normalize; the
|
|
545
|
+
* overlay compares strings verbatim.
|
|
546
|
+
*
|
|
547
|
+
* @public
|
|
548
|
+
*/
|
|
549
|
+
interface OverlayDocumentsShape {
|
|
550
|
+
/** Open or replace `path`; `version` defaults to 0. */
|
|
551
|
+
readonly open: (path: string, text: string, version?: number) => Effect.Effect<void>;
|
|
552
|
+
/** Replace `path`'s text; `version` defaults to the previous version plus one (0 plus one when not open). */
|
|
553
|
+
readonly change: (path: string, text: string, version?: number) => Effect.Effect<void>;
|
|
554
|
+
/** Forget `path`; later reads fall through to the underlying filesystem. */
|
|
555
|
+
readonly close: (path: string) => Effect.Effect<void>;
|
|
556
|
+
readonly get: (path: string) => Effect.Effect<Option.Option<OverlayDocument>>;
|
|
557
|
+
readonly entries: () => Effect.Effect<ReadonlyArray<readonly [string, OverlayDocument]>>;
|
|
558
|
+
}
|
|
559
|
+
declare const OverlayDocuments_base: Context.ServiceClass<OverlayDocuments, "@okfit/engine/OverlayDocuments", OverlayDocumentsShape>;
|
|
560
|
+
/**
|
|
561
|
+
* Editor buffers that shadow the filesystem (spec 3.2). Mutable by design: an
|
|
562
|
+
* LSP server changes it on every keystroke-level `didChange`.
|
|
563
|
+
*
|
|
564
|
+
* @public
|
|
565
|
+
*/
|
|
566
|
+
export declare class OverlayDocuments extends OverlayDocuments_base {
|
|
567
|
+
/** A fresh, empty overlay. */
|
|
568
|
+
static readonly make: () => OverlayDocumentsShape;
|
|
569
|
+
/** One fresh overlay per layer build. */
|
|
570
|
+
static readonly layer: Layer.Layer<OverlayDocuments>;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Wrap `underlying` so `readFile`, `readFileString`, `exists` and `stat` answer
|
|
574
|
+
* from `documents` for open paths, and a non-recursive `readDirectory` also lists
|
|
575
|
+
* open documents whose parent is the directory read (so a brand-new unsaved file
|
|
576
|
+
* is walked). Every other member, and every path not open, is `underlying`'s.
|
|
577
|
+
*
|
|
578
|
+
* @public
|
|
579
|
+
*/
|
|
580
|
+
export declare const makeOverlayFileSystem: (underlying: FileSystem.FileSystem, documents: OverlayDocumentsShape) => FileSystem.FileSystem;
|
|
581
|
+
/**
|
|
582
|
+
* The ambient `FileSystem`, shadowed by the ambient `OverlayDocuments`.
|
|
583
|
+
*
|
|
584
|
+
* @public
|
|
585
|
+
*/
|
|
586
|
+
export declare const layerOverlayFileSystem: Layer.Layer<FileSystem.FileSystem, never, FileSystem.FileSystem | OverlayDocuments>;
|
|
587
|
+
//#endregion
|
|
445
588
|
//#region src/platform.d.ts
|
|
446
589
|
/**
|
|
447
590
|
* K-9/K-11: `AppDirs.layer(options)` requires `Xdg | FileSystem | Path`, so
|
|
@@ -1203,6 +1346,101 @@ export declare const verifyBatchEnvelope: (input: {
|
|
|
1203
1346
|
readonly distribution?: Distribution;
|
|
1204
1347
|
}) => VerifyBatchEnvelope;
|
|
1205
1348
|
//#endregion
|
|
1349
|
+
//#region src/session/BundleSession.d.ts
|
|
1350
|
+
/**
|
|
1351
|
+
* One bundle root's fixed inputs; `config` and `profile` come from the caller's
|
|
1352
|
+
* `resolveProjectConfig`.
|
|
1353
|
+
*
|
|
1354
|
+
* @public
|
|
1355
|
+
*/
|
|
1356
|
+
interface BundleSessionOptions {
|
|
1357
|
+
readonly root: string;
|
|
1358
|
+
readonly config: OkfitConfig;
|
|
1359
|
+
readonly profile: Option.Option<Profile>;
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* `edit` skips the git tier of the provenance lint (`skipProvenance: true`); `full` runs every tier.
|
|
1363
|
+
*
|
|
1364
|
+
* @public
|
|
1365
|
+
*/
|
|
1366
|
+
type RevalidateTier = "edit" | "full";
|
|
1367
|
+
/** @public */
|
|
1368
|
+
interface RevalidateOptions {
|
|
1369
|
+
/** Read once by the caller per revalidate; the engine never reads a Clock. */
|
|
1370
|
+
readonly now: DateTime.Utc;
|
|
1371
|
+
readonly tier: RevalidateTier;
|
|
1372
|
+
}
|
|
1373
|
+
/** @public */
|
|
1374
|
+
interface RevalidateResult {
|
|
1375
|
+
/**
|
|
1376
|
+
* Only files whose diagnostic set differs from the previous revalidate, keyed by
|
|
1377
|
+
* bundle-relative posix path (`""` for bundle-level); a file whose set became
|
|
1378
|
+
* empty maps to `[]`. Includes files that are not open.
|
|
1379
|
+
*/
|
|
1380
|
+
readonly changed: ReadonlyMap<string, ReadonlyArray<RenderedDiagnostic>>;
|
|
1381
|
+
readonly bundle: LoadedBundle;
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Exactly `run`'s failure union.
|
|
1385
|
+
*
|
|
1386
|
+
* @public
|
|
1387
|
+
*/
|
|
1388
|
+
type RevalidateError = BundleLoadError | GitHistoryError | GitCommandError | UnknownRefError | PlatformError.PlatformError;
|
|
1389
|
+
/** @public */
|
|
1390
|
+
interface WatchedFilesOutcome {
|
|
1391
|
+
/** A config discovery file changed: re-resolve config and replace this session. */
|
|
1392
|
+
readonly configChanged: boolean;
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* What `BundleSession.make` captures once.
|
|
1396
|
+
*
|
|
1397
|
+
* @public
|
|
1398
|
+
*/
|
|
1399
|
+
type BundleSessionServices = FileSystem.FileSystem | Path.Path | Git | GitHistory | Crypto.Crypto;
|
|
1400
|
+
/** @public */
|
|
1401
|
+
interface BundleSessionShape {
|
|
1402
|
+
/** Absolute, resolved bundle root. */
|
|
1403
|
+
readonly root: string;
|
|
1404
|
+
/**
|
|
1405
|
+
* Shadow `path` with an editor buffer. A document whose parent directory does
|
|
1406
|
+
* not exist on disk is not walked until that directory exists.
|
|
1407
|
+
*/
|
|
1408
|
+
readonly open: (path: string, text: string, version?: number) => Effect.Effect<void>;
|
|
1409
|
+
readonly change: (path: string, text: string, version?: number) => Effect.Effect<void>;
|
|
1410
|
+
readonly close: (path: string) => Effect.Effect<void>;
|
|
1411
|
+
readonly watchedFilesChanged: (paths: ReadonlyArray<string>) => Effect.Effect<WatchedFilesOutcome>;
|
|
1412
|
+
readonly revalidate: (options: RevalidateOptions) => Effect.Effect<RevalidateResult, RevalidateError>;
|
|
1413
|
+
readonly bundle: () => Effect.Effect<Option.Option<LoadedBundle>>;
|
|
1414
|
+
readonly graph: () => Effect.Effect<Option.Option<LinkGraph>>;
|
|
1415
|
+
readonly config: () => OkfitConfig;
|
|
1416
|
+
}
|
|
1417
|
+
declare const BundleSession_base: Context.ServiceClass<BundleSession, "@okfit/engine/BundleSession", BundleSessionShape>;
|
|
1418
|
+
/**
|
|
1419
|
+
* One bundle root's editor state (spec 4): open-document overlay, last loaded
|
|
1420
|
+
* bundle and graph, and last published diagnostics per file. Debounce is NOT
|
|
1421
|
+
* here: the caller (the LSP server) schedules revalidates; overlapping calls are
|
|
1422
|
+
* serialized so every change is reported exactly once.
|
|
1423
|
+
*
|
|
1424
|
+
* @public
|
|
1425
|
+
*/
|
|
1426
|
+
export declare class BundleSession extends BundleSession_base {
|
|
1427
|
+
/** Build a session; the returned operations need no services. */
|
|
1428
|
+
static readonly make: (options: BundleSessionOptions) => Effect.Effect<BundleSessionShape, never, BundleSessionServices>;
|
|
1429
|
+
}
|
|
1430
|
+
//#endregion
|
|
1431
|
+
//#region src/session/range.d.ts
|
|
1432
|
+
/**
|
|
1433
|
+
* Give a range-less diagnostic somewhere to point (spec 4.3): the concept's
|
|
1434
|
+
* frontmatter block when the file loaded as a concept with frontmatter, else a
|
|
1435
|
+
* zero-length range at line 0. Ranged and bundle-level (`file === ""`)
|
|
1436
|
+
* diagnostics are returned unchanged. A file whose frontmatter failed to decode
|
|
1437
|
+
* is not a loaded concept and falls to line 0; core ranges its own decode
|
|
1438
|
+
* diagnostics already.
|
|
1439
|
+
*
|
|
1440
|
+
* @public
|
|
1441
|
+
*/
|
|
1442
|
+
export declare const withFallbackRange: (bundle: LoadedBundle, diagnostic: RenderedDiagnostic) => RenderedDiagnostic;
|
|
1443
|
+
//#endregion
|
|
1206
1444
|
//#region src/stale/run.d.ts
|
|
1207
1445
|
/** @public */
|
|
1208
1446
|
interface StaleRunOptions {
|
|
@@ -1442,5 +1680,5 @@ export declare const runVerifyBatch: (options: VerifyBatchOptions) => Effect.Eff
|
|
|
1442
1680
|
*/
|
|
1443
1681
|
export declare const ENGINE_VERSION: string;
|
|
1444
1682
|
//#endregion
|
|
1445
|
-
export type { ContextResult, ContextRunOptions, DiagnosticSource, DiscoveredConfig, Distribution, GraphRunOptions, GraphRunResult, RenderedDiagnostic, ResolveProjectConfigInput, ResolvedProjectConfig, RunOptions, RunResult, ScaffoldFile, ScaffoldOptions, StaleRunOptions, StaleRunResult, SyncMode, SyncModeResult, SyncOptions, SyncResult, Tally, VerifyBatchOptions, VerifyBatchResult, VerifyBatchSkipReason, VerifyOptions, VerifyResult };
|
|
1683
|
+
export type { BundleSessionOptions, BundleSessionServices, BundleSessionShape, ContextResult, ContextRunOptions, DiagnosticSource, DiscoveredConfig, Distribution, DocumentInput, ExternalReferencesShape, GraphRunOptions, GraphRunResult, OverlayDocument, OverlayDocumentsShape, RenderedDiagnostic, ResolveProjectConfigInput, ResolvedProjectConfig, RevalidateError, RevalidateOptions, RevalidateResult, RevalidateTier, RunOptions, RunResult, ScaffoldFile, ScaffoldOptions, StaleRunOptions, StaleRunResult, SyncMode, SyncModeResult, SyncOptions, SyncResult, Tally, VerifyBatchOptions, VerifyBatchResult, VerifyBatchSkipReason, VerifyOptions, VerifyResult, WatchedFilesOutcome };
|
|
1446
1684
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { resolveBundleRoot, resolveProjectRoot } from "./config/anchor.js";
|
|
2
|
-
import { ConfigMalformedError, ConfigPathNotFoundError, InitOverwriteError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError } from "./errors.js";
|
|
2
|
+
import { ConfigMalformedError, ConfigPathNotFoundError, DocumentPathError, InitOverwriteError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError } from "./errors.js";
|
|
3
3
|
import { buildConfigLayer, provideConfig } from "./config/layer.js";
|
|
4
4
|
import { DEFAULT_PROFILE_NAME, resolveProjectConfig } from "./config/resolve.js";
|
|
5
5
|
import { runContext } from "./context/run.js";
|
|
6
|
+
import { ExternalReferences, ReferenceCheck, ReferenceState } from "./external/ExternalReferences.js";
|
|
6
7
|
import { runGraph } from "./graph/run.js";
|
|
7
8
|
import { CONFIG_RELATIVE_PATH, configValue, files, targetPaths } from "./init/scaffold.js";
|
|
9
|
+
import { OverlayDocuments, layerOverlayFileSystem, makeOverlayFileSystem } from "./overlay/layer.js";
|
|
10
|
+
import { provideDocuments, resolveDocumentPath } from "./overlay/documents.js";
|
|
8
11
|
import { OkfitPlatform } from "./platform.js";
|
|
9
12
|
import { ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, contextEnvelope } from "./render/context.js";
|
|
10
13
|
import { DistributionField } from "./render/distribution.js";
|
|
@@ -17,8 +20,10 @@ import { StaleEnvelope, StaleItem, StaleSummary, staleEnvelope } from "./render/
|
|
|
17
20
|
import { SkipReason, runSync } from "./sync/run.js";
|
|
18
21
|
import { SyncEnvelope, SyncModeEnvelope, syncEnvelope } from "./render/sync.js";
|
|
19
22
|
import { VerifyBatchEnvelope, VerifyEnvelope, verifyBatchEnvelope, verifyEnvelope } from "./render/verify.js";
|
|
20
|
-
import { runStale } from "./stale/run.js";
|
|
21
23
|
import { Now, run } from "./validate/run.js";
|
|
24
|
+
import { withFallbackRange } from "./session/range.js";
|
|
25
|
+
import { BundleSession } from "./session/BundleSession.js";
|
|
26
|
+
import { runStale } from "./stale/run.js";
|
|
22
27
|
import { runVerify, runVerifyBatch } from "./verify/run.js";
|
|
23
28
|
|
|
24
|
-
export { CONFIG_RELATIVE_PATH, ConfigMalformedError, ConfigPathNotFoundError, ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, DEFAULT_PROFILE_NAME, DistributionField, ENGINE_VERSION, GraphEdgeEnvelope, GraphEnvelope, GraphNodeEnvelope, GraphSummary, InitOverwriteError, JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, Now, OkfitPlatform, SkipReason, StaleEnvelope, StaleItem, StaleSummary, SyncEnvelope, SyncModeEnvelope, SyncStagedLogError, VerifyBatchEnvelope, VerifyConceptNotFoundError, VerifyEnvelope, VerifySelectionError, VerifyUnsupportedFrontmatterError, buildConfigLayer, collect, configValue, contextEnvelope, files, forDiagnostics, graphEnvelope, json, jsonError, provideConfig, resolveBundleRoot, resolveProjectConfig, resolveProjectRoot, run, runContext, runGraph, runStale, runSync, runVerify, runVerifyBatch, sort, staleEnvelope, syncEnvelope, tally, targetPaths, verifyBatchEnvelope, verifyEnvelope };
|
|
29
|
+
export { BundleSession, CONFIG_RELATIVE_PATH, ConfigMalformedError, ConfigPathNotFoundError, ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, DEFAULT_PROFILE_NAME, DistributionField, DocumentPathError, ENGINE_VERSION, ExternalReferences, GraphEdgeEnvelope, GraphEnvelope, GraphNodeEnvelope, GraphSummary, InitOverwriteError, JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, Now, OkfitPlatform, OverlayDocuments, ReferenceCheck, ReferenceState, SkipReason, StaleEnvelope, StaleItem, StaleSummary, SyncEnvelope, SyncModeEnvelope, SyncStagedLogError, VerifyBatchEnvelope, VerifyConceptNotFoundError, VerifyEnvelope, VerifySelectionError, VerifyUnsupportedFrontmatterError, buildConfigLayer, collect, configValue, contextEnvelope, files, forDiagnostics, graphEnvelope, json, jsonError, layerOverlayFileSystem, makeOverlayFileSystem, provideConfig, provideDocuments, resolveBundleRoot, resolveDocumentPath, resolveProjectConfig, resolveProjectRoot, run, runContext, runGraph, runStale, runSync, runVerify, runVerifyBatch, sort, staleEnvelope, syncEnvelope, tally, targetPaths, verifyBatchEnvelope, verifyEnvelope, withFallbackRange };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { DocumentPathError } from "../errors.js";
|
|
2
|
+
import { OverlayDocuments, makeOverlayFileSystem } from "./layer.js";
|
|
3
|
+
import { Effect, FileSystem, Path, Result } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/overlay/documents.ts
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a bundle-relative posix path to the absolute key `Bundle.load` will
|
|
8
|
+
* read (`path.join(root, relative)`, which also normalizes `.`/`..`), or say
|
|
9
|
+
* why it cannot be.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
const resolveDocumentPath = (path, root, relative) => {
|
|
14
|
+
const reject = (reason) => Result.fail(new DocumentPathError({
|
|
15
|
+
path: relative,
|
|
16
|
+
reason
|
|
17
|
+
}));
|
|
18
|
+
if (relative === "" || relative.includes("\\") || path.isAbsolute(relative)) return reject("not-relative");
|
|
19
|
+
if (!relative.endsWith(".md")) return reject("not-markdown");
|
|
20
|
+
const absolute = path.join(root, relative);
|
|
21
|
+
const back = path.relative(root, absolute);
|
|
22
|
+
if (back === "" || back === ".." || back.startsWith(`..${path.sep}`) || path.isAbsolute(back)) return reject("escapes-bundle");
|
|
23
|
+
return Result.succeed(absolute);
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Run `self` with `documents` shadowing the ambient `FileSystem` under `root`
|
|
27
|
+
* (spec 4.5). Every path is validated before `self` starts; the first bad one
|
|
28
|
+
* fails `DocumentPathError` and `self` never runs. Beyond `resolveDocumentPath`'s
|
|
29
|
+
* lexical rules, each document's parent directory must exist on the underlying
|
|
30
|
+
* `FileSystem` (`no-directory`): the bundle walk lists one existing directory at a
|
|
31
|
+
* time, so a draft under a missing directory would otherwise never be read while
|
|
32
|
+
* validation silently checked disk. An empty list runs `self` unchanged. The
|
|
33
|
+
* overlay is private to this call.
|
|
34
|
+
*
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
const provideDocuments = (root, documents) => (self) => documents.length === 0 ? self : Effect.gen(function* () {
|
|
38
|
+
const path = yield* Path.Path;
|
|
39
|
+
const underlying = yield* FileSystem.FileSystem;
|
|
40
|
+
const overlay = OverlayDocuments.make();
|
|
41
|
+
const seen = /* @__PURE__ */ new Set();
|
|
42
|
+
for (const document of documents) {
|
|
43
|
+
const resolved = resolveDocumentPath(path, root, document.path);
|
|
44
|
+
if (Result.isFailure(resolved)) return yield* resolved.failure;
|
|
45
|
+
if (seen.has(resolved.success)) return yield* new DocumentPathError({
|
|
46
|
+
path: document.path,
|
|
47
|
+
reason: "duplicate"
|
|
48
|
+
});
|
|
49
|
+
seen.add(resolved.success);
|
|
50
|
+
const parent = path.dirname(resolved.success);
|
|
51
|
+
if (!(yield* underlying.exists(parent).pipe(Effect.flatMap((exists) => exists ? Effect.map(underlying.stat(parent), (info) => info.type === "Directory") : Effect.succeed(false)), Effect.orElseSucceed(() => false)))) return yield* new DocumentPathError({
|
|
52
|
+
path: document.path,
|
|
53
|
+
reason: "no-directory"
|
|
54
|
+
});
|
|
55
|
+
yield* overlay.open(resolved.success, document.text);
|
|
56
|
+
}
|
|
57
|
+
return yield* self.pipe(Effect.provideService(FileSystem.FileSystem, makeOverlayFileSystem(underlying, overlay)));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { provideDocuments, resolveDocumentPath };
|
package/overlay/layer.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ByteSize, Context, Effect, FileSystem, Layer, Option } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/overlay/layer.ts
|
|
4
|
+
/**
|
|
5
|
+
* Editor buffers that shadow the filesystem (spec 3.2). Mutable by design: an
|
|
6
|
+
* LSP server changes it on every keystroke-level `didChange`.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
var OverlayDocuments = class OverlayDocuments extends Context.Service()("@okfit/engine/OverlayDocuments") {
|
|
11
|
+
/** A fresh, empty overlay. */
|
|
12
|
+
static make = () => {
|
|
13
|
+
const documents = /* @__PURE__ */ new Map();
|
|
14
|
+
return {
|
|
15
|
+
open: (path, text, version = 0) => Effect.sync(() => {
|
|
16
|
+
documents.set(path, {
|
|
17
|
+
text,
|
|
18
|
+
version
|
|
19
|
+
});
|
|
20
|
+
}),
|
|
21
|
+
change: (path, text, version) => Effect.sync(() => {
|
|
22
|
+
documents.set(path, {
|
|
23
|
+
text,
|
|
24
|
+
version: version ?? (documents.get(path)?.version ?? 0) + 1
|
|
25
|
+
});
|
|
26
|
+
}),
|
|
27
|
+
close: (path) => Effect.sync(() => {
|
|
28
|
+
documents.delete(path);
|
|
29
|
+
}),
|
|
30
|
+
get: (path) => Effect.sync(() => Option.fromUndefinedOr(documents.get(path))),
|
|
31
|
+
entries: () => Effect.sync(() => [...documents.entries()])
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/** One fresh overlay per layer build. */
|
|
35
|
+
static layer = Layer.sync(OverlayDocuments, () => OverlayDocuments.make());
|
|
36
|
+
};
|
|
37
|
+
const encoder = new TextEncoder();
|
|
38
|
+
/** Parent directory and base name of an absolute path, on either separator. */
|
|
39
|
+
const parentAndName = (path) => {
|
|
40
|
+
const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
41
|
+
return [index <= 0 ? path.slice(0, index + 1) : path.slice(0, index), path.slice(index + 1)];
|
|
42
|
+
};
|
|
43
|
+
const trimTrailingSeparators = (path) => path.length > 1 ? path.replace(/[\\/]+$/, "") : path;
|
|
44
|
+
/** A `File.Info` for a document that exists only in the overlay. */
|
|
45
|
+
const overlayInfo = (text) => ({
|
|
46
|
+
type: "File",
|
|
47
|
+
mtime: Option.none(),
|
|
48
|
+
atime: Option.none(),
|
|
49
|
+
birthtime: Option.none(),
|
|
50
|
+
dev: 0,
|
|
51
|
+
ino: Option.none(),
|
|
52
|
+
mode: 420,
|
|
53
|
+
nlink: Option.none(),
|
|
54
|
+
uid: Option.none(),
|
|
55
|
+
gid: Option.none(),
|
|
56
|
+
rdev: Option.none(),
|
|
57
|
+
size: ByteSize.bytes(encoder.encode(text).byteLength),
|
|
58
|
+
blksize: Option.none(),
|
|
59
|
+
blocks: Option.none()
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* Wrap `underlying` so `readFile`, `readFileString`, `exists` and `stat` answer
|
|
63
|
+
* from `documents` for open paths, and a non-recursive `readDirectory` also lists
|
|
64
|
+
* open documents whose parent is the directory read (so a brand-new unsaved file
|
|
65
|
+
* is walked). Every other member, and every path not open, is `underlying`'s.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
const makeOverlayFileSystem = (underlying, documents) => FileSystem.FileSystem.of({
|
|
70
|
+
...underlying,
|
|
71
|
+
readFile: (path) => Effect.flatMap(documents.get(path), Option.match({
|
|
72
|
+
onNone: () => underlying.readFile(path),
|
|
73
|
+
onSome: (document) => Effect.succeed(encoder.encode(document.text))
|
|
74
|
+
})),
|
|
75
|
+
readFileString: (path, encoding) => Effect.flatMap(documents.get(path), Option.match({
|
|
76
|
+
onNone: () => underlying.readFileString(path, encoding),
|
|
77
|
+
onSome: (document) => Effect.succeed(document.text)
|
|
78
|
+
})),
|
|
79
|
+
exists: (path) => Effect.flatMap(documents.get(path), Option.match({
|
|
80
|
+
onNone: () => underlying.exists(path),
|
|
81
|
+
onSome: () => Effect.succeed(true)
|
|
82
|
+
})),
|
|
83
|
+
stat: (path) => Effect.flatMap(documents.get(path), Option.match({
|
|
84
|
+
onNone: () => underlying.stat(path),
|
|
85
|
+
onSome: (document) => underlying.stat(path).pipe(Effect.map((info) => ({
|
|
86
|
+
...info,
|
|
87
|
+
size: ByteSize.bytes(encoder.encode(document.text).byteLength)
|
|
88
|
+
})), Effect.catchTag("PlatformError", (error) => error.reason._tag === "NotFound" ? Effect.succeed(overlayInfo(document.text)) : Effect.fail(error)))
|
|
89
|
+
})),
|
|
90
|
+
readDirectory: (path, options) => options?.recursive === true ? underlying.readDirectory(path, options) : Effect.gen(function* () {
|
|
91
|
+
const names = yield* underlying.readDirectory(path, options);
|
|
92
|
+
const directory = trimTrailingSeparators(path);
|
|
93
|
+
const extra = (yield* documents.entries()).map(([key]) => parentAndName(key)).filter(([parent, name]) => parent === directory && !names.includes(name)).map(([, name]) => name);
|
|
94
|
+
return extra.length === 0 ? names : [...names, ...extra];
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* The ambient `FileSystem`, shadowed by the ambient `OverlayDocuments`.
|
|
99
|
+
*
|
|
100
|
+
* @public
|
|
101
|
+
*/
|
|
102
|
+
const layerOverlayFileSystem = Layer.effect(FileSystem.FileSystem, Effect.gen(function* () {
|
|
103
|
+
const underlying = yield* FileSystem.FileSystem;
|
|
104
|
+
const documents = yield* OverlayDocuments;
|
|
105
|
+
return makeOverlayFileSystem(underlying, documents);
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
//#endregion
|
|
109
|
+
export { OverlayDocuments, layerOverlayFileSystem, makeOverlayFileSystem };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okfit/engine",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The okfit engine: platform layer, config discovery, and the validate, verify, sync, init and context programs shared by the okfit CLI and MCP server.",
|
|
6
6
|
"keywords": [
|
|
@@ -34,21 +34,21 @@
|
|
|
34
34
|
"./package.json": "./package.json"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@effect/platform-node": "4.0.0-rc.
|
|
38
|
-
"@effected/app": "^0.
|
|
39
|
-
"@effected/config-file": "^0.
|
|
40
|
-
"@effected/git": "^0.
|
|
41
|
-
"@effected/glob": "^0.
|
|
42
|
-
"@effected/jsonc": "^0.
|
|
43
|
-
"@effected/markdown": "^0.
|
|
44
|
-
"@effected/store": "^0.
|
|
45
|
-
"@effected/toml": "^0.
|
|
46
|
-
"@effected/walker": "^0.
|
|
47
|
-
"@effected/xdg": "^0.
|
|
48
|
-
"@effected/yaml": "^0.
|
|
49
|
-
"@okfit/core": "0.7.
|
|
50
|
-
"@okfit/profiles": "0.7.
|
|
51
|
-
"effect": "4.0.0-rc.
|
|
37
|
+
"@effect/platform-node": "4.0.0-rc.117",
|
|
38
|
+
"@effected/app": "^0.18.0",
|
|
39
|
+
"@effected/config-file": "^0.12.0",
|
|
40
|
+
"@effected/git": "^0.17.0",
|
|
41
|
+
"@effected/glob": "^0.8.0",
|
|
42
|
+
"@effected/jsonc": "^0.13.0",
|
|
43
|
+
"@effected/markdown": "^0.12.0",
|
|
44
|
+
"@effected/store": "^0.10.0",
|
|
45
|
+
"@effected/toml": "^0.9.0",
|
|
46
|
+
"@effected/walker": "^0.12.0",
|
|
47
|
+
"@effected/xdg": "^0.7.0",
|
|
48
|
+
"@effected/yaml": "^0.17.0",
|
|
49
|
+
"@okfit/core": "0.7.4",
|
|
50
|
+
"@okfit/profiles": "0.7.5",
|
|
51
|
+
"effect": "4.0.0-rc.117"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=24.11.0"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { PROJECT_CONFIG_NAMES } from "../init/scaffold.js";
|
|
2
|
+
import { OverlayDocuments, makeOverlayFileSystem } from "../overlay/layer.js";
|
|
3
|
+
import { collect, sort } from "../render/sort.js";
|
|
4
|
+
import { run } from "../validate/run.js";
|
|
5
|
+
import { diffDiagnostics, groupByFile } from "./diff.js";
|
|
6
|
+
import { withFallbackRange } from "./range.js";
|
|
7
|
+
import { Graph } from "@okfit/core";
|
|
8
|
+
import { Context, Effect, FileSystem, Option, Path, Ref, Semaphore } from "effect";
|
|
9
|
+
|
|
10
|
+
//#region src/session/BundleSession.ts
|
|
11
|
+
const CONFIG_BASENAMES = new Set(PROJECT_CONFIG_NAMES.map((name) => name.slice(name.lastIndexOf("/") + 1)));
|
|
12
|
+
/**
|
|
13
|
+
* One bundle root's editor state (spec 4): open-document overlay, last loaded
|
|
14
|
+
* bundle and graph, and last published diagnostics per file. Debounce is NOT
|
|
15
|
+
* here: the caller (the LSP server) schedules revalidates; overlapping calls are
|
|
16
|
+
* serialized so every change is reported exactly once.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
var BundleSession = class BundleSession extends Context.Service()("@okfit/engine/BundleSession") {
|
|
21
|
+
/** Build a session; the returned operations need no services. */
|
|
22
|
+
static make = (options) => Effect.gen(function* () {
|
|
23
|
+
const path = yield* Path.Path;
|
|
24
|
+
const underlying = yield* FileSystem.FileSystem;
|
|
25
|
+
const context = yield* Effect.context();
|
|
26
|
+
const root = path.resolve(options.root);
|
|
27
|
+
const overlay = OverlayDocuments.make();
|
|
28
|
+
const fileSystem = makeOverlayFileSystem(underlying, overlay);
|
|
29
|
+
const semaphore = yield* Semaphore.make(1);
|
|
30
|
+
const published = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
31
|
+
const loaded = yield* Ref.make(Option.none());
|
|
32
|
+
const revalidateNow = Effect.fn("BundleSession.revalidate")(function* (revalidate) {
|
|
33
|
+
const result = yield* run({
|
|
34
|
+
root,
|
|
35
|
+
config: options.config,
|
|
36
|
+
profile: options.profile,
|
|
37
|
+
now: revalidate.now,
|
|
38
|
+
skipProvenance: revalidate.tier === "edit"
|
|
39
|
+
}).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideContext(context));
|
|
40
|
+
const rendered = sort(collect(result.report.conformance, result.report.lint, result.profileDiagnostics).map((diagnostic) => withFallbackRange(result.bundle, diagnostic)));
|
|
41
|
+
const next = groupByFile(rendered);
|
|
42
|
+
const changed = yield* Ref.modify(published, (previous) => [diffDiagnostics(previous, next), next]);
|
|
43
|
+
yield* Ref.set(loaded, Option.some({
|
|
44
|
+
bundle: result.bundle,
|
|
45
|
+
graph: Graph.fromBundle(result.bundle)
|
|
46
|
+
}));
|
|
47
|
+
return {
|
|
48
|
+
changed,
|
|
49
|
+
bundle: result.bundle
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
return BundleSession.of({
|
|
53
|
+
root,
|
|
54
|
+
open: (file, text, version) => overlay.open(path.resolve(file), text, version),
|
|
55
|
+
change: (file, text, version) => overlay.change(path.resolve(file), text, version),
|
|
56
|
+
close: (file) => overlay.close(path.resolve(file)),
|
|
57
|
+
watchedFilesChanged: (paths) => Effect.succeed({ configChanged: paths.some((file) => CONFIG_BASENAMES.has(path.basename(file))) }),
|
|
58
|
+
revalidate: (revalidate) => semaphore.withPermit(revalidateNow(revalidate)),
|
|
59
|
+
bundle: () => Effect.map(Ref.get(loaded), Option.map((state) => state.bundle)),
|
|
60
|
+
graph: () => Effect.map(Ref.get(loaded), Option.map((state) => state.graph)),
|
|
61
|
+
config: () => options.config
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { BundleSession };
|
package/session/diff.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/session/diff.ts
|
|
2
|
+
/**
|
|
3
|
+
* Group `diagnostics` by `file`, keeping input order within each file.
|
|
4
|
+
*
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
const groupByFile = (diagnostics) => {
|
|
8
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
9
|
+
for (const diagnostic of diagnostics) {
|
|
10
|
+
const list = grouped.get(diagnostic.file);
|
|
11
|
+
if (list === void 0) grouped.set(diagnostic.file, [diagnostic]);
|
|
12
|
+
else list.push(diagnostic);
|
|
13
|
+
}
|
|
14
|
+
return grouped;
|
|
15
|
+
};
|
|
16
|
+
const fingerprint = (list) => list.map((d) => JSON.stringify([
|
|
17
|
+
d.source,
|
|
18
|
+
d.code,
|
|
19
|
+
d.severity,
|
|
20
|
+
d.message,
|
|
21
|
+
d.range?.offset ?? null,
|
|
22
|
+
d.range?.length ?? null,
|
|
23
|
+
d.range?.line ?? null,
|
|
24
|
+
d.range?.character ?? null
|
|
25
|
+
])).join("\n");
|
|
26
|
+
/**
|
|
27
|
+
* The files whose diagnostic list differs between `previous` and `next`,
|
|
28
|
+
* structurally and in order, mapped to their `next` list; a file present only in
|
|
29
|
+
* `previous` maps to `[]` (its set became empty). Each diagnostic compares by
|
|
30
|
+
* source, code, severity, message and its range's offset, length, line and
|
|
31
|
+
* character: across an edit the text changes, so an unchanged offset can still
|
|
32
|
+
* sit on a different line.
|
|
33
|
+
*
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
const diffDiagnostics = (previous, next) => {
|
|
37
|
+
const changed = /* @__PURE__ */ new Map();
|
|
38
|
+
for (const [file, list] of next) if (fingerprint(previous.get(file) ?? []) !== fingerprint(list)) changed.set(file, list);
|
|
39
|
+
for (const file of previous.keys()) if (!next.has(file)) changed.set(file, []);
|
|
40
|
+
return changed;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { diffDiagnostics, groupByFile };
|
package/session/range.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ConceptId, DiagnosticRange } from "@okfit/core";
|
|
2
|
+
import { Option } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/session/range.ts
|
|
5
|
+
const FILE_START = DiagnosticRange.make({
|
|
6
|
+
offset: 0,
|
|
7
|
+
length: 0,
|
|
8
|
+
line: 0,
|
|
9
|
+
character: 0
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Give a range-less diagnostic somewhere to point (spec 4.3): the concept's
|
|
13
|
+
* frontmatter block when the file loaded as a concept with frontmatter, else a
|
|
14
|
+
* zero-length range at line 0. Ranged and bundle-level (`file === ""`)
|
|
15
|
+
* diagnostics are returned unchanged. A file whose frontmatter failed to decode
|
|
16
|
+
* is not a loaded concept and falls to line 0; core ranges its own decode
|
|
17
|
+
* diagnostics already.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
const withFallbackRange = (bundle, diagnostic) => {
|
|
22
|
+
if (diagnostic.range !== void 0 || diagnostic.file === "") return diagnostic;
|
|
23
|
+
const concept = Option.match(ConceptId.fromPath(diagnostic.file), {
|
|
24
|
+
onNone: () => void 0,
|
|
25
|
+
onSome: (id) => bundle.concepts.get(id)
|
|
26
|
+
});
|
|
27
|
+
const node = concept?.document.frontmatter;
|
|
28
|
+
if (concept === void 0 || node === void 0) return {
|
|
29
|
+
...diagnostic,
|
|
30
|
+
range: FILE_START
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
...diagnostic,
|
|
34
|
+
range: DiagnosticRange.fromOffset(concept.document.source, node.position.start.offset, node.position.end.offset - node.position.start.offset)
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { withFallbackRange };
|