@happyvertical/smrt-app-shell 0.60.18 → 0.61.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.
Files changed (52) hide show
  1. package/AGENTS.md +51 -9
  2. package/README.md +378 -23
  3. package/agents/draft-ui.md +74 -0
  4. package/agents/engine.md +81 -0
  5. package/agents/shared-pieces.md +127 -0
  6. package/dist/engine/app-runtime.d.ts +7 -0
  7. package/dist/engine/app-runtime.js +30 -3
  8. package/dist/engine/commands.d.ts +23 -0
  9. package/dist/engine/commands.js +29 -0
  10. package/dist/engine/draft.d.ts +31 -0
  11. package/dist/engine/draft.js +88 -0
  12. package/dist/engine/host.d.ts +175 -1
  13. package/dist/engine/host.js +501 -9
  14. package/dist/engine/index.d.ts +4 -2
  15. package/dist/engine/index.js +2 -1
  16. package/dist/engine/publish.d.ts +100 -0
  17. package/dist/engine/publish.js +218 -0
  18. package/dist/engine/store.d.ts +8 -0
  19. package/dist/engine/store.js +8 -0
  20. package/dist/engine/worker-protocol.d.ts +1 -1
  21. package/dist/engine/worker-protocol.js +1 -0
  22. package/dist/lib/objects/App.d.ts +42 -1
  23. package/dist/lib/objects/App.js +72 -4
  24. package/dist/lib/objects/AppDraft.d.ts +31 -0
  25. package/dist/lib/objects/AppDraft.js +73 -0
  26. package/dist/lib/objects/AppDraftVersion.d.ts +25 -0
  27. package/dist/lib/objects/AppDraftVersion.js +68 -0
  28. package/dist/lib/objects/Approval.d.ts +4 -4
  29. package/dist/lib/objects/Approval.js +3 -3
  30. package/dist/lib/objects/index.d.ts +2 -0
  31. package/dist/lib/objects/index.js +2 -0
  32. package/dist/lib/server/approval-store.d.ts +6 -0
  33. package/dist/lib/server/approval-store.js +102 -0
  34. package/dist/lib/server/approvals.d.ts +15 -0
  35. package/dist/lib/server/approvals.js +62 -43
  36. package/dist/lib/server/builder.d.ts +18 -10
  37. package/dist/lib/server/builder.js +46 -18
  38. package/dist/lib/server/drafts.d.ts +100 -0
  39. package/dist/lib/server/drafts.js +324 -0
  40. package/dist/lib/server/events.d.ts +14 -0
  41. package/dist/lib/server/events.js +3 -0
  42. package/dist/lib/server/host.d.ts +44 -2
  43. package/dist/lib/server/host.js +155 -11
  44. package/dist/lib/server/results.d.ts +6 -0
  45. package/dist/lib/server/results.js +8 -0
  46. package/dist/lib/server/target.d.ts +24 -0
  47. package/dist/lib/server/target.js +64 -0
  48. package/dist/lib/server/version-store.d.ts +40 -1
  49. package/dist/lib/server/version-store.js +144 -32
  50. package/dist/manifest.json +699 -6
  51. package/dist/smrt-knowledge.okf.md +19 -2
  52. package/package.json +19 -15
@@ -1,12 +1,96 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import { type Command, type ExecuteOptions, type Plan, type Template } from './commands.ts';
3
- import { type AppDefinition } from './definition.ts';
3
+ import { type AppDefinition, type Step } from './definition.ts';
4
+ import { type DraftMarker } from './draft.ts';
5
+ import { type DefinitionDiff, type DraftChain, type PublishMethod } from './publish.ts';
4
6
  import type { VersionStore } from './store.ts';
5
7
  type Row = Record<string, unknown>;
