@reventlessdev/trait-attachments 1.0.0-alpha.2 → 1.0.0-alpha.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/trait-attachments",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.4",
4
4
  "description": "Attachments domain trait: rules, host contract, conformance suite and emitter for an ordered set of stored files on an entity, with a primary and captions",
5
5
  "license": "Apache-2.0",
6
6
  "files": [
@@ -13,8 +13,8 @@
13
13
  "dependencies": {
14
14
  "sury": "11.0.0-rc.2",
15
15
  "sury-ppx": "11.0.0-rc.2",
16
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.200",
17
- "@reventlessdev/reventless-spec": "3.0.0-alpha.126"
16
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.202",
17
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.128"
18
18
  },
19
19
  "devDependencies": {
20
20
  "rescript": "12.3.0"
@@ -64,3 +64,53 @@ module type Binding = {
64
64
  /** The refusal for a primary or caption on a ref that is not in the set. */
65
65
  let notAttached: Spec.error
66
66
  }
67
+
68
+ /**
69
+ One host of the **bounded** cardinality, bound.
70
+
71
+ A separate contract rather than optional members on {!Binding}, because what
72
+ `Attachments_Rules.Single` changes is the host's command surface and not just a
73
+ rule: there is no primary to choose between one member, so the graft declares no
74
+ such command, and there is no ref for a remove to name, so it declares none.
75
+ Both absences are facts a module type states better than a test does — a
76
+ `Single` graft that grew a `SetPrimary` command would fail to satisfy this
77
+ signature, which is the check, and it happens at compile time.
78
+
79
+ Everything the two contracts share is spelled identically on purpose: a reader
80
+ comparing them should find the difference in what is missing, not in how the
81
+ common half is phrased.
82
+ */
83
+ module type SingleBinding = {
84
+ type ref
85
+ /** Two refs that differ — the second is what replaces the first. */
86
+ let refA: ref
87
+ let refB: ref
88
+
89
+ module Spec: ReventlessGwt.Behavior_GWT.BehaviorSpec
90
+ module Behavior: ReventlessGwt.Behavior_GWT.Behavior with module Spec = Spec
91
+
92
+ /** History that brings the entity into existence with an empty set. */
93
+ let created: array<Spec.consumedEvent>
94
+ /** The set's own facts, as the slice consumes them. No `primarySetC`: nothing
95
+ emits one. */
96
+ let attachedC: ref => Spec.consumedEvent
97
+ let removedC: ref => Spec.consumedEvent
98
+ let altTextSetC: (ref, string) => Spec.consumedEvent
99
+
100
+ let attach: ref => Spec.command
101
+ /** Remove whatever is held. Takes no ref — there is only one, and asking the
102
+ caller to name it is asking them to repeat what the row already says. */
103
+ let clear: Spec.command
104
+ /** Caption whatever is held. Ref-less for `clear`'s reason, and there is a
105
+ second one here: a bounded host's view carries the reference as a scalar
106
+ and no collection at all, so there is no field for a selection to be a
107
+ member *of*. */
108
+ let setAltText: string => Spec.command
109
+
110
+ /** The set's facts, as the slice emits them. */
111
+ let attached: ref => Spec.event
112
+ let removed: ref => Spec.event
113
+ let altTextSet: (ref, string) => Spec.event
114
+ /** The refusal for a caption on a ref that is not held. */
115
+ let notAttached: Spec.error
116
+ }
@@ -6,7 +6,12 @@ inside a Jest test file registers one `describe` block over the binding.
6
6
  /** The suite's title, composed once and read twice: the suite registers it,
7
7
  and `certify-trait` computes the same string to find the run's assertions in
8
8
  a test report. Exported rather than inlined so neither side parses the
9
- other's prose. */
9
+ other's prose.
10
+
11
+ One title for both cardinalities: a host conforms to this trait or it does
12
+ not, and which of the two suites established that is not a fact a
13
+ certificate should have to know. `MakeSingle` registers under the same
14
+ name. */
10
15
  let suiteName = (host: string) => `${host} conforms to the attachments trait`
11
16
 
12
17
  module Make = (B: Attachments.Binding) => {
@@ -86,3 +91,81 @@ module Make = (B: Attachments.Binding) => {
86
91
  )
87
92
  })
88
93
  }
