@omgbase/core 0.15.0 → 0.19.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.
@@ -7,112 +7,30 @@ import { ChangesetPlanner } from "./apply.js";
7
7
  import { commitChangeset, readDocBase } from "./commit.js";
8
8
  import { resolveDocStore } from "./doc-store.js";
9
9
  import { parseNewDoc } from "./docs.js";
10
- import { ConflictTracker, SymbolTable, isNewSymbol, lowerEffect, symbolicMinter, validateEffects, effectInvalid, } from "./effects.js";
10
+ import { ConflictTracker, SymbolTable, isNewSymbol, lowerEffect, symbolicMinter, effectInvalid, } from "./effects.js";
11
+ import { compilePlain, compileSelection, selectionInvalid } from "./select.js";
11
12
  import { MutationError } from "./tree.js";
13
+ import { canonicalJson, fixRequest, rootOfId, narrowedTo } from "./canonical.js";
12
14
  // The Plan (spec/mutate §11.4–§11.7, 2.3): the frozen, executable, digest-named
13
15
  // result of lowering an ordered effect list. `plan` writes nothing; `execute`
14
16
  // commits exactly a plan — its preconditions re-validated inside the
15
17
  // transaction by `commit.ts` — and `apply({ effects })` is plan + execute under
16
18
  // one writer lock and one transaction. New identities are symbolic (`$new.n`,
17
19
  // minted at execute, mapped in the receipt); a plan's identity is the sha256 of
18
- // the canonical JSON of its executable core.
20
+ // the canonical JSON of its executable core. Since 3.1 (§11.8) a request may
21
+ // carry an OQX `select` whose hits, bound to the `effects` as templates, ARE
22
+ // the effects (`select.ts`); the frozen `selection` is the core's seventh member.
19
23
  export const PLAN_VERSION = 1;
20
24
  /** The lowering + conflict rules version (§11.4; the `matcher_v` precedent). */
21
- export const PLANNER_V = "p2";
25
+ export const PLANNER_V = "p3";
22
26
  // ---- canonical JSON and the digest (§11.5) ----------------------------------------------
