@wildwinter/toolkit 0.2.1 → 0.2.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/CHANGELOG.md +11 -0
- package/conformance/extensions/merge.json +46 -1
- package/dist/extensions.cjs +70 -0
- package/dist/extensions.cjs.map +1 -1
- package/dist/extensions.d.cts +25 -1
- package/dist/extensions.d.ts +25 -1
- package/dist/extensions.js +69 -1
- package/dist/extensions.js.map +1 -1
- package/extensions.md +6 -1
- package/package.json +1 -1
package/dist/extensions.d.cts
CHANGED
|
@@ -209,6 +209,8 @@ declare function renameStages(target: TargetDecl, entry: Readonly<Record<string,
|
|
|
209
209
|
* sorted by key, or in the authored order with `order: "preserve"`
|
|
210
210
|
* object(fields) named keys by their own strategy, every other key by `other`
|
|
211
211
|
* (atomic when absent)
|
|
212
|
+
* comments a list of comment threads, merged message by message
|
|
213
|
+
* (`mergeComments`)
|
|
212
214
|
*/
|
|
213
215
|
type Strategy = {
|
|
214
216
|
readonly kind: "atomic";
|
|
@@ -227,9 +229,12 @@ type Strategy = {
|
|
|
227
229
|
readonly kind: "object";
|
|
228
230
|
readonly fields: Readonly<Record<string, Strategy>>;
|
|
229
231
|
readonly other?: Strategy;
|
|
232
|
+
} | {
|
|
233
|
+
readonly kind: "comments";
|
|
230
234
|
};
|
|
231
235
|
declare const ATOMIC: Strategy;
|
|
232
236
|
declare const SET: Strategy;
|
|
237
|
+
declare const COMMENTS: Strategy;
|
|
233
238
|
declare const mapOf: (of: Strategy) => Strategy;
|
|
234
239
|
declare const keyedBy: (key: string, of: Strategy, opts?: {
|
|
235
240
|
order?: "sorted" | "preserve";
|
|
@@ -267,6 +272,25 @@ interface TreeMergeOptions {
|
|
|
267
272
|
declare function stableStringify(value: unknown): string;
|
|
268
273
|
/** Three-way merge `base`, `ours` and `theirs` by `strategy`. A clash keeps ours. */
|
|
269
274
|
declare function mergeTree(strategy: Strategy, base: unknown, ours: unknown, theirs: unknown, opts?: TreeMergeOptions): TreeMergeResult;
|
|
275
|
+
/**
|
|
276
|
+
* Three-way merge two lists of comment threads, the shape both families'
|
|
277
|
+
* comments share: `{ id, messages: [{ author, ts, body, deleted? }], resolved?,
|
|
278
|
+
* ...}`. Lifted from Patter's merge (patterkit design/family-parity-2026-10.md
|
|
279
|
+
* item 5), where a thread merged whole made two replies to one thread clash.
|
|
280
|
+
*
|
|
281
|
+
* - Messages are keyed by author and timestamp, and are the union of both
|
|
282
|
+
* sides', oldest first. A withdrawn message (a tombstone: body emptied,
|
|
283
|
+
* `deleted` set) stays withdrawn whichever side withdrew it, and a message a
|
|
284
|
+
* side removed outright, or lost with its whole thread, counts as withdrawn
|
|
285
|
+
* by that side. One message edited two ways is a clash that keeps ours.
|
|
286
|
+
* - A thread's other fields (its anchor, its marker, `resolved`) merge one by
|
|
287
|
+
* one, a field changed two ways being a clash; a thread one side removed
|
|
288
|
+
* takes the other side's.
|
|
289
|
+
* - A thread with nothing readable left goes, as the editors drop it.
|
|
290
|
+
*
|
|
291
|
+
* Threads come back oldest first, by their first message, then by id.
|
|
292
|
+
*/
|
|
293
|
+
declare function mergeComments(base: unknown, ours: unknown, theirs: unknown, opts?: TreeMergeOptions): TreeMergeResult;
|
|
270
294
|
/** How a value of this type merges (the README's table). */
|
|
271
295
|
declare function strategyForType(decl: TypeDecl): Strategy;
|
|
272
296
|
/** How one node's entry merges: field by field, each by its type. */
|
|
@@ -321,4 +345,4 @@ declare const atKey: (base: string, key: string) => string;
|
|
|
321
345
|
/** `base` and then list position `i`. */
|
|
322
346
|
declare const atIndex: (base: string, i: number) => string;
|
|
323
347
|
|
|
324
|
-
export { ATOMIC, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
348
|
+
export { ATOMIC, COMMENTS, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
package/dist/extensions.d.ts
CHANGED
|
@@ -209,6 +209,8 @@ declare function renameStages(target: TargetDecl, entry: Readonly<Record<string,
|
|
|
209
209
|
* sorted by key, or in the authored order with `order: "preserve"`
|
|
210
210
|
* object(fields) named keys by their own strategy, every other key by `other`
|
|
211
211
|
* (atomic when absent)
|
|
212
|
+
* comments a list of comment threads, merged message by message
|
|
213
|
+
* (`mergeComments`)
|
|
212
214
|
*/
|
|
213
215
|
type Strategy = {
|
|
214
216
|
readonly kind: "atomic";
|
|
@@ -227,9 +229,12 @@ type Strategy = {
|
|
|
227
229
|
readonly kind: "object";
|
|
228
230
|
readonly fields: Readonly<Record<string, Strategy>>;
|
|
229
231
|
readonly other?: Strategy;
|
|
232
|
+
} | {
|
|
233
|
+
readonly kind: "comments";
|
|
230
234
|
};
|
|
231
235
|
declare const ATOMIC: Strategy;
|
|
232
236
|
declare const SET: Strategy;
|
|
237
|
+
declare const COMMENTS: Strategy;
|
|
233
238
|
declare const mapOf: (of: Strategy) => Strategy;
|
|
234
239
|
declare const keyedBy: (key: string, of: Strategy, opts?: {
|
|
235
240
|
order?: "sorted" | "preserve";
|
|
@@ -267,6 +272,25 @@ interface TreeMergeOptions {
|
|
|
267
272
|
declare function stableStringify(value: unknown): string;
|
|
268
273
|
/** Three-way merge `base`, `ours` and `theirs` by `strategy`. A clash keeps ours. */
|
|
269
274
|
declare function mergeTree(strategy: Strategy, base: unknown, ours: unknown, theirs: unknown, opts?: TreeMergeOptions): TreeMergeResult;
|
|
275
|
+
/**
|
|
276
|
+
* Three-way merge two lists of comment threads, the shape both families'
|
|
277
|
+
* comments share: `{ id, messages: [{ author, ts, body, deleted? }], resolved?,
|
|
278
|
+
* ...}`. Lifted from Patter's merge (patterkit design/family-parity-2026-10.md
|
|
279
|
+
* item 5), where a thread merged whole made two replies to one thread clash.
|
|
280
|
+
*
|
|
281
|
+
* - Messages are keyed by author and timestamp, and are the union of both
|
|
282
|
+
* sides', oldest first. A withdrawn message (a tombstone: body emptied,
|
|
283
|
+
* `deleted` set) stays withdrawn whichever side withdrew it, and a message a
|
|
284
|
+
* side removed outright, or lost with its whole thread, counts as withdrawn
|
|
285
|
+
* by that side. One message edited two ways is a clash that keeps ours.
|
|
286
|
+
* - A thread's other fields (its anchor, its marker, `resolved`) merge one by
|
|
287
|
+
* one, a field changed two ways being a clash; a thread one side removed
|
|
288
|
+
* takes the other side's.
|
|
289
|
+
* - A thread with nothing readable left goes, as the editors drop it.
|
|
290
|
+
*
|
|
291
|
+
* Threads come back oldest first, by their first message, then by id.
|
|
292
|
+
*/
|
|
293
|
+
declare function mergeComments(base: unknown, ours: unknown, theirs: unknown, opts?: TreeMergeOptions): TreeMergeResult;
|
|
270
294
|
/** How a value of this type merges (the README's table). */
|
|
271
295
|
declare function strategyForType(decl: TypeDecl): Strategy;
|
|
272
296
|
/** How one node's entry merges: field by field, each by its type. */
|
|
@@ -321,4 +345,4 @@ declare const atKey: (base: string, key: string) => string;
|
|
|
321
345
|
/** `base` and then list position `i`. */
|
|
322
346
|
declare const atIndex: (base: string, i: number) => string;
|
|
323
347
|
|
|
324
|
-
export { ATOMIC, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
348
|
+
export { ATOMIC, COMMENTS, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
package/dist/extensions.js
CHANGED
|
@@ -631,6 +631,7 @@ function effectiveClaim(claim, hostDecl) {
|
|
|
631
631
|
// src/extensions/merge.ts
|
|
632
632
|
var ATOMIC = { kind: "atomic" };
|
|
633
633
|
var SET = { kind: "set" };
|
|
634
|
+
var COMMENTS = { kind: "comments" };
|
|
634
635
|
var mapOf = (of) => ({ kind: "map", of });
|
|
635
636
|
var keyedBy = (key, of, opts) => ({
|
|
636
637
|
kind: "keyed",
|
|
@@ -673,6 +674,7 @@ function hasShape(spec, v) {
|
|
|
673
674
|
return true;
|
|
674
675
|
case "set":
|
|
675
676
|
case "keyed":
|
|
677
|
+
case "comments":
|
|
676
678
|
return Array.isArray(v);
|
|
677
679
|
case "map":
|
|
678
680
|
case "object":
|
|
@@ -712,6 +714,8 @@ function mergeValue(spec, b, o, t, id, path, ctx) {
|
|
|
712
714
|
}
|
|
713
715
|
case "keyed":
|
|
714
716
|
return mergeKeyed(spec, asArr(b), o, t, path, ctx);
|
|
717
|
+
case "comments":
|
|
718
|
+
return mergeThreads(asArr(b), o, t, path, ctx);
|
|
715
719
|
}
|
|
716
720
|
}
|
|
717
721
|
function mergeKeyed(spec, B, O, T, path, ctx) {
|
|
@@ -790,6 +794,70 @@ function preservedOrder(B, O, T, survivors, path, ctx) {
|
|
|
790
794
|
for (const k of [...survivors.keys()].sort()) if (!result.includes(k)) result.push(k);
|
|
791
795
|
return result;
|
|
792
796
|
}
|
|
797
|
+
function mergeComments(base, ours, theirs, opts) {
|
|
798
|
+
return mergeTree(COMMENTS, base, ours, theirs, opts);
|
|
799
|
+
}
|
|
800
|
+
function mergeThreads(B, O, T, path, ctx) {
|
|
801
|
+
const index = (arr) => {
|
|
802
|
+
const m = /* @__PURE__ */ new Map();
|
|
803
|
+
for (const c of arr) if (isObject(c) && typeof c["id"] === "string") m.set(c["id"], c);
|
|
804
|
+
return m;
|
|
805
|
+
};
|
|
806
|
+
const Bm = index(B), Om = index(O), Tm = index(T);
|
|
807
|
+
const out = [];
|
|
808
|
+
for (const id of /* @__PURE__ */ new Set([...Bm.keys(), ...Om.keys(), ...Tm.keys()])) {
|
|
809
|
+
const b = Bm.get(id), o = Om.get(id), t = Tm.get(id);
|
|
810
|
+
const here = `${path}[${id}]`;
|
|
811
|
+
if (o === void 0 && t === void 0) continue;
|
|
812
|
+
if (![b, o, t].some((c) => Array.isArray(c?.["messages"]))) {
|
|
813
|
+
const v = o !== void 0 && t !== void 0 ? merge3(b, o, t, id, here, ctx) : b !== void 0 && ctx.eq(o ?? t, b) ? void 0 : o ?? t;
|
|
814
|
+
if (isObject(v)) out.push(v);
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
const messages = mergeMessages(b, o, t, id, here, ctx);
|
|
818
|
+
if (messages.length === 0 || messages.every((m) => m["deleted"] === true)) continue;
|
|
819
|
+
const fields = {};
|
|
820
|
+
if (o !== void 0 && t !== void 0) {
|
|
821
|
+
const B0 = b ?? {};
|
|
822
|
+
const flag = (c, k) => k === "resolved" ? c[k] === true ? true : void 0 : c[k];
|
|
823
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(o), ...Object.keys(t), ...Object.keys(B0)])) {
|
|
824
|
+
if (k === "messages") continue;
|
|
825
|
+
const v = merge3(flag(B0, k), flag(o, k), flag(t, k), id, `${here}.${k}`, ctx);
|
|
826
|
+
if (v !== void 0) fields[k] = v;
|
|
827
|
+
}
|
|
828
|
+
} else {
|
|
829
|
+
for (const [k, v] of Object.entries(o ?? t)) if (k !== "messages") fields[k] = v;
|
|
830
|
+
}
|
|
831
|
+
out.push({ ...fields, messages });
|
|
832
|
+
}
|
|
833
|
+
const first = (c) => {
|
|
834
|
+
const m = asArr(c["messages"])[0];
|
|
835
|
+
return isObject(m) && typeof m["ts"] === "string" ? m["ts"] : "";
|
|
836
|
+
};
|
|
837
|
+
return out.sort((x, y) => first(x).localeCompare(first(y)) || String(x["id"]).localeCompare(String(y["id"])));
|
|
838
|
+
}
|
|
839
|
+
function mergeMessages(b, o, t, id, here, ctx) {
|
|
840
|
+
const keyOf = (m) => `${String(m["author"] ?? "")}|${String(m["ts"] ?? "")}`;
|
|
841
|
+
const index = (c) => {
|
|
842
|
+
const m = /* @__PURE__ */ new Map();
|
|
843
|
+
for (const x of asArr(c?.["messages"])) if (isObject(x)) m.set(keyOf(x), x);
|
|
844
|
+
return m;
|
|
845
|
+
};
|
|
846
|
+
const Bm = index(b), Om = index(o), Tm = index(t);
|
|
847
|
+
const out = [];
|
|
848
|
+
for (const k of /* @__PURE__ */ new Set([...Bm.keys(), ...Om.keys(), ...Tm.keys()])) {
|
|
849
|
+
const mb = Bm.get(k), mo = Om.get(k), mt = Tm.get(k);
|
|
850
|
+
if ([mb, mo, mt].some((m) => m?.["deleted"] === true) || mb !== void 0 && (mo === void 0 || mt === void 0)) {
|
|
851
|
+
out.push({ ...mo ?? mt ?? mb, body: "", deleted: true });
|
|
852
|
+
} else if (mo !== void 0 && mt !== void 0) {
|
|
853
|
+
out.push(merge3(mb, mo, mt, id, `${here}.messages[${k}]`, ctx));
|
|
854
|
+
} else {
|
|
855
|
+
out.push(mo ?? mt);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
const ts = (m) => String(m["ts"] ?? "");
|
|
859
|
+
return out.sort((x, y) => ts(x).localeCompare(ts(y)) || String(x["author"] ?? "").localeCompare(String(y["author"] ?? "")));
|
|
860
|
+
}
|
|
793
861
|
function strategyForType(decl) {
|
|
794
862
|
switch (decl.type) {
|
|
795
863
|
case "flags":
|
|
@@ -897,6 +965,6 @@ function orphanedEntries(data, exists) {
|
|
|
897
965
|
return out;
|
|
898
966
|
}
|
|
899
967
|
|
|
900
|
-
export { ATOMIC, HOSTS, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, SCALAR_TYPES, SET, UNRESOLVED_KEY, VALUE_TYPES, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
968
|
+
export { ATOMIC, COMMENTS, HOSTS, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, SCALAR_TYPES, SET, UNRESOLVED_KEY, VALUE_TYPES, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
901
969
|
//# sourceMappingURL=extensions.js.map
|
|
902
970
|
//# sourceMappingURL=extensions.js.map
|