@effected/jsonl 0.7.1-dogfood-20260927171341 → 0.9.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/Envelope.js +35 -3
- package/Journal.js +6 -3
- package/JsonlError.js +0 -9
- package/Line.js +0 -10
- package/LineSlice.js +0 -5
- package/README.md +4 -4
- package/index.d.ts +42 -7
- package/internal/merge.js +0 -9
- package/internal/utf8.js +0 -9
- package/package.json +2 -2
package/Envelope.js
CHANGED
|
@@ -87,6 +87,23 @@ const completeResult = (events, line, frame) => {
|
|
|
87
87
|
* defined **in terms of** the sync ones via `Effect.fromResult`, so the two can
|
|
88
88
|
* never drift into two implementations of the same rule.
|
|
89
89
|
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* import { Envelope, JsonlEvent, Line } from "@effected/jsonl";
|
|
93
|
+
* import { Result, Schema } from "effect";
|
|
94
|
+
*
|
|
95
|
+
* const events = [JsonlEvent.make("started", { data: Schema.Struct({ id: Schema.String }) })] as const;
|
|
96
|
+
*
|
|
97
|
+
* declare const sourceText: string;
|
|
98
|
+
*
|
|
99
|
+
* for (const line of Line.split(sourceText)) {
|
|
100
|
+
* const decoded = Envelope.decodeResult(events, line);
|
|
101
|
+
* if (Result.isSuccess(decoded) && decoded.success.event === "started") {
|
|
102
|
+
* decoded.success.data.id; // typed from the registered payload schema
|
|
103
|
+
* }
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
90
107
|
* @public
|
|
91
108
|
*/
|
|
92
109
|
const Envelope = {
|
|
@@ -98,6 +115,9 @@ const Envelope = {
|
|
|
98
115
|
* filtered out never pays for its payload at all.
|
|
99
116
|
*
|
|
100
117
|
* @param line - A slice from `Line.split`.
|
|
118
|
+
* @returns The frame with its source `line` attached, or `MalformedLine`
|
|
119
|
+
* when the line is not JSON, or `InvalidData` when it is JSON that is not
|
|
120
|
+
* an envelope frame.
|
|
101
121
|
*/
|
|
102
122
|
frameResult: (line) => {
|
|
103
123
|
const parsed = Line.parseResult(line);
|
|
@@ -157,6 +177,10 @@ const Envelope = {
|
|
|
157
177
|
*
|
|
158
178
|
* Same contract as `Line.parseAll` one layer down: a line that is not a
|
|
159
179
|
* legal envelope is a `Result` in the array, never a gap in it.
|
|
180
|
+
*
|
|
181
|
+
* @param events - The registry to validate against.
|
|
182
|
+
* @param text - JSONL source text.
|
|
183
|
+
* @returns One `Result` per non-blank line, in file order.
|
|
160
184
|
*/
|
|
161
185
|
decodeAllResult: (events, text) => {
|
|
162
186
|
const results = [];
|
|
@@ -207,6 +231,10 @@ const Envelope = {
|
|
|
207
231
|
* @param events - The registry to validate against.
|
|
208
232
|
* @param envelope - The event tag, its payload, the service-assigned
|
|
209
233
|
* timestamp, and an optional scope.
|
|
234
|
+
* @returns The encoded line, or `UnknownEvent` for a tag the registry does
|
|
235
|
+
* not define, `InvalidData` when the payload does not satisfy its schema,
|
|
236
|
+
* or `UnserializableData` when the encoded payload cannot be turned into
|
|
237
|
+
* JSON (a bigint or a reference cycle).
|
|
210
238
|
*/
|
|
211
239
|
encodeResult: (events, envelope) => {
|
|
212
240
|
const empty = new LineSlice({
|
|
@@ -251,14 +279,18 @@ const Envelope = {
|
|
|
251
279
|
return Result.succeed(`${text}\n`);
|
|
252
280
|
},
|
|
253
281
|
/**
|
|
254
|
-
*
|
|
282
|
+
* `Envelope.decodeResult` in the `Effect` channel.
|
|
255
283
|
*
|
|
256
284
|
* Prefer the sync form in a hook or any caller that has no runtime; this
|
|
257
|
-
* exists for programs that already are one.
|
|
285
|
+
* exists for programs that already are one. Fails with `MalformedLine`,
|
|
286
|
+
* `InvalidData` or `UnknownEvent`; requires nothing.
|
|
258
287
|
*/
|
|
259
288
|
decode: (events, line) => Effect.fromResult(Envelope.decodeResult(events, line)),
|
|
260
289
|
/**
|
|
261
|
-
*
|
|
290
|
+
* `Envelope.encodeResult` in the `Effect` channel.
|
|
291
|
+
*
|
|
292
|
+
* Fails with `UnknownEvent`, `InvalidData` or `UnserializableData`; requires
|
|
293
|
+
* nothing.
|
|
262
294
|
*/
|
|
263
295
|
encode: (events, envelope) => Effect.fromResult(Envelope.encodeResult(events, envelope))
|
|
264
296
|
};
|
package/Journal.js
CHANGED
|
@@ -120,7 +120,7 @@ const makeEngine = (events, config, fs) => Effect.gen(function* () {
|
|
|
120
120
|
const latest = yield* SubscriptionRef.make(Option.none());
|
|
121
121
|
/** Refusal state. Read before the permit so a late append fails fast. */
|
|
122
122
|
let closed = false;
|
|
123
|
-
/** Logical bytes decoded so far — the resume cursor the watcher
|
|
123
|
+
/** Logical bytes decoded so far — the resume cursor the watcher advances. */
|
|
124
124
|
let consumed = 0;
|
|
125
125
|
const exists = () => fs.exists(config.path);
|
|
126
126
|
const requireFile = Effect.gen(function* () {
|
|
@@ -446,6 +446,9 @@ const makeEngine = (events, config, fs) => Effect.gen(function* () {
|
|
|
446
446
|
* one layer graph with each one's operations typed by *its* registry. This
|
|
447
447
|
* mirrors `ConfigFile.Service<Self, A>()(id)`.
|
|
448
448
|
*
|
|
449
|
+
* Pass the registry as `options.events`; the returned class exposes it as
|
|
450
|
+
* `events` and builds the scoped layer with `.layer(config)`.
|
|
451
|
+
*
|
|
449
452
|
* @example
|
|
450
453
|
* ```ts
|
|
451
454
|
* import { Journal, JsonlEvent } from "@effected/jsonl";
|
|
@@ -453,9 +456,9 @@ const makeEngine = (events, config, fs) => Effect.gen(function* () {
|
|
|
453
456
|
*
|
|
454
457
|
* const events = [JsonlEvent.make("started", { data: Schema.Void })] as const;
|
|
455
458
|
*
|
|
456
|
-
* class MailJournal extends Journal.Service<MailJournal>()("
|
|
459
|
+
* class MailJournal extends Journal.Service<MailJournal>()("app/MailJournal", { events }) {}
|
|
457
460
|
*
|
|
458
|
-
* export const layer = MailJournal.layer({ path: ".
|
|
461
|
+
* export const layer = MailJournal.layer({ path: ".app/mail.jsonl" });
|
|
459
462
|
* ```
|
|
460
463
|
*
|
|
461
464
|
* @public
|
package/JsonlError.js
CHANGED
|
@@ -3,15 +3,6 @@ import { Option, Schema } from "effect";
|
|
|
3
3
|
|
|
4
4
|
//#region src/JsonlError.ts
|
|
5
5
|
/**
|
|
6
|
-
* The `@effected/jsonl` error taxonomy.
|
|
7
|
-
*
|
|
8
|
-
* Every tag names a distinct recovery a caller would actually make, and every
|
|
9
|
-
* cause is carried structurally rather than stringified. Core's `PlatformError`
|
|
10
|
-
* passes through untranslated rather than being wrapped.
|
|
11
|
-
*
|
|
12
|
-
* @since 0.1.0
|
|
13
|
-
*/
|
|
14
|
-
/**
|
|
15
6
|
* `Schema.SchemaError` as a schema of itself.
|
|
16
7
|
*
|
|
17
8
|
* A schema issue tree is a live object graph, not something with a wire form,
|
package/Line.js
CHANGED
|
@@ -5,16 +5,6 @@ import { Option, Result, Schema } from "effect";
|
|
|
5
5
|
|
|
6
6
|
//#region src/Line.ts
|
|
7
7
|
/**
|
|
8
|
-
* The pure, synchronous line layer of a JSONL journal.
|
|
9
|
-
*
|
|
10
|
-
* Nothing here touches `FileSystem`, builds an `Effect`, or needs a runtime —
|
|
11
|
-
* a `PreToolUse` hook script reads the current state of a journal with one
|
|
12
|
-
* call. That is a contract, not a convenience: a hook that had to construct an
|
|
13
|
-
* Effect runtime to read one line would not adopt this package.
|
|
14
|
-
*
|
|
15
|
-
* @since 0.1.0
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
8
|
* A line that parsed as JSON, paired with the slice it came from.
|
|
19
9
|
*
|
|
20
10
|
* `value` is deliberately `unknown`: this layer knows JSON, not envelopes.
|
package/LineSlice.js
CHANGED
|
@@ -2,11 +2,6 @@ import { Schema } from "effect";
|
|
|
2
2
|
|
|
3
3
|
//#region src/LineSlice.ts
|
|
4
4
|
/**
|
|
5
|
-
* One candidate line of a JSONL journal, located in the source by byte.
|
|
6
|
-
*
|
|
7
|
-
* @since 0.1.0
|
|
8
|
-
*/
|
|
9
|
-
/**
|
|
10
5
|
* A single candidate line: its text, and where it lives in the source **in
|
|
11
6
|
* bytes**.
|
|
12
7
|
*
|
package/README.md
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
Append-only, schema-validated JSONL journals as a definable Effect service. The subject is not the format — one JSON value per line is a two-sentence specification and needs no library — but the file as a live object: a journal that only ever grows, whose current state is its last valid line, whose tail may be torn mid-append, and which several processes read while one writes. Every line is an envelope (`at`, `event`, an optional `scope`, and a `data` payload validated by the schema you registered for that event), so a journal stays greppable, `jq`-able and diffable in a pull request while the reads that matter stay typed.
|
|
9
9
|
|
|
10
|
-
> **Pre
|
|
11
|
-
>
|
|
12
|
-
> `1.0.0`
|
|
13
|
-
>
|
|
10
|
+
> **Pre-`1.0.0`.** This package is part of the `@effected/*` kit, built on stable
|
|
11
|
+
> Effect v4 (`effect` `^4.0.0`) and still in `0.x` development. Stable Effect
|
|
12
|
+
> makes a kit `1.0.0` possible, not automatic. To keep your `effect` and
|
|
13
|
+
> `@effect/*` versions on the line the kit is built and tested against, install
|
|
14
14
|
> [`@effected/pnpm-plugin-effect`](https://www.npmjs.com/package/@effected/pnpm-plugin-effect).
|
|
15
15
|
>
|
|
16
16
|
> **Stability: unstable.** This package's API surface is not yet considered
|
package/index.d.ts
CHANGED
|
@@ -490,6 +490,23 @@ type EnvelopeWithTag<R extends JsonlEvent.Registry, T extends string> = Extract<
|
|
|
490
490
|
* defined **in terms of** the sync ones via `Effect.fromResult`, so the two can
|
|
491
491
|
* never drift into two implementations of the same rule.
|
|
492
492
|
*
|
|
493
|
+
* @example
|
|
494
|
+
* ```ts
|
|
495
|
+
* import { Envelope, JsonlEvent, Line } from "@effected/jsonl";
|
|
496
|
+
* import { Result, Schema } from "effect";
|
|
497
|
+
*
|
|
498
|
+
* const events = [JsonlEvent.make("started", { data: Schema.Struct({ id: Schema.String }) })] as const;
|
|
499
|
+
*
|
|
500
|
+
* declare const sourceText: string;
|
|
501
|
+
*
|
|
502
|
+
* for (const line of Line.split(sourceText)) {
|
|
503
|
+
* const decoded = Envelope.decodeResult(events, line);
|
|
504
|
+
* if (Result.isSuccess(decoded) && decoded.success.event === "started") {
|
|
505
|
+
* decoded.success.data.id; // typed from the registered payload schema
|
|
506
|
+
* }
|
|
507
|
+
* }
|
|
508
|
+
* ```
|
|
509
|
+
*
|
|
493
510
|
* @public
|
|
494
511
|
*/
|
|
495
512
|
export declare const Envelope: {
|
|
@@ -501,6 +518,9 @@ export declare const Envelope: {
|
|
|
501
518
|
* filtered out never pays for its payload at all.
|
|
502
519
|
*
|
|
503
520
|
* @param line - A slice from `Line.split`.
|
|
521
|
+
* @returns The frame with its source `line` attached, or `MalformedLine`
|
|
522
|
+
* when the line is not JSON, or `InvalidData` when it is JSON that is not
|
|
523
|
+
* an envelope frame.
|
|
504
524
|
*/
|
|
505
525
|
readonly frameResult: (line: LineSlice) => Result.Result<typeof EnvelopeFrame.Type & {
|
|
506
526
|
readonly line: LineSlice;
|
|
@@ -540,6 +560,10 @@ export declare const Envelope: {
|
|
|
540
560
|
*
|
|
541
561
|
* Same contract as `Line.parseAll` one layer down: a line that is not a
|
|
542
562
|
* legal envelope is a `Result` in the array, never a gap in it.
|
|
563
|
+
*
|
|
564
|
+
* @param events - The registry to validate against.
|
|
565
|
+
* @param text - JSONL source text.
|
|
566
|
+
* @returns One `Result` per non-blank line, in file order.
|
|
543
567
|
*/
|
|
544
568
|
readonly decodeAllResult: <const R extends JsonlEvent.Registry>(events: R, text: string) => ReadonlyArray<Result.Result<EnvelopeUnion<R>, MalformedLine | InvalidData | UnknownEvent>>;
|
|
545
569
|
/**
|
|
@@ -577,6 +601,10 @@ export declare const Envelope: {
|
|
|
577
601
|
* @param events - The registry to validate against.
|
|
578
602
|
* @param envelope - The event tag, its payload, the service-assigned
|
|
579
603
|
* timestamp, and an optional scope.
|
|
604
|
+
* @returns The encoded line, or `UnknownEvent` for a tag the registry does
|
|
605
|
+
* not define, `InvalidData` when the payload does not satisfy its schema,
|
|
606
|
+
* or `UnserializableData` when the encoded payload cannot be turned into
|
|
607
|
+
* JSON (a bigint or a reference cycle).
|
|
580
608
|
*/
|
|
581
609
|
readonly encodeResult: <const R extends JsonlEvent.Registry, const T extends JsonlEvent.Tag<R>>(events: R, envelope: {
|
|
582
610
|
readonly event: T;
|
|
@@ -585,14 +613,18 @@ export declare const Envelope: {
|
|
|
585
613
|
readonly scope?: string | undefined;
|
|
586
614
|
}) => Result.Result<string, UnknownEvent | InvalidData | UnserializableData>;
|
|
587
615
|
/**
|
|
588
|
-
*
|
|
616
|
+
* `Envelope.decodeResult` in the `Effect` channel.
|
|
589
617
|
*
|
|
590
618
|
* Prefer the sync form in a hook or any caller that has no runtime; this
|
|
591
|
-
* exists for programs that already are one.
|
|
619
|
+
* exists for programs that already are one. Fails with `MalformedLine`,
|
|
620
|
+
* `InvalidData` or `UnknownEvent`; requires nothing.
|
|
592
621
|
*/
|
|
593
622
|
readonly decode: <const R extends JsonlEvent.Registry>(events: R, line: LineSlice) => Effect.Effect<EnvelopeUnion<R>, MalformedLine | InvalidData | UnknownEvent>;
|
|
594
623
|
/**
|
|
595
|
-
*
|
|
624
|
+
* `Envelope.encodeResult` in the `Effect` channel.
|
|
625
|
+
*
|
|
626
|
+
* Fails with `UnknownEvent`, `InvalidData` or `UnserializableData`; requires
|
|
627
|
+
* nothing.
|
|
596
628
|
*/
|
|
597
629
|
readonly encode: <const R extends JsonlEvent.Registry, const T extends JsonlEvent.Tag<R>>(events: R, envelope: {
|
|
598
630
|
readonly event: T;
|
|
@@ -781,8 +813,8 @@ interface JournalShape<R extends JsonlEvent.Registry> {
|
|
|
781
813
|
* Elements are `Take` chunks and end-of-stream is a published `Exit`, which
|
|
782
814
|
* is what lets quiescence and graceful shutdown arrive at a subscriber as a
|
|
783
815
|
* normal stream end. Never shut this down to signal completion —
|
|
784
|
-
* `PubSub.shutdown` *interrupts* subscribers, which
|
|
785
|
-
*
|
|
816
|
+
* `PubSub.shutdown` *interrupts* subscribers, which tears an in-flight
|
|
817
|
+
* stream instead of ending it.
|
|
786
818
|
*
|
|
787
819
|
* @internal
|
|
788
820
|
*/
|
|
@@ -871,6 +903,9 @@ interface JournalClass<Self, Id extends string, R extends JsonlEvent.Registry> e
|
|
|
871
903
|
* one layer graph with each one's operations typed by *its* registry. This
|
|
872
904
|
* mirrors `ConfigFile.Service<Self, A>()(id)`.
|
|
873
905
|
*
|
|
906
|
+
* Pass the registry as `options.events`; the returned class exposes it as
|
|
907
|
+
* `events` and builds the scoped layer with `.layer(config)`.
|
|
908
|
+
*
|
|
874
909
|
* @example
|
|
875
910
|
* ```ts
|
|
876
911
|
* import { Journal, JsonlEvent } from "@effected/jsonl";
|
|
@@ -878,9 +913,9 @@ interface JournalClass<Self, Id extends string, R extends JsonlEvent.Registry> e
|
|
|
878
913
|
*
|
|
879
914
|
* const events = [JsonlEvent.make("started", { data: Schema.Void })] as const;
|
|
880
915
|
*
|
|
881
|
-
* class MailJournal extends Journal.Service<MailJournal>()("
|
|
916
|
+
* class MailJournal extends Journal.Service<MailJournal>()("app/MailJournal", { events }) {}
|
|
882
917
|
*
|
|
883
|
-
* export const layer = MailJournal.layer({ path: ".
|
|
918
|
+
* export const layer = MailJournal.layer({ path: ".app/mail.jsonl" });
|
|
884
919
|
* ```
|
|
885
920
|
*
|
|
886
921
|
* @public
|
package/internal/merge.js
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
//#region src/internal/merge.ts
|
|
2
2
|
/**
|
|
3
|
-
* Pollution-safe shallow merge for `appendPatch`.
|
|
4
|
-
*
|
|
5
|
-
* Ported from `@effected/config-file`'s `internal/deepMerge.ts` recipe, minus
|
|
6
|
-
* the recursion: `appendPatch` is a **shallow** merge by decision, so a nested
|
|
7
|
-
* object in the patch replaces the one beneath it rather than merging into it.
|
|
8
|
-
*
|
|
9
|
-
* @internal
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
3
|
* Keys that must never be copied from either side.
|
|
13
4
|
*
|
|
14
5
|
* `__proto__` reaches `Object.prototype`'s inherited accessor; `constructor`
|
package/internal/utf8.js
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
//#region src/internal/utf8.ts
|
|
2
2
|
/**
|
|
3
|
-
* UTF-8 byte accounting.
|
|
4
|
-
*
|
|
5
|
-
* A zero-dependency leaf so that both the line splitter and the error taxonomy
|
|
6
|
-
* can measure bytes without either importing the other — the same cycle
|
|
7
|
-
* firewall the other pure-tier packages use.
|
|
8
|
-
*
|
|
9
|
-
* @internal
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
3
|
* The UTF-8 byte length of a JavaScript string.
|
|
13
4
|
*
|
|
14
5
|
* Journal offsets are **bytes**, because that is what `FileSystem.stream`'s
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/jsonl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Append-only, schema-validated JSONL journals as a definable Effect service.",
|
|
6
6
|
"keywords": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"./package.json": "./package.json"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"effect": "4.0.0
|
|
40
|
+
"effect": "^4.0.0"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=24.11.0"
|