6
8
  export interface HostOptions {
7
9
  store: VersionStore;
8
10
  /** The worker entry. Defaults to `./app-worker.ts` or `.js` next to this module. */
9
11
  workerUrl?: URL | string;
12
+ /**
13
+ * Start frozen: every structural command is refused (see
14
+ * {@link AppHost.setFrozen}). Records, reports, the app's own tools and REST
15
+ * routes, and `exportTemplate` keep working.
16
+ */
17
+ frozen?: boolean;
18
+ /**
19
+ * Approve risky commands, `_delete` tools and REST `DELETE` without being
20
+ * asked. For a DRAFT host only: `open` refuses it unless `dataDir` is a draft
21
+ * directory made by `forkDraft` (it holds the engine's marker, naming a
22
+ * different live directory). It is read once, here, into a private field:
23
+ * there is no setter, so it cannot be turned on later or for the live host.
24
+ */
25
+ autoApprove?: boolean;
26
+ }
27
+ export interface ForkDraftOptions {
28
+ /**
29
+ * The new draft's version store, EMPTY. `forkDraft` seeds it with a copy of
30
+ * the live current version (the draft's BASE record, same version number)
31
+ * and points `current` at it; every draft version after that has a higher
32
+ * number. The base record is not a draft version.
33
+ */
34
+ store: VersionStore;
35
+ }
36
+ /** Where a publish is, for `PublishOptions.onStage` (tests inject failures there). */
37
+ export type PublishStage = 'copied' | 'rehearsed' | 'snapshotted' | 'journaled' | 'retired' | 'moved-aside' | 'swapped' | 'respawned' | 'committed';
38
+ export interface PublishVersionPreview {
39
+ /** The number this version gets in live. */
40
+ version: number;
41
+ /** Its number in the draft. */
42
+ draftVersion: number;
43
+ author: string;
44
+ summary: string;
45
+ steps: Step[];
46
+ /** The SQL the rehearsal ran for it. */
47
+ statements: string[];
48
+ }
49
+ /** What a publish would do, from a rehearsal on a fresh copy of the current live data. */
50
+ export interface PublishPreview {
51
+ /** Pass this as `PublishOptions.expect`; publish refuses if the draft's chain no longer matches. */
52
+ digest: string;
53
+ risky: true;
54
+ fromVersion: number;
55
+ toVersion: number;
56
+ versions: PublishVersionPreview[];
57
+ /** Live's current definition against the draft's final one. */
58
+ definition: DefinitionDiff;
59
+ /** Archived values and tables the changes create (what undo restores from). */
60
+ archives: {
61
+ values: number;
62
+ tables: number;
63
+ };
64
+ /** EVERY method of the final definition, in full, marked added/changed/unchanged. */
65
+ methods: PublishMethod[];
66
+ }
67
+ export interface PublishOptions {
68
+ author: string;
69
+ /** Required: a publish is always risky. Only the shell's approval sets it. */
70
+ approved?: boolean;
71
+ /** The `digest` of the preview the owner saw. */
72
+ expect: string;
73
+ /** Tags the history summaries: "[draft: <name>] <summary>". */
74
+ draftName?: string;
75
+ /**
76
+ * Called after each stage; throwing aborts and rolls back as if that stage
77
+ * had failed. For tests. After `committed` the publish is final: errors are ignored.
78
+ */
79
+ onStage?: (stage: PublishStage) => void | Promise<void>;
80
+ }
81
+ export interface PublishResult {
82
+ /** The version live is on now. */
83
+ version: number;
84
+ /** The versions the publish added, oldest first. */
85
+ versions: number[];
86
+ summary: string;
87
+ /** The retained copy of the data as it was before (snapshots/…-migrate-prepublish.db). */
88
+ snapshot: string;
89
+ archives: {
90
+ values: number;
91
+ tables: number;
92
+ };
93
+ appRestartMs: number;
10
94
  }