94
+
95
+ /**
96
+ The suite for a host of the bounded cardinality.
97
+
98
+ Smaller than `Make`'s, and smaller for a reason worth reading: most of what the
99
+ larger suite asserts is about choosing between members, and a set that holds one
100
+ member has nothing to choose. Those assertions are not skipped here — they are
101
+ unreachable, because {!Attachments.SingleBinding} does not admit the commands
102
+ that would reach them. The absent primary is checked by the compiler.
103
+
104
+ What is left is the one rule cardinality actually changes — a second attachment
105
+ replaces rather than joins — plus the ref-less remove and the captions, which a
106
+ single image wants exactly as much as a gallery member does.
107
+ */
108
+ module MakeSingle = (B: Attachments.SingleBinding) => {
109
+ module G = ReventlessGwt.Behavior_GWT.Make(B.Spec, B.Behavior)
110
+
111
+ let withA = Array.concat(B.created, [B.attachedC(B.refA)])
112
+
113
+ let register = () =>
114
+ G.describe(suiteName(B.Spec.name), () => {
115
+ G.test("the first attachment is appended", () =>
116
+ G.givenEvents(B.created)->G.whenCmd(B.attach(B.refA))->G.thenEvent(B.attached(B.refA))
117
+ )
118
+
119
+ G.test("attaching the ref already held is a no-op", () =>
120
+ G.givenEvents(withA)->G.whenCmd(B.attach(B.refA))->G.thenNoEvent
121
+ )
122
+
123
+ // The assertion the cardinality exists for. Two facts, in order: what was
124
+ // there leaves before what replaces it arrives, so a reader replaying the
125
+ // log never sees the set hold both.
126
+ G.test("a second ref replaces the first rather than joining it", () =>
127
+ G.givenEvents(withA)
128
+ ->G.whenCmd(B.attach(B.refB))
129
+ ->G.thenEvents([B.removed(B.refA), B.attached(B.refB)])
130
+ )
131
+
132
+ G.test("clearing a held set removes what it holds", () =>
133
+ G.givenEvents(withA)->G.whenCmd(B.clear)->G.thenEvent(B.removed(B.refA))
134
+ )
135
+
136
+ G.test("clearing an empty set is a no-op", () =>
137
+ G.givenEvents(B.created)->G.whenCmd(B.clear)->G.thenNoEvent
138
+ )
139
+
140
+ G.test("a replaced ref can be attached again", () =>
141
+ G.givenEvents(Array.concat(withA, [B.removedC(B.refA), B.attachedC(B.refB)]))
142
+ ->G.whenCmd(B.attach(B.refA))
143
+ ->G.thenEvents([B.removed(B.refB), B.attached(B.refA)])
144
+ )
145
+
146
+ // The caption names no ref either, so what it lands on is whatever is
147
+ // held — and the assertion is that it lands on the *right* ref, which is
148
+ // the only thing a ref-less command can get wrong.
149
+ G.test("a caption lands on the ref that is held", () =>
150
+ G.givenEvents(withA)
151
+ ->G.whenCmd(B.setAltText("front"))
152
+ ->G.thenEvent(B.altTextSet(B.refA, "front"))
153
+ )
154
+
155
+ G.test("a caption follows a replacement onto the new ref", () =>
156
+ G.givenEvents(Array.concat(withA, [B.removedC(B.refA), B.attachedC(B.refB)]))
157
+ ->G.whenCmd(B.setAltText("side"))
158
+ ->G.thenEvent(B.altTextSet(B.refB, "side"))
159
+ )
160
+
161
+ G.test("captioning an empty set is refused", () =>
162
+ G.givenEvents(B.created)->G.whenCmd(B.setAltText("front"))->G.thenError(B.notAttached)
163
+ )
164
+
165
+ G.test("repeating the caption is a no-op", () =>
166
+ G.givenEvents(Array.concat(withA, [B.altTextSetC(B.refA, "front")]))
167
+ ->G.whenCmd(B.setAltText("front"))
168
+ ->G.thenNoEvent
169
+ )
170
+ })
171
+ }
@@ -42,8 +42,48 @@ function Make(B) {
42
42
  };
43
43
  }
44
44
 
