@thomasfosterau/effect-richtext 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +118 -2
  3. package/dist/_virtual/_rolldown/runtime.js +12 -0
  4. package/dist/commands/commands.d.ts +323 -0
  5. package/dist/commands/commands.js +722 -0
  6. package/dist/commands/internal.js +43 -0
  7. package/dist/commands/keymaps.d.ts +46 -0
  8. package/dist/commands/keymaps.js +64 -0
  9. package/dist/commands.d.ts +3 -0
  10. package/dist/commands.js +3 -0
  11. package/dist/compat/commands.d.ts +250 -0
  12. package/dist/compat/commands.js +275 -0
  13. package/dist/compat/internal/core.d.ts +20 -0
  14. package/dist/compat/internal/core.js +148 -0
  15. package/dist/compat/internal/props.js +82 -0
  16. package/dist/compat/keymap.d.ts +21 -0
  17. package/dist/compat/keymap.js +38 -0
  18. package/dist/compat/model.d.ts +893 -0
  19. package/dist/compat/model.js +1212 -0
  20. package/dist/compat/orderedmap.d.ts +74 -0
  21. package/dist/compat/orderedmap.js +124 -0
  22. package/dist/compat/schema-basic.d.ts +38 -0
  23. package/dist/compat/schema-basic.js +238 -0
  24. package/dist/compat/schema-list.d.ts +81 -0
  25. package/dist/compat/schema-list.js +156 -0
  26. package/dist/compat/state.d.ts +415 -0
  27. package/dist/compat/state.js +689 -0
  28. package/dist/compat/test-builder.d.ts +98 -0
  29. package/dist/compat/test-builder.js +197 -0
  30. package/dist/compat/transform.d.ts +458 -0
  31. package/dist/compat/transform.js +803 -0
  32. package/dist/compat/view.d.ts +411 -0
  33. package/dist/compat/view.js +409 -0
  34. package/dist/dropcursor.d.ts +46 -0
  35. package/dist/dropcursor.js +176 -0
  36. package/dist/extensions/defaults.d.ts +716 -0
  37. package/dist/extensions/defaults.js +209 -0
  38. package/dist/extensions/editor.d.ts +246 -0
  39. package/dist/extensions/editor.js +271 -0
  40. package/dist/extensions/extension.d.ts +375 -0
  41. package/dist/extensions/extension.js +233 -0
  42. package/dist/extensions.d.ts +4 -0
  43. package/dist/extensions.js +4 -0
  44. package/dist/gapcursor.d.ts +66 -0
  45. package/dist/gapcursor.js +273 -0
  46. package/dist/history.d.ts +178 -0
  47. package/dist/history.js +412 -0
  48. package/dist/html.d.ts +158 -0
  49. package/dist/html.js +164 -0
  50. package/dist/inputrules.d.ts +271 -0
  51. package/dist/inputrules.js +321 -0
  52. package/dist/internal/attempt.js +15 -0
  53. package/dist/internal/json.js +29 -0
  54. package/dist/keymap/keymap.d.ts +130 -0
  55. package/dist/keymap/keymap.js +159 -0
  56. package/dist/keymap.d.ts +2 -0
  57. package/dist/keymap.js +1 -0
  58. package/dist/markdown/errors.d.ts +23 -0
  59. package/dist/markdown/errors.js +26 -0
  60. package/dist/markdown/from_markdown.d.ts +185 -0
  61. package/dist/markdown/from_markdown.js +266 -0
  62. package/dist/markdown/schema.d.ts +522 -0
  63. package/dist/markdown/schema.js +292 -0
  64. package/dist/markdown/to_markdown.d.ts +251 -0
  65. package/dist/markdown/to_markdown.js +413 -0
  66. package/dist/markdown.d.ts +5 -0
  67. package/dist/markdown.js +5 -0
  68. package/dist/model/attrsCodec.js +48 -0
  69. package/dist/model/comparedeep.js +17 -0
  70. package/dist/model/content.d.ts +232 -0
  71. package/dist/model/content.js +630 -0
  72. package/dist/model/diff.js +66 -0
  73. package/dist/model/dom.d.ts +3 -0
  74. package/dist/model/errors.d.ts +90 -0
  75. package/dist/model/errors.js +76 -0
  76. package/dist/model/fragment.d.ts +131 -0
  77. package/dist/model/fragment.js +262 -0
  78. package/dist/model/from_dom.d.ts +299 -0
  79. package/dist/model/from_dom.js +618 -0
  80. package/dist/model/json.d.ts +37 -0
  81. package/dist/model/json.js +126 -0
  82. package/dist/model/mark.d.ts +73 -0
  83. package/dist/model/mark.js +121 -0
  84. package/dist/model/node.d.ts +267 -0
  85. package/dist/model/node.js +434 -0
  86. package/dist/model/replace.d.ts +78 -0
  87. package/dist/model/replace.js +207 -0
  88. package/dist/model/resolvedpos.d.ts +196 -0
  89. package/dist/model/resolvedpos.js +305 -0
  90. package/dist/model/schema.d.ts +740 -0
  91. package/dist/model/schema.js +451 -0
  92. package/dist/model/to_dom.d.ts +169 -0
  93. package/dist/model/to_dom.js +238 -0
  94. package/dist/model.d.ts +12 -0
  95. package/dist/model.js +12 -0
  96. package/dist/placeholder.d.ts +53 -0
  97. package/dist/placeholder.js +79 -0
  98. package/dist/reactive/observer.d.ts +54 -0
  99. package/dist/reactive/observer.js +61 -0
  100. package/dist/reactive/selectors.d.ts +22 -0
  101. package/dist/reactive/selectors.js +61 -0
  102. package/dist/reactive/stateref.d.ts +72 -0
  103. package/dist/reactive/stateref.js +71 -0
  104. package/dist/reactive/streams.d.ts +83 -0
  105. package/dist/reactive/streams.js +85 -0
  106. package/dist/reactive.d.ts +16 -0
  107. package/dist/reactive.js +26 -0
  108. package/dist/schema-basic.d.ts +448 -0
  109. package/dist/schema-basic.js +269 -0
  110. package/dist/schema-list/commands.d.ts +66 -0
  111. package/dist/schema-list/commands.js +203 -0
  112. package/dist/schema-list.d.ts +92 -0
  113. package/dist/schema-list.js +100 -0
  114. package/dist/state/errors.d.ts +39 -0
  115. package/dist/state/errors.js +32 -0
  116. package/dist/state/plugin.d.ts +232 -0
  117. package/dist/state/plugin.js +82 -0
  118. package/dist/state/selection.d.ts +297 -0
  119. package/dist/state/selection.js +486 -0
  120. package/dist/state/state.d.ts +212 -0
  121. package/dist/state/state.js +489 -0
  122. package/dist/state/transaction.d.ts +145 -0
  123. package/dist/state/transaction.js +195 -0
  124. package/dist/state.d.ts +6 -0
  125. package/dist/state.js +6 -0
  126. package/dist/test-builder.d.ts +3582 -0
  127. package/dist/test-builder.js +239 -0
  128. package/dist/transform/attr_step.d.ts +60 -0
  129. package/dist/transform/attr_step.js +105 -0
  130. package/dist/transform/errors.d.ts +50 -0
  131. package/dist/transform/errors.js +35 -0
  132. package/dist/transform/map.d.ts +182 -0
  133. package/dist/transform/map.js +280 -0
  134. package/dist/transform/mark.js +98 -0
  135. package/dist/transform/mark_set_step.js +69 -0
  136. package/dist/transform/mark_step.d.ts +113 -0
  137. package/dist/transform/mark_step.js +225 -0
  138. package/dist/transform/replace.d.ts +18 -0
  139. package/dist/transform/replace.js +381 -0
  140. package/dist/transform/replace_step.d.ts +109 -0
  141. package/dist/transform/replace_step.js +175 -0
  142. package/dist/transform/step.d.ts +157 -0
  143. package/dist/transform/step.js +168 -0
  144. package/dist/transform/structure.d.ts +89 -0
  145. package/dist/transform/structure.js +350 -0
  146. package/dist/transform/transform.d.ts +248 -0
  147. package/dist/transform/transform.js +373 -0
  148. package/dist/transform.d.ts +10 -0
  149. package/dist/transform.js +10 -0
  150. package/dist/view/browser.js +39 -0
  151. package/dist/view/capturekeys.js +282 -0
  152. package/dist/view/clipboard.d.ts +8 -0
  153. package/dist/view/clipboard.js +245 -0
  154. package/dist/view/decoration.d.ts +339 -0
  155. package/dist/view/decoration.js +566 -0
  156. package/dist/view/dom.d.ts +18 -0
  157. package/dist/view/dom.js +140 -0
  158. package/dist/view/domchange.js +295 -0
  159. package/dist/view/domcoords.d.ts +22 -0
  160. package/dist/view/domcoords.js +448 -0
  161. package/dist/view/domobserver.d.ts +47 -0
  162. package/dist/view/domobserver.js +328 -0
  163. package/dist/view/errors.d.ts +18 -0
  164. package/dist/view/errors.js +21 -0
  165. package/dist/view/input.d.ts +58 -0
  166. package/dist/view/input.js +704 -0
  167. package/dist/view/selection.js +198 -0
  168. package/dist/view/view.d.ts +699 -0
  169. package/dist/view/view.js +623 -0
  170. package/dist/view/viewdesc.d.ts +236 -0
  171. package/dist/view/viewdesc.js +1205 -0
  172. package/dist/view-css.d.ts +43 -0
  173. package/dist/view-css.js +124 -0
  174. package/dist/view.d.ts +6 -0
  175. package/dist/view.js +4 -0
  176. package/package.json +206 -3
