@reventlessdev/trait-attachments 1.0.0-alpha.1 → 1.0.0-alpha.10
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 +3 -3
- package/src/Attachments.res +83 -8
- package/src/Attachments_Conformance.res +131 -12
- package/src/Attachments_Conformance.res.mjs +79 -10
- package/src/Attachments_Rules.res +167 -17
- package/src/Attachments_Rules.res.mjs +113 -36
- package/src/Attachments_Scaffold.res +495 -193
- package/src/Attachments_Scaffold.res.mjs +390 -156
|
@@ -7,11 +7,37 @@ A host maps its own constructors onto `op` and `fact` and keeps the spec surface
|
|
|
7
7
|
the variants, their annotations, its own refusals. Nothing here knows what an
|
|
8
8
|
entity is. `Attachments_Conformance` asserts these rules through a host.
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
10
|
/** The stored file's reference. A `StorageRef` path today, so `string` carries it
|
|
12
11
|
without a wrapper the host would have to unwrap on every arm. */
|
|
13
12
|
type ref = string
|
|
14
13
|
|
|
14
|
+
// `@schema` so `Attachments_Scaffold`'s config can take this very type rather
|
|
15
|
+
// than a second spelling of it. A graft's config and the rule it selects being
|
|
16
|
+
// the same value is the point: a config that could say `"single"` where the
|
|
17
|
+
// rules say `Single` is a config that can be misspelled.
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
How many members a host's set may hold.
|
|
21
|
+
|
|
22
|
+
Not a runtime variation: a graft fixes this once, and every command the graft
|
|
23
|
+
emits is shaped by the answer. It reaches `decide` as an argument rather than
|
|
24
|
+
sitting on `t` so that `empty` stays one value and a host that never mentions
|
|
25
|
+
cardinality gets the behaviour it has today.
|
|
26
|
+
|
|
27
|
+
Exactly one rule reads it — `Attach` — and that is the whole of the difference:
|
|
28
|
+
a bounded set replaces its member where an unbounded one appends beside it.
|
|
29
|
+
Everything else is already cardinality-blind. `SetPrimary` on a one-member set
|
|
30
|
+
resolves to a no-op through the rules it already has (the only member is already
|
|
31
|
+
the effective primary), which is why `Single` needs no branch for it and why the
|
|
32
|
+
scaffold simply declines to emit the command rather than the rules refusing it.
|
|
33
|
+
*/
|
|
34
|
+
@schema
|
|
35
|
+
type cardinality =
|
|
36
|
+
/** An unbounded, ordered set: a gallery. */
|
|
37
|
+
| Many
|
|
38
|
+
/** At most one member, replaced rather than added to. */
|
|
39
|
+
| Single
|
|
40
|
+
|
|
15
41
|
/** Refolded per decision, never stored — a StateChangeSlice's state is. */
|
|
16
42
|
type t = {
|
|
17
43
|
attached: array<ref>,
|
|
@@ -24,11 +50,28 @@ let empty = {attached: [], primary: None, altTexts: []}
|
|
|
24
50
|
|
|
25
51
|
/** What a host asks the set to do. */
|
|
26
52
|
type op =
|
|
27
|
-
|
|
28
|
-
Attach({ref: ref, altText: option<string>})
|
|
53
|
+
/** `altText` is the caption a host may supply with the file itself. */
|
|
54
|
+
| Attach({ref: ref, altText: option<string>})
|
|
29
55
|
| Remove({ref: ref})
|
|
56
|
+
/** Empty the set, whatever it holds.
|
|
57
|
+
|
|
58
|
+
The op a bounded set's remove command maps onto: with one member there is
|
|
59
|
+
no ref for the caller to name, and naming it would be asking them to
|
|
60
|
+
repeat what the row already says. Well defined at either cardinality —
|
|
61
|
+
an unbounded host that wants a "remove them all" command gets it here
|
|
62
|
+
rather than by looping its own remove. */
|
|
63
|
+
| Clear
|
|
30
64
|
| SetPrimary({ref: ref})
|
|
31
65
|
| SetAltText({ref: ref, altText: string})
|
|
66
|
+
/** Caption whichever member is the primary, without naming it.
|
|
67
|
+
|
|
68
|
+
The counterpart of `Clear`, and the op a bounded set's caption command
|
|
69
|
+
maps onto — with one member there is nothing to name. It is not
|
|
70
|
+
bounded-only, though: the primary is the one image a reader actually
|
|
71
|
+
sees, so "caption the hero" is a command an unbounded host wants too,
|
|
72
|
+
and resolving it here through `effectivePrimary` is what keeps the
|
|
73
|
+
answer the same as the member a projection puts first. */
|
|
74
|
+
| SetPrimaryAltText({altText: string})
|
|
32
75
|
|
|
33
76
|
/** What the set decided, for the host to name in its own event. */
|
|
34
77
|
type fact =
|
|
@@ -36,6 +79,25 @@ type fact =
|
|
|
36
79
|
| Removed({ref: ref})
|
|
37
80
|
| PrimarySet({ref: ref})
|
|
38
81
|
| AltTextSet({ref: ref, altText: string})
|
|
82
|
+
/** The member a reader should now show, or `None` where the set has none.
|
|
83
|
+
|
|
84
|
+
**A conclusion, not a decision.** `PrimarySet` records that somebody
|
|
85
|
+
chose; this records what the set resolved to, however it got there —
|
|
86
|
+
which is a different fact, and the one anything outside the set can act
|
|
87
|
+
on. The two coincide on an explicit choice and diverge everywhere else:
|
|
88
|
+
the first attachment stands in with nobody choosing, and removing the
|
|
89
|
+
one that stood in promotes the next.
|
|
90
|
+
|
|
91
|
+
Emitted at **both** cardinalities even though a `Single` host could
|
|
92
|
+
derive it from its own `Attached`/`Removed` pair, for the reason
|
|
93
|
+
`decide` gives below for not collapsing those two: a fact that only some
|
|
94
|
+
grafts emit makes a subscriber ask how many members the host allows,
|
|
95
|
+
which is not its business.
|
|
96
|
+
|
|
97
|
+
Optional because "there is no longer one to show" is as much a change as
|
|
98
|
+
any other, and a subscriber that cannot be told it keeps showing a file
|
|
99
|
+
the set no longer holds. */
|
|
100
|
+
| EffectiveChanged({ref: option<ref>})
|
|
39
101
|
|
|
40
102
|
/** The primary a reader should show: the one chosen, else the first attached, so
|
|
41
103
|
a set never shows no file while it holds one. The read model applies the same
|
|
@@ -50,6 +112,33 @@ let effectivePrimary = t => primaryOf(~chosen=t.primary, ~attached=t.attached)
|
|
|
50
112
|
|
|
51
113
|
let altTextOf = (t, ref) => t.altTexts->Array.find(((r, _)) => r == ref)->Option.map(((_, t)) => t)
|
|
52
114
|
|
|
115
|
+
/**
|
|
116
|
+
The set with the chosen member first, for a read model projecting it onto a row.
|
|
117
|
+
|
|
118
|
+
This is where `primaryOf`'s rule lands on a view. The view carries the set and
|
|
119
|
+
nothing beside it, so *being first* is how the row says which member is the
|
|
120
|
+
primary — and "the chosen one, else the first attached" then needs no second
|
|
121
|
+
field to hold the answer and no rule to keep that field in step. Attach appends,
|
|
122
|
+
remove filters, and both leave the head alone unless they moved it; only this
|
|
123
|
+
reorders.
|
|
124
|
+
|
|
125
|
+
Sound for a projection because the order is derived: the events fully determine
|
|
126
|
+
it, so a replay reproduces it. What it costs is that attachment order stops being
|
|
127
|
+
readable off the view — the log still has it, and a consumer that wanted it back
|
|
128
|
+
would have to be given it deliberately.
|
|
129
|
+
|
|
130
|
+
Parameterised on `~ref` rather than owning the member type, for the reason
|
|
131
|
+
`primaryOf` takes two arrays: the member record is the host's own `@schema` type
|
|
132
|
+
and the field holding its reference is named for the host's store. A `chosen` the
|
|
133
|
+
set does not hold leaves the order alone — a `SetPrimary` naming a member a
|
|
134
|
+
removal already took away must not empty the row.
|
|
135
|
+
*/
|
|
136
|
+
let primaryFirst = (~chosen: ref, ~members: array<'m>, ~ref: 'm => ref): array<'m> =>
|
|
137
|
+
switch members->Array.find(m => ref(m) == chosen) {
|
|
138
|
+
| Some(primary) => Array.concat([primary], members->Array.filter(m => ref(m) != chosen))
|
|
139
|
+
| None => members
|
|
140
|
+
}
|
|
141
|
+
|
|
53
142
|
let evolve = (t, fact) =>
|
|
54
143
|
switch fact {
|
|
55
144
|
| Attached({ref, altText}) =>
|
|
@@ -69,33 +158,94 @@ let evolve = (t, fact) =>
|
|
|
69
158
|
altTexts: t.altTexts->Array.filter(((r, _)) => r != ref),
|
|
70
159
|
}
|
|
71
160
|
| PrimarySet({ref}) => {...t, primary: Some(ref)}
|
|
161
|
+
// Carries no state of its own: it is what `effectivePrimary` already answers,
|
|
162
|
+
// said out loud. Folding it would be storing a derivation beside the values it
|
|
163
|
+
// derives from, which is the one way the two could disagree.
|
|
164
|
+
| EffectiveChanged(_) => t
|
|
72
165
|
| AltTextSet({ref, altText}) => {
|
|
73
166
|
...t,
|
|
74
167
|
altTexts: t.altTexts->Array.filter(((r, _)) => r != ref)->Array.concat([(ref, altText)]),
|
|
75
168
|
}
|
|
76
169
|
}
|
|
77
170
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
171
|
+
// Shared by the two captioning ops, which differ only in how they arrive at a
|
|
172
|
+
// ref. Spelled once so a named caption and the primary's caption cannot end up
|
|
173
|
+
// with different ideas of what a repeat is.
|
|
174
|
+
let setAltText = (t, ~ref, ~altText): result<array<fact>, [#NotAttached]> =>
|
|
175
|
+
if !(t.attached->Array.includes(ref)) {
|
|
176
|
+
Error(#NotAttached)
|
|
177
|
+
} else if altTextOf(t, ref) == Some(altText) {
|
|
178
|
+
Ok([])
|
|
179
|
+
} else {
|
|
180
|
+
Ok([AltTextSet({ref, altText})])
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
What the set decided, as facts in the order they happened.
|
|
185
|
+
|
|
186
|
+
`Ok([])` is the no-op a retried command must produce; the one refusal the set
|
|
187
|
+
owns is a primary or a caption on a ref it does not hold.
|
|
188
|
+
|
|
189
|
+
An array rather than one optional fact, because a bounded set replacing its
|
|
190
|
+
member decides two things at once — the old one leaves and the new one arrives —
|
|
191
|
+
and an event log records both. Collapsing them into a single "replaced" fact
|
|
192
|
+
would make a host declare an event that only bounded hosts have, which is one
|
|
193
|
+
more way the two cardinalities' emitted surfaces could diverge.
|
|
194
|
+
*/
|
|
195
|
+
let decideFacts = (t, ~cardinality: cardinality=Many, op): result<array<fact>, [#NotAttached]> =>
|
|
81
196
|
switch op {
|
|
82
197
|
| Attach({ref, altText}) =>
|
|
83
|
-
t.attached->Array.includes(ref)
|
|
84
|
-
|
|
198
|
+
if t.attached->Array.includes(ref) {
|
|
199
|
+
Ok([])
|
|
200
|
+
} else {
|
|
201
|
+
switch cardinality {
|
|
202
|
+
| Many => Ok([Attached({ref, altText})])
|
|
203
|
+
// The whole of what `Single` changes. The members that leave are named
|
|
204
|
+
// individually rather than through `Clear` so the facts read the same
|
|
205
|
+
// whether one was there or (through some history nothing produces) more.
|
|
206
|
+
| Single =>
|
|
207
|
+
Ok(Array.concat(t.attached->Array.map(r => Removed({ref: r})), [Attached({ref, altText})]))
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
| Remove({ref}) => t.attached->Array.includes(ref) ? Ok([Removed({ref: ref})]) : Ok([])
|
|
211
|
+
| Clear => Ok(t.attached->Array.map(r => Removed({ref: r})))
|
|
85
212
|
| SetPrimary({ref}) =>
|
|
86
213
|
if !(t.attached->Array.includes(ref)) {
|
|
87
214
|
Error(#NotAttached)
|
|
88
215
|
} else if effectivePrimary(t) == Some(ref) {
|
|
89
|
-
Ok(
|
|
216
|
+
Ok([])
|
|
90
217
|
} else {
|
|
91
|
-
Ok(
|
|
218
|
+
Ok([PrimarySet({ref: ref})])
|
|
92
219
|
}
|
|
93
|
-
| SetAltText({ref, altText}) =>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
220
|
+
| SetAltText({ref, altText}) => setAltText(t, ~ref, ~altText)
|
|
221
|
+
// An empty set has no primary, so there is nothing to caption — the same
|
|
222
|
+
// refusal a named ref the set does not hold gets, and for the same reason.
|
|
223
|
+
| SetPrimaryAltText({altText}) =>
|
|
224
|
+
switch effectivePrimary(t) {
|
|
225
|
+
| Some(ref) => setAltText(t, ~ref, ~altText)
|
|
226
|
+
| None => Error(#NotAttached)
|
|
100
227
|
}
|
|
101
228
|
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
The set's own facts, followed by `EffectiveChanged` when those facts moved which
|
|
232
|
+
member a reader should show.
|
|
233
|
+
|
|
234
|
+
Appended rather than woven into each arm because the question is the same one
|
|
235
|
+
after every op: fold what was decided, ask `effectivePrimary` again, and say so
|
|
236
|
+
if the answer differs. An arm that had to remember to announce would eventually
|
|
237
|
+
be an arm that forgot — and the ops where it moves without anybody choosing (a
|
|
238
|
+
first attachment, a removal that promotes the next) are exactly the ones where
|
|
239
|
+
forgetting is easiest.
|
|
240
|
+
*/
|
|
241
|
+
let decide = (t, ~cardinality: cardinality=Many, op): result<array<fact>, [#NotAttached]> =>
|
|
242
|
+
switch decideFacts(t, ~cardinality, op) {
|
|
243
|
+
| Error(_) as e => e
|
|
244
|
+
| Ok(facts) =>
|
|
245
|
+
let after = facts->Array.reduce(t, evolve)
|
|
246
|
+
Ok(
|
|
247
|
+
effectivePrimary(t) == effectivePrimary(after)
|
|
248
|
+
? facts
|
|
249
|
+
: facts->Array.concat([EffectiveChanged({ref: effectivePrimary(after)})]),
|
|
250
|
+
)
|
|
251
|
+
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Sury from "sury";
|
|
4
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
3
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
6
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
7
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
8
|
+
|
|
9
|
+
let cardinalitySchema = Sury.union([
|
|
10
|
+
Sury.literal("Many"),
|
|
11
|
+
Sury.literal("Single")
|
|
12
|
+
]);
|
|
5
13
|
|
|
6
14
|
let empty_attached = [];
|
|
7
15
|
|
|
@@ -29,6 +37,15 @@ function altTextOf(t, ref) {
|
|
|
29
37
|
return Stdlib_Option.map(t.altTexts.find(param => param[0] === ref), param => param[1]);
|
|
30
38
|
}
|
|
31
39
|
|
|
40
|
+
function primaryFirst(chosen, members, ref) {
|
|
41
|
+
let primary = members.find(m => ref(m) === chosen);
|
|
42
|
+
if (primary !== undefined) {
|
|
43
|
+
return [Primitive_option.valFromOption(primary)].concat(members.filter(m => ref(m) !== chosen));
|
|
44
|
+
} else {
|
|
45
|
+
return members;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
function evolve(t, fact) {
|
|
33
50
|
switch (fact.TAG) {
|
|
34
51
|
case "Attached" :
|
|
@@ -68,26 +85,77 @@ function evolve(t, fact) {
|
|
|
68
85
|
fact.altText
|
|
69
86
|
]])
|
|
70
87
|
};
|
|
88
|
+
case "EffectiveChanged" :
|
|
89
|
+
return t;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function setAltText(t, ref, altText) {
|
|
94
|
+
if (t.attached.includes(ref)) {
|
|
95
|
+
if (Primitive_object.equal(altTextOf(t, ref), altText)) {
|
|
96
|
+
return {
|
|
97
|
+
TAG: "Ok",
|
|
98
|
+
_0: []
|
|
99
|
+
};
|
|
100
|
+
} else {
|
|
101
|
+
return {
|
|
102
|
+
TAG: "Ok",
|
|
103
|
+
_0: [{
|
|
104
|
+
TAG: "AltTextSet",
|
|
105
|
+
ref: ref,
|
|
106
|
+
altText: altText
|
|
107
|
+
}]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
return {
|
|
112
|
+
TAG: "Error",
|
|
113
|
+
_0: "NotAttached"
|
|
114
|
+
};
|
|
71
115
|
}
|
|
72
116
|
}
|
|
73
117
|
|
|
74
|
-
function
|
|
118
|
+
function decideFacts(t, cardinalityOpt, op) {
|
|
119
|
+
let cardinality = cardinalityOpt !== undefined ? cardinalityOpt : "Many";
|
|
120
|
+
if (typeof op !== "object") {
|
|
121
|
+
return {
|
|
122
|
+
TAG: "Ok",
|
|
123
|
+
_0: t.attached.map(r => ({
|
|
124
|
+
TAG: "Removed",
|
|
125
|
+
ref: r
|
|
126
|
+
}))
|
|
127
|
+
};
|
|
128
|
+
}
|
|
75
129
|
switch (op.TAG) {
|
|
76
130
|
case "Attach" :
|
|
77
131
|
let ref = op.ref;
|
|
78
132
|
if (t.attached.includes(ref)) {
|
|
79
133
|
return {
|
|
80
134
|
TAG: "Ok",
|
|
81
|
-
_0:
|
|
135
|
+
_0: []
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
let altText = op.altText;
|
|
139
|
+
if (cardinality === "Many") {
|
|
140
|
+
return {
|
|
141
|
+
TAG: "Ok",
|
|
142
|
+
_0: [{
|
|
143
|
+
TAG: "Attached",
|
|
144
|
+
ref: ref,
|
|
145
|
+
altText: altText
|
|
146
|
+
}]
|
|
82
147
|
};
|
|
83
148
|
} else {
|
|
84
149
|
return {
|
|
85
150
|
TAG: "Ok",
|
|
86
|
-
_0: {
|
|
87
|
-
TAG: "
|
|
88
|
-
ref:
|
|
89
|
-
|
|
90
|
-
|
|
151
|
+
_0: t.attached.map(r => ({
|
|
152
|
+
TAG: "Removed",
|
|
153
|
+
ref: r
|
|
154
|
+
})).concat([{
|
|
155
|
+
TAG: "Attached",
|
|
156
|
+
ref: ref,
|
|
157
|
+
altText: altText
|
|
158
|
+
}])
|
|
91
159
|
};
|
|
92
160
|
}
|
|
93
161
|
case "Remove" :
|
|
@@ -95,15 +163,15 @@ function decide(t, op) {
|
|
|
95
163
|
if (t.attached.includes(ref$1)) {
|
|
96
164
|
return {
|
|
97
165
|
TAG: "Ok",
|
|
98
|
-
_0: {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
166
|
+
_0: [{
|
|
167
|
+
TAG: "Removed",
|
|
168
|
+
ref: ref$1
|
|
169
|
+
}]
|
|
102
170
|
};
|
|
103
171
|
} else {
|
|
104
172
|
return {
|
|
105
173
|
TAG: "Ok",
|
|
106
|
-
_0:
|
|
174
|
+
_0: []
|
|
107
175
|
};
|
|
108
176
|
}
|
|
109
177
|
case "SetPrimary" :
|
|
@@ -112,15 +180,15 @@ function decide(t, op) {
|
|
|
112
180
|
if (Primitive_object.equal(effectivePrimary(t), ref$2)) {
|
|
113
181
|
return {
|
|
114
182
|
TAG: "Ok",
|
|
115
|
-
_0:
|
|
183
|
+
_0: []
|
|
116
184
|
};
|
|
117
185
|
} else {
|
|
118
186
|
return {
|
|
119
187
|
TAG: "Ok",
|
|
120
|
-
_0: {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
188
|
+
_0: [{
|
|
189
|
+
TAG: "PrimarySet",
|
|
190
|
+
ref: ref$2
|
|
191
|
+
}]
|
|
124
192
|
};
|
|
125
193
|
}
|
|
126
194
|
} else {
|
|
@@ -130,38 +198,47 @@ function decide(t, op) {
|
|
|
130
198
|
};
|
|
131
199
|
}
|
|
132
200
|
case "SetAltText" :
|
|
133
|
-
|
|
134
|
-
|
|
201
|
+
return setAltText(t, op.ref, op.altText);
|
|
202
|
+
case "SetPrimaryAltText" :
|
|
203
|
+
let ref$3 = effectivePrimary(t);
|
|
204
|
+
if (ref$3 !== undefined) {
|
|
205
|
+
return setAltText(t, ref$3, op.altText);
|
|
206
|
+
} else {
|
|
135
207
|
return {
|
|
136
208
|
TAG: "Error",
|
|
137
209
|
_0: "NotAttached"
|
|
138
210
|
};
|
|
139
211
|
}
|
|
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
212
|
}
|
|
157
213
|
}
|
|
158
214
|
|
|
215
|
+
function decide(t, cardinalityOpt, op) {
|
|
216
|
+
let cardinality = cardinalityOpt !== undefined ? cardinalityOpt : "Many";
|
|
217
|
+
let e = decideFacts(t, cardinality, op);
|
|
218
|
+
if (e.TAG !== "Ok") {
|
|
219
|
+
return e;
|
|
220
|
+
}
|
|
221
|
+
let facts = e._0;
|
|
222
|
+
let after = Stdlib_Array.reduce(facts, t, evolve);
|
|
223
|
+
return {
|
|
224
|
+
TAG: "Ok",
|
|
225
|
+
_0: Primitive_object.equal(effectivePrimary(t), effectivePrimary(after)) ? facts : facts.concat([{
|
|
226
|
+
TAG: "EffectiveChanged",
|
|
227
|
+
ref: effectivePrimary(after)
|
|
228
|
+
}])
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
159
232
|
export {
|
|
233
|
+
cardinalitySchema,
|
|
160
234
|
empty,
|
|
161
235
|
primaryOf,
|
|
162
236
|
effectivePrimary,
|
|
163
237
|
altTextOf,
|
|
238
|
+
primaryFirst,
|
|
164
239
|
evolve,
|
|
240
|
+
setAltText,
|
|
241
|
+
decideFacts,
|
|
165
242
|
decide,
|
|
166
243
|
}
|
|
167
|
-
/*
|
|
244
|
+
/* cardinalitySchema Not a pure module */
|