45
+ function MakeSingle(B) {
46
+ let $$let = B.Behavior;
47
+ let G = Behavior_GWT$ReventlessGwt.Make(B.Spec)({
48
+ initialState: $$let.initialState,
49
+ evolve: $$let.evolve,
50
+ decide: $$let.decide
51
+ });
52
+ let withA = B.created.concat([B.attachedC(B.refA)]);
53
+ let register = () => G.describe(suiteName(B.Spec.name), () => {
54
+ G.test("the first attachment is appended", () => G.thenEvent(G.whenCmd(G.givenEvents(B.created), B.attach(B.refA)), B.attached(B.refA)));
55
+ G.test("attaching the ref already held is a no-op", () => G.thenNoEvent(G.whenCmd(G.givenEvents(withA), B.attach(B.refA))));
56
+ G.test("a second ref replaces the first rather than joining it", () => G.thenEvents(G.whenCmd(G.givenEvents(withA), B.attach(B.refB)), [
57
+ B.removed(B.refA),
58
+ B.attached(B.refB)
59
+ ]));
60
+ G.test("clearing a held set removes what it holds", () => G.thenEvent(G.whenCmd(G.givenEvents(withA), B.clear), B.removed(B.refA)));
61
+ G.test("clearing an empty set is a no-op", () => G.thenNoEvent(G.whenCmd(G.givenEvents(B.created), B.clear)));
62
+ G.test("a replaced ref can be attached again", () => G.thenEvents(G.whenCmd(G.givenEvents(withA.concat([
63
+ B.removedC(B.refA),
64
+ B.attachedC(B.refB)
65
+ ])), B.attach(B.refA)), [
66
+ B.removed(B.refB),
67
+ B.attached(B.refA)
68
+ ]));
69
+ G.test("a caption lands on the ref that is held", () => G.thenEvent(G.whenCmd(G.givenEvents(withA), B.setAltText("front")), B.altTextSet(B.refA, "front")));
70
+ G.test("a caption follows a replacement onto the new ref", () => G.thenEvent(G.whenCmd(G.givenEvents(withA.concat([
71
+ B.removedC(B.refA),
72
+ B.attachedC(B.refB)
73
+ ])), B.setAltText("side")), B.altTextSet(B.refB, "side")));
74
+ G.test("captioning an empty set is refused", () => G.thenError(G.whenCmd(G.givenEvents(B.created), B.setAltText("front")), B.notAttached));
75
+ G.test("repeating the caption is a no-op", () => G.thenNoEvent(G.whenCmd(G.givenEvents(withA.concat([B.altTextSetC(B.refA, "front")])), B.setAltText("front"))));
76
+ });
77
+ return {
78
+ G: G,
79
+ withA: withA,
80
+ register: register
81
+ };
82
+ }
83
+
45
84
  export {
46
85
  suiteName,
47
86
  Make,
87
+ MakeSingle,
48
88
  }
49
89
  /* Behavior_GWT-ReventlessGwt Not a pure module */
@@ -12,6 +12,30 @@ entity is. `Attachments_Conformance` asserts these rules through a host.
12
12
  without a wrapper the host would have to unwrap on every arm. */
13
13
  type ref = string
14
14
 
15
+ /**
16
+ How many members a host's set may hold.
17
+
18
+ Not a runtime variation: a graft fixes this once, and every command the graft
19
+ emits is shaped by the answer. It reaches `decide` as an argument rather than
20
+ sitting on `t` so that `empty` stays one value and a host that never mentions
21
+ cardinality gets the behaviour it has today.
22
+
23
+ Exactly one rule reads it — `Attach` — and that is the whole of the difference:
24
+ a bounded set replaces its member where an unbounded one appends beside it.
25
+ Everything else is already cardinality-blind. `SetPrimary` on a one-member set
26
+ resolves to a no-op through the rules it already has (the only member is already
27
+ the effective primary), which is why `Single` needs no branch for it and why the
28
+ scaffold simply declines to emit the command rather than the rules refusing it.
29
+ */
30
+ // `@schema` so `Attachments_Scaffold`'s config can take this very type rather
31
+ // than a second spelling of it. A graft's config and the rule it selects being
32
+ // the same value is the point: a config that could say `"single"` where the
33
+ // rules say `Single` is a config that can be misspelled.
34
+ @schema
35
+ type cardinality =
36
+ | /** An unbounded, ordered set: a gallery. */ Many
37
+ | /** At most one member, replaced rather than added to. */ Single
38
+
15
39
  /** Refolded per decision, never stored — a StateChangeSlice's state is. */
