@nubbin/core 0.4.0 → 0.5.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/index.d.ts +25 -1
- package/dist/index.js +116 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1531,6 +1531,27 @@ declare class NubbinError extends Error {
|
|
|
1531
1531
|
*/
|
|
1532
1532
|
declare function parseMatchKind(route: string): RoutePointer["matchKind"];
|
|
1533
1533
|
|
|
1534
|
+
/** One value as it existed at a conflicting path. `present` distinguishes deletion from `undefined`. */
|
|
1535
|
+
interface ReconciliationValue {
|
|
1536
|
+
readonly present: boolean;
|
|
1537
|
+
readonly value?: unknown;
|
|
1538
|
+
}
|
|
1539
|
+
/** A path changed differently in both descendants of the same base. */
|
|
1540
|
+
interface DocumentConflict {
|
|
1541
|
+
readonly path: readonly string[];
|
|
1542
|
+
readonly base: ReconciliationValue;
|
|
1543
|
+
readonly local: ReconciliationValue;
|
|
1544
|
+
readonly remote: ReconciliationValue;
|
|
1545
|
+
}
|
|
1546
|
+
/** A local-first working copy plus every choice an author still has to make. */
|
|
1547
|
+
interface DocumentReconciliation {
|
|
1548
|
+
readonly version: DocumentVersion;
|
|
1549
|
+
readonly conflicts: readonly DocumentConflict[];
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
/** Reconciles two descendants of one document version without discarding either side's work. */
|
|
1553
|
+
declare function reconcileDocumentVersion(base: DocumentVersion, local: DocumentVersion, remote: DocumentVersion): DocumentReconciliation;
|
|
1554
|
+
|
|
1534
1555
|
/**
|
|
1535
1556
|
* Throws the one-cause refusal almost every Nubbin surface raises, in one line rather than
|
|
1536
1557
|
* assembling an issue array around it.
|
|
@@ -1591,6 +1612,9 @@ declare function refuse(code: NubbinIssue["code"], message: string, at?: string)
|
|
|
1591
1612
|
*/
|
|
1592
1613
|
declare function removeNode(version: DocumentVersion, nodeId: string): DocumentVersion;
|
|
1593
1614
|
|
|
1615
|
+
/** Applies one explicit local-or-remote conflict choice to a reconciled working document. */
|
|
1616
|
+
declare function resolveDocumentConflict(version: DocumentVersion, conflict: DocumentConflict, choice: "local" | "remote"): DocumentVersion;
|
|
1617
|
+
|
|
1594
1618
|
/**
|
|
1595
1619
|
* A schema `core` writes itself, such as the one `richText()` returns. It satisfies both
|
|
1596
1620
|
* `StandardSchemaV1` and `StandardJSONSchemaV1`, so a block, a catalog entry or an adapter takes
|
|
@@ -1738,4 +1762,4 @@ declare function setAtPath(target: Record<string, unknown>, path: string, value:
|
|
|
1738
1762
|
*/
|
|
1739
1763
|
declare function setNodeProp(version: DocumentVersion, nodeId: string, path: string, value: unknown): DocumentVersion;
|
|
1740
1764
|
|
|
1741
|
-
export { type Artifact, type ArtifactNode, type ArtifactStore, type Block, type BlockDrift, type BlockUi, type Catalog, type CatalogEntry, type CompatibilityReport, type CompileResult, type DocumentMeta, type DocumentVersion, type FieldHint, type FieldHintData, type FieldKind, type FieldNode, type Holes, type InferProps, type LiveRoute, type Manifest, type Node, NubbinError, type NubbinIssue, NubbinIssueCode, type PointerMove, RICH_TEXT_BLOCK_KINDS, RICH_TEXT_MARKS, type Registry, type RichText, type RichTextBlock, type RichTextBlockKind, type RichTextMark, type RichTextSpan, type RollbackCheck, type RouteIncompatibility, type RoutePointer, type SchemaAdapter, type SlotConstraint, type StandardDataSchema, type UnknownProps, addNode, checkCompatibility, checkRollback, compile, createRegistry, defineBlock, defineCatalog, formatCompatibilityReport, isRichTextBlockKind, isRichTextMark, moveNode, parseMatchKind, refuse, removeNode, richText, setAtPath, setNodeProp, zodAdapter };
|
|
1765
|
+
export { type Artifact, type ArtifactNode, type ArtifactStore, type Block, type BlockDrift, type BlockUi, type Catalog, type CatalogEntry, type CompatibilityReport, type CompileResult, type DocumentConflict, type DocumentMeta, type DocumentReconciliation, type DocumentVersion, type FieldHint, type FieldHintData, type FieldKind, type FieldNode, type Holes, type InferProps, type LiveRoute, type Manifest, type Node, NubbinError, type NubbinIssue, NubbinIssueCode, type PointerMove, RICH_TEXT_BLOCK_KINDS, RICH_TEXT_MARKS, type ReconciliationValue, type Registry, type RichText, type RichTextBlock, type RichTextBlockKind, type RichTextMark, type RichTextSpan, type RollbackCheck, type RouteIncompatibility, type RoutePointer, type SchemaAdapter, type SlotConstraint, type StandardDataSchema, type UnknownProps, addNode, checkCompatibility, checkRollback, compile, createRegistry, defineBlock, defineCatalog, formatCompatibilityReport, isRichTextBlockKind, isRichTextMark, moveNode, parseMatchKind, reconcileDocumentVersion, refuse, removeNode, resolveDocumentConflict, richText, setAtPath, setNodeProp, zodAdapter };
|
package/dist/index.js
CHANGED
|
@@ -883,7 +883,7 @@ function validateStructure(version, registry) {
|
|
|
883
883
|
}
|
|
884
884
|
|
|
885
885
|
// src/version.constants.ts
|
|
886
|
-
var NUBBIN_VERSION = "0.
|
|
886
|
+
var NUBBIN_VERSION = "0.5.0";
|
|
887
887
|
|
|
888
888
|
// src/compile.ts
|
|
889
889
|
function compile(version, catalog, registry, route) {
|
|
@@ -1112,6 +1112,97 @@ function parseMatchKind(route) {
|
|
|
1112
1112
|
return "exact";
|
|
1113
1113
|
}
|
|
1114
1114
|
|
|
1115
|
+
// src/reconciliationValueAt.ts
|
|
1116
|
+
function reconciliationValueAt(record, key) {
|
|
1117
|
+
return Object.hasOwn(record, key) ? { present: true, value: record[key] } : { present: false };
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// src/reconcileRecords.ts
|
|
1121
|
+
function reconcileRecords([base, local, remote], path, reconcile) {
|
|
1122
|
+
const merged = {};
|
|
1123
|
+
const conflicts = [];
|
|
1124
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(local), ...Object.keys(remote)]);
|
|
1125
|
+
for (const key of keys) {
|
|
1126
|
+
const result = reconcile(
|
|
1127
|
+
reconciliationValueAt(base, key),
|
|
1128
|
+
reconciliationValueAt(local, key),
|
|
1129
|
+
reconciliationValueAt(remote, key),
|
|
1130
|
+
[...path, key]
|
|
1131
|
+
);
|
|
1132
|
+
if (result.value.present) merged[key] = result.value.value;
|
|
1133
|
+
conflicts.push(...result.conflicts);
|
|
1134
|
+
}
|
|
1135
|
+
return { value: { present: true, value: merged }, conflicts };
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// src/isPlainRecord.ts
|
|
1139
|
+
function isPlainRecord(value) {
|
|
1140
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1141
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1142
|
+
return prototype === Object.prototype || prototype === null;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// src/sameReconciliationValue.ts
|
|
1146
|
+
function sameReconciliationValue(left, right) {
|
|
1147
|
+
if (left.present !== right.present) return false;
|
|
1148
|
+
if (!left.present) return true;
|
|
1149
|
+
if (Object.is(left.value, right.value)) return true;
|
|
1150
|
+
if (Array.isArray(left.value) && Array.isArray(right.value)) {
|
|
1151
|
+
const leftArray = left.value;
|
|
1152
|
+
const rightArray = right.value;
|
|
1153
|
+
return leftArray.length === rightArray.length && leftArray.every(
|
|
1154
|
+
(value, index) => sameReconciliationValue(
|
|
1155
|
+
{ present: true, value },
|
|
1156
|
+
{ present: true, value: rightArray[index] }
|
|
1157
|
+
)
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
if (!isPlainRecord(left.value) || !isPlainRecord(right.value)) return false;
|
|
1161
|
+
const leftRecord = left.value;
|
|
1162
|
+
const rightRecord = right.value;
|
|
1163
|
+
const leftKeys = Object.keys(leftRecord);
|
|
1164
|
+
const rightKeys = Object.keys(rightRecord);
|
|
1165
|
+
return leftKeys.length === rightKeys.length && leftKeys.every(
|
|
1166
|
+
(key) => Object.hasOwn(rightRecord, key) && sameReconciliationValue(
|
|
1167
|
+
{ present: true, value: leftRecord[key] },
|
|
1168
|
+
{ present: true, value: rightRecord[key] }
|
|
1169
|
+
)
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// src/toPlainRecord.ts
|
|
1174
|
+
function toPlainRecord(value) {
|
|
1175
|
+
return value.present && isPlainRecord(value.value) ? value.value : void 0;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// src/reconcileValue.ts
|
|
1179
|
+
function reconcileValue(base, local, remote, path = []) {
|
|
1180
|
+
if (sameReconciliationValue(local, remote)) return { value: local, conflicts: [] };
|
|
1181
|
+
if (sameReconciliationValue(local, base)) return { value: remote, conflicts: [] };
|
|
1182
|
+
if (sameReconciliationValue(remote, base)) return { value: local, conflicts: [] };
|
|
1183
|
+
const records = [toPlainRecord(base), toPlainRecord(local), toPlainRecord(remote)];
|
|
1184
|
+
if (records.some((value) => value === void 0)) {
|
|
1185
|
+
return { value: local, conflicts: [{ path, base, local, remote }] };
|
|
1186
|
+
}
|
|
1187
|
+
return reconcileRecords(records, path, reconcileValue);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/reconcileDocumentVersion.ts
|
|
1191
|
+
function reconcileDocumentVersion(base, local, remote) {
|
|
1192
|
+
if (base.documentId !== local.documentId || base.documentId !== remote.documentId) {
|
|
1193
|
+
throw new TypeError("cannot reconcile versions from different documents");
|
|
1194
|
+
}
|
|
1195
|
+
const result = reconcileValue(
|
|
1196
|
+
{ present: true, value: base },
|
|
1197
|
+
{ present: true, value: local },
|
|
1198
|
+
{ present: true, value: remote }
|
|
1199
|
+
);
|
|
1200
|
+
return {
|
|
1201
|
+
version: result.value.value,
|
|
1202
|
+
conflicts: result.conflicts
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1115
1206
|
// src/removeNode.ts
|
|
1116
1207
|
function removeNode(version, nodeId) {
|
|
1117
1208
|
requireNode(version, nodeId);
|
|
@@ -1126,6 +1217,28 @@ function removeNode(version, nodeId) {
|
|
|
1126
1217
|
return { ...detached, elements };
|
|
1127
1218
|
}
|
|
1128
1219
|
|
|
1220
|
+
// src/withReconciliationValueAtPath.ts
|
|
1221
|
+
function withReconciliationValueAtPath(root, path, replacement) {
|
|
1222
|
+
const [head, ...tail] = path;
|
|
1223
|
+
if (head === void 0) return root;
|
|
1224
|
+
const record = root;
|
|
1225
|
+
const result = { ...record };
|
|
1226
|
+
if (tail.length === 0) {
|
|
1227
|
+
if (replacement.present) result[head] = replacement.value;
|
|
1228
|
+
else delete result[head];
|
|
1229
|
+
return result;
|
|
1230
|
+
}
|
|
1231
|
+
const child = record[head];
|
|
1232
|
+
if (typeof child !== "object" || child === null || Array.isArray(child)) return root;
|
|
1233
|
+
result[head] = withReconciliationValueAtPath(child, tail, replacement);
|
|
1234
|
+
return result;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// src/resolveDocumentConflict.ts
|
|
1238
|
+
function resolveDocumentConflict(version, conflict, choice) {
|
|
1239
|
+
return withReconciliationValueAtPath(version, conflict.path, conflict[choice]);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1129
1242
|
// src/defineStandardSchema.ts
|
|
1130
1243
|
var STANDARD_SCHEMA_VERSION = 1;
|
|
1131
1244
|
function defineStandardSchema(issuesOf, jsonSchemaOf) {
|
|
@@ -1296,8 +1409,10 @@ export {
|
|
|
1296
1409
|
isRichTextMark,
|
|
1297
1410
|
moveNode,
|
|
1298
1411
|
parseMatchKind,
|
|
1412
|
+
reconcileDocumentVersion,
|
|
1299
1413
|
refuse,
|
|
1300
1414
|
removeNode,
|
|
1415
|
+
resolveDocumentConflict,
|
|
1301
1416
|
richText,
|
|
1302
1417
|
setAtPath,
|
|
1303
1418
|
setNodeProp,
|
package/package.json
CHANGED