@reventlessdev/trait-attachments 1.0.0-alpha.15 → 1.0.0-alpha.17

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/README.md CHANGED
@@ -43,12 +43,14 @@ Unlike the geocoding trait this one writes nothing back into its host: the graft
43
43
  ```sh
44
44
  pnpm exec graft-trait @reventlessdev/trait-attachments \
45
45
  --into src/Product --tests tests/Product \
46
- --entity Product --entityId productId --noun Image \
47
- --file productImage --created ProductAdded --view Products
46
+ --entity Product --entityId productId --entityIdType CatalogSpec.ProductId \
47
+ --noun Image --file productImage --created ProductAdded --view Products
48
48
  ```
49
49
 
50
50
  The field is named for the store it draws from, and `--noun` is what this host calls
51
- one attachment — it runs through every name the graft declares.
51
+ one attachment — it runs through every name the graft declares. `--entityIdType` names
52
+ the host's identity module, so the emitted ids are typed; leave it out for a host that
53
+ keeps its ids as strings.
52
54
 
53
55
  2. Fill the `TODO(graft)` markers. They are the host's own policy: the events its
54
56
  refusal turns on, the error it raises, and the `else if` in `decide` that raises it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/trait-attachments",
3
- "version": "1.0.0-alpha.15",
3
+ "version": "1.0.0-alpha.17",
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.213",
17
- "@reventlessdev/reventless-spec": "3.0.0-alpha.139"
16
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.140",
17
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.215"
18
18
  },