16
40
  type t = {
17
41
  attached: array<ref>,
@@ -27,8 +51,25 @@ type op =
27
51
  | /** `altText` is the caption a host may supply with the file itself. */
28
52
  Attach({ref: ref, altText: option<string>})
29
53
  | Remove({ref: ref})
54
+ | /** Empty the set, whatever it holds.
55
+
56
+ The op a bounded set's remove command maps onto: with one member there is
57
+ no ref for the caller to name, and naming it would be asking them to
58
+ repeat what the row already says. Well defined at either cardinality —
59
+ an unbounded host that wants a "remove them all" command gets it here
60
+ rather than by looping its own remove. */
61
+ Clear
30
62
  | SetPrimary({ref: ref})
31
63
  | SetAltText({ref: ref, altText: string})
64
+ | /** Caption whichever member is the primary, without naming it.
65
+
66
+ The counterpart of `Clear`, and the op a bounded set's caption command
67
+ maps onto — with one member there is nothing to name. It is not
68
+ bounded-only, though: the primary is the one image a reader actually
69
+ sees, so "caption the hero" is a command an unbounded host wants too,
70
+ and resolving it here through `effectivePrimary` is what keeps the
71
+ answer the same as the member a projection puts first. */
72
+ SetPrimaryAltText({altText: string})
32
73
 
33
74
  /** What the set decided, for the host to name in its own event. */
34
75
  type fact =
@@ -50,6 +91,33 @@ let effectivePrimary = t => primaryOf(~chosen=t.primary, ~attached=t.attached)
50
91
 
51
92
  let altTextOf = (t, ref) => t.altTexts->Array.find(((r, _)) => r == ref)->Option.map(((_, t)) => t)
52
93
 
94
+ /**
95
+ The set with the chosen member first, for a read model projecting it onto a row.
96
+
97
+ This is where `primaryOf`'s rule lands on a view. The view carries the set and
98
+ nothing beside it, so *being first* is how the row says which member is the
99
+ primary — and "the chosen one, else the first attached" then needs no second
100
+ field to hold the answer and no rule to keep that field in step. Attach appends,
101
+ remove filters, and both leave the head alone unless they moved it; only this
102
+ reorders.
103
+
104
+ Sound for a projection because the order is derived: the events fully determine
105
+ it, so a replay reproduces it. What it costs is that attachment order stops being
106
+ readable off the view — the log still has it, and a consumer that wanted it back
107
+ would have to be given it deliberately.
108
+
109
+ Parameterised on `~ref` rather than owning the member type, for the reason
110
+ `primaryOf` takes two arrays: the member record is the host's own `@schema` type
111
+ and the field holding its reference is named for the host's store. A `chosen` the
112
+ set does not hold leaves the order alone — a `SetPrimary` naming a member a
113
+ removal already took away must not empty the row.
114
+ */
115
+ let primaryFirst = (~chosen: ref, ~members: array<'m>, ~ref: 'm => ref): array<'m> =>
116
+ switch members->Array.find(m => ref(m) == chosen) {
117
+ | Some(primary) => Array.concat([primary], members->Array.filter(m => ref(m) != chosen))
118
+ | None => members
119
+ }
120
+
53
121
  let evolve = (t, fact) =>
54
122
  switch fact {
55
123
  | Attached({ref, altText}) =>
@@ -75,27 +143,66 @@ let evolve = (t, fact) =>
75
143
  }
76
144
  }
77
145
 
