@ccmsg/cli 0.5.1 → 0.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ccmsg/cli",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "The ccmsg daemon, CLI and agent plugins for one instance (= one config home)",
5
5
  "license": "MIT",
6
6
  "author": "kawaz",
@@ -13,4 +13,4 @@ export {
13
13
  } from "./fold.ts";
14
14
  export { READ_LIMIT, readSlice } from "./read.ts";
15
15
  export { type Appended, FOLD_TAIL_BYTES, TranscriptTail } from "./tail.ts";
16
- export { Transcripts, type TranscriptsDeps } from "./transcripts.ts";
16
+ export { ITEMS_SNAPSHOT, Transcripts, type TranscriptsDeps } from "./transcripts.ts";
@@ -135,8 +135,12 @@ export class Classification {
135
135
  read(record: Row, source: { offset: number; bytes: number }): void {
136
136
  const type = str(record["type"]);
137
137
  if (type === undefined || NOT_ITEMS.has(type)) return;
138
- const uuid = str(record["uuid"]) ?? "";
139
- if (uuid === "") return;
138
+ // A record the harness wrote without an id of its own still happened, and
139
+ // an item is pointed at by the record it came from — so where the record
140
+ // stands in the file stands in for the id it lacks. The `@` says which of
141
+ // the two it is, since a reader that groups by record must not take an
142
+ // address for something the harness will write again.
143
+ const uuid = str(record["uuid"]) ?? `@${String(source.offset)}`;
140
144
  const at = instant(record["timestamp"]);
141
145
  // Where in its record an item stood. One record becomes the thinking, the
142
146
  // words and each call of a turn, and a link that named only the record
@@ -1,4 +1,4 @@
1
- import { type FSWatcher, statSync, watch } from "node:fs";
1
+ import { closeSync, type FSWatcher, openSync, readSync, statSync, watch } from "node:fs";
2
2
  import { open, stat } from "node:fs/promises";
3
3
  import { CONFIRM_POLL_MS } from "../sessions/harness.ts";
4
4
 
@@ -86,11 +86,16 @@ export class TranscriptTail {
86
86
  }
87
87
 
88
88
  /** Begin following, seeding the fold from the end of what is already there.
89
- * Resolves once the seed has been read, so a snapshot taken after it states
90
- * a size the fold has caught up with. */
89
+ *
90
+ * The seed is read before this returns rather than awaited, for the reason
91
+ * the size is read in the constructor: a subscription's snapshot is answered
92
+ * in the same turn the tail is started, and what the seed settles — the
93
+ * fold's values, and the items a subscriber opens on — would otherwise be
94
+ * stated as empty and the whole existing end of the file would arrive later
95
+ * as though it had just been appended. */
91
96
  async start(): Promise<void> {
92
97
  if (this.running) return;
93
- await this.#seed();
98
+ this.#seed();
94
99
  try {
95
100
  this.#watcher = watch(this.path, () => void this.refresh());
96
101
  } catch {
@@ -116,11 +121,11 @@ export class TranscriptTail {
116
121
  return this.#reading;
117
122
  }
118
123
 
119
- async #seed(): Promise<void> {
124
+ #seed(): void {
120
125
  const size = this.#size;
121
126
  if (size === 0) return;
122
127
  const from = Math.max(0, size - FOLD_TAIL_BYTES);
123
- const complete = whole(await this.#slice(from, size));
128
+ const complete = whole(this.#sliceSync(from, size));
124
129
  this.#offset = from + complete.byteLength;
125
130
  // The first line is half a record whenever the read began mid-file, so it
126
131
  // is dropped: what is read are whole records or nothing.
@@ -164,6 +169,26 @@ export class TranscriptTail {
164
169
  * arithmetic puts it, inside a character as readily as before one, and
165
170
  * decoding first would turn those bytes into a replacement character of a
166
171
  * different length and move every offset derived from it. */
172
+ /** The same range, read without yielding, which is what the seed is read
173
+ * through: the turn that starts a tail is the turn that answers a
174
+ * subscription, and it has to hold the end of the file by then. Bounded by
175
+ * `FOLD_TAIL_BYTES` however large the transcript is. */
176
+ #sliceSync(from: number, to: number): Buffer {
177
+ if (to <= from) return Buffer.alloc(0);
178
+ let handle: number;
179
+ try {
180
+ handle = openSync(this.path, "r");
181
+ } catch {
182
+ return Buffer.alloc(0);
183
+ }
184
+ try {
185
+ const buffer = Buffer.alloc(to - from);
186
+ return buffer.subarray(0, readSync(handle, buffer, 0, buffer.length, from));
187
+ } finally {
188
+ closeSync(handle);
189
+ }
190
+ }
191
+
167
192
  async #slice(from: number, to: number): Promise<Buffer> {
168
193
  if (to <= from) return Buffer.alloc(0);
169
194
  const handle = await open(this.path, "r").catch(() => undefined);