19
19
  "devDependencies": {
20
20
  "rescript": "12.3.0"
@@ -38,6 +38,9 @@ type config = {
38
38
  entity: string,
39
39
  /** Its id field: `"productId"`. */
40
40
  entityId: string,
41
+ /** The host's identity module for that id: `"ProductId"`. Absent ⇒ `string`,
42
+ for a host that has not declared one. */
43
+ entityIdType?: string,
41
44
  /**
42
45
  What this host calls one attachment, capitalised and singular: `"Image"`,
43
46
  `"Document"`, `"Attachment"`.
@@ -161,6 +164,15 @@ let namesOf = (c: config): names => {
161
164
 
162
165
  let refTypeOf = (c: config) => c.refType->Option.getOr("Reventless.UploadableImage.t")
163
166
 
167
+ // The entity id's type, and a fixture value of it.
168
+ let idTypeOf = (c: config) => c.entityIdType->Option.mapOr("string", t => t ++ ".t")
169
+
170
+ let idValueOf = (c: config, literal: string) =>
171
+ switch c.entityIdType {
172
+ | Some(t) => `${t}.make("${literal}")`
173
+ | None => `"${literal}"`
174
+ }
175
+
164
176
  // What the VIEW holds, as against what an event's reference field holds. An
165
177
  // event names a file; a view holds that file together with the text that goes
166
178
  // with it, because a cell renderer is handed a field and a value and never the
@@ -304,7 +316,7 @@ let sliceSpec = (c: config): string => {
304
316
  let attrs = commandAttributes(c)
305
317
  let createdArm =
306
318
  c.createdCarriesEntityId->Option.getOr(true)
307
- ? ` | ${c.created}({ ${c.entityId}: string})`
319
+ ? ` | ${c.created}({ ${c.entityId}: ${idTypeOf(c)}})`
308
320
  : ` | ${c.created}`
309
321
  // Present only where a choice exists. A bounded set has one member, so
310
322
  // nothing consumes or emits a primary — and the trait's rules never produce
@@ -365,16 +377,22 @@ let sliceSpec = (c: config): string => {
365
377
  ],
366
378
  `@schema`,
367
379
  `type command =`,
368
- `${attrs}${n.attachCmd}({ ${c.entityId}: string, ${c.file}: ${ref}, altText?: string})`,
380
+ `${attrs}${n.attachCmd}({ ${c.entityId}: ${idTypeOf(
381
+ c,
382
+ )}, ${c.file}: ${ref}, altText?: string})`,
369
383
  // The bounded set's remove names nothing. This is the reported defect in its
370
384
  // purest form — the old command asked for an upload in order to delete.
371
385
  single
372
- ? `${attrs}${n.removeCmd}({ ${c.entityId}: string})`
373
- : `${attrs}${n.removeCmd}({ ${c.entityId}: string, ${c.file}: ${sel}})`,
374
- ...single ? [] : [`${attrs}${n.setPrimaryCmd}({ ${c.entityId}: string, ${c.file}: ${sel}})`],
386
+ ? `${attrs}${n.removeCmd}({ ${c.entityId}: ${idTypeOf(c)}})`
387
+ : `${attrs}${n.removeCmd}({ ${c.entityId}: ${idTypeOf(c)}, ${c.file}: ${sel}})`,
388
+ ...single
389
+ ? []
390
+ : [`${attrs}${n.setPrimaryCmd}({ ${c.entityId}: ${idTypeOf(c)}, ${c.file}: ${sel}})`],
375
391
  single
376
- ? `${attrs}${n.setAltTextCmd}({ ${c.entityId}: string, altText: string})`
377
- : `${attrs}${n.setAltTextCmd}({ ${c.entityId}: string, ${c.file}: ${sel}, altText: string})`,
392
+ ? `${attrs}${n.setAltTextCmd}({ ${c.entityId}: ${idTypeOf(c)}, altText: string})`
393
+ : `${attrs}${n.setAltTextCmd}({ ${c.entityId}: ${idTypeOf(
394
+ c,
395
+ )}, ${c.file}: ${sel}, altText: string})`,
378
396
  ``,
379
397
  `@schema`,
380
398
  `type error =`,
@@ -384,14 +402,14 @@ let sliceSpec = (c: config): string => {
384
402
  ``,
385
403
  `@schema`,
386
404
  `type event =`,
387
- ` | ${n.attached}({ ${c.entityId}: string, ${c.file}: ${ref}, altText?: string})`,
388
- ` | ${n.removed}({ ${c.entityId}: string, ${c.file}: ${ref}})`,
389
- ...primaryArm(`${c.entityId}: string, `),
390
- ` | ${n.altTextSet}({ ${c.entityId}: string, ${c.file}: ${ref}, altText: string})`,
405
+ ` | ${n.attached}({ ${c.entityId}: ${idTypeOf(c)}, ${c.file}: ${ref}, altText?: string})`,
406
+ ` | ${n.removed}({ ${c.entityId}: ${idTypeOf(c)}, ${c.file}: ${ref}})`,
407
+ ...primaryArm(`${c.entityId}: ${idTypeOf(c)}, `),
408
+ ` | ${n.altTextSet}({ ${c.entityId}: ${idTypeOf(c)}, ${c.file}: ${ref}, altText: string})`,
391
409
  ` // The member a reader should now show, or none. A conclusion rather than a`,
392
410
  ` // decision: most of the moves that change it — a first attachment, a removal`,
393
411
  ` // promoting the next — are nobody's choice, so nothing else announces them.`,
394
- ` | ${n.effectiveChanged}({ ${c.entityId}: string, ${c.file}?: ${ref}})`,
412
+ ` | ${n.effectiveChanged}({ ${c.entityId}: ${idTypeOf(c)}, ${c.file}?: ${ref}})`,
395
413
  ``,
396
414
  ...commandTransitionBinding(c),
397
415
  `// The graft's own record of itself. Nothing else survives into a deployed`,
@@ -544,7 +562,7 @@ let conformanceBinding = (c: config): string => {
544
562
  let refB = c.refB->Option.getOr(`/uploads/00000000-0000-4000-8000-000000000002/b`)
545
563
  let createdValue =
546
564
  c.createdCarriesEntityId->Option.getOr(true)
547
- ? `${c.created}({ ${c.entityId}: "${id}"})`
565
+ ? `${c.created}({ ${c.entityId}: ${idValueOf(c, id)}})`
548
566
  : c.created
549
567
  lines(
550
568
  [
@@ -577,30 +595,57 @@ let conformanceBinding = (c: config): string => {
577
595
  ` ${n.effectiveChanged}({ ${c.file}: ?ref})`,
578
596
  ` }`,
579
597
  ``,
580
- ` let attach = ref => ${n.slice}.${n.attachCmd}({ ${c.entityId}: "${id}", ${c.file}: ref})`,
598
+ ` let attach = ref => ${n.slice}.${n.attachCmd}({ ${c.entityId}: ${idValueOf(
599
+ c,
600
+ id,
601
+ )}, ${c.file}: ref})`,
581
602
  ...single
582
- ? [` let clear = ${n.slice}.${n.removeCmd}({ ${c.entityId}: "${id}"})`]
603
+ ? [` let clear = ${n.slice}.${n.removeCmd}({ ${c.entityId}: ${idValueOf(c, id)}})`]
583
604
  : [
584
- ` let remove = ref => ${n.slice}.${n.removeCmd}({ ${c.entityId}: "${id}", ${c.file}: ref})`,
605
+ ` let remove = ref => ${n.slice}.${n.removeCmd}({ ${c.entityId}: ${idValueOf(
606
+ c,
607
+ id,
608
+ )}, ${c.file}: ref})`,
585
609
  ` let setPrimary = ref =>`,
586
- ` ${n.slice}.${n.setPrimaryCmd}({ ${c.entityId}: "${id}", ${c.file}: ref})`,
610
+ ` ${n.slice}.${n.setPrimaryCmd}({ ${c.entityId}: ${idValueOf(
611
+ c,
612
+ id,
613
+ )}, ${c.file}: ref})`,
587
614
  ],
588
615
  single
589
- ? ` let setAltText = altText => ${n.slice}.${n.setAltTextCmd}({ ${c.entityId}: "${id}", altText})`
590
- : ` let setAltText = (ref, altText) =>\n ${n.slice}.${n.setAltTextCmd}({ ${c.entityId}: "${id}", ${c.file}: ref, altText})`,
616
+ ? ` let setAltText = altText => ${n.slice}.${n.setAltTextCmd}({ ${c.entityId}: ${idValueOf(
617
+ c,
618
+ id,
619
+ )}, altText})`
620
+ : ` let setAltText = (ref, altText) =>\n ${n.slice}.${n.setAltTextCmd}({ ${c.entityId}: ${idValueOf(
621
+ c,
622
+ id,
623
+ )}, ${c.file}: ref, altText})`,
591
624
  ``,
592
- ` let attached = ref => ${n.slice}.${n.attached}({ ${c.entityId}: "${id}", ${c.file}: ref})`,
593
- ` let removed = ref => ${n.slice}.${n.removed}({ ${c.entityId}: "${id}", ${c.file}: ref})`,
625
+ ` let attached = ref => ${n.slice}.${n.attached}({ ${c.entityId}: ${idValueOf(
626
+ c,
627
+ id,
628
+ )}, ${c.file}: ref})`,
629
+ ` let removed = ref => ${n.slice}.${n.removed}({ ${c.entityId}: ${idValueOf(
630
+ c,
631
+ id,
632
+ )}, ${c.file}: ref})`,
594
633
  ...single
595
634
  ? []
596
635
  : [
597
636
  ` let primarySet = ref =>`,
598
- ` ${n.slice}.${n.primarySet}({ ${c.entityId}: "${id}", ${c.file}: ref})`,
637
+ ` ${n.slice}.${n.primarySet}({ ${c.entityId}: ${idValueOf(c, id)}, ${c.file}: ref})`,
599
638
  ],
600
639
  ` let altTextSet = (ref, altText) =>`,
601
- ` ${n.slice}.${n.altTextSet}({ ${c.entityId}: "${id}", ${c.file}: ref, altText})`,
640
+ ` ${n.slice}.${n.altTextSet}({ ${c.entityId}: ${idValueOf(
641
+ c,
642
+ id,
643
+ )}, ${c.file}: ref, altText})`,
602
644
  ` let effectiveChanged = ref =>`,
603
- ` ${n.slice}.${n.effectiveChanged}({ ${c.entityId}: "${id}", ${c.file}: ?ref})`,
645
+ ` ${n.slice}.${n.effectiveChanged}({ ${c.entityId}: ${idValueOf(
646
+ c,
647
+ id,
648
+ )}, ${c.file}: ?ref})`,
604
649
  ` let notAttached = ${n.slice}.${n.notAttached}`,
605
650
  `}`,
606
651
  ``,
@@ -8,6 +8,7 @@ import * as Attachments_Rules$TraitAttachments from "./Attachments_Rules.res.mjs
8
8
  let configSchema = Sury.$schema(s => ({
9
9
  entity: s.m(Sury.string),
10
10
  entityId: s.m(Sury.string),
11
+ entityIdType: s.m(Sury.$option(Sury.string)),
11
12
  noun: s.m(Sury.string),
12
13
  file: s.m(Sury.string),
13
14
  cardinality: s.m(Sury.$option(Attachments_Rules$TraitAttachments.cardinalitySchema)),
@@ -54,6 +55,19 @@ function refTypeOf(c) {
54
55
  return Stdlib_Option.getOr(c.refType, "Reventless.UploadableImage.t");
55
56
  }
56
57
 
58
+ function idTypeOf(c) {
59
+ return Stdlib_Option.mapOr(c.entityIdType, "string", t => t + ".t");
60
+ }
61
+
62
+ function idValueOf(c, literal) {
63
+ let t = c.entityIdType;
64
+ if (t !== undefined) {
65
+ return t + `.make("` + literal + `")`;
66
+ } else {
67
+ return `"` + literal + `"`;
68
+ }
69
+ }
70
+
57
71
  function viewTypeOf(c) {
58
72
  if (Stdlib_Option.getOr(c.refType, "Reventless.UploadableImage.t").includes("Image")) {
59
73
  return "Reventless.CaptionedImage.t";
@@ -156,7 +170,7 @@ function sliceSpec(c) {
156
170
  let sel = selectionTypeOf(c);
157
171
  let single = Stdlib_Option.getOr(c.cardinality, "Many") === "Single";
158
172
  let attrs = commandAttributes(c);
159
- let createdArm = Stdlib_Option.getOr(c.createdCarriesEntityId, true) ? ` | ` + c.created + `({ ` + c.entityId + `: string})` : ` | ` + c.created;
173
+ let createdArm = Stdlib_Option.getOr(c.createdCarriesEntityId, true) ? ` | ` + c.created + `({ ` + c.entityId + `: ` + idTypeOf(c) + `})` : ` | ` + c.created;
160
174
  let primaryArm = prefix => {
161
175
  if (single) {
162
176
  return [];
@@ -216,12 +230,12 @@ function sliceSpec(c) {
216
230
  [
217
231
  `@schema`,
218
232
  `type command =`,
219
- attrs + n.attachCmd + `({ ` + c.entityId + `: string, ` + c.file + `: ` + ref + `, altText?: string})`,
220
- single ? attrs + n.removeCmd + `({ ` + c.entityId + `: string})` : attrs + n.removeCmd + `({ ` + c.entityId + `: string, ` + c.file + `: ` + sel + `})`
233
+ attrs + n.attachCmd + `({ ` + c.entityId + `: ` + idTypeOf(c) + `, ` + c.file + `: ` + ref + `, altText?: string})`,
234
+ single ? attrs + n.removeCmd + `({ ` + c.entityId + `: ` + idTypeOf(c) + `})` : attrs + n.removeCmd + `({ ` + c.entityId + `: ` + idTypeOf(c) + `, ` + c.file + `: ` + sel + `})`
221
235
  ],
222
- single ? [] : [attrs + n.setPrimaryCmd + `({ ` + c.entityId + `: string, ` + c.file + `: ` + sel + `})`],
236
+ single ? [] : [attrs + n.setPrimaryCmd + `({ ` + c.entityId + `: ` + idTypeOf(c) + `, ` + c.file + `: ` + sel + `})`],
223
237
  [
224
- single ? attrs + n.setAltTextCmd + `({ ` + c.entityId + `: string, altText: string})` : attrs + n.setAltTextCmd + `({ ` + c.entityId + `: string, ` + c.file + `: ` + sel + `, altText: string})`,
238
+ single ? attrs + n.setAltTextCmd + `({ ` + c.entityId + `: ` + idTypeOf(c) + `, altText: string})` : attrs + n.setAltTextCmd + `({ ` + c.entityId + `: ` + idTypeOf(c) + `, ` + c.file + `: ` + sel + `, altText: string})`,
225
239
  ``,
226
240
  `@schema`,
227
241
  `type error =`,
@@ -231,16 +245,16 @@ function sliceSpec(c) {
231
245
  ``,
232
246
  `@schema`,
233
247
  `type event =`,
234
- ` | ` + n.attached + `({ ` + c.entityId + `: string, ` + c.file + `: ` + ref + `, altText?: string})`,
235
- ` | ` + n.removed + `({ ` + c.entityId + `: string, ` + c.file + `: ` + ref + `})`
248
+ ` | ` + n.attached + `({ ` + c.entityId + `: ` + idTypeOf(c) + `, ` + c.file + `: ` + ref + `, altText?: string})`,
249
+ ` | ` + n.removed + `({ ` + c.entityId + `: ` + idTypeOf(c) + `, ` + c.file + `: ` + ref + `})`
236
250
  ],
237
- primaryArm(c.entityId + `: string, `),
251
+ primaryArm(c.entityId + `: ` + idTypeOf(c) + `, `),
238
252
  [
239
- ` | ` + n.altTextSet + `({ ` + c.entityId + `: string, ` + c.file + `: ` + ref + `, altText: string})`,
253
+ ` | ` + n.altTextSet + `({ ` + c.entityId + `: ` + idTypeOf(c) + `, ` + c.file + `: ` + ref + `, altText: string})`,
240
254
  ` // The member a reader should now show, or none. A conclusion rather than a`,
241
255
  ` // decision: most of the moves that change it — a first attachment, a removal`,
242
256
  ` // promoting the next — are nobody's choice, so nothing else announces them.`,
243
- ` | ` + n.effectiveChanged + `({ ` + c.entityId + `: string, ` + c.file + `?: ` + ref + `})`,
257
+ ` | ` + n.effectiveChanged + `({ ` + c.entityId + `: ` + idTypeOf(c) + `, ` + c.file + `?: ` + ref + `})`,
244
258
  ``
245
259
  ],
246
260
  commandTransitionBinding(c),
@@ -371,7 +385,7 @@ function conformanceBinding(c) {
371
385
  let id = "e1";
372
386
  let refA = Stdlib_Option.getOr(c.refA, `/uploads/00000000-0000-4000-8000-000000000001/a`);
373
387
  let refB = Stdlib_Option.getOr(c.refB, `/uploads/00000000-0000-4000-8000-000000000002/b`);
374
- let createdValue = Stdlib_Option.getOr(c.createdCarriesEntityId, true) ? c.created + `({ ` + c.entityId + `: "` + id + `"})` : c.created;
388
+ let createdValue = Stdlib_Option.getOr(c.createdCarriesEntityId, true) ? c.created + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `})` : c.created;
375
389
  return Belt_Array.concatMany([
376
390
  [
377
391
  `// The Attachments trait's conformance suite, bound to \`` + n.slice + `\`.`,
@@ -401,28 +415,28 @@ function conformanceBinding(c) {
401
415
  ` ` + n.effectiveChanged + `({ ` + c.file + `: ?ref})`,
402
416
  ` }`,
403
417
  ``,
404
- ` let attach = ref => ` + n.slice + `.` + n.attachCmd + `({ ` + c.entityId + `: "` + id + `", ` + c.file + `: ref})`
418
+ ` let attach = ref => ` + n.slice + `.` + n.attachCmd + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `, ` + c.file + `: ref})`
405
419
  ],
406
- single ? [` let clear = ` + n.slice + `.` + n.removeCmd + `({ ` + c.entityId + `: "` + id + `"})`] : [
407
- ` let remove = ref => ` + n.slice + `.` + n.removeCmd + `({ ` + c.entityId + `: "` + id + `", ` + c.file + `: ref})`,
420
+ single ? [` let clear = ` + n.slice + `.` + n.removeCmd + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `})`] : [
421
+ ` let remove = ref => ` + n.slice + `.` + n.removeCmd + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `, ` + c.file + `: ref})`,
408
422
  ` let setPrimary = ref =>`,
409
- ` ` + n.slice + `.` + n.setPrimaryCmd + `({ ` + c.entityId + `: "` + id + `", ` + c.file + `: ref})`
423
+ ` ` + n.slice + `.` + n.setPrimaryCmd + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `, ` + c.file + `: ref})`
410
424
  ],
411
425
  [
412
- single ? ` let setAltText = altText => ` + n.slice + `.` + n.setAltTextCmd + `({ ` + c.entityId + `: "` + id + `", altText})` : ` let setAltText = (ref, altText) =>\n ` + n.slice + `.` + n.setAltTextCmd + `({ ` + c.entityId + `: "` + id + `", ` + c.file + `: ref, altText})`,
426
+ single ? ` let setAltText = altText => ` + n.slice + `.` + n.setAltTextCmd + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `, altText})` : ` let setAltText = (ref, altText) =>\n ` + n.slice + `.` + n.setAltTextCmd + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `, ` + c.file + `: ref, altText})`,
413
427
  ``,
414
- ` let attached = ref => ` + n.slice + `.` + n.attached + `({ ` + c.entityId + `: "` + id + `", ` + c.file + `: ref})`,
415
- ` let removed = ref => ` + n.slice + `.` + n.removed + `({ ` + c.entityId + `: "` + id + `", ` + c.file + `: ref})`
428
+ ` let attached = ref => ` + n.slice + `.` + n.attached + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `, ` + c.file + `: ref})`,
429
+ ` let removed = ref => ` + n.slice + `.` + n.removed + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `, ` + c.file + `: ref})`
416
430
  ],
417
431
  single ? [] : [
418
432
  ` let primarySet = ref =>`,
419
- ` ` + n.slice + `.` + n.primarySet + `({ ` + c.entityId + `: "` + id + `", ` + c.file + `: ref})`
433
+ ` ` + n.slice + `.` + n.primarySet + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `, ` + c.file + `: ref})`
420
434
  ],
421
435
  [
422
436
  ` let altTextSet = (ref, altText) =>`,
423
- ` ` + n.slice + `.` + n.altTextSet + `({ ` + c.entityId + `: "` + id + `", ` + c.file + `: ref, altText})`,
437
+ ` ` + n.slice + `.` + n.altTextSet + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `, ` + c.file + `: ref, altText})`,
424
438
  ` let effectiveChanged = ref =>`,
425
- ` ` + n.slice + `.` + n.effectiveChanged + `({ ` + c.entityId + `: "` + id + `", ` + c.file + `: ?ref})`,
439
+ ` ` + n.slice + `.` + n.effectiveChanged + `({ ` + c.entityId + `: ` + idValueOf(c, id) + `, ` + c.file + `: ?ref})`,
426
440
  ` let notAttached = ` + n.slice + `.` + n.notAttached,
427
441
  `}`,
428
442
  ``,
@@ -568,6 +582,8 @@ export {
568
582
  isSingle,
569
583
  namesOf,
570
584
  refTypeOf,
585
+ idTypeOf,
586
+ idValueOf,
571
587
  viewTypeOf,
572
588
  setFieldOf,
573
589
  selectionBinding,