78
- /** `Ok(None)` is the no-op a retried command must produce; the one refusal the set
79
- owns is a primary or a caption on a ref it does not hold. */
80
- let decide = (t, op): result<option<fact>, [#NotAttached]> =>
146
+ // Shared by the two captioning ops, which differ only in how they arrive at a
147
+ // ref. Spelled once so a named caption and the primary's caption cannot end up
148
+ // with different ideas of what a repeat is.
149
+ let setAltText = (t, ~ref, ~altText): result<array<fact>, [#NotAttached]> =>
150
+ if !(t.attached->Array.includes(ref)) {
151
+ Error(#NotAttached)
152
+ } else if altTextOf(t, ref) == Some(altText) {
153
+ Ok([])
154
+ } else {
155
+ Ok([AltTextSet({ref, altText})])
156
+ }
157
+
158
+ /**
159
+ What the set decided, as facts in the order they happened.
160
+
161
+ `Ok([])` is the no-op a retried command must produce; the one refusal the set
162
+ owns is a primary or a caption on a ref it does not hold.
163
+
164
+ An array rather than one optional fact, because a bounded set replacing its
165
+ member decides two things at once — the old one leaves and the new one arrives —
166
+ and an event log records both. Collapsing them into a single "replaced" fact
167
+ would make a host declare an event that only bounded hosts have, which is one
168
+ more way the two cardinalities' emitted surfaces could diverge.
169
+ */
170
+ let decide = (t, ~cardinality: cardinality=Many, op): result<array<fact>, [#NotAttached]> =>
81
171
  switch op {
82
172
  | Attach({ref, altText}) =>
83
- t.attached->Array.includes(ref) ? Ok(None) : Ok(Some(Attached({ref, altText})))
84
- | Remove({ref}) => t.attached->Array.includes(ref) ? Ok(Some(Removed({ref: ref}))) : Ok(None)
173
+ if t.attached->Array.includes(ref) {
174
+ Ok([])
175
+ } else {
176
+ switch cardinality {
177
+ | Many => Ok([Attached({ref, altText})])
178
+ // The whole of what `Single` changes. The members that leave are named
179
+ // individually rather than through `Clear` so the facts read the same
180
+ // whether one was there or (through some history nothing produces) more.
181
+ | Single =>
182
+ Ok(
183
+ Array.concat(
184
+ t.attached->Array.map(r => Removed({ref: r})),
185
+ [Attached({ref, altText})],
186
+ ),
187
+ )
188
+ }
189
+ }
190
+ | Remove({ref}) => t.attached->Array.includes(ref) ? Ok([Removed({ref: ref})]) : Ok([])
191
+ | Clear => Ok(t.attached->Array.map(r => Removed({ref: r})))
85
192
  | SetPrimary({ref}) =>
86
193
  if !(t.attached->Array.includes(ref)) {
87
194
  Error(#NotAttached)
88
195
  } else if effectivePrimary(t) == Some(ref) {
89
- Ok(None)
196
+ Ok([])
90
197
  } else {
91
- Ok(Some(PrimarySet({ref: ref})))
198
+ Ok([PrimarySet({ref: ref})])
92
199
  }
93
- | SetAltText({ref, altText}) =>
94
- if !(t.attached->Array.includes(ref)) {
95
- Error(#NotAttached)
96
- } else if altTextOf(t, ref) == Some(altText) {
97
- Ok(None)
98
- } else {
99
- Ok(Some(AltTextSet({ref, altText})))
200
+ | SetAltText({ref, altText}) => setAltText(t, ~ref, ~altText)
201
+ // An empty set has no primary, so there is nothing to caption — the same
202
+ // refusal a named ref the set does not hold gets, and for the same reason.
203
+ | SetPrimaryAltText({altText}) =>
204
+ switch effectivePrimary(t) {
205
+ | Some(ref) => setAltText(t, ~ref, ~altText)
206
+ | None => Error(#NotAttached)
100
207
  }
101
208
  }
@@ -1,7 +1,14 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
+ import * as Sury from "sury";
3
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
5
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
6
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
7
+
8
+ let cardinalitySchema = Sury.union([
9
+ Sury.literal("Many"),
10
+ Sury.literal("Single")
11
+ ]);
5
12
 
6
13
  let empty_attached = [];
7
14
 
@@ -29,6 +36,15 @@ function altTextOf(t, ref) {
29
36
  return Stdlib_Option.map(t.altTexts.find(param => param[0] === ref), param => param[1]);
30
37
  }
31
38
 
39
+ function primaryFirst(chosen, members, ref) {
40
+ let primary = members.find(m => ref(m) === chosen);
41
+ if (primary !== undefined) {
42
+ return [Primitive_option.valFromOption(primary)].concat(members.filter(m => ref(m) !== chosen));
43
+ } else {
44
+ return members;
45
+ }
46
+ }
47
+
32
48
  function evolve(t, fact) {
33
49
  switch (fact.TAG) {
34
50
  case "Attached" :
@@ -71,23 +87,72 @@ function evolve(t, fact) {
71
87
  }
72
88
  }
73
89
 
74
- function decide(t, op) {
90
+ function setAltText(t, ref, altText) {
91
+ if (t.attached.includes(ref)) {
92
+ if (Primitive_object.equal(altTextOf(t, ref), altText)) {
93
+ return {
94
+ TAG: "Ok",
95
+ _0: []
96
+ };
97
+ } else {
98
+ return {
99
+ TAG: "Ok",
100
+ _0: [{
101
+ TAG: "AltTextSet",
102
+ ref: ref,
103
+ altText: altText
104
+ }]
105
+ };
106
+ }
107
+ } else {
108
+ return {
109
+ TAG: "Error",
110
+ _0: "NotAttached"
111
+ };
112
+ }
113
+ }
114
+
115
+ function decide(t, cardinalityOpt, op) {
116
+ let cardinality = cardinalityOpt !== undefined ? cardinalityOpt : "Many";
117
+ if (typeof op !== "object") {
118
+ return {
119
+ TAG: "Ok",
120
+ _0: t.attached.map(r => ({
121
+ TAG: "Removed",
122
+ ref: r
123
+ }))
124
+ };
125
+ }
75
126
  switch (op.TAG) {
76
127
  case "Attach" :
77
128
  let ref = op.ref;
78
129
  if (t.attached.includes(ref)) {
79
130
  return {
80
131
  TAG: "Ok",
81
- _0: undefined
132
+ _0: []
133
+ };
134
+ }
135
+ let altText = op.altText;
136
+ if (cardinality === "Many") {
137
+ return {
138
+ TAG: "Ok",
139
+ _0: [{
140
+ TAG: "Attached",
141
+ ref: ref,
142
+ altText: altText
143
+ }]
82
144
  };
83
145
  } else {
84
146
  return {
85
147
  TAG: "Ok",
86
- _0: {
87
- TAG: "Attached",
88
- ref: ref,
89
- altText: op.altText
90
- }
148
+ _0: t.attached.map(r => ({
149
+ TAG: "Removed",
150
+ ref: r
151
+ })).concat([{
152
+ TAG: "Attached",
153
+ ref: ref,
154
+ altText: altText
155
+ }])
91
156
  };
92
157
  }
93
158
  case "Remove" :
@@ -95,15 +160,15 @@ function decide(t, op) {
95
160
  if (t.attached.includes(ref$1)) {
96
161
  return {
97
162
  TAG: "Ok",
98
- _0: {
99
- TAG: "Removed",
100
- ref: ref$1
101
- }
163
+ _0: [{
164
+ TAG: "Removed",
165
+ ref: ref$1
166
+ }]
102
167
  };
103
168
  } else {
104
169
  return {
105
170
  TAG: "Ok",
106
- _0: undefined
171
+ _0: []
107
172
  };
108
173
  }
109
174
  case "SetPrimary" :
@@ -112,15 +177,15 @@ function decide(t, op) {
112
177
  if (Primitive_object.equal(effectivePrimary(t), ref$2)) {
113
178
  return {
114
179
  TAG: "Ok",
115
- _0: undefined
180
+ _0: []
116
181
  };
117
182
  } else {
118
183
  return {
119
184
  TAG: "Ok",
120
- _0: {
121
- TAG: "PrimarySet",
122
- ref: ref$2
123
- }
185
+ _0: [{
186
+ TAG: "PrimarySet",
187
+ ref: ref$2
188
+ }]
124
189
  };
125
190
  }
126
191
  } else {
@@ -130,38 +195,29 @@ function decide(t, op) {
130
195
  };
131
196
  }
132
197
  case "SetAltText" :
133
- let ref$3 = op.ref;
134
- if (!t.attached.includes(ref$3)) {
198
+ return setAltText(t, op.ref, op.altText);
199
+ case "SetPrimaryAltText" :
200
+ let ref$3 = effectivePrimary(t);
201
+ if (ref$3 !== undefined) {
202
+ return setAltText(t, ref$3, op.altText);
203
+ } else {
135
204
  return {
136
205
  TAG: "Error",
137
206
  _0: "NotAttached"
138
207
  };
139
208
  }
140
- let altText = op.altText;
141
- if (Primitive_object.equal(altTextOf(t, ref$3), altText)) {
142
- return {
143
- TAG: "Ok",
144
- _0: undefined
145
- };
146
- } else {
147
- return {
148
- TAG: "Ok",
149
- _0: {
150
- TAG: "AltTextSet",
151
- ref: ref$3,
152
- altText: altText
153
- }
154
- };
155
- }
156
209
  }
157
210
  }
158
211
 
159
212
  export {
213
+ cardinalitySchema,
160
214
  empty,
161
215
  primaryOf,
162
216
  effectivePrimary,
163
217
  altTextOf,
218
+ primaryFirst,
164
219
  evolve,
220
+ setAltText,
165
221
  decide,
166
222
  }
167
- /* No side effect */
223
+ /* cardinalitySchema Not a pure module */