@@ -0,0 +1,109 @@
1
+ import { Slice } from "../model/replace.js";
2
+ import { AnySchema } from "../model/schema.js";
3
+ import { Node } from "../model/node.js";
4
+ import "../model.js";
5
+ import { Mappable, StepMap } from "./map.js";
6
+ import { Step, StepJSON, StepResult } from "./step.js";
7
+ import * as Option from "effect/Option";
8
+ /**
9
+ * Replace a part of the document with a slice of new content.
10
+ *
11
+ * @since 0.1.0
12
+ * @category steps
13
+ */
14
+ export declare class ReplaceStep<Sc extends AnySchema = AnySchema> extends Step<Sc> {
15
+ /** The start position of the replaced range. */
16
+ readonly from: number;
17
+ /** The end position of the replaced range. */
18
+ readonly to: number;
19
+ /** The slice to insert. */
20
+ readonly slice: Slice<Sc>;
21
+ /** @internal */
22
+ readonly structure: boolean;
23
+ /**
24
+ * The given `slice` should fit the 'gap' between `from` and `to`—the
25
+ * depths must line up, and the surrounding nodes must be able to be
26
+ * joined with the open sides of the slice. When `structure` is true,
27
+ * the step will fail if the content between from and to is not just
28
+ * a sequence of closing and then opening tokens (this is to guard
29
+ * against rebased replace steps overwriting something they weren't
30
+ * supposed to).
31
+ */
32
+ constructor(
33
+ /** The start position of the replaced range. */
34
+ from: number,
35
+ /** The end position of the replaced range. */
36
+ to: number,
37
+ /** The slice to insert. */
38
+ slice: Slice<Sc>,
39
+ /** @internal */
40
+ structure?: boolean);
41
+ apply(doc: Node<Sc>): StepResult<Sc>;
42
+ getMap(): StepMap;
43
+ invert(doc: Node<Sc>): Step<Sc>;
44
+ map(mapping: Mappable): Option.Option<Step<Sc>>;
45
+ merge(other: Step<Sc>): Option.Option<Step<Sc>>;
46
+ toJSON(): StepJSON;
47
+ /** @internal */
48
+ static fromJSONUnsafe<Sc extends AnySchema>(schema: Sc, json: any): ReplaceStep<Sc>;
49
+ /**
50
+ * By default, for backwards compatibility, an inserting step mapped
51
+ * over an insertion at that same position fill move after the
52
+ * inserted content. In a collaborative editing situation, that can
53
+ * make redone insertions appear in unexpected places. You can set
54
+ * this to -1 to make such mapping keep the step before the insertion
55
+ * instead.
56
+ */
57
+ static MAP_BIAS: -1 | 1;
58
+ }
59
+ /**
60
+ * Replace a part of the document with a slice of content, but preserve
61
+ * a range of the replaced content by moving it into the slice.
62
+ *
63
+ * @since 0.1.0
64
+ * @category steps
65
+ */
66
+ export declare class ReplaceAroundStep<Sc extends AnySchema = AnySchema> extends Step<Sc> {
67
+ /** The start position of the replaced range. */
68
+ readonly from: number;
69
+ /** The end position of the replaced range. */
70
+ readonly to: number;
71
+ /** The start of preserved range. */
72
+ readonly gapFrom: number;
73
+ /** The end of preserved range. */
74
+ readonly gapTo: number;
75
+ /** The slice to insert. */
76
+ readonly slice: Slice<Sc>;
77
+ /** The position in the slice where the preserved range should be inserted. */
78
+ readonly insert: number;
79
+ /** @internal */
80
+ readonly structure: boolean;
81
+ /**
82
+ * Create a replace-around step with the given range and gap.
83
+ * `insert` should be the point in the slice into which the content of
84
+ * the gap should be moved. `structure` has the same meaning as it has
85
+ * in the `ReplaceStep` class.
86
+ */
87
+ constructor(
88
+ /** The start position of the replaced range. */
89
+ from: number,
90
+ /** The end position of the replaced range. */
91
+ to: number,
92
+ /** The start of preserved range. */
93
+ gapFrom: number,
94
+ /** The end of preserved range. */
95
+ gapTo: number,
96
+ /** The slice to insert. */
97
+ slice: Slice<Sc>,
98
+ /** The position in the slice where the preserved range should be inserted. */
99
+ insert: number,
100
+ /** @internal */
101
+ structure?: boolean);
102
+ apply(doc: Node<Sc>): StepResult<Sc>;
103
+ getMap(): StepMap;
104
+ invert(doc: Node<Sc>): Step<Sc>;
105
+ map(mapping: Mappable): Option.Option<Step<Sc>>;
106
+ toJSON(): StepJSON;
107
+ /** @internal */
108
+ static fromJSONUnsafe<Sc extends AnySchema>(schema: Sc, json: any): ReplaceAroundStep<Sc>;
109
+ }
@@ -0,0 +1,175 @@
1
+ import { JSONError } from "../model/errors.js";
2
+ import { Slice } from "../model/replace.js";
3
+ import { StepMap } from "./map.js";
4
+ import { Step, StepResult } from "./step.js";
5
+ import * as Option from "effect/Option";
6
+ import * as Predicate from "effect/Predicate";
7
+ // src/transform/replace_step.ts
8
+ /**
9
+ * Replace steps.
10
+ *
11
+ * @since 0.1.0
12
+ */
13
+ /**
14
+ * Replace a part of the document with a slice of new content.
15
+ *
16
+ * @since 0.1.0
17
+ * @category steps
18
+ */
19
+ var ReplaceStep = class ReplaceStep extends Step {
20
+ /**
21
+ * The given `slice` should fit the 'gap' between `from` and `to`—the
22
+ * depths must line up, and the surrounding nodes must be able to be
23
+ * joined with the open sides of the slice. When `structure` is true,
24
+ * the step will fail if the content between from and to is not just
25
+ * a sequence of closing and then opening tokens (this is to guard
26
+ * against rebased replace steps overwriting something they weren't
27
+ * supposed to).
28
+ */
29
+ constructor(from, to, slice, structure = false) {
30
+ super();
31
+ this.from = from;
32
+ this.to = to;
33
+ this.slice = slice;
34
+ this.structure = structure;
35
+ }
36
+ apply(doc) {
37
+ if (this.structure && contentBetween(doc, this.from, this.to)) return StepResult.fail("Structure replace would overwrite content");
38
+ return StepResult.fromReplace(doc, this.from, this.to, this.slice);
39
+ }
40
+ getMap() {
41
+ return new StepMap([
42
+ this.from,
43
+ this.to - this.from,
44
+ this.slice.size
45
+ ]);
46
+ }
47
+ invert(doc) {
48
+ return new ReplaceStep(this.from, this.from + this.slice.size, doc.sliceUnsafe(this.from, this.to));
49
+ }
50
+ map(mapping) {
51
+ const to = mapping.mapResult(this.to, -1);
52
+ const from = this.from === this.to && ReplaceStep.MAP_BIAS < 0 ? to : mapping.mapResult(this.from, 1);
53
+ if (from.deletedAcross && to.deletedAcross) return Option.none();
54
+ return Option.some(new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice, this.structure));
55
+ }
56
+ merge(other) {
57
+ if (!(other instanceof ReplaceStep) || other.structure || this.structure) return Option.none();
58
+ if (this.from + this.slice.size === other.from && !this.slice.openEnd && !other.slice.openStart) {
59
+ const slice = this.slice.size + other.slice.size === 0 ? Slice.empty : new Slice(this.slice.content.append(other.slice.content), this.slice.openStart, other.slice.openEnd);
60
+ return Option.some(new ReplaceStep(this.from, this.to + (other.to - other.from), slice, this.structure));
61
+ } else if (other.to === this.from && !this.slice.openStart && !other.slice.openEnd) {
62
+ const slice = this.slice.size + other.slice.size === 0 ? Slice.empty : new Slice(other.slice.content.append(this.slice.content), other.slice.openStart, this.slice.openEnd);
63
+ return Option.some(new ReplaceStep(other.from, this.to, slice, this.structure));
64
+ }
65
+ return Option.none();
66
+ }
67
+ toJSON() {
68
+ return {
69
+ stepType: "replace",
70
+ from: this.from,
71
+ to: this.to,
72
+ ...this.slice.size ? { slice: this.slice.toJSON() } : void 0,
73
+ ...this.structure ? { structure: true } : void 0
74
+ };
75
+ }
76
+ /** @internal */
77
+ static fromJSONUnsafe(schema, json) {
78
+ if (!Predicate.isNumber(json.from) || !Predicate.isNumber(json.to)) throw new JSONError({ message: "Invalid input for ReplaceStep.fromJSON" });
79
+ return new ReplaceStep(json.from, json.to, Slice.fromJSONUnsafe(schema, json.slice), !!json.structure);
80
+ }
81
+ static {
82
+ this.MAP_BIAS = 1;
83
+ }
84
+ };
85
+ Step.jsonID("replace", ReplaceStep);
86
+ /**
87
+ * Replace a part of the document with a slice of content, but preserve
88
+ * a range of the replaced content by moving it into the slice.
89
+ *
90
+ * @since 0.1.0
91
+ * @category steps
92
+ */
93
+ var ReplaceAroundStep = class ReplaceAroundStep extends Step {
94
+ /**
95
+ * Create a replace-around step with the given range and gap.
96
+ * `insert` should be the point in the slice into which the content of
97
+ * the gap should be moved. `structure` has the same meaning as it has
98
+ * in the `ReplaceStep` class.
99
+ */
100
+ constructor(from, to, gapFrom, gapTo, slice, insert, structure = false) {
101
+ super();
102
+ this.from = from;
103
+ this.to = to;
104
+ this.gapFrom = gapFrom;
105
+ this.gapTo = gapTo;
106
+ this.slice = slice;
107
+ this.insert = insert;
108
+ this.structure = structure;
109
+ }
110
+ apply(doc) {
111
+ if (this.structure && (contentBetween(doc, this.from, this.gapFrom) || contentBetween(doc, this.gapTo, this.to))) return StepResult.fail("Structure gap-replace would overwrite content");
112
+ const gap = doc.sliceUnsafe(this.gapFrom, this.gapTo);
113
+ if (gap.openStart || gap.openEnd) return StepResult.fail("Gap is not a flat range");
114
+ const inserted = this.slice.insertAt(this.insert, gap.content);
115
+ if (!inserted) return StepResult.fail("Content does not fit in gap");
116
+ return StepResult.fromReplace(doc, this.from, this.to, inserted);
117
+ }
118
+ getMap() {
119
+ return new StepMap([
120
+ this.from,
121
+ this.gapFrom - this.from,
122
+ this.insert,
123
+ this.gapTo,
124
+ this.to - this.gapTo,
125
+ this.slice.size - this.insert
126
+ ]);
127
+ }
128
+ invert(doc) {
129
+ const gap = this.gapTo - this.gapFrom;
130
+ return new ReplaceAroundStep(this.from, this.from + this.slice.size + gap, this.from + this.insert, this.from + this.insert + gap, doc.sliceUnsafe(this.from, this.to).removeBetween(this.gapFrom - this.from, this.gapTo - this.from), this.gapFrom - this.from, this.structure);
131
+ }
132
+ map(mapping) {
133
+ const from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1);
134
+ const gapFrom = this.from === this.gapFrom ? from.pos : mapping.map(this.gapFrom, -1);
135
+ const gapTo = this.to === this.gapTo ? to.pos : mapping.map(this.gapTo, 1);
136
+ if (from.deletedAcross && to.deletedAcross || gapFrom < from.pos || gapTo > to.pos || gapFrom > gapTo) return Option.none();
137
+ return Option.some(new ReplaceAroundStep(from.pos, to.pos, gapFrom, gapTo, this.slice, this.insert, this.structure));
138
+ }
139
+ toJSON() {
140
+ return {
141
+ stepType: "replaceAround",
142
+ from: this.from,
143
+ to: this.to,
144
+ gapFrom: this.gapFrom,
145
+ gapTo: this.gapTo,
146
+ insert: this.insert,
147
+ ...this.slice.size ? { slice: this.slice.toJSON() } : void 0,
148
+ ...this.structure ? { structure: true } : void 0
149
+ };
150
+ }
151
+ /** @internal */
152
+ static fromJSONUnsafe(schema, json) {
153
+ if (!Predicate.isNumber(json.from) || !Predicate.isNumber(json.to) || !Predicate.isNumber(json.gapFrom) || !Predicate.isNumber(json.gapTo) || !Predicate.isNumber(json.insert)) throw new JSONError({ message: "Invalid input for ReplaceAroundStep.fromJSON" });
154
+ return new ReplaceAroundStep(json.from, json.to, json.gapFrom, json.gapTo, Slice.fromJSONUnsafe(schema, json.slice), json.insert, !!json.structure);
155
+ }
156
+ };
157
+ Step.jsonID("replaceAround", ReplaceAroundStep);
158
+ function contentBetween(doc, from, to) {
159
+ const $from = doc.resolveUnsafe(from);
160
+ let dist = to - from, depth = $from.depth;
161
+ while (dist > 0 && depth > 0 && $from.indexAfter(depth) === $from.node(depth).childCount) {
162
+ depth--;
163
+ dist--;
164
+ }
165
+ if (dist > 0) {
166
+ let next = $from.node(depth).content.content[$from.indexAfter(depth)];
167
+ while (dist > 0) {
168
+ if (!next || next.isLeaf) return true;
169
+ next = next.content.content[0];
170
+ dist--;
171
+ }
172
+ }
173
+ return false;
174
+ }
175
+ export { ReplaceAroundStep, ReplaceStep };
@@ -0,0 +1,157 @@
1
+ import { ModelError } from "../model/errors.js";
2
+ import { Slice } from "../model/replace.js";
3
+ import { AnySchema } from "../model/schema.js";
4
+ import { Node } from "../model/node.js";
5
+ import "../model.js";
6
+ import { StepError } from "./errors.js";
7
+ import { Mappable, StepMap } from "./map.js";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Option from "effect/Option";
10
+ import * as S from "effect/Schema";
11
+ /**
12
+ * The JSON representation of a step. It always includes the step
13
+ * type's JSON id (see `Step.jsonID`) under `stepType`.
14
+ *
15
+ * @since 0.1.0
16
+ * @category models
17
+ */
18
+ export interface StepJSON {
19
+ readonly stepType: string;
20
+ readonly [key: string]: unknown;
21
+ }
22
+ /**
23
+ * Something that can deserialize steps of a given type: usually the
24
+ * step class itself, via a static `fromJSONUnsafe` method that throws
25
+ * a `JSONError` (or another model error) for invalid input.
26
+ *
27
+ * @since 0.1.0
28
+ * @category models
29
+ */
30
+ export interface StepDeserializer {
31
+ readonly fromJSONUnsafe: (schema: AnySchema, json: any) => Step<any>;
32
+ }
33
+ /**
34
+ * A step object represents an atomic change. It generally applies only
35
+ * to the document it was created for, since the positions stored in it
36
+ * will only make sense for that document.
37
+ *
38
+ * New steps are defined by creating classes that extend `Step`,
39
+ * overriding the `apply`, `invert`, `map`, `getMap` and `toJSON`
40
+ * methods (plus a static `fromJSONUnsafe`), and registering the class
41
+ * with a unique JSON-serialization identifier using `Step.jsonID`.
42
+ *
43
+ * @since 0.1.0
44
+ * @category models
45
+ */
46
+ export declare abstract class Step<Sc extends AnySchema = AnySchema> {
47
+ /**
48
+ * Applies this step to the given document, returning a result object
49
+ * that either indicates failure, if the step can not be applied to
50
+ * this document, or indicates success by containing a transformed
51
+ * document.
52
+ */
53
+ abstract apply(doc: Node<Sc>): StepResult<Sc>;
54
+ /**
55
+ * Like `apply`, but returns an Effect that fails with a `StepError`
56
+ * when the step can't be applied.
57
+ */
58
+ applyEffect(doc: Node<Sc>): Effect.Effect<Node<Sc>, StepError>;
59
+ /**
60
+ * Get the step map that represents the changes made by this step,
61
+ * and which can be used to transform between positions in the old
62
+ * and the new document.
63
+ */
64
+ getMap(): StepMap;
65
+ /**
66
+ * Create an inverted version of this step. Needs the document as it
67
+ * was before the step as argument.
68
+ */
69
+ abstract invert(doc: Node<Sc>): Step<Sc>;
70
+ /**
71
+ * Map this step through a mappable thing, returning either a version
72
+ * of that step with its positions adjusted, or `None` if the step was
73
+ * entirely deleted by the mapping.
74
+ */
75
+ abstract map(mapping: Mappable): Option.Option<Step<Sc>>;
76
+ /**
77
+ * Try to merge this step with another one, to be applied directly
78
+ * after it. Returns the merged step when possible, `None` if the
79
+ * steps can't be merged.
80
+ */
81
+ merge(_other: Step<Sc>): Option.Option<Step<Sc>>;
82
+ /**
83
+ * Create a JSON-serializeable representation of this step. When
84
+ * defining this for a custom subclass, make sure the result object
85
+ * includes the step type's JSON id under the `stepType` property.
86
+ */
87
+ abstract toJSON(): StepJSON;
88
+ /**
89
+ * Deserialize a step from its JSON representation. Will call through
90
+ * to the step class' own implementation.
91
+ */
92
+ static fromJSON<Sc extends AnySchema>(schema: Sc, json: unknown): Effect.Effect<Step<Sc>, ModelError>;
93
+ /** Like `fromJSON`, but throws a `JSONError` (or other model error) for invalid input. */
94
+ static fromJSONUnsafe<Sc extends AnySchema>(schema: Sc, json: any): Step<Sc>;
95
+ /**
96
+ * To be able to serialize steps to JSON, each step needs a string ID
97
+ * to attach to its JSON representation. Use this method to register
98
+ * an ID for your step classes. Try to pick something that's unlikely
99
+ * to clash with steps from other modules. Throws a `TransformError`
100
+ * when the ID is already in use.
101
+ */
102
+ static jsonID<C extends StepDeserializer>(id: string, stepClass: C): C;
103
+ }
104
+ /**
105
+ * The result of applying a step. Contains either a new document or a
106
+ * failure value.
107
+ *
108
+ * @since 0.1.0
109
+ * @category models
110
+ */
111
+ export declare class StepResult<Sc extends AnySchema = AnySchema> {
112
+ /** @internal */
113
+ readonly docOrNull: Node<Sc> | null;
114
+ /** @internal */
115
+ readonly failedOrNull: string | null;
116
+ /** @internal */
117
+ constructor(
118
+ /** @internal */
119
+ docOrNull: Node<Sc> | null,
120
+ /** @internal */
121
+ failedOrNull: string | null);
122
+ /** The transformed document, if successful. */
123
+ get doc(): Option.Option<Node<Sc>>;
124
+ /** The failure message, if unsuccessful. */
125
+ get failed(): Option.Option<string>;
126
+ /** Convert the result to an Effect that fails with a `StepError` for failed results. */
127
+ toEffect(): Effect.Effect<Node<Sc>, StepError>;
128
+ /** Get the resulting document, throwing a `StepError` for failed results. */
129
+ getUnsafe(): Node<Sc>;
130
+ /** Create a successful step result. */
131
+ static ok<Sc extends AnySchema>(doc: Node<Sc>): StepResult<Sc>;
132
+ /** Create a failed step result. */
133
+ static fail<Sc extends AnySchema = AnySchema>(message: string): StepResult<Sc>;
134
+ /**
135
+ * Call `Node.replace` with the given arguments. Create a successful
136
+ * result if it succeeds, and a failed one if it fails with a model
137
+ * error (such as a `ReplaceError`).
138
+ */
139
+ static fromReplace<Sc extends AnySchema>(doc: Node<Sc>, from: number, to: number, slice: Slice<Sc>): StepResult<Sc>;
140
+ }
141
+ /**
142
+ * Apply a step, converting model errors thrown by its `apply` method
143
+ * (for example for positions outside of the document) to failed
144
+ * results.
145
+ *
146
+ * @internal
147
+ */
148
+ export declare const applyStep: <Sc extends AnySchema>(step: Step<Sc>, doc: Node<Sc>) => StepResult<Sc>;
149
+ /**
150
+ * An Effect `Schema` that decodes the JSON representation of a step
151
+ * (using the `Step.jsonID` registry) into a `Step` for the given
152
+ * document schema, and encodes steps back to JSON.
153
+ *
154
+ * @since 0.1.0
155
+ * @category schemas
156
+ */
157
+ export declare const StepFromJSON: <Sc extends AnySchema>(schema: Sc) => S.Codec<Step<Sc>, StepJSON>;
@@ -0,0 +1,168 @@
1
+ import { attempt } from "../internal/attempt.js";
2
+ import { JSONError, isModelError } from "../model/errors.js";
3
+ import { StepError, TransformError } from "./errors.js";
4
+ import { StepMap } from "./map.js";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Option from "effect/Option";
7
+ import * as Predicate from "effect/Predicate";
8
+ import * as S from "effect/Schema";
9
+ import * as Result from "effect/Result";
10
+ import * as SchemaGetter from "effect/SchemaGetter";
11
+ import * as SchemaIssue from "effect/SchemaIssue";
12
+ // src/transform/step.ts
13
+ /**
14
+ * Steps: atomic, invertible, serializable document changes.
15
+ *
16
+ * @since 0.1.0
17
+ */
18
+ const stepsByID = Object.create(null);
19
+ /**
20
+ * A step object represents an atomic change. It generally applies only
21
+ * to the document it was created for, since the positions stored in it
22
+ * will only make sense for that document.
23
+ *
24
+ * New steps are defined by creating classes that extend `Step`,
25
+ * overriding the `apply`, `invert`, `map`, `getMap` and `toJSON`
26
+ * methods (plus a static `fromJSONUnsafe`), and registering the class
27
+ * with a unique JSON-serialization identifier using `Step.jsonID`.
28
+ *
29
+ * @since 0.1.0
30
+ * @category models
31
+ */
32
+ var Step = class Step {
33
+ /**
34
+ * Like `apply`, but returns an Effect that fails with a `StepError`
35
+ * when the step can't be applied.
36
+ */
37
+ applyEffect(doc) {
38
+ return Effect.suspend(() => applyStep(this, doc).toEffect());
39
+ }
40
+ /**
41
+ * Get the step map that represents the changes made by this step,
42
+ * and which can be used to transform between positions in the old
43
+ * and the new document.
44
+ */
45
+ getMap() {
46
+ return StepMap.empty;
47
+ }
48
+ /**
49
+ * Try to merge this step with another one, to be applied directly
50
+ * after it. Returns the merged step when possible, `None` if the
51
+ * steps can't be merged.
52
+ */
53
+ merge(_other) {
54
+ return Option.none();
55
+ }
56
+ /**
57
+ * Deserialize a step from its JSON representation. Will call through
58
+ * to the step class' own implementation.
59
+ */
60
+ static fromJSON(schema, json) {
61
+ return attempt(() => Step.fromJSONUnsafe(schema, json), isModelError);
62
+ }
63
+ /** Like `fromJSON`, but throws a `JSONError` (or other model error) for invalid input. */
64
+ static fromJSONUnsafe(schema, json) {
65
+ const stepType = Predicate.isObjectOrArray(json) && "stepType" in json ? json.stepType : void 0;
66
+ if (!Predicate.isString(stepType) || stepType === "") throw new JSONError({ message: "Invalid input for Step.fromJSON" });
67
+ const type = stepsByID[stepType];
68
+ if (!type) throw new JSONError({ message: `No step type ${stepType} defined` });
69
+ return type.fromJSONUnsafe(schema, json);
70
+ }
71
+ /**
72
+ * To be able to serialize steps to JSON, each step needs a string ID
73
+ * to attach to its JSON representation. Use this method to register
74
+ * an ID for your step classes. Try to pick something that's unlikely
75
+ * to clash with steps from other modules. Throws a `TransformError`
76
+ * when the ID is already in use.
77
+ */
78
+ static jsonID(id, stepClass) {
79
+ if (id in stepsByID) throw new TransformError({ message: "Duplicate use of step JSON ID " + id });
80
+ stepsByID[id] = stepClass;
81
+ const proto = "prototype" in stepClass ? stepClass.prototype : void 0;
82
+ if (proto) proto.jsonID = id;
83
+ return stepClass;
84
+ }
85
+ };
86
+ /**
87
+ * The result of applying a step. Contains either a new document or a
88
+ * failure value.
89
+ *
90
+ * @since 0.1.0
91
+ * @category models
92
+ */
93
+ var StepResult = class StepResult {
94
+ /** @internal */
95
+ constructor(docOrNull, failedOrNull) {
96
+ this.docOrNull = docOrNull;
97
+ this.failedOrNull = failedOrNull;
98
+ }
99
+ /** The transformed document, if successful. */
100
+ get doc() {
101
+ return Option.fromNullishOr(this.docOrNull);
102
+ }
103
+ /** The failure message, if unsuccessful. */
104
+ get failed() {
105
+ return Option.fromNullishOr(this.failedOrNull);
106
+ }
107
+ /** Convert the result to an Effect that fails with a `StepError` for failed results. */
108
+ toEffect() {
109
+ return this.failedOrNull !== null ? Effect.fail(new StepError({ message: this.failedOrNull })) : Effect.succeed(this.docOrNull);
110
+ }
111
+ /** Get the resulting document, throwing a `StepError` for failed results. */
112
+ getUnsafe() {
113
+ if (this.failedOrNull !== null) throw new StepError({ message: this.failedOrNull });
114
+ return this.docOrNull;
115
+ }
116
+ /** Create a successful step result. */
117
+ static ok(doc) {
118
+ return new StepResult(doc, null);
119
+ }
120
+ /** Create a failed step result. */
121
+ static fail(message) {
122
+ return new StepResult(null, message);
123
+ }
124
+ /**
125
+ * Call `Node.replace` with the given arguments. Create a successful
126
+ * result if it succeeds, and a failed one if it fails with a model
127
+ * error (such as a `ReplaceError`).
128
+ */
129
+ static fromReplace(doc, from, to, slice) {
130
+ return failOnModelError(() => StepResult.ok(doc.replaceUnsafe(from, to, slice)));
131
+ }
132
+ };
133
+ /**
134
+ * Apply a step, converting model errors thrown by its `apply` method
135
+ * (for example for positions outside of the document) to failed
136
+ * results.
137
+ *
138
+ * @internal
139
+ */
140
+ const applyStep = (step, doc) => failOnModelError(() => step.apply(doc));
141
+ /**
142
+ * Run `f`, converting a model error it throws to a failed result and
143
+ * rethrowing anything else unchanged.
144
+ */
145
+ const failOnModelError = (f) => Result.getOrElse(Result.try(f), (e) => {
146
+ if (isModelError(e)) return StepResult.fail(e.message);
147
+ throw e;
148
+ });
149
+ const StepJSONSchema = S.declare((u) => Predicate.isObjectOrArray(u) && "stepType" in u && Predicate.isString(u.stepType), { title: "StepJSON" });
150
+ /**
151
+ * An Effect `Schema` that decodes the JSON representation of a step
152
+ * (using the `Step.jsonID` registry) into a `Step` for the given
153
+ * document schema, and encodes steps back to JSON.
154
+ *
155
+ * @since 0.1.0
156
+ * @category schemas
157
+ */
158
+ const StepFromJSON = (schema) => {
159
+ const target = S.declare((u) => u instanceof Step, { title: "Step" });
160
+ return StepJSONSchema.pipe(S.decodeTo(target, {
161
+ decode: SchemaGetter.transformEffect((json) => Effect.try({
162
+ try: () => Step.fromJSONUnsafe(schema, json),
163
+ catch: (e) => new SchemaIssue.InvalidValue({ message: Predicate.isError(e) ? e.message : String(e) }, json)
164
+ })),
165
+ encode: SchemaGetter.transform((step) => step.toJSON())
166
+ }));
167
+ };
168
+ export { Step, StepFromJSON, StepResult, applyStep };