23
- function cmpUtf8(a, b) {
24
- return Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
25
- }
26
- // A lone surrogate has no UTF-8 encoding: it cannot be digested (nor sent as JSON text).
27
- function isWellFormed(s) {
28
- for (let i = 0; i < s.length; i++) {
29
- const c = s.charCodeAt(i);
30
- if (c >= 0xd800 && c <= 0xdbff) {
31
- const n = s.charCodeAt(i + 1);
32
- if (!(n >= 0xdc00 && n <= 0xdfff))
33
- return false;
34
- i++;
35
- }
36
- else if (c >= 0xdc00 && c <= 0xdfff)
37
- return false;
38
- }
39
- return true;
40
- }
41
- function canonicalString(s) {
42
- if (!isWellFormed(s))
43
- throw effectInvalid("strings must be well-formed Unicode (a lone surrogate cannot be digested)");
44
- let out = '"';
45
- for (let i = 0; i < s.length; i++) {
46
- const c = s.charCodeAt(i);
47
- switch (c) {
48
- case 0x22:
49
- out += '\\"';
50
- break;
51
- case 0x5c:
52
- out += "\\\\";
53
- break;
54
- case 0x08:
55
- out += "\\b";
56
- break;
57
- case 0x0c:
58
- out += "\\f";
59
- break;
60
- case 0x0a:
61
- out += "\\n";
62
- break;
63
- case 0x0d:
64
- out += "\\r";
65
- break;
66
- case 0x09:
67
- out += "\\t";
68
- break;
69
- default:
70
- out += c < 0x20 ? `\\u${c.toString(16).padStart(4, "0")}` : s[i];
71
- }
72
- }
73
- return out + '"';
74
- }
75
- function canonicalNumber(n) {
76
- if (!Number.isFinite(n))
77
- throw effectInvalid("a number in a plan must be finite");
78
- if (Number.isInteger(n) && Math.abs(n) < 2 ** 53)
79
- return String(n === 0 ? 0 : n);
80
- return String(n);
81
- }
82
- /**
83
- * Canonical JSON (§11.5): no whitespace; object keys sorted by their UTF-8
84
- * bytes; members whose value is `undefined` omitted (an optional key absent is
85
- * absent, never `null`; a JSON `null` value stays `null`); strings escaped only
86
- * as JSON requires — `"`, `\`, and U+0000–U+001F (`\b \f \n \r \t`, else
87
- * `\u00xx` lower-case) — everything else literal UTF-8; a number that is
88
- * integral with |n| < 2^53 printed as a plain integer (`-0` as `0`), any other
89
- * finite number by ECMAScript `Number::toString` (shortest round-trip digits);
90
- * `true`/`false`/`null`.
91
- */
92
- export function canonicalJson(v) {
93
- if (v === null)
94
- return "null";
95
- switch (typeof v) {
96
- case "boolean":
97
- return v ? "true" : "false";
98
- case "number":
99
- return canonicalNumber(v);
100
- case "string":
101
- return canonicalString(v);
102
- case "object": {
103
- if (Array.isArray(v))
104
- return `[${v.map((x) => canonicalJson(x === undefined ? null : x)).join(",")}]`;
105
- const o = v;
106
- const keys = Object.keys(o).filter((k) => o[k] !== undefined).sort(cmpUtf8);
107
- return `{${keys.map((k) => `${canonicalString(k)}:${canonicalJson(o[k])}`).join(",")}}`;
108
- }
109
- default:
110
- throw effectInvalid(`a ${typeof v} cannot be digested`);
111
- }
112
- }
113
- /** The executable core of a plan — the six digested keys, nothing else. */
27
+ export { canonicalJson } from "./canonical.js";
28
+ /** The executable core of a plan — the six digested keys (seven with `selection`), nothing else. */
114
29
  export function planCore(p) {
115
- return { version: p.version, planner_v: p.planner_v, effects: p.effects, opset: p.opset, preconditions: p.preconditions, new_ids: p.new_ids };
30
+ return {
31
+ version: p.version, planner_v: p.planner_v, effects: p.effects, opset: p.opset, preconditions: p.preconditions, new_ids: p.new_ids,
32
+ ...(p.selection !== undefined ? { selection: p.selection } : {}),
33
+ };
116
34
  }
117
35
  /** `plan_id = hex(sha256(canonical(core)))` (§11.5). */
