@omgbase/core 0.27.1 → 0.29.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/dist/src/core/read/document.d.ts +11 -10
- package/dist/src/core/read/document.js +12 -11
- package/dist/src/core/read/nodes.d.ts +10 -10
- package/dist/src/core/read/nodes.js +11 -11
- package/dist/src/core/read/reader.d.ts +37 -24
- package/dist/src/core/read/reader.js +44 -31
- package/dist/src/graph/history.d.ts +25 -22
- package/dist/src/graph/history.js +17 -17
- package/dist/src/graph/inbound-links.d.ts +7 -7
- package/dist/src/graph/inbound-links.js +7 -7
- package/dist/src/graph/link-health.d.ts +9 -9
- package/dist/src/graph/link-health.js +9 -9
- package/dist/src/graph/links.d.ts +3 -3
- package/dist/src/graph/links.js +4 -4
- package/dist/src/mcp/arguments.d.ts +10 -3
- package/dist/src/mcp/arguments.js +32 -9
- package/dist/src/mcp/errors.d.ts +1 -1
- package/dist/src/mcp/graph.d.ts +22 -22
- package/dist/src/mcp/graph.js +53 -46
- package/dist/src/mcp/reference.d.ts +29 -1
- package/dist/src/mcp/reference.js +350 -222
- package/dist/src/mcp/server.d.ts +9 -0
- package/dist/src/mcp/server.js +169 -107
- package/dist/src/mutate/apply.d.ts +6 -6
- package/dist/src/mutate/apply.js +1 -1
- package/dist/src/mutate/canonical.d.ts +1 -1
- package/dist/src/mutate/canonical.js +2 -2
- package/dist/src/mutate/commit.d.ts +3 -3
- package/dist/src/mutate/commit.js +4 -4
- package/dist/src/mutate/docs.d.ts +1 -1
- package/dist/src/mutate/docs.js +8 -8
- package/dist/src/mutate/effects.d.ts +17 -17
- package/dist/src/mutate/effects.js +113 -113
- package/dist/src/mutate/macros.d.ts +5 -5
- package/dist/src/mutate/macros.js +1 -1
- package/dist/src/mutate/materialize.js +2 -2
- package/dist/src/mutate/ops.js +2 -2
- package/dist/src/mutate/opset.d.ts +7 -7
- package/dist/src/mutate/plan-update.js +2 -2
- package/dist/src/mutate/plan.d.ts +8 -8
- package/dist/src/mutate/plan.js +25 -25
- package/dist/src/mutate/select.d.ts +4 -4
- package/dist/src/mutate/select.js +52 -52
- package/dist/src/mutate/sugar.d.ts +10 -10
- package/dist/src/mutate/sugar.js +21 -21
- package/dist/src/oqx-js/context.d.ts +8 -4
- package/dist/src/oqx-js/context.js +206 -189
- package/dist/src/oqx-js/names.d.ts +28 -17
- package/dist/src/oqx-js/names.js +115 -78
- package/dist/src/oqx-js/planner.d.ts +4 -8
- package/dist/src/oqx-js/planner.js +6 -10
- package/dist/src/oqx-js/root-row.d.ts +6 -5
- package/dist/src/oqx-js/root-row.js +15 -16
- package/dist/src/oqx-js/run.d.ts +18 -17
- package/dist/src/oqx-js/run.js +57 -54
- package/dist/src/oqx-js/sql/translate.d.ts +5 -5
- package/dist/src/oqx-js/sql/translate.js +162 -161
- package/dist/src/oqx-js/store-index.d.ts +1 -1
- package/dist/src/oqx-js/store-index.js +28 -30
- package/dist/src/search/resolve.d.ts +2 -2
- package/dist/src/search/resolve.js +1 -1
- package/dist/src/search/rrf.js +2 -2
- package/dist/src/search/text.d.ts +5 -5
- package/dist/src/search/text.js +2 -2
- package/dist/src/spec-versions.d.ts +6 -6
- package/dist/src/spec-versions.js +6 -6
- package/dist/src/surface-paths.d.ts +18 -17
- package/dist/src/surface-paths.js +23 -21
- package/dist/src/sync/admin.d.ts +2 -2
- package/dist/src/sync/admin.js +1 -1
- package/package.json +1 -1
|
@@ -10,16 +10,16 @@ export type OpsetKind = "doc_update";
|
|
|
10
10
|
export type PlanDisposition = "same" | "edited" | "moved" | "edited_moved" | "inserted" | "deleted" | "split_from" | "merged_into" | "copied_from" | "resurrected" | "bulk_rewrite" | "retiled";
|
|
11
11
|
/** State the opset was planned against; applying against different state
|
|
12
12
|
* fails. The document is named by its row attributes (spec/surface §1.2,
|
|
13
|
-
*
|
|
13
|
+
* 5.0 "underscore = engine"): `_id`, `_path`, and `_hash` — its current hash at plan time;
|
|
14
14
|
* `baseRevision` names a role and keeps its name. */
|
|
15
15
|
export interface OpsetPrecondition {
|
|
16
16
|
/** doc id the plan targets */
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
_id: string;
|
|
18
|
+
_path: string;
|
|
19
19
|
/** the document's current revision id at plan time (null for a doc with no committed revision yet) */
|
|
20
20
|
baseRevision: string | null;
|
|
21
|
-
/** sha256 hex of the document's current rendered bytes (docs.file_hash) at plan time — its `
|
|
22
|
-
|
|
21
|
+
/** sha256 hex of the document's current rendered bytes (docs.file_hash) at plan time — its `_hash` */
|
|
22
|
+
_hash: string;
|
|
23
23
|
}
|
|
24
24
|
/** One kernel op plus the identity consequence the planner attributes to it. */
|
|
25
25
|
export interface PlanOp {
|
|
@@ -54,8 +54,8 @@ export interface Opset {
|
|
|
54
54
|
version: 1;
|
|
55
55
|
kind: OpsetKind;
|
|
56
56
|
target: {
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
_id: string;
|
|
58
|
+
_path: string;
|
|
59
59
|
};
|
|
60
60
|
precondition: OpsetPrecondition;
|
|
61
61
|
/** matcher version that produced the identity decisions (reproducibility) */
|
|
@@ -86,8 +86,8 @@ export function planUpdate(store, repoId, rootPath, docRef, content, opts = {})
|
|
|
86
86
|
return {
|
|
87
87
|
version: 1,
|
|
88
88
|
kind: "doc_update",
|
|
89
|
-
target: {
|
|
90
|
-
precondition: {
|
|
89
|
+
target: { _id: docId, _path: path },
|
|
90
|
+
precondition: { _id: docId, _path: path, baseRevision: info.currentRev, _hash: baseContentHash },
|
|
91
91
|
matcherV: config.matcherV,
|
|
92
92
|
ops,
|
|
93
93
|
...(fmChanged ? { frontmatter: { raw: targetFm } } : {}),
|
|
@@ -24,18 +24,18 @@ export type WireOp = Exclude<Op, {
|
|
|
24
24
|
};
|
|
25
25
|
/** The lowered form `execute` replays (§11.4): every key present, `[]` when empty. */
|
|
26
26
|
export interface PlanOpset {
|
|
27
|
-
/** `create` → the composed file, its document symbol and the index of the first kernel op planned after it (3.0; a later `set
|
|
27
|
+
/** `create` → the composed file, its document symbol and the index of the first kernel op planned after it (3.0; a later `set _frontmatter.<k>` on the symbol is a head below) */
|
|
28
28
|
creates: CreateEntry[];
|
|
29
29
|
/** `move document` → the row/file rename (its retarget updates are in `ops`) */
|
|
30
30
|
moves: {
|
|
31
|
-
|
|
31
|
+
_id: string;
|
|
32
32
|
from: string;
|
|
33
33
|
to: string;
|
|
34
34
|
}[];
|
|
35
35
|
/** `remove document` */
|
|
36
36
|
tombstones: {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
_id: string;
|
|
38
|
+
_path: string;
|
|
39
39
|
}[];
|
|
40
40
|
/** per document, the file head — everything before the first block (frontmatter incl. fences and separator) */
|
|
41
41
|
set_frontmatter: {
|
|
@@ -48,14 +48,14 @@ export interface PlanOpset {
|
|
|
48
48
|
export interface PlanPreconditions {
|
|
49
49
|
/** the row triples (`spec/mutate` §4 step 5) of every document the plan touches, loaded ones first in load order */
|
|
50
50
|
docs: {
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
_id: string;
|
|
52
|
+
_path: string;
|
|
53
53
|
base_revision: string | null;
|
|
54
|
-
|
|
54
|
+
_hash: string | null;
|
|
55
55
|
}[];
|
|
56
56
|
/** every block content hash the plan pins (caller-given or pinned from the live row), first-pin order */
|
|
57
57
|
blocks: {
|
|
58
|
-
|
|
58
|
+
_id: string;
|
|
59
59
|
content_hash: string;
|
|
60
60
|
}[];
|
|
61
61
|
/** the caller's destination-parent CAS pins */
|
package/dist/src/mutate/plan.js
CHANGED
|
@@ -116,19 +116,19 @@ export function planEffects(store, req) {
|
|
|
116
116
|
const changeset = planner.finish();
|
|
117
117
|
const opset = {
|
|
118
118
|
creates: ctx.creates,
|
|
119
|
-
moves: ctx.moves.map((m) => ({
|
|
120
|
-
tombstones: ctx.tombstones.map((t) => ({
|
|
119
|
+
moves: ctx.moves.map((m) => ({ _id: m.id, from: m.from, to: m.to })),
|
|
120
|
+
tombstones: ctx.tombstones.map((t) => ({ _id: t.id, _path: t.path })),
|
|
121
121
|
set_frontmatter: [...ctx.heads].map(([doc, raw]) => ({ doc, raw })),
|
|
122
122
|
ops: ctx.ops.map(wireOp),
|
|
123
123
|
};
|
|
124
|
-
const docs = changeset.preconditions.map((p) => ({
|
|
124
|
+
const docs = changeset.preconditions.map((p) => ({ _id: p.id, _path: p.path, base_revision: p.currentRev, _hash: p.fileHash }));
|
|
125
125
|
for (const [id, base] of ctx.docBases) {
|
|
126
|
-
if (!docs.some((d) => d.
|
|
127
|
-
docs.push({ id,
|
|
126
|
+
if (!docs.some((d) => d._id === id))
|
|
127
|
+
docs.push({ _id: id, _path: base.path, base_revision: base.currentRev, _hash: base.fileHash });
|
|
128
128
|
}
|
|
129
129
|
const preconditions = {
|
|
130
130
|
docs,
|
|
131
|
-
blocks: [...ctx.pins].map(([id, content_hash]) => ({ id, content_hash })),
|
|
131
|
+
blocks: [...ctx.pins].map(([id, content_hash]) => ({ _id: id, content_hash })),
|
|
132
132
|
parents: ctx.parents,
|
|
133
133
|
absent_paths: ctx.absentPaths,
|
|
134
134
|
};
|
|
@@ -151,9 +151,9 @@ export function planEffects(store, req) {
|
|
|
151
151
|
for (const t of ctx.tombstones)
|
|
152
152
|
diffs[t.path] = { before: docStore.read(t.path) ?? "", after: "" };
|
|
153
153
|
for (const r of revisions) {
|
|
154
|
-
const moved = ctx.moves.find((m) => m.id === r.
|
|
154
|
+
const moved = ctx.moves.find((m) => m.id === r._id);
|
|
155
155
|
if (moved)
|
|
156
|
-
r.
|
|
156
|
+
r._path = moved.to;
|
|
157
157
|
}
|
|
158
158
|
const preview = { results: changeset.results, revisions, diffs, summary: ctx.summary };
|
|
159
159
|
if (ctx.identity.size > 0)
|
|
@@ -171,7 +171,7 @@ function requestOf(req) {
|
|
|
171
171
|
/**
|
|
172
172
|
* `conflicting_effects`: keep the later effect — the request minus the earlier
|
|
173
173
|
* one; under a selector, a cross-hit collision narrows the selector to the
|
|
174
|
-
* later hit (`from <root> where
|
|
174
|
+
* later hit (`from <root> where _id == …`, `expect.max: 1`) and a same-hit
|
|
175
175
|
* collision drops the earlier template. `stale_plan` at plan time (an
|
|
176
176
|
* `expect.revision` that no longer holds): the same request with the current
|
|
177
177
|
* revision in that guard. `target_missing { symbol }` for a `$new.n` no earlier
|
|
@@ -191,8 +191,8 @@ function planningFix(err, i, req, origins, symbols) {
|
|
|
191
191
|
const ob = origins[b];
|
|
192
192
|
if (oa === undefined || ob === undefined || oa.template_index === undefined)
|
|
193
193
|
return undefined;
|
|
194
|
-
if (oa.row.
|
|
195
|
-
return fixRequest({ select: narrowedTo(rootOfId(ob.row.
|
|
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
196
|
return fixRequest({ ...requestOf(req), effects: effects.filter((_, k) => k !== oa.template_index) });
|
|
197
197
|
}
|
|
198
198
|
if (err.code === "stale_plan" && isRecord(err.data.current) && typeof err.data.current.revision === "string") {
|
|
@@ -386,14 +386,14 @@ function underLock(req, fn) {
|
|
|
386
386
|
*/
|
|
387
387
|
function executeAdmitted(store, req, plan, docStore) {
|
|
388
388
|
for (const d of plan.preconditions.docs) {
|
|
389
|
-
const row = readDocBase(store.db, d.
|
|
389
|
+
const row = readDocBase(store.db, d._id);
|
|
390
390
|
if (!row)
|
|
391
|
-
throw new MutationError("doc_missing", `doc ${d.
|
|
392
|
-
if (row.path !== d.
|
|
393
|
-
throw new MutationError("stale_plan", `document ${d.
|
|
394
|
-
doc: d.
|
|
391
|
+
throw new MutationError("doc_missing", `doc ${d._id} no longer exists`, { doc: d._id });
|
|
392
|
+
if (row.path !== d._path || row.currentRev !== d.base_revision || row.fileHash !== d._hash) {
|
|
393
|
+
throw new MutationError("stale_plan", `document ${d._id} changed since the plan was made; re-plan`, {
|
|
394
|
+
doc: d._id,
|
|
395
395
|
expected_revision: d.base_revision,
|
|
396
|
-
current: { revision: row.currentRev,
|
|
396
|
+
current: { revision: row.currentRev, _hash: row.fileHash },
|
|
397
397
|
retriable: true,
|
|
398
398
|
fix: replanFix(plan),
|
|
399
399
|
});
|
|
@@ -418,7 +418,7 @@ function executeAdmitted(store, req, plan, docStore) {
|
|
|
418
418
|
}),
|
|
419
419
|
};
|
|
420
420
|
const stat = req.omgbaseDir !== undefined;
|
|
421
|
-
const moved = new Map(plan.opset.moves.map((m) => [m.
|
|
421
|
+
const moved = new Map(plan.opset.moves.map((m) => [m._id, m]));
|
|
422
422
|
const blockSymbols = plan.new_ids.filter((s) => s.kind === "block").map((s) => s.symbol);
|
|
423
423
|
const planner = new ChangesetPlanner(store, req.repoId, req.omgbaseDir);
|
|
424
424
|
const { effects, outcome } = observeMints((prefix, id) => {
|
|
@@ -459,8 +459,8 @@ function executeAdmitted(store, req, plan, docStore) {
|
|
|
459
459
|
const effects = [];
|
|
460
460
|
for (const m of plan.opset.moves) {
|
|
461
461
|
const bytes = docStore.read(m.from) ?? "";
|
|
462
|
-
const rev = readDocBase(store.db, m.
|
|
463
|
-
effects.push({ kind: "move_path", docId: m.
|
|
462
|
+
const rev = readDocBase(store.db, m._id)?.currentRev ?? null;
|
|
463
|
+
effects.push({ kind: "move_path", docId: m._id, from: m.from, to: m.to, currentRev: rev, inbound: inboundLinksTo(store, req.repoId, m._id, m.from), bytes });
|
|
464
464
|
if (stat)
|
|
465
465
|
effects.push({ kind: "clear_stat", path: m.from }, { kind: "record_stat", path: m.to, bytes });
|
|
466
466
|
}
|
|
@@ -477,10 +477,10 @@ function executeAdmitted(store, req, plan, docStore) {
|
|
|
477
477
|
effects.push(e);
|
|
478
478
|
}
|
|
479
479
|
for (const t of plan.opset.tombstones) {
|
|
480
|
-
const rev = readDocBase(store.db, t.
|
|
481
|
-
effects.push({ kind: "tombstone", docId: t.
|
|
480
|
+
const rev = readDocBase(store.db, t._id)?.currentRev ?? null;
|
|
481
|
+
effects.push({ kind: "tombstone", docId: t._id, path: t._path, currentRev: rev, pool: false });
|
|
482
482
|
if (stat)
|
|
483
|
-
effects.push({ kind: "clear_stat", path: t.
|
|
483
|
+
effects.push({ kind: "clear_stat", path: t._path });
|
|
484
484
|
}
|
|
485
485
|
const outcome = commitChangeset(store, req.repoId, head, effects, ts, { docStore, ...(req.omgbaseDir !== undefined ? { omgbaseDir: req.omgbaseDir } : {}) });
|
|
486
486
|
return { effects, outcome };
|
|
@@ -491,9 +491,9 @@ function executeAdmitted(store, req, plan, docStore) {
|
|
|
491
491
|
let j = 0;
|
|
492
492
|
for (const e of effects) {
|
|
493
493
|
if (e.kind === "ingest_reconciled")
|
|
494
|
-
revisions.push({
|
|
494
|
+
revisions.push({ _id: outcome.ingests[j++].docId, _path: e.path, _hash: sha256(e.source).toString("hex") });
|
|
495
495
|
else if (e.kind === "ingest_known")
|
|
496
|
-
revisions.push({
|
|
496
|
+
revisions.push({ _id: outcome.ingests[j++].docId, _path: e.path, _hash: sha256(e.rendered).toString("hex") });
|
|
497
497
|
}
|
|
498
498
|
// `$new.n` → minted id: the i-th block symbol is the i-th `b` the replay
|
|
499
499
|
// minted (all before the transaction, in the plan's order — the aliases
|
|
@@ -22,8 +22,8 @@ export interface Selection {
|
|
|
22
22
|
export interface EffectOrigin {
|
|
23
23
|
template_index?: number;
|
|
24
24
|
row: {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
_id: string;
|
|
26
|
+
_path: string;
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
export interface CompiledSelection {
|
|
@@ -56,8 +56,8 @@ export declare function desugarLeader(v: string, here: string): unknown;
|
|
|
56
56
|
* Desugar one effect or template as written (§11.8 "Sugar"), before the shape
|
|
57
57
|
* check and the admission: in every slot that admits an expression — the
|
|
58
58
|
* strings `markdown`, `to`, `path`, `separator`, `at.before` / `at.after`,
|
|
59
|
-
* the JSON `value`, each top-level `create.frontmatter` value, a target's `
|
|
60
|
-
* / `
|
|
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
61
|
* beginning `-> ` becomes `{ oqx }` (or folds to its literal); a `-> ` string
|
|
62
62
|
* anywhere else (`expect`, `field`, `op`, a section's `heading_id`, …) is
|
|
63
63
|
* refused. Every other string is literal data, with no escaping and no `${`
|
|
@@ -42,9 +42,9 @@ function isRecord(v) {
|
|
|
42
42
|
function isOqx(v) {
|
|
43
43
|
return isRecord(v) && Object.keys(v).length === 1 && typeof v.oqx === "string";
|
|
44
44
|
}
|
|
45
|
-
/** A row rendered as a value by the runner (`{
|
|
45
|
+
/** A row rendered as a value by the runner (`{ _id, _path }` and nothing else, spec/surface §1.4). */
|
|
46
46
|
function isRowRef(v) {
|
|
47
|
-
return isRecord(v) && Object.keys(v).length === 2 && typeof v.
|
|
47
|
+
return isRecord(v) && Object.keys(v).length === 2 && typeof v._id === "string" && typeof v._path === "string";
|
|
48
48
|
}
|
|
49
49
|
function describeValue(v) {
|
|
50
50
|
if (v === null || v === undefined)
|
|
@@ -144,8 +144,8 @@ function refuseLeader(v, here, path, fix) {
|
|
|
144
144
|
* Desugar one effect or template as written (§11.8 "Sugar"), before the shape
|
|
145
145
|
* check and the admission: in every slot that admits an expression — the
|
|
146
146
|
* strings `markdown`, `to`, `path`, `separator`, `at.before` / `at.after`,
|
|
147
|
-
* the JSON `value`, each top-level `create.frontmatter` value, a target's `
|
|
148
|
-
* / `
|
|
147
|
+
* the JSON `value`, each top-level `create.frontmatter` value, a target's `_id`
|
|
148
|
+
* / `_path`, and a whole target (`into`, `target`, `targets[k]`) — a string
|
|
149
149
|
* beginning `-> ` becomes `{ oqx }` (or folds to its literal); a `-> ` string
|
|
150
150
|
* anywhere else (`expect`, `field`, `op`, a section's `heading_id`, …) is
|
|
151
151
|
* refused. Every other string is literal data, with no escaping and no `${`
|
|
@@ -174,11 +174,11 @@ export function desugarEffect(raw, index, request) {
|
|
|
174
174
|
if (isLeader(t))
|
|
175
175
|
out[key] = desugarLeader(t, `${here}.${key}`);
|
|
176
176
|
else if (isRecord(t)) {
|
|
177
|
-
for (const member of ["
|
|
177
|
+
for (const member of ["_id", "_path"])
|
|
178
178
|
if (isLeader(t[member]))
|
|
179
179
|
t[member] = desugarLeader(t[member], `${here}.${key}.${member}`);
|
|
180
180
|
for (const k of Object.keys(t))
|
|
181
|
-
if (k !== "
|
|
181
|
+
if (k !== "_id" && k !== "_path")
|
|
182
182
|
refuse(t[k], `${here}.${key}.${k}`, [key, k]);
|
|
183
183
|
}
|
|
184
184
|
};
|
|
@@ -191,10 +191,10 @@ export function desugarEffect(raw, index, request) {
|
|
|
191
191
|
if (isLeader(t))
|
|
192
192
|
list[k] = desugarLeader(t, `${here}.targets[${k}]`);
|
|
193
193
|
else if (isRecord(t)) {
|
|
194
|
-
if (isLeader(t.
|
|
195
|
-
t.
|
|
194
|
+
if (isLeader(t._id))
|
|
195
|
+
t._id = desugarLeader(t._id, `${here}.targets[${k}]._id`);
|
|
196
196
|
for (const m of Object.keys(t))
|
|
197
|
-
if (m !== "
|
|
197
|
+
if (m !== "_id")
|
|
198
198
|
refuse(t[m], `${here}.targets[${k}].${m}`, ["targets", String(k), m]);
|
|
199
199
|
}
|
|
200
200
|
});
|
|
@@ -328,7 +328,7 @@ function admitTemplate(raw, index, root, plain, fix) {
|
|
|
328
328
|
probe[key] = null;
|
|
329
329
|
slots.push({ name: key, kind: "json", here: `${here}.${key}`, path: [key], expression: src, item: expr(src, key), put: (e, v) => { e[key] = v; } });
|
|
330
330
|
};
|
|
331
|
-
// A target member: `{ kind,
|
|
331
|
+
// A target member: `{ kind, _id: { oqx } }` or `{ kind: "document", _path: { oqx } }`, or (3.2) the whole target
|
|
332
332
|
// `{ oqx }` — the identity the value names; absent → the hit when `defaultable` (never on a plain effect).
|
|
333
333
|
// `probeKind` is a kind the slot admits, standing in for the whole-target expression's unknown kind.
|
|
334
334
|
const targetSlot = (key, defaultable, probeKind, primary) => {
|
|
@@ -337,7 +337,7 @@ function admitTemplate(raw, index, root, plain, fix) {
|
|
|
337
337
|
if (t === undefined) {
|
|
338
338
|
if (!defaultable || plain)
|
|
339
339
|
return null;
|
|
340
|
-
probe[key] = { kind: HIT_KIND[root],
|
|
340
|
+
probe[key] = { kind: HIT_KIND[root], _id: "$sel" };
|
|
341
341
|
return { key, list: false };
|
|
342
342
|
}
|
|
343
343
|
if (isOqx(t)) {
|
|
@@ -345,19 +345,19 @@ function admitTemplate(raw, index, root, plain, fix) {
|
|
|
345
345
|
throw needsSelector(`${here}.${key}`);
|
|
346
346
|
const src = t.oqx;
|
|
347
347
|
delete base[key];
|
|
348
|
-
probe[key] = { kind: probeKind,
|
|
348
|
+
probe[key] = { kind: probeKind, _id: "$sel" };
|
|
349
349
|
slots.push({ name: key, kind: "target", here: `${here}.${key}`, path: [key], expression: src, item: expr(src, key), put: (e, v) => { e[key] = v; } });
|
|
350
350
|
return null;
|
|
351
351
|
}
|
|
352
352
|
if (isRecord(t)) {
|
|
353
|
-
const idX = isOqx(t.
|
|
354
|
-
const pathX = isOqx(t.
|
|
353
|
+
const idX = isOqx(t._id);
|
|
354
|
+
const pathX = isOqx(t._path);
|
|
355
355
|
if (idX && pathX)
|
|
356
|
-
throw selectionInvalid(`${here}.${key}: give \`
|
|
356
|
+
throw selectionInvalid(`${here}.${key}: give \`_id\` or \`_path\` as an expression, not both`);
|
|
357
357
|
if (idX || pathX) {
|
|
358
358
|
if (plain && primary)
|
|
359
359
|
throw needsSelector(`${here}.${key}`);
|
|
360
|
-
const member = idX ? "
|
|
360
|
+
const member = idX ? "_id" : "_path";
|
|
361
361
|
const src = t[member].oqx;
|
|
362
362
|
const literal = { ...t };
|
|
363
363
|
delete literal[member];
|
|
@@ -369,12 +369,12 @@ function admitTemplate(raw, index, root, plain, fix) {
|
|
|
369
369
|
});
|
|
370
370
|
}
|
|
371
371
|
for (const k of Object.keys(t))
|
|
372
|
-
if (k !== "
|
|
372
|
+
if (k !== "_id" && k !== "_path" && containsOqx(t[k]))
|
|
373
373
|
throw selectionInvalid(`${here}.${key}.${k}: \`{ oqx }\` is not allowed here`);
|
|
374
374
|
}
|
|
375
375
|
return null;
|
|
376
376
|
};
|
|
377
|
-
// A `targets` list: each element `{ kind: "block",
|
|
377
|
+
// A `targets` list: each element `{ kind: "block", _id: { oqx } }` (or, 3.2, a whole `{ oqx }`) yields one block;
|
|
378
378
|
// absent → `[the hit]` when `defaultable`. The list is always the primary target of its op.
|
|
379
379
|
const targetsSlot = (defaultable) => {
|
|
380
380
|
slotKeys.add("targets");
|
|
@@ -382,7 +382,7 @@ function admitTemplate(raw, index, root, plain, fix) {
|
|
|
382
382
|
if (list === undefined) {
|
|
383
383
|
if (!defaultable || plain)
|
|
384
384
|
return null;
|
|
385
|
-
probe.targets = [{ kind: HIT_KIND[root],
|
|
385
|
+
probe.targets = [{ kind: HIT_KIND[root], _id: "$sel" }];
|
|
386
386
|
return { key: "targets", list: true };
|
|
387
387
|
}
|
|
388
388
|
if (Array.isArray(list)) {
|
|
@@ -392,7 +392,7 @@ function admitTemplate(raw, index, root, plain, fix) {
|
|
|
392
392
|
throw needsSelector(`${here}.targets[${k}]`);
|
|
393
393
|
const src = t.oqx;
|
|
394
394
|
base.targets[k] = {};
|
|
395
|
-
probe.targets[k] = { kind: "block",
|
|
395
|
+
probe.targets[k] = { kind: "block", _id: "$sel" };
|
|
396
396
|
slots.push({
|
|
397
397
|
name: `targets_${k}`, kind: "target", here: `${here}.targets[${k}]`, path: ["targets", String(k)], expression: src, item: expr(src, `targets[${k}]`),
|
|
398
398
|
put: (e, v) => { e.targets[k] = v; },
|
|
@@ -401,23 +401,23 @@ function admitTemplate(raw, index, root, plain, fix) {
|
|
|
401
401
|
}
|
|
402
402
|
if (!isRecord(t))
|
|
403
403
|
return;
|
|
404
|
-
if (isOqx(t.
|
|
405
|
-
throw selectionInvalid(`${here}.targets[${k}]: a block target has \`
|
|
406
|
-
if (isOqx(t.
|
|
404
|
+
if (isOqx(t._path))
|
|
405
|
+
throw selectionInvalid(`${here}.targets[${k}]: a block target has \`_id\`, not \`_path\``);
|
|
406
|
+
if (isOqx(t._id)) {
|
|
407
407
|
if (plain)
|
|
408
|
-
throw needsSelector(`${here}.targets[${k}].
|
|
409
|
-
const src = t.
|
|
408
|
+
throw needsSelector(`${here}.targets[${k}]._id`);
|
|
409
|
+
const src = t._id.oqx;
|
|
410
410
|
const literal = { ...t };
|
|
411
|
-
delete literal.
|
|
411
|
+
delete literal._id;
|
|
412
412
|
base.targets[k] = literal;
|
|
413
|
-
probe.targets[k].
|
|
413
|
+
probe.targets[k]._id = "$sel";
|
|
414
414
|
slots.push({
|
|
415
|
-
name: `targets_${k}`, kind: "identity", here: `${here}.targets[${k}].
|
|
416
|
-
put: (e, v) => { const arr = e.targets; arr[k] = { ...arr[k], ...identityMember("
|
|
415
|
+
name: `targets_${k}`, kind: "identity", here: `${here}.targets[${k}]._id`, path: ["targets", String(k), "_id"], expression: src, item: expr(src, `targets[${k}]._id`),
|
|
416
|
+
put: (e, v) => { const arr = e.targets; arr[k] = { ...arr[k], ...identityMember("_id", v) }; },
|
|
417
417
|
});
|
|
418
418
|
}
|
|
419
419
|
for (const key of Object.keys(t))
|
|
420
|
-
if (key !== "
|
|
420
|
+
if (key !== "_id" && containsOqx(t[key]))
|
|
421
421
|
throw selectionInvalid(`${here}.targets[${k}].${key}: \`{ oqx }\` is not allowed here`);
|
|
422
422
|
});
|
|
423
423
|
}
|
|
@@ -505,10 +505,10 @@ function admitTemplate(raw, index, root, plain, fix) {
|
|
|
505
505
|
validateEffect(probe, index, fix);
|
|
506
506
|
return { index, base, slots, defaultTarget };
|
|
507
507
|
}
|
|
508
|
-
/** The `
|
|
508
|
+
/** The `_id`/`_path` member a bound identity value supplies: a string as given, a row ref by its id. */
|
|
509
509
|
function identityMember(member, v) {
|
|
510
510
|
if (isRowRef(v))
|
|
511
|
-
return {
|
|
511
|
+
return { _id: v._id };
|
|
512
512
|
return { [member]: v };
|
|
513
513
|
}
|
|
514
514
|
// ---- the request ---------------------------------------------------------------------
|
|
@@ -624,7 +624,7 @@ export function compileSelection(store, repoId, select, expectRaw, templatesRaw)
|
|
|
624
624
|
}
|
|
625
625
|
// Row → effects: hits in result order × templates in order (k = h·T + t). A
|
|
626
626
|
// selector with `flatten` (oqx 0.25; §11.8) fans out per FLATTENED row: each
|
|
627
|
-
// hit is the parent row (its `
|
|
627
|
+
// hit is the parent row (its `_id`/`_path`), the slot items read the bound name
|
|
628
628
|
// (`-> tag`) in the flattened scope, and `expect.max` — the page bound above —
|
|
629
629
|
// counts the flattened hits.
|
|
630
630
|
const effects = [];
|
|
@@ -632,7 +632,7 @@ export function compileSelection(store, repoId, select, expectRaw, templatesRaw)
|
|
|
632
632
|
hits.forEach((hit) => {
|
|
633
633
|
for (const t of templates) {
|
|
634
634
|
const k = effects.length;
|
|
635
|
-
const row = {
|
|
635
|
+
const row = { _id: hit._id, _path: hit._path };
|
|
636
636
|
const origin = { template_index: t.index, row };
|
|
637
637
|
effects.push(bind(hit, t, k, origin, root, request));
|
|
638
638
|
origins.push(origin);
|
|
@@ -643,7 +643,7 @@ export function compileSelection(store, repoId, select, expectRaw, templatesRaw)
|
|
|
643
643
|
...(expect !== undefined ? { expect } : {}),
|
|
644
644
|
templates: templatesRaw,
|
|
645
645
|
cardinality: hits.length,
|
|
646
|
-
identities: hits.map((h) => h.
|
|
646
|
+
identities: hits.map((h) => h._id),
|
|
647
647
|
};
|
|
648
648
|
return { effects, selection, origins };
|
|
649
649
|
}
|
|
@@ -682,20 +682,20 @@ function slotName(t, slot) {
|
|
|
682
682
|
*/
|
|
683
683
|
function ambiguousFix(request, t, s, hit, root, list) {
|
|
684
684
|
const head = list[0];
|
|
685
|
-
const first = isRowRef(head) ? head.
|
|
685
|
+
const first = isRowRef(head) ? head._id : head;
|
|
686
686
|
if (typeof first !== "string")
|
|
687
687
|
return {};
|
|
688
688
|
const effects = withMember(request.effects, [String(t.index), ...s.path], first);
|
|
689
689
|
if (request.select === undefined)
|
|
690
690
|
return { fix: fixRequest({ effects }) };
|
|
691
|
-
return { fix: fixRequest({ select: narrowedTo(root, hit.
|
|
691
|
+
return { fix: fixRequest({ select: narrowedTo(root, hit._id), effects, expect: { max: 1 } }) };
|
|
692
692
|
}
|
|
693
693
|
/** One hit × one template → the concrete effect (the binding table of §11.8). */
|
|
694
694
|
function bind(hit, t, k, origin, root, request) {
|
|
695
695
|
const e = clone(t.base);
|
|
696
696
|
const data = { effect_index: k, ...(origin.template_index !== undefined ? { template_index: origin.template_index } : {}), row: origin.row };
|
|
697
697
|
if (t.defaultTarget !== null) {
|
|
698
|
-
const target = { kind: HIT_KIND[root],
|
|
698
|
+
const target = { kind: HIT_KIND[root], _id: hit._id };
|
|
699
699
|
e[t.defaultTarget.key] = t.defaultTarget.list ? [target] : target;
|
|
700
700
|
}
|
|
701
701
|
for (const s of t.slots) {
|
|
@@ -705,10 +705,10 @@ function bind(hit, t, k, origin, root, request) {
|
|
|
705
705
|
case "identity":
|
|
706
706
|
case "target": {
|
|
707
707
|
if (absent || (Array.isArray(v) && v.length === 0)) {
|
|
708
|
-
throw new MutationError("target_missing", `${s.here}: the expression yielded no identity for row ${hit.
|
|
708
|
+
throw new MutationError("target_missing", `${s.here}: the expression yielded no identity for row ${hit._id}`, { ...data, expression: s.expression });
|
|
709
709
|
}
|
|
710
710
|
if (Array.isArray(v)) {
|
|
711
|
-
throw new MutationError("ambiguous_target", `${s.here}: the expression yielded ${v.length} identities for row ${hit.
|
|
711
|
+
throw new MutationError("ambiguous_target", `${s.here}: the expression yielded ${v.length} identities for row ${hit._id}; a target resolves to exactly one`, { ...data, expression: s.expression, count: v.length, ...ambiguousFix(request, t, s, hit, root, v) });
|
|
712
712
|
}
|
|
713
713
|
if (typeof v !== "string" && !isRowRef(v))
|
|
714
714
|
throw selectionInvalid(`${s.here}: the expression yielded ${describeValue(v)}, not an identity (a string or a row)`, { ...data, expression: s.expression });
|
|
@@ -717,7 +717,7 @@ function bind(hit, t, k, origin, root, request) {
|
|
|
717
717
|
break;
|
|
718
718
|
}
|
|
719
719
|
// (3.2) a whole target: a row by its id's prefix, a string as a bare target (a path → document, an id → its kind).
|
|
720
|
-
const ref = isRowRef(v) ? v.
|
|
720
|
+
const ref = isRowRef(v) ? v._id : v;
|
|
721
721
|
if (!isRowRef(v) && !looksLikePath(ref) && !isTargetId(ref) && !isNewSymbol(ref)) {
|
|
722
722
|
throw selectionInvalid(`${s.here}: the expression yielded ${describeValue(v)}, not an identity (a document path, an id or a row)`, { ...data, expression: s.expression });
|
|
723
723
|
}
|
|
@@ -726,14 +726,14 @@ function bind(hit, t, k, origin, root, request) {
|
|
|
726
726
|
}
|
|
727
727
|
case "string":
|
|
728
728
|
if (absent)
|
|
729
|
-
throw selectionInvalid(`${s.here}: the expression yielded absent for row ${hit.
|
|
729
|
+
throw selectionInvalid(`${s.here}: the expression yielded absent for row ${hit._id}`, { ...data, expression: s.expression });
|
|
730
730
|
if (typeof v !== "string")
|
|
731
731
|
throw selectionInvalid(`${s.here}: the expression yielded ${describeValue(v)}, expected a string`, { ...data, expression: s.expression });
|
|
732
732
|
s.put(e, v);
|
|
733
733
|
break;
|
|
734
734
|
case "json":
|
|
735
735
|
if (absent)
|
|
736
|
-
throw selectionInvalid(`${s.here}: the expression yielded absent for row ${hit.
|
|
736
|
+
throw selectionInvalid(`${s.here}: the expression yielded absent for row ${hit._id}${s.name === "value" ? " — use `unset` to remove a key" : ""}`, { ...data, expression: s.expression });
|
|
737
737
|
s.put(e, v);
|
|
738
738
|
break;
|
|
739
739
|
}
|
|
@@ -813,25 +813,25 @@ function scopeOf(store, repoId, e, i) {
|
|
|
813
813
|
validateEffect(e, i); // an unknown op (or `retarget`): the shape check refuses it
|
|
814
814
|
throw effectInvalid(`${here}: \`op\` ${JSON.stringify(e.op)} is not row-shaped`);
|
|
815
815
|
}
|
|
816
|
-
if (isOqx(v) || (isRecord(v) && (isOqx(v.
|
|
816
|
+
if (isOqx(v) || (isRecord(v) && (isOqx(v._id) || isOqx(v._path))))
|
|
817
817
|
throw needsSelector(`${here}.${key}`);
|
|
818
818
|
const t = validateTarget(v, `${here}.${key}`, kinds);
|
|
819
819
|
switch (t.kind) {
|
|
820
820
|
case "symbol":
|
|
821
|
-
throw selectionInvalid(`${here}.${key}: ${t.
|
|
821
|
+
throw selectionInvalid(`${here}.${key}: ${t._id} has no row yet — it is created by this plan; use a literal target or a selector`);
|
|
822
822
|
case "document": {
|
|
823
823
|
const d = findDocumentTarget(store, repoId, t);
|
|
824
|
-
return { root: "docs", id: d.
|
|
824
|
+
return { root: "docs", id: d._id, missing: () => new MutationError("doc_missing", `doc ${d._id} not found`, { doc: d._id }) };
|
|
825
825
|
}
|
|
826
826
|
case "block":
|
|
827
827
|
return {
|
|
828
|
-
root: "blocks", id: t.
|
|
829
|
-
missing: () => (placement ? new MutationError("parent_missing", `parent ${t.
|
|
828
|
+
root: "blocks", id: t._id,
|
|
829
|
+
missing: () => (placement ? new MutationError("parent_missing", `parent ${t._id} not found`) : new MutationError("block_missing", `block ${t._id} not found`, { block: t._id })),
|
|
830
830
|
};
|
|
831
831
|
case "section":
|
|
832
832
|
return { root: "blocks", id: t.heading_id, missing: () => new MutationError("parent_missing", `parent ${t.heading_id} not found`) };
|
|
833
833
|
case "node":
|
|
834
|
-
return { root: "nodes", id: t.
|
|
834
|
+
return { root: "nodes", id: t._id, missing: () => new MutationError("block_missing", `node ${t._id} not found`, {}) };
|
|
835
835
|
}
|
|
836
836
|
}
|
|
837
837
|
/**
|
|
@@ -839,7 +839,7 @@ function scopeOf(store, repoId, e, i) {
|
|
|
839
839
|
* then — when nothing is an expression — the shape check as before (3.0
|
|
840
840
|
* byte-identical). Otherwise each effect with expressions is admitted as a
|
|
841
841
|
* template over its own primary target's row: the effects are grouped by that
|
|
842
|
-
* row's identity, one synthetic query per identity — `from <root> where
|
|
842
|
+
* row's identity, one synthetic query per identity — `from <root> where _id ==
|
|
843
843
|
* "<id>"` with the slot items appended, named `__omg_sel_<effectIndex>_<slot>`
|
|
844
844
|
* — runs, and the row binds the slots exactly as a selector's hit would. The
|
|
845
845
|
* concrete effects feed the lowering unchanged; `templates` is the list as
|
|
@@ -893,7 +893,7 @@ export function compilePlain(store, repoId, raw) {
|
|
|
893
893
|
g.members.push(t);
|
|
894
894
|
});
|
|
895
895
|
for (const g of scopes.values()) {
|
|
896
|
-
const q = parse(`from ${g.root} where
|
|
896
|
+
const q = parse(`from ${g.root} where _id == ${JSON.stringify(g.id)}`);
|
|
897
897
|
const items = [];
|
|
898
898
|
for (const t of g.members)
|
|
899
899
|
for (const s of t.slots)
|
|
@@ -903,7 +903,7 @@ export function compilePlain(store, repoId, raw) {
|
|
|
903
903
|
if (hit === undefined)
|
|
904
904
|
throw g.missing();
|
|
905
905
|
for (const t of g.members) {
|
|
906
|
-
const origin = { row: {
|
|
906
|
+
const origin = { row: { _id: hit._id, _path: hit._path } };
|
|
907
907
|
effects[t.index] = bind(hit, t, t.index, origin, g.root, request);
|
|
908
908
|
origins[t.index] = origin;
|
|
909
909
|
}
|
|
@@ -8,10 +8,10 @@ export interface Sugar {
|
|
|
8
8
|
effects: Effect[];
|
|
9
9
|
reason?: string;
|
|
10
10
|
}
|
|
11
|
-
/** A live document as the builders address it: its
|
|
11
|
+
/** A live document as the builders address it: its row attributes `_id` and `_path` (the storage-form path; the reasons spell it). */
|
|
12
12
|
export interface SugarDoc {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
_id: string;
|
|
14
|
+
_path: string;
|
|
15
15
|
}
|
|
16
16
|
/** A caller's guard as an effect guard: absent stays absent, an empty object stays an empty guard (the planner then pins what it needs). */
|
|
17
17
|
export declare function guardOf(e: {
|
|
@@ -40,21 +40,21 @@ export declare function moveDocEffects(doc: SugarDoc, toPath: string, retargetIn
|
|
|
40
40
|
/** `docs_delete` / `omg rm --doc`: one `remove { target: document }`; reason `delete <path>`. */
|
|
41
41
|
export declare function deleteDocEffects(doc: SugarDoc): Sugar;
|
|
42
42
|
/**
|
|
43
|
-
* `docs_set_meta` / `omg meta`: one `set`/`unset
|
|
43
|
+
* `docs_set_meta` / `omg meta`: one `set`/`unset _frontmatter.<k>` per key, `unset`
|
|
44
44
|
* winning a key named in both (the legacy merge spread `set` then deleted `unset`);
|
|
45
45
|
* reason `set_meta <path>`. An empty patch yields no effect — the caller refuses
|
|
46
46
|
* it (`filter_invalid` "pass `set` and/or `unset`", spec/surface §9).
|
|
47
47
|
*/
|
|
48
48
|
export declare function setMetaEffects(doc: SugarDoc, set: Record<string, unknown> | undefined, unset: readonly string[] | undefined): Sugar;
|
|
49
49
|
/** `docs_update` / `omg update <doc>`: one `set { document, content }`; the reason is the caller's. */
|
|
50
|
-
export declare function updateContentEffects(doc: Pick<SugarDoc, "
|
|
50
|
+
export declare function updateContentEffects(doc: Pick<SugarDoc, "_id">, content: string, reason?: string): Sugar;
|
|
51
51
|
/** `blocks_insert` / `omg insert`: `insert into <document|block> at …`; the caller's `expect` is the destination-parent CAS. */
|
|
52
52
|
export declare function insertEffects(into: EffectTarget, at: EffectAt, markdown: string, expect?: {
|
|
53
53
|
parent_children_hash?: string | undefined;
|
|
54
54
|
}): Sugar;
|
|
55
55
|
/**
|
|
56
56
|
* `blocks_update` / `omg update <block>` / `omg edit`: `set markdown` and/or `set
|
|
57
|
-
*
|
|
57
|
+
* _attrs.checked`. The caller's `expect` guards the FIRST effect (the content CAS
|
|
58
58
|
* is checked once, against the bytes the caller saw); a second effect is pinned
|
|
59
59
|
* by the planner to the block as the first left it. Nothing to set → no effect
|
|
60
60
|
* (the caller refuses it).
|
|
@@ -77,7 +77,7 @@ export declare function removeBlocksEffects(ids: readonly string[]): Sugar;
|
|
|
77
77
|
export declare function splitEffects(id: string, at: readonly number[]): Sugar;
|
|
78
78
|
/** `blocks_merge` / `omg merge`: one `merge { targets, separator? }`. */
|
|
79
79
|
export declare function mergeEffects(ids: readonly string[], separator?: string): Sugar;
|
|
80
|
-
/** `blocks_check` / `omg done`: N × `set
|
|
80
|
+
/** `blocks_check` / `omg done`: N × `set _attrs.checked` (a block named twice is one write — the planner drops the duplicate). */
|
|
81
81
|
export declare function checkEffects(ids: readonly string[], checked: boolean): Sugar;
|
|
82
82
|
/** `nodes_set` / `omg node set`: one `set { node, field: prop, value }`. */
|
|
83
83
|
export declare function nodeSetEffects(nodeId: string, prop: string, value: string): Sugar;
|
|
@@ -108,10 +108,10 @@ export declare function applySugar(store: Store, request: SugarRequest & {
|
|
|
108
108
|
plan: Plan;
|
|
109
109
|
receipt?: Receipt;
|
|
110
110
|
};
|
|
111
|
-
/** The document-operation result the `omg` document verbs and the fixture `docs` step record (`spec/mutate` §9): `{
|
|
111
|
+
/** The document-operation result the `omg` document verbs and the fixture `docs` step record (`spec/mutate` §9): `{ _id, _path, committed }`, the path in the storage form. */
|
|
112
112
|
export interface DocOpOutcome {
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
_id: string;
|
|
114
|
+
_path: string;
|
|
115
115
|
committed: true;
|
|
116
116
|
}
|
|
117
117
|
/** `docs_create`'s document is the receipt's first revision (`new_ids["$new.0"]` is the same id); every other operation's is the one addressed. */
|