@avytheone/efmesh 0.2.0 → 0.2.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,41 @@ versioning — [SemVer](https://semver.org/).
5
5
  Internal development history was tracked in phases F0–F6 (SPEC.md §13);
6
6
  the first version gathers them in full.
7
7
 
8
+ ## [0.2.1] — 2026-07-16
9
+
10
+ Theme: hygiene for the first stranger — the whole surface is English,
11
+ failures explain themselves, execution is visible.
12
+
13
+ - Detailed execution log (#14). `apply` and `run` now narrate their work
14
+ through Effect's logging system: per-model build start/finish with duration,
15
+ backfill batch progress (`batch n of m` with interval bounds), warn-audits
16
+ and promotion. Levels — **info** for lifecycle (visible by default), **warn**
17
+ for warn-audits/retries, **debug** for the rendered SQL and lock internals;
18
+ the existing `--log-level` flag sets the minimum. Every line carries
19
+ structured fields (`model`, `env`, `interval`) as annotations. Logs go to
20
+ **stderr** (stdout and `--json` stay byte-clean); a TTY gets pretty colored
21
+ output, a pipe/journal gets one-line logfmt with no ANSI. Embedders provide
22
+ their own `Logger` layer to redirect sinks and levels. Exit codes unchanged.
23
+ - Human-readable, precise errors everywhere (#13). Every tagged error now
24
+ derives its `message` from its typed fields, so the culprit (model, env,
25
+ file, interval) and the underlying engine/system text are always present —
26
+ an empty `EngineError:` is constructively impossible. `EngineError` carries
27
+ the failing model and the engine's own message; a failing `apply` names the
28
+ model, quotes DuckDB/Postgres verbatim, and shows the SQL context. The CLI
29
+ now renders one failure screen (cause first, an actionable hint where one
30
+ exists) and prints the Effect fiber trace only under `--log-level debug`.
31
+ Exit codes (0/1/2) and `--json` shapes are unchanged. New exported error
32
+ `UnknownModelError` (render/lineage against a name not in the project).
33
+ - The entire user-facing surface is English: CLI output and help, error
34
+ messages, `--json` string values (key names and exit codes unchanged),
35
+ the `init` scaffold, the hospital example data (#11). Source comments
36
+ and test names too (#12).
37
+ - **Breaking:** the `apply` confirmation prompt accepts only `y`/`yes`
38
+ (case-insensitive); the Cyrillic tokens are no longer recognized.
39
+ - `efmesh init` scaffold now teaches the core lifecycle: a seed feeding
40
+ an incremental-by-time-range model with a blocking audit and a full
41
+ rollup on top, runnable immediately after `init` (#11).
42
+
8
43
  ## [0.2.0] — 2026-07-16
9
44
 
10
45
  Theme: "operator and team" — efmesh in the hands of a non-author: the
package/README.md CHANGED
@@ -211,6 +211,28 @@ changes await a human.
211
211
 
212
212
  Exit codes: `0` — success, `1` — error, `2` — "awaiting a human": the plan needs confirmation in a non-TTY (add `--yes`), or `run` hit unapplied changes. In a non-TTY, `apply` with changes and no `--yes` refuses — efmesh will not silently roll out a plan nobody has seen.
213
213
 
214
+ ## Logging
215
+
216
+ `apply` and `run` narrate what they do. Logs go to **stderr** — stdout stays
217
+ reserved for the plan screen, summaries and `--json`, which stays byte-clean.
218
+ Levels, set by the built-in `--log-level` flag (minimum level, default `info`):
219
+
220
+ - **info** — lifecycle a human watches: per-model build start/finish with
221
+ duration, backfill batch progress (`batch 3 of 7` with the interval bounds),
222
+ promotion.
223
+ - **warn** — warn-audits (violations that do not block) and retries.
224
+ - **debug** — the rendered SQL about to run, lock acquire/release, and other
225
+ internals. `--log-level debug` also prints the full fiber trace on a failure.
226
+
227
+ Each line carries structured fields as annotations (`model`, `env`, `interval`,
228
+ …). At a TTY the output is pretty and colored; piped to a file or the systemd
229
+ journal it is one-line [logfmt](https://brandur.org/logfmt) with no ANSI, so a
230
+ log reader (or an AI agent post-morteming a 3am tick) can group by field.
231
+
232
+ Embedding efmesh as a library? Logging is Effect's `Effect.log*` — provide your
233
+ own `Logger` layer (sink, format, minimum level) and the CLI's choices do not
234
+ apply. Row counts are not logged: efmesh never runs an extra query just to count.
235
+
214
236
  ## Performance
215
237
 
216
238
  The framework overhead is negligible for any realistic project (in-memory DuckDB, `bun bench/plan-bench.ts N`):
package/README.ru.md CHANGED
@@ -218,6 +218,29 @@ Exit-коды: `0` — успех, `1` — ошибка, `2` — «ждёт че
218
218
  изменения. В не-TTY `apply` с изменениями БЕЗ `--yes` не применяется —
219
219
  молча катить план, который никто не видел, efmesh отказывается.
220
220
 
221
+ ## Логирование
222
+
223
+ `apply` и `run` рассказывают, что делают. Логи идут в **stderr** — stdout
224
+ остаётся под план, сводки и `--json`, который остаётся байт-чистым JSON.
225
+ Уровни задаёт встроенный флаг `--log-level` (минимальный уровень, по
226
+ умолчанию `info`):
227
+
228
+ - **info** — жизненный цикл, за которым следит человек: старт/финиш сборки
229
+ каждой модели с длительностью, прогресс бэкфила (`batch 3 of 7` с границами
230
+ интервала), промоушен.
231
+ - **warn** — warn-аудиты (нарушения, которые не блокируют) и ретраи.
232
+ - **debug** — рендер SQL перед выполнением, взятие/освобождение лока и прочие
233
+ внутренности. `--log-level debug` также печатает полный fiber-trace при сбое.
234
+
235
+ В каждой строке структурные поля-аннотации (`model`, `env`, `interval`, …). В
236
+ TTY вывод красивый и цветной; в файл или systemd journal — однострочный
237
+ [logfmt](https://brandur.org/logfmt) без ANSI, чтобы читатель логов (или
238
+ ИИ-агент, разбирающий трёхчасовой ночной тик) группировал по полям.
239
+
240
+ Встраиваете efmesh как библиотеку? Логирование — это `Effect.log*`: дайте свой
241
+ `Logger`-слой (приёмник, формат, минимальный уровень), и выбор CLI не действует.
242
+ Число строк не логируется: efmesh не делает лишний запрос ради подсчёта.
243
+
221
244
  ## Перфоманс
222
245
 
223
246
  Оверхед фреймворка пренебрежим для любого реалистичного проекта (in-memory DuckDB, `bun bench/plan-bench.ts N`):
package/SPEC.md CHANGED
@@ -220,7 +220,7 @@ on encountering a snapshot of another version, honestly stops
220
220
  how many intervals will be recomputed, a textual SQL diff.
221
221
  6. `efmesh apply` shows the same plan and applies it: creates physical
222
222
  tables, runs the backfill, swaps the views. *Implemented in F4: in a TTY the plan
223
- is applied only after an explicit "y" (y/yes/д/да; `--yes`/`-y` skips the
223
+ is applied only after an explicit "y" (y/yes, case-insensitive; `--yes`/`-y` skips the
224
224
  question), exactly the shown plan is applied without recomputation; non-TTY (CI,
225
225
  pipes) proceeds without asking.*
226
226
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avytheone/efmesh",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "sqlmesh-like data transformation framework on TypeScript, Bun and Effect",
5
5
  "license": "MIT",
6
6
  "author": "Alexey Yakimanskiy",
package/src/bin.ts CHANGED
@@ -1,12 +1,49 @@
1
1
  #!/usr/bin/env bun
2
2
  import { BunRuntime, BunServices } from "@effect/platform-bun"
3
- import { Effect } from "effect"
3
+ import { Cause, Effect, Logger } from "effect"
4
4
  import { Command } from "effect/unstable/cli"
5
- import { rootCommand } from "./cli.ts"
5
+ import { renderFailure, rootCommand, wantsTrace } from "./cli.ts"
6
6
  import { version } from "../package.json"
7
7
 
8
+ // Logging (#14) goes to STDERR so `--json` stdout stays byte-clean — a contract
9
+ // for CI and agents. A human at a stderr TTY gets colored pretty output; a pipe,
10
+ // log file or the systemd journal gets plain one-line logfmt with no ANSI, which
11
+ // the fields (model=, env=, interval=) make machine-readable. The minimum level
12
+ // is Info by default (lifecycle visible during apply/run); the CLI's built-in
13
+ // `--log-level` flag lowers it to debug (SQL, lock internals) or raises it. An
14
+ // embedder wanting different sinks/levels provides its own Logger layer instead.
15
+ const loggerLayer = Logger.layer([
16
+ process.stderr.isTTY === true
17
+ ? Logger.consolePretty({ stderr: true })
18
+ : Logger.withConsoleError(Logger.formatLogFmt),
19
+ ])
20
+
21
+ // One central failure screen (#13): catch every cause here and render it
22
+ // ourselves (cause first, one screen, fiber trace only under --log-level
23
+ // debug) instead of letting runMain dump a pretty cause over an empty message.
24
+ // Interrupts (Ctrl+C) are not failures — leave them to the default teardown.
25
+ // The exit code stays a frozen contract: a rendered failure is 1, unless a
26
+ // command already claimed 2 ("awaiting a human") before failing.
27
+ const debug = wantsTrace(process.argv)
28
+
29
+ // Interrupt with nothing failed/died — Ctrl+C during a clean wait: not a
30
+ // failure to render, hand it back to the default teardown.
31
+ const isInterruptOnly = (cause: Cause.Cause<unknown>): boolean =>
32
+ !Cause.hasFails(cause) && Cause.interruptors(cause).size > 0
33
+
8
34
  rootCommand.pipe(
9
35
  Command.run({ version }),
36
+ Effect.catchCause((cause) =>
37
+ isInterruptOnly(cause)
38
+ ? Effect.failCause(cause)
39
+ : Effect.sync(() => {
40
+ process.stderr.write(`${renderFailure(cause, { debug })}\n`)
41
+ if (process.exitCode === undefined || process.exitCode === 0) {
42
+ process.exitCode = 1
43
+ }
44
+ }),
45
+ ),
46
+ Effect.provide(loggerLayer),
10
47
  Effect.provide(BunServices.layer),
11
48
  BunRuntime.runMain,
12
49
  )