118
36
  export function planDigest(core) {
@@ -141,7 +59,28 @@ export function kernelOp(o) {
141
59
  * freeze the preconditions, digest the core. Reads only; mints no real id.
142
60
  */
143
61
  export function planEffects(store, req) {
144
- const effects = validateEffects(req.effects);
62
+ // A selector request (§11.8): the templates compile into the concrete effects
63
+ // — hits × templates — and the frozen `selection` joins the core; an empty
64
+ // selection is an empty plan (the non-empty rule of §11.1 is the templates').
65
+ let effects;
66
+ let selection;
67
+ let templates;
68
+ let origins = [];
69
+ if (req.select !== undefined || req.expect !== undefined) {
70
+ if (req.select === undefined)
71
+ throw selectionInvalid("`expect` bounds a selection — pass `select` with it");
72
+ const compiled = compileSelection(store, req.repoId, req.select, req.expect, req.effects);
73
+ effects = compiled.effects;
74
+ selection = compiled.selection;
75
+ origins = compiled.origins;
76
+ }
77
+ else {
78
+ // (3.2) the `-> ` sugar and expressions over each effect's own target row; without any, the shape check alone.
79
+ const compiled = compilePlain(store, req.repoId, req.effects);
80
+ effects = compiled.effects;
81
+ templates = compiled.templates;
82
+ origins = compiled.origins;
83
+ }
145
84
  const docStore = resolveDocStore(req);
146
85
  const symbols = new SymbolTable();
147
86
  return withIdMinter(symbolicMinter(symbols), () => {
@@ -150,16 +89,27 @@ export function planEffects(store, req) {
150
89
  store, repoId: req.repoId, docStore, planner, symbols,
151
90
  blockSymbols: new Map(), creates: [], identity: new Map(), retargets: [], moves: [], tombstones: [], heads: new Map(), ops: [],
152
91
  pins: new Map(), parents: [], absentPaths: [], docBases: new Map(),
153
- tracker: new ConflictTracker(canonicalJson), canonical: canonicalJson, resolved: [],
92
+ tracker: new ConflictTracker(canonicalJson), canonical: canonicalJson, resolved: [], summary: [],
154
93
  };
155
94
  effects.forEach((e, i) => {
156
95
  try {
157
96
  lowerEffect(ctx, i, e);
158
97
  }
159
98
  catch (err) {
160
- // Every failure names the effect it arose from (the kernel's own data — `op_index`, `block` — stays).
161
- if (err instanceof MutationError && err.data.effect_index === undefined)
162
- err.data = { ...err.data, effect_index: i };
99
+ // Every failure names the effect it arose from (the kernel's own data — `op_index`, `block` — stays);
100
+ // an expanded effect's names its template and the hit it was bound to as well (§11.8).
101
+ if (err instanceof MutationError) {
102
+ if (err.data.effect_index === undefined)
103
+ err.data = { ...err.data, effect_index: i };
104
+ const origin = origins[i];
105
+ if (origin !== undefined && err.data.row === undefined) {
106
+ err.data = { ...err.data, ...(origin.template_index !== undefined ? { template_index: origin.template_index } : {}), row: origin.row };
107
+ }
108
+ // (3.3) the corrected example, built from the request as given (§8 "fix").
109
+ const fix = planningFix(err, i, req, origins, symbols);
110
+ if (fix !== undefined && err.data.fix === undefined)
111
+ err.data = { ...err.data, fix };
112
+ }
163
113
  throw err;
164
114
  }
165
115
  });
@@ -182,7 +132,7 @@ export function planEffects(store, req) {
182
132
  parents: ctx.parents,
183
133
  absent_paths: ctx.absentPaths,
184
134
  };
185
- const core = { version: PLAN_VERSION, planner_v: PLANNER_V, effects: ctx.resolved, opset, preconditions, new_ids: symbols.entries };
135
+ const core = { version: PLAN_VERSION, planner_v: PLANNER_V, effects: ctx.resolved, opset, preconditions, new_ids: symbols.entries, ...(selection !== undefined ? { selection } : {}) };
186
136
  const plan_id = planDigest(core);
187
137
  // The preview (not digested): the kernel results, every revision the commit
188
138
  // would record — a created document's under its symbol, from its adopted
@@ -205,14 +155,73 @@ export function planEffects(store, req) {
205
155
  if (moved)
206
156
  r.path = moved.to;
207
157
  }
208
- const preview = { results: changeset.results, revisions, diffs };
158
+ const preview = { results: changeset.results, revisions, diffs, summary: ctx.summary };
209
159
  if (ctx.identity.size > 0)
210
160
  preview.identity = Object.fromEntries(ctx.identity);
211
161
  if (ctx.retargets.length > 0)
212
162
  preview.retarget = ctx.retargets;
213
- return { ...core, plan_id, diagnostics: ctx.tracker.diagnostics, preview };
163
+ return { ...core, plan_id, diagnostics: ctx.tracker.diagnostics, ...(templates !== undefined ? { templates } : {}), preview };
214
164
  });
215
165
  }
166
+ // ---- the corrected examples of planning errors (§8 "fix", 3.3) ------------------------------
167
+ /** The request as the caller gave it, for a `fix`: `select`/`expect` only when present. */
168
+ function requestOf(req) {
169
+ return { effects: req.effects, ...(req.select !== undefined ? { select: req.select } : {}), ...(req.expect !== undefined ? { expect: req.expect } : {}) };
170
+ }
171
+ /**
172
+ * `conflicting_effects`: keep the later effect — the request minus the earlier
173
+ * one; under a selector, a cross-hit collision narrows the selector to the
174
+ * later hit (`from <root> where id == …`, `expect.max: 1`) and a same-hit
175
+ * collision drops the earlier template. `stale_plan` at plan time (an
176
+ * `expect.revision` that no longer holds): the same request with the current
177
+ * revision in that guard. `target_missing { symbol }` for a `$new.n` no earlier
178
+ * effect created: the next `create` of a plain request moved before the effect
179
+ * that uses it, the symbol renamed to the one that create will allocate.
180
+ */
181
+ function planningFix(err, i, req, origins, symbols) {
182
+ const effects = Array.isArray(req.effects) ? req.effects : null;
183
+ if (effects === null)
184
+ return undefined;
185
+ const selector = req.select !== undefined;
186
+ if (err.code === "conflicting_effects" && Array.isArray(err.data.effects)) {
187
+ const [a, b] = err.data.effects;
188
+ if (!selector)
189
+ return fixRequest({ effects: effects.filter((_, k) => k !== a) });
190
+ const oa = origins[a];
191
+ const ob = origins[b];
192
+ if (oa === undefined || ob === undefined || oa.template_index === undefined)
193
+ return undefined;
194
+ if (oa.row.id !== ob.row.id)
195
+ return fixRequest({ select: narrowedTo(rootOfId(ob.row.id), ob.row.id), effects, expect: { max: 1 } });
196
+ return fixRequest({ ...requestOf(req), effects: effects.filter((_, k) => k !== oa.template_index) });
197
+ }
198
+ if (err.code === "stale_plan" && isRecord(err.data.current) && typeof err.data.current.revision === "string") {
199
+ const t = selector ? origins[i]?.template_index : i;
200
+ if (t === undefined || !isRecord(effects[t]))
201
+ return undefined;
202
+ const e = effects[t];
203
+ const expect = { ...(isRecord(e.expect) ? e.expect : {}), revision: err.data.current.revision };
204
+ return fixRequest({ ...requestOf(req), effects: effects.map((x, k) => (k === t ? { ...e, expect } : x)) });
205
+ }
206
+ if (err.code === "target_missing" && typeof err.data.symbol === "string" && !selector && symbols.kindOf(err.data.symbol) === undefined) {
207
+ const c = effects.findIndex((e, k) => k > i && isRecord(e) && e.op === "create");
208
+ if (c < 0)
209
+ return undefined;
210
+ const symbol = `$new.${symbols.entries.length}`;
211
+ const reordered = effects.filter((_, k) => k !== c);
212
+ reordered.splice(i, 0, effects[c]);
213
+ return fixRequest({ effects: reordered.map((e, k) => (k === i + 1 ? substituteSymbols(e, { [err.data.symbol]: symbol }) : e)) });
214
+ }
215
+ return undefined;
216
+ }
217
+ /** The `fix` of a plan that cannot be executed as it stands (`stale_plan`, `plan_mismatch`): re-plan and commit in one call — `apply` over the plan's selector form when it has one, else over its concrete effects (none when the plan has no effects: `apply` takes at least one). */
218
+ function replanFix(plan) {
219
+ if (plan.selection !== undefined) {
220
+ const sel = plan.selection;
221
+ return fixRequest({ select: sel.query, effects: sel.templates, expect: sel.expect ?? { max: sel.cardinality } });
222
+ }
223
+ return Array.isArray(plan.effects) && plan.effects.length > 0 ? fixRequest({ effects: plan.effects }) : undefined;
224
+ }
216
225
  function isRecord(v) {
217
226
  return typeof v === "object" && v !== null && !Array.isArray(v);
218
227
  }
@@ -256,12 +265,24 @@ export function admitPlan(input) {
256
265
  }
257
266
  if (!Array.isArray(input.new_ids))
258
267
  throw effectInvalid("`plan.new_ids` must be an array");
268
+ if (input.selection !== undefined) {
269
+ // (3.1) the optional seventh member: its shape, so the digest below covers what it says.
270
+ const sel = input.selection;
271
+ if (!isRecord(sel) || typeof sel.query !== "string" || !Array.isArray(sel.templates) || typeof sel.cardinality !== "number" || !Array.isArray(sel.identities)) {
272
+ throw effectInvalid("`plan.selection` is { query, expect?, templates, cardinality, identities }");
273
+ }
274
+ if (sel.expect !== undefined && (!isRecord(sel.expect) || typeof sel.expect.max !== "number"))
275
+ throw effectInvalid("`plan.selection.expect` is { max }");
276
+ }
259
277
  if (typeof input.plan_id !== "string")
260
278
  throw effectInvalid("`plan.plan_id` must be a string");
279
+ // (3.2) the written effects ride outside the digest, like `diagnostics`; their shape is all that is checked.
280
+ if (input.templates !== undefined && !Array.isArray(input.templates))
281
+ throw effectInvalid("`plan.templates` must be an array");
261
282
  const plan = input;
262
283
  const actual = planDigest(plan);
263
284
  if (actual !== plan.plan_id) {
264
- throw new MutationError("plan_mismatch", "the plan's digest does not match its plan_id; it was edited or produced by a different planner — re-plan", { expected: plan.plan_id, actual });
285
+ throw new MutationError("plan_mismatch", "the plan's digest does not match its plan_id; it was edited or produced by a different planner — re-plan", { expected: plan.plan_id, actual, fix: replanFix(plan) });
265
286
  }
266
287
  return plan;
267
288
  }
@@ -271,13 +292,17 @@ export function executePlan(store, req) {
271
292
  const docStore = resolveDocStore(req);
272
293
  return underLock(req, () => executeAdmitted(store, req, plan, docStore));
273
294
  }
274
- /** `execute { plan_id, effects }` (§11.7): re-plan; the digest must equal `plan_id`, else `plan_mismatch`. */
295
+ /** The selector half of a request, threaded to the planner as given (absent keys stay absent). */
296
+ function selectorOf(req) {
297
+ return { ...(req.select !== undefined ? { select: req.select } : {}), ...(req.expect !== undefined ? { expect: req.expect } : {}) };
298
+ }
299
+ /** `execute { plan_id, effects, select?, expect? }` (§11.7): re-plan (with the selector when one is given, §11.8); the digest must equal `plan_id`, else `plan_mismatch`. */
275
300
  export function executeByDigest(store, req) {
276
301
  const docStore = resolveDocStore(req);
277
302
  return underLock(req, () => {
278
- const plan = planEffects(store, { repoId: req.repoId, docStore, ...(req.omgbaseDir !== undefined ? { omgbaseDir: req.omgbaseDir } : {}), effects: req.effects });
303
+ const plan = planEffects(store, { repoId: req.repoId, docStore, ...(req.omgbaseDir !== undefined ? { omgbaseDir: req.omgbaseDir } : {}), effects: req.effects, ...selectorOf(req) });
279
304
  if (plan.plan_id !== req.planId) {
280
- throw new MutationError("plan_mismatch", "the effects or the documents they target changed since the plan was made; re-plan", { expected: req.planId, actual: plan.plan_id });
305
+ throw new MutationError("plan_mismatch", "the effects or the documents they target changed since the plan was made; re-plan", { expected: req.planId, actual: plan.plan_id, fix: fixRequest(requestOf(req)) });
281
306
  }
282
307
  return executeAdmitted(store, req, plan, docStore);
283
308
  });
@@ -291,15 +316,23 @@ export function executeByDigest(store, req) {
291
316
  */
292
317
  export function applyEffectsDetailed(store, req) {
293
318
  const docStore = resolveDocStore(req);
294
- const planIt = () => planEffects(store, { repoId: req.repoId, docStore, ...(req.omgbaseDir !== undefined ? { omgbaseDir: req.omgbaseDir } : {}), effects: req.effects });
319
+ const planIt = () => planEffects(store, { repoId: req.repoId, docStore, ...(req.omgbaseDir !== undefined ? { omgbaseDir: req.omgbaseDir } : {}), effects: req.effects, ...selectorOf(req) });
295
320
  if (req.dryRun)
296
321
  return { plan: planIt() };
322
+ // One-shot `apply` with a selector must bound it (§11.8): the caller never saw
323
+ // the hits. The fix (3.3) is the same call bounded at the selection's actual
324
+ // size — the selector compiles (reads only) to count it; a selection the
325
+ // compile itself refuses reports its own error and fix.
326
+ if (req.select !== undefined && req.expect === undefined) {
327
+ const { selection } = compileSelection(store, req.repoId, req.select, undefined, req.effects);
328
+ throw selectionInvalid("`expect.max` is required on `apply` with `select` — bound the selection, or preview it with `plan`", { fix: fixRequest({ select: req.select, effects: req.effects, expect: { max: selection.cardinality } }) });
329
+ }
297
330
  return underLock(req, () => {
298
331
  const plan = planIt();
299
332
  return { plan, receipt: executeAdmitted(store, req, plan, docStore) };
300
333
  });
301
334
  }
302
- /** `apply { effects }` (§11.7): plan + execute under one lock and one transaction; `dryRun` returns the Plan. */
335
+ /** `apply { effects, select?, expect? }` (§11.7): plan + execute under one lock and one transaction; `dryRun` returns the Plan. */
303
336
  export function applyEffects(store, req) {
304
337
  const { plan, receipt } = applyEffectsDetailed(store, req);
305
338
  return receipt ?? plan;
@@ -347,7 +380,9 @@ function underLock(req, fn) {
347
380
  * `ingest_known { base: null }`; a moved document's revision lands at its new
348
381
  * path), tombstones — and `commitChangeset` re-validates every triple inside
349
382
  * `BEGIN IMMEDIATE`. The commit row's `ops` is the intent record
350
- * `canonical({ v: 1, plan_id, effects })`.
383
+ * `canonical({ v: 1, plan_id, effects, selection?, templates? })` (§11.8: the
384
+ * selector, its templates and the hits it bound ride along when the plan had
385
+ * one; a plain request's written effects when one carried an expression, 3.2).
351
386
  */
352
387
  function executeAdmitted(store, req, plan, docStore) {
353
388
  for (const d of plan.preconditions.docs) {
@@ -360,6 +395,7 @@ function executeAdmitted(store, req, plan, docStore) {
360
395
  expected_revision: d.base_revision,
361
396
  current: { revision: row.currentRev, hash: row.fileHash },
362
397
  retriable: true,
398
+ fix: replanFix(plan),
363
399
  });
364
400
  }
365
401
  }
@@ -375,7 +411,11 @@ function executeAdmitted(store, req, plan, docStore) {
375
411
  origin: "api",
376
412
  actor: req.origin.actor,
377
413
  reason: req.origin.reason ?? null,
378
- ops: canonicalJson({ v: 1, plan_id: plan.plan_id, effects: plan.effects }),
414
+ ops: canonicalJson({
415
+ v: 1, plan_id: plan.plan_id, effects: plan.effects,
416
+ ...(plan.selection !== undefined ? { selection: plan.selection } : {}),
417
+ ...(plan.templates !== undefined ? { templates: plan.templates } : {}),
418
+ }),
379
419
  };
380
420
  const stat = req.omgbaseDir !== undefined;
381
421
  const moved = new Map(plan.opset.moves.map((m) => [m.id, m]));
@@ -467,6 +507,6 @@ function executeAdmitted(store, req, plan, docStore) {
467
507
  if (id !== undefined)
468
508
  newIds[s.symbol] = id;
469
509
  }
470
- return { plan_id: plan.plan_id, commit: outcome.commitId, revisions, new_ids: newIds, committed: true };
510
+ return { plan_id: plan.plan_id, commit: outcome.commitId, revisions, new_ids: newIds, committed: true, summary: plan.preview?.summary ?? [] };
471
511
  }
472
512
  //# sourceMappingURL=plan.js.map
@@ -0,0 +1,89 @@
1
+ import { type Query } from "@omgbase/oqx";
2
+ import type { Store } from "../core/store/store.js";
3
+ import { type Effect } from "./effects.js";
4
+ import { MutationError } from "./tree.js";
5
+ import { type FixRequest } from "./canonical.js";
6
+ /** The implicit bound on a selection without `expect.max` (hits beyond it are `selection_too_large`). */
7
+ export declare const SELECTION_CEILING = 1000;
8
+ export interface SelectionExpect {
9
+ max: number;
10
+ }
11
+ /** The seventh digested member of a plan's core (§11.4): the selector and its templates AS GIVEN, the hits it bound. */
12
+ export interface Selection {
13
+ query: string;
14
+ expect?: SelectionExpect;
15
+ templates: unknown[];
16
+ cardinality: number;
17
+ /** the hit ids, result order */
18
+ identities: string[];
19
+ }
20
+ /** Where an expanded effect came from (every error from one carries both, §11.8); a plain effect's
21
+ * expressions (3.2) have a row but no template. */
22
+ export interface EffectOrigin {
23
+ template_index?: number;
24
+ row: {
25
+ id: string;
26
+ path: string;
27
+ };
28
+ }
29
+ export interface CompiledSelection {
30
+ /** the concrete, validated effects: hits × templates */
31
+ effects: Effect[];
32
+ selection: Selection;
33
+ /** expanded index → its origin */
34
+ origins: EffectOrigin[];
35
+ }
36
+ /** (3.2) A plain request compiled: the concrete effects and, when any effect carried an expression, the list as written. */
37
+ export interface CompiledPlain {
38
+ effects: Effect[];
39
+ /** the effects AS WRITTEN — present only when at least one slot was an expression (the intent record's `templates`) */
40
+ templates?: unknown[];
41
+ /** effect index → the row its expressions were read from (sparse; literal effects have none) */
42
+ origins: (EffectOrigin | undefined)[];
43
+ }
44
+ /** The admission pass's refusal: `filter_invalid { reason, hint: "selection" }`. */
45
+ export declare function selectionInvalid(message: string, data?: Record<string, unknown>): MutationError;
46
+ /**
47
+ * `"-> <expr>"` → `{ oqx: "<expr>" }` — or, when the expression is a bare
48
+ * string literal or a template literal without interpolation, the literal
49
+ * itself (constant folding: `-> "x"` and `"x"` plan identically, and
50
+ * `-> "-> note"` is how a literal that begins with the leader is spelled).
51
+ * A parse error is `filter_invalid { hint: "selection" }` with its offset
52
+ * counted from the start of the written string (past the leader).
53
+ */
54
+ export declare function desugarLeader(v: string, here: string): unknown;
55
+ /**
56
+ * Desugar one effect or template as written (§11.8 "Sugar"), before the shape
57
+ * check and the admission: in every slot that admits an expression — the
58
+ * strings `markdown`, `to`, `path`, `separator`, `at.before` / `at.after`,
59
+ * the JSON `value`, each top-level `create.frontmatter` value, a target's `id`
60
+ * / `path`, and a whole target (`into`, `target`, `targets[k]`) — a string
61
+ * beginning `-> ` becomes `{ oqx }` (or folds to its literal); a `-> ` string
62
+ * anywhere else (`expect`, `field`, `op`, a section's `heading_id`, …) is
63
+ * refused. Every other string is literal data, with no escaping and no `${`
64
+ * scanning; a string nested inside a `value` or a frontmatter value is data
65
+ * (the slot is the top-level value, as for `{ oqx }`). Shape problems are the
66
+ * shape check's.
67
+ */
68
+ export declare function desugarEffect(raw: unknown, index: number, request?: FixRequest): unknown;
69
+ /**
70
+ * Compile `{ select, expect?, effects: templates }` into the concrete effects
71
+ * (§11.8): admit statically, rewrite, run once over one snapshot with
72
+ * `limit = bound + 1`, refuse a selection beyond the bound, bind every hit to
73
+ * every template. Reads only.
74
+ */
75
+ export declare function compileSelection(store: Store, repoId: string, select: unknown, expectRaw: unknown, templatesRaw: unknown): CompiledSelection;
76
+ /** The query a selector request compiles to (admission + rewrite, before any row is read) — `print` it to see what runs. */
77
+ export declare function selectionQuery(select: unknown, templates: unknown): Query;
78
+ /**
79
+ * Compile a plain `effects` list (no selector): desugar the `-> ` leaders,
80
+ * then — when nothing is an expression — the shape check as before (3.0
81
+ * byte-identical). Otherwise each effect with expressions is admitted as a
82
+ * template over its own primary target's row: the effects are grouped by that
83
+ * row's identity, one synthetic query per identity — `from <root> where id ==
84
+ * "<id>"` with the slot items appended, named `__omg_sel_<effectIndex>_<slot>`
85
+ * — runs, and the row binds the slots exactly as a selector's hit would. The
86
+ * concrete effects feed the lowering unchanged; `templates` is the list as
87
+ * written, for the intent record.
88
+ */
89
+ export declare function compilePlain(store: Store, repoId: string, raw: unknown): CompiledPlain;