11
95
  export interface ChangeResult {
12
96
  version: number;
@@ -53,6 +137,7 @@ export interface VersionSummary {
53
137
  current: boolean;
54
138
  }
55
139
  export declare class AppHost extends EventEmitter {
140
+ #private;
56
141
  def: AppDefinition;
57
142
  version: number;
58
143
  readonly dataDir: string;
@@ -61,6 +146,14 @@ export declare class AppHost extends EventEmitter {
61
146
  private workerUrl;
62
147
  private app;
63
148
  private closed;
149
+ private frozenNow;
150
+ /** What boot did about an interrupted publish: `rolled-back` put the old data back. */
151
+ recovery: 'none' | 'rolled-forward' | 'rolled-back';
152
+ /** Set when this host is a draft copy (its directory holds the draft marker). */
153
+ readonly draft: DraftMarker | null;
154
+ /** Record/tool/REST calls currently inside the worker (see {@link drainInflight}). */
155
+ private inflight;
156
+ private idleWaiters;
64
157
  /** Changes are applied one at a time. Work inside the queue must not wait on it. */
65
158
  private queue;
66
159
  private constructor();
@@ -84,6 +177,15 @@ export declare class AppHost extends EventEmitter {
84
177
  private applyChange;
85
178
  /** What a command would do, without changing anything. */
86
179
  plan(cmd: Command): Promise<Plan>;
180
+ /** True while structural commands are refused. */
181
+ get frozen(): boolean;
182
+ /**
183
+ * Freeze or thaw the host. Frozen, every command in `STRUCTURAL_COMMANDS`
184
+ * (and `plan` of it) is refused with code `refused`; nothing about records
185
+ * changes. The shell freezes the live host while a draft is open.
186
+ */
187
+ setFrozen(frozen: boolean): void;
188
+ private refuseIfFrozen;
87
189
  /**
88
190
  * Apply a command. A risky one throws {@link ApprovalRequiredError} unless
89
191
  * `approved` is true, and then nothing has changed.
@@ -98,6 +200,13 @@ export declare class AppHost extends EventEmitter {
98
200
  private analyse;
99
201
  private assertValid;
100
202
  private analyseUndo;
203
+ /**
204
+ * Undo back to `target`: the same step as `undo`, repeated newest first, as
205
+ * one command with one decision. Each step is its own consistent state (its
206
+ * own swap and store write), so a failure part-way leaves the app at the
207
+ * version it had reached, and says so.
208
+ */
209
+ private analyseUndoTo;
101
210
  /** The model and field stored in `table.column` under `def`. */
102
211
  private locate;
103
212
  /** The chain from the current version back to the root, newest first. */
@@ -108,6 +217,71 @@ export declare class AppHost extends EventEmitter {
108
217
  /** Talk to the app. Callers inside the change queue use this; others use onApp. */
109
218
  private callApp;
110
219
  private onApp;
220
+ /**
221
+ * Wait for the calls already inside the worker to finish. Used inside the
222
+ * change queue, where no new call can start (see {@link onApp}), before a
223
+ * consistent copy of the data is taken.
224
+ */
225
+ private drainInflight;
226
+ /**
227
+ * Start a draft: copy the live data into `draftDir` (a directory that must
228
+ * not exist yet; this call creates it) and return a second host over the
229
+ * copy, with `autoApprove` on. The copy is a `VACUUM INTO` taken in the
230
+ * change queue with no call in flight, so it is one consistent state; live is
231
+ * only read. If anything fails, the directory this call created is removed.
232
+ * Only an ordinary (live) host can fork. Live may keep serving records while
233
+ * the draft is open; the caller freezes live structure with `setFrozen`.
234
+ */
235
+ forkDraft(draftDir: string, opts: ForkDraftOptions): Promise<AppHost>;
236
+ /**
237
+ * Throw this draft away: stop its app and delete its directory (only a
238
+ * directory holding the draft marker is ever deleted). The caller discards
239
+ * the draft's version store.
240
+ */
241
+ discard(): Promise<void>;
242
+ /**
243
+ * This draft's versions since its base, oldest first, with the base record.
244
+ * What a publish takes from the draft.
245
+ */
246
+ draftChain(): Promise<DraftChain>;
247
+ /** Checks shared by preview and publish; runs inside the change queue. */
248
+ private publishInputs;
249
+ /** The version numbers a publish of `chain` would give: after everything live's store holds. */
250
+ private nextNumbers;
251
+ /**
252
+ * Replay the draft's versions, in order, onto the copy of live in `dir`: one
253
+ * short worker per version, with that version's stored definition and steps
254
+ * and the number it will have in live (archives are keyed by it). Ends by
255
+ * writing the result as one standalone file, `dir/final/app.db`. Touches
256
+ * nothing but `dir`.
257
+ */
258
+ private replay;
259
+ /**
260
+ * What publishing `draft` would do: the draft's versions replayed on a fresh
261
+ * copy of the CURRENT live data (the live app is not touched and only waits
262
+ * while the copy is taken). `digest` pins the answer for `publishDraft`.
263
+ */
264
+ previewPublish(draft: AppHost): Promise<PublishPreview>;
265
+ /**
266
+ * Publish `draft` into live. Always needs `approved: true` and the `expect`
267
+ * digest of a preview; refuses if the draft's chain, or the app it started
268
+ * from, is not what that preview was of.
269
+ *
270
+ * It rehearses on a fresh copy of the current live data (so records added
271
+ * since the fork are kept and draft records are not), then swaps the file:
272
+ * the old data is first copied to snapshots/<ts>-0-migrate-prepublish.db,
273
+ * live's files are renamed aside and the rehearsed file renamed into place.
274
+ * The versions land in live as N ordinary versions (numbers after live's
275
+ * highest, summaries tagged with `draftName`), so `undo` / `undoTo` work.
276
+ * Any failure before the store is written puts the old files back (live's
277
+ * `app.db` is byte-for-byte what it was) and leaves the version unchanged.
278
+ * Live records wait (not fail) while this runs. The caller discards the draft.
279
+ */
280
+ publishDraft(draft: AppHost, opts: PublishOptions): Promise<PublishResult>;
281
+ private persistVersions;
282
+ private doPublish;
283
+ /** Undo a failed publish: put live back exactly as it was and start it again. */
284
+ private abortPublish;
111
285
  list(model: string, limit?: number): Promise<Row[]>;
112
286
  get(model: string, id: string): Promise<Row>;
113
287
  create(model: string, data: Row): Promise<Row>;