vintasend-managed-templates 1.0.0-alpha3 → 1.0.0-alpha4

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
@@ -95,7 +95,53 @@ await notificationService.createNotification({
95
95
  });
96
96
  ```
97
97
 
98
- Nothing else about creating or sending notifications changes.
98
+ Nothing else about creating or sending notifications changes. A send renders the key's newest
99
+ **active** version — see [Which version a send renders](#which-version-a-send-renders) — so a
100
+ template has to be activated before it goes out.
101
+
102
+ ### Sending before a template is written: fallbacks
103
+
104
+ An application can register a default for each key, so it can send a notification before anyone
105
+ has written its template in the store. While a key has nothing published, `render` hands the
106
+ default to a renderer of your choosing; once a version of the key is activated, sends use it.
107
+
108
+ ```ts
109
+ import { PugEmailTemplateRendererFactory } from 'vintasend-pug';
110
+
111
+ const renderer = new ManagedTemplateEmailRenderer<Config>(managerBackend, innerRenderer, {
112
+ fallback: {
113
+ // Optional; defaults to the inner renderer. Its `render` gets a copy of the notification
114
+ // with `bodyTemplate` / `subjectTemplate` replaced by the values registered below.
115
+ renderer: new PugEmailTemplateRendererFactory<Config>().create(),
116
+ templates: {
117
+ welcome: {
118
+ subjectTemplate: 'emails/welcome/subject.pug', // file paths, for a file renderer
119
+ bodyTemplate: 'emails/welcome/body.pug',
120
+ },
121
+ },
122
+ },
123
+ });
124
+
125
+ renderer.getFallbackTemplate('welcome'); // the registered default, or null — for a dashboard
126
+ ```
127
+
128
+ The text renderer takes the same option. The rules:
129
+
130
+ * **Only when the key itself has nothing published** — no versions at all, or only drafts and
131
+ retired ones. A stored template that fails to compose (it extends a base that is missing) throws
132
+ `ManagedTemplateCompositionReferenceError` instead: that template exists and is broken, and a
133
+ default must not hide it.
134
+ * **Only for an unpinned notification.** If a pinned version is missing, the template existed when
135
+ the notification was created, so the render throws.
136
+ * **Only for a registered key**, matched as an own property — a key such as `constructor` never
137
+ matches the prototype.
138
+ * **Only on `render`**, the send path. `renderManaged`, `renderTemplate` and the service's
139
+ `render` (what previews use) never fall back, and `getLatestTemplateVersion` still answers `null`
140
+ for a key with nothing published.
141
+
142
+ A fallback payload carries `templateSource: 'fallback'` and no `templateVersion`, so the
143
+ notification's `usedTemplateVersion` stays `null` and a host can tell the default went out. The
144
+ renderer logs the key and the notification id when it falls back — never the context.
99
145
 
100
146
  ### What the inner renderer has to do
101
147
 
@@ -307,14 +353,33 @@ await service.updateTemplate('welcome', {
307
353
  // tags: undefined carries them forward; [] clears them
308
354
  });
309
355
 
310
- await service.getTemplate('welcome'); // latest version
356
+ await service.getTemplate('welcome'); // latest version, whatever its status
311
357
  await service.getTemplate('welcome', 1); // a specific one
358
+ await service.getActiveTemplate('welcome'); // what an unpinned send renders
312
359
  await service.getTemplateVersions('welcome'); // every version, newest first
313
360
  ```
314
361
 
315
- An absent `version` means "the latest version of this key" everywhere in the service — reads,
316
- status changes, tagging, and rendering — so callers only deal with version numbers when they
317
- actually want a specific one.
362
+ An absent `version` means "the latest version of this key" for reads, status changes and tagging —
363
+ the editing view, which includes a draft someone is working on. Sends are the exception, below.
364
+
365
+ ### Which version a send renders
366
+
367
+ "Latest" means two things, and they are kept apart:
368
+
369
+ | | Resolves to | Used by |
370
+ |---|---|---|
371
+ | The editing view | the newest version, whatever its status | `getTemplate(key)`, status changes, tagging, the API's reads |
372
+ | The send path | the newest **`active`** version | `render`, `renderManaged` with no version and no pin, `getLatestTemplateVersion` |
373
+
374
+ A draft is never sent: publishing is the deliberate act that puts a version in front of
375
+ recipients. When several versions of a key are active at once, the **highest-numbered** active one
376
+ renders. A key with no active version throws `ManagedTemplateNoActiveVersionError`, a subclass of
377
+ `ManagedTemplateNotFoundError` — so a key holding only drafts counts as "not customized yet", and a
378
+ registered fallback applies.
379
+
380
+ A notification **pinned** to a version renders that version whatever its status today. The pin is
381
+ there so a notification renders what was reviewed when it was created, so deactivating the version
382
+ later does not change what a pinned notification renders.
318
383
 
319
384
  ### Version-pinned rendering
320
385
 
@@ -326,12 +391,13 @@ const { version, rendered } = await renderer.renderManaged(notification, context
326
391
  ```
327
392
 
328
393
  Which version renders is decided in this order: an explicit `version` argument, then the
329
- notification's own `requestedTemplateVersion`, then whatever the backend considers current.
394
+ notification's own `requestedTemplateVersion`, then the key's newest active version.
330
395
 
331
396
  `requestedTemplateVersion` is a first-class VintaSend field: pass it to `createNotification`, or
332
397
  let the service resolve it for you with `pinTemplateVersions`. This package is what makes it mean
333
- anything — `getLatestTemplateVersion` is how the service resolves "whatever is current right now",
334
- and it is overridden here to read the store.
398
+ anything — `getLatestTemplateVersion` is how the service resolves "what would be sent right now",
399
+ and it is overridden here to answer the key's newest active version (or `null` when nothing is
400
+ published).
335
401
 
336
402
  `render` also stamps the version it used onto the payload it returns, as `templateVersion`. An
337
403
  adapter returns that payload from `send()`, and the service records it on the notification as
@@ -370,9 +436,27 @@ application.
370
436
  Two things the service deliberately does *not* decide for you:
371
437
 
372
438
  * **A key may have several `active` versions at once.** Activating one does not deactivate the
373
- others; choosing which active version wins at render time is the host's call.
439
+ others. An unpinned send renders the highest-numbered active one.
374
440
  * **`changedBy` is passed through untouched, `null` included.** Attribution is never required.
375
441
 
442
+ ### Deleting a version
443
+
444
+ Only a version that was **never published** can be deleted: one still in `draft` whose status
445
+ history records nothing but `draft`. Anything else throws `ManagedTemplateDeletionNotAllowedError`
446
+ — a published version may have rendered a notification that is pinned to it, and its status history
447
+ is the record of who published it. Retire it with `archive` instead.
448
+
449
+ ```ts
450
+ await service.deleteTemplate('welcome', 3); // fine while v3 is an unpublished draft
451
+ await service.deleteTemplate('welcome', 1); // throws once v1 has been activated
452
+ ```
453
+
454
+ The status history is never deleted, even when the version is. The service checks the rule
455
+ before it calls the backend, so every backend is held to it; the backends shipped with VintaSend
456
+ check it themselves too. If an operator genuinely needs to hard-delete a published version, pass
457
+ `allowDeletingPublishedVersions: true` to both the service and the backend.
458
+ `isTemplateVersionDeletable(template, history)` answers the question without attempting the delete.
459
+
376
460
  ## Tags
377
461
 
378
462
  Tags are many-to-many with template *versions* and are identified by a slug derived from the text
@@ -502,15 +586,25 @@ Implement `BaseTemplateManagerBackend`. `InMemoryTemplateManagerBackend` is a co
502
586
  implementation to read against, and the seam's own test suite
503
587
  (`src/__tests__/in-memory-backend.test.ts`) doubles as a conformance checklist.
504
588
 
505
- The three rules that are easy to miss:
589
+ The four rules that are easy to miss:
506
590
 
507
591
  1. **Derive `isAbstract` on every write** that touches a source field, with `isAbstract()` from
508
592
  this package, and store the answer. A source whose composition tags are malformed has no answer:
509
593
  store `false` rather than letting the syntax error out of the write.
510
- 2. **`updateTemplate` inserts, never updates.** Copy the latest version forward, bump the version,
511
- start the copy in `draft`, and leave the version it was copied from untouched.
594
+ 2. **`updateTemplate` inserts, never updates.** Copy the latest version forward, start the copy in
595
+ `draft`, and leave the version it was copied from untouched. Number it one above the highest
596
+ version the key has *ever* had — a deleted version's number is never reused, or the new version
597
+ would inherit its status history and the notifications pinned to it.
512
598
  3. **`mostRecentActiveVersion` is answered against the key, not the row.** "This row is `active` or
513
599
  `draft`, and no `active`-or-`draft` row of the same key is numbered higher."
600
+ 4. **`deleteTemplate` refuses a published version and keeps the status history.** Use
601
+ `assertTemplateVersionDeletable`, and put any hard delete of a published version behind an
602
+ option that is off by default.
603
+
604
+ `getActiveTemplate` — the newest `active` version, for the send path — is optional. Leave it out
605
+ and the library answers it with `getFilteredTemplates({ key, status: 'active' })`; implement it when
606
+ your store can answer more cheaply, throwing `noActiveVersion(key)` for a key with no active
607
+ version.
514
608
 
515
609
  Slug every tag with `slugifyTag` and keep slugs unique with `nextAvailableSlug`, so your store and
516
610
  every other one derive the same identity from the same text.
@@ -38,11 +38,30 @@ export interface BaseTemplateManagerBackend {
38
38
  */
39
39
  createTemplate(data: ManagedTemplateCreateInput): Promise<ManagedTemplate>;
40
40
  /**
41
- * One version of a template. `version` absent or `null` returns the latest version.
41
+ * One version of a template. `version` absent or `null` returns the latest version, whatever
42
+ * its status.
43
+ *
44
+ * That is the *editing view*: an editor or an API wants the draft someone is working on. A send
45
+ * never renders it — sends go through {@link getActiveTemplate} instead.
42
46
  *
43
47
  * @throws ManagedTemplateNotFoundError if the key (or that version of it) does not exist.
44
48
  */
45
49
  getTemplate(templateKey: string, version?: number | null): Promise<ManagedTemplate>;
50
+ /**
51
+ * The version an unpinned send renders: the highest-numbered version whose status is `active`.
52
+ *
53
+ * Drafts are skipped whatever their number, and so are `inactive` and `archived` versions. A key
54
+ * may hold several active versions at once; the newest of them wins.
55
+ *
56
+ * Optional, so a backend written before it existed keeps compiling. The library answers for a
57
+ * backend that leaves it out by filtering on `{ key, status: 'active' }` — implement it when the
58
+ * store can answer more cheaply. `newestActiveVersion` and `noActiveVersion` are exported so an
59
+ * implementation shares the rule and the error rather than re-deriving them.
60
+ *
61
+ * @throws ManagedTemplateNotFoundError if the key does not exist.
62
+ * @throws ManagedTemplateNoActiveVersionError if the key exists but no version of it is active.
63
+ */
64
+ getActiveTemplate?(templateKey: string): Promise<ManagedTemplate>;
46
65
  /**
47
66
  * Create a new version of an existing template, copied forward from its latest one.
48
67
  *
@@ -51,6 +70,12 @@ export interface BaseTemplateManagerBackend {
51
70
  * went out against v1 renders v1 forever, however many versions follow it, and several
52
71
  * versions of one key are live at the same time as a matter of course.
53
72
  *
73
+ * The new version number is one above the highest the key has **ever** had, not one above the
74
+ * latest live version. A deleted version's status history survives it and is read by key and
75
+ * version, so a reused number would inherit that history — and a notification pinned to the
76
+ * deleted number would start rendering the newcomer. The same goes for `createTemplate` on a key
77
+ * whose versions were all deleted.
78
+ *
54
79
  * The new version starts in `draft` whatever its predecessor's status was, so a copy nobody
55
80
  * has reviewed is never published by the act of creating it. Fields left absent on the input —
56
81
  * tags included — carry forward from the version copied.
@@ -64,7 +89,17 @@ export interface BaseTemplateManagerBackend {
64
89
  /**
65
90
  * Delete one version of a template, or its latest version when `version` is absent.
66
91
  *
92
+ * Only a version that was never published may be deleted — still in `draft`, with nothing but
93
+ * `draft` in its status history (`isTemplateVersionDeletable`). A backend should refuse anything
94
+ * else with `ManagedTemplateDeletionNotAllowedError` unless its operator explicitly configured
95
+ * it to allow hard deletes; `ManagedTemplateService` checks the rule before calling this either
96
+ * way, so a backend that does not enforce it is still protected behind the service.
97
+ *
98
+ * Never delete the version's status history. It is the audit trail of who published what, and
99
+ * it has to outlive the version it describes.
100
+ *
67
101
  * @throws ManagedTemplateNotFoundError if the key (or that version of it) does not exist.
102
+ * @throws ManagedTemplateDeletionNotAllowedError if the version has been published.
68
103
  */
69
104
  deleteTemplate(templateKey: string, version?: number | null): Promise<void>;
70
105
  /** Record a status change for one version in the audit trail. */
@@ -1 +1 @@
1
- {"version":3,"file":"base-template-manager-backend.d.ts","sourceRoot":"","sources":["../src/base-template-manager-backend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AACtF,OAAO,KAAK,EACV,qBAAqB,EACrB,iCAAiC,EACjC,sBAAsB,EACvB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAC1B,4BAA4B,EAC5B,kBAAkB,EAClB,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;;;OAOG;IACH,qBAAqB,CAAC,IAAI,iCAAiC,CAAC;IAE5D;;;;OAIG;IACH,cAAc,CAAC,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAE3E;;;;OAIG;IACH,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAEpF;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAEhG;;;;OAIG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5E,iEAAiE;IACjE,0BAA0B,CAAC,MAAM,EAAE;QACjC,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,qBAAqB,CAAC;QAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElB;;;;;OAKG;IACH,wBAAwB,CACtB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,4BAA4B,EAAE,CAAC,CAAC;IAY3C;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAExF;;;;;;;;OAQG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAE7E;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAElD;;;;;;;;OAQG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAEnE;;;;;;;;OAQG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAE1F;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC;;;;;;OAMG;IACH,OAAO,CACL,MAAM,CAAC,EAAE,wBAAwB,EAAE,GAAG,IAAI,EAC1C,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAEjC;;;;OAIG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAE7F;;;;;;;;;;OAUG;IACH,eAAe,CACb,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,eAAe,CAAC,CAAC;IAM5B,oDAAoD;IACpD,eAAe,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAE9C,2DAA2D;IAC3D,oBAAoB,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAElF;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAEjF;;;;;;OAMG;IACH,qBAAqB,CACnB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAE9B;;;;;;;;OAQG;IACH,6BAA6B,CAC3B,OAAO,EAAE,qBAAqB,EAC9B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CAC/B"}
1
+ {"version":3,"file":"base-template-manager-backend.d.ts","sourceRoot":"","sources":["../src/base-template-manager-backend.ts"],"names":[],"mappings":"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"}
package/dist/errors.d.ts CHANGED
@@ -11,6 +11,27 @@ export declare class ManagedTemplateError extends Error {
11
11
  /** Raised when a template is not found in the backend. */
12
12
  export declare class ManagedTemplateNotFoundError extends ManagedTemplateError {
13
13
  }
14
+ /**
15
+ * Raised when a send resolves a key that has versions, but none of them is `active`.
16
+ *
17
+ * A subclass of {@link ManagedTemplateNotFoundError} on purpose: from a send's point of view a
18
+ * key holding only drafts (or only retired versions) has nothing published to render, which is
19
+ * the same answer as a key with nothing stored. That is what lets a renderer's fallback treat a
20
+ * key nobody has published yet as "not customized", and what lets an API keep mapping it to 404.
21
+ * Catch this subclass to tell "exists but unpublished" apart from "never created".
22
+ */
23
+ export declare class ManagedTemplateNoActiveVersionError extends ManagedTemplateNotFoundError {
24
+ }
25
+ /**
26
+ * Raised when deleting a template version the deletion rule protects.
27
+ *
28
+ * Only a version that was never published can be deleted: one still in `draft` whose status
29
+ * history records nothing but its creation. Anything else may have rendered a notification that
30
+ * is pinned to it, and its status history is the record of who published it — retire it with
31
+ * `archive` instead. See `isTemplateVersionDeletable`.
32
+ */
33
+ export declare class ManagedTemplateDeletionNotAllowedError extends ManagedTemplateError {
34
+ }
14
35
  /** Raised when a filter is malformed or names a field the vocabulary does not have. */
15
36
  export declare class ManagedTemplateInvalidFilterError extends ManagedTemplateError {
16
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAK5B;AAED,0DAA0D;AAC1D,qBAAa,4BAA6B,SAAQ,oBAAoB;CAAG;AAEzE,uFAAuF;AACvF,qBAAa,iCAAkC,SAAQ,oBAAoB;CAAG;AAE9E;;;;;;;GAOG;AACH,qBAAa,uCAAwC,SAAQ,oBAAoB;CAAG;AAEpF,sFAAsF;AACtF,qBAAa,sCAAuC,SAAQ,oBAAoB;CAAG;AAEnF,oFAAoF;AACpF,qBAAa,oCAAqC,SAAQ,oBAAoB;CAAG;AAEjF,qDAAqD;AACrD,qBAAa,+BAAgC,SAAQ,oBAAoB;CAAG;AAE5E,gFAAgF;AAChF,qBAAa,oCAAqC,SAAQ,oBAAoB;CAAG;AAEjF,8EAA8E;AAC9E,qBAAa,8BAA+B,SAAQ,oBAAoB;CAAG;AAE3E;;;;;;GAMG;AACH,qBAAa,+BAAgC,SAAQ,oBAAoB;CAAG;AAE5E,qFAAqF;AACrF,qBAAa,qCAAsC,SAAQ,+BAA+B;CAAG;AAE7F;;;;;;;;;GASG;AACH,qBAAa,wCAAyC,SAAQ,+BAA+B;CAAG;AAEhG,wFAAwF;AACxF,qBAAa,oCAAqC,SAAQ,+BAA+B;CAAG;AAE5F,oFAAoF;AACpF,qBAAa,oCAAqC,SAAQ,+BAA+B;CAAG;AAE5F;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKvD"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAK5B;AAED,0DAA0D;AAC1D,qBAAa,4BAA6B,SAAQ,oBAAoB;CAAG;AAEzE;;;;;;;;GAQG;AACH,qBAAa,mCAAoC,SAAQ,4BAA4B;CAAG;AAExF;;;;;;;GAOG;AACH,qBAAa,sCAAuC,SAAQ,oBAAoB;CAAG;AAEnF,uFAAuF;AACvF,qBAAa,iCAAkC,SAAQ,oBAAoB;CAAG;AAE9E;;;;;;;GAOG;AACH,qBAAa,uCAAwC,SAAQ,oBAAoB;CAAG;AAEpF,sFAAsF;AACtF,qBAAa,sCAAuC,SAAQ,oBAAoB;CAAG;AAEnF,oFAAoF;AACpF,qBAAa,oCAAqC,SAAQ,oBAAoB;CAAG;AAEjF,qDAAqD;AACrD,qBAAa,+BAAgC,SAAQ,oBAAoB;CAAG;AAE5E,gFAAgF;AAChF,qBAAa,oCAAqC,SAAQ,oBAAoB;CAAG;AAEjF,8EAA8E;AAC9E,qBAAa,8BAA+B,SAAQ,oBAAoB;CAAG;AAE3E;;;;;;GAMG;AACH,qBAAa,+BAAgC,SAAQ,oBAAoB;CAAG;AAE5E,qFAAqF;AACrF,qBAAa,qCAAsC,SAAQ,+BAA+B;CAAG;AAE7F;;;;;;;;;GASG;AACH,qBAAa,wCAAyC,SAAQ,+BAA+B;CAAG;AAEhG,wFAAwF;AACxF,qBAAa,oCAAqC,SAAQ,+BAA+B;CAAG;AAE5F,oFAAoF;AACpF,qBAAa,oCAAqC,SAAQ,+BAA+B;CAAG;AAE5F;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKvD"}
package/dist/errors.js CHANGED
@@ -15,6 +15,27 @@ export class ManagedTemplateError extends Error {
15
15
  /** Raised when a template is not found in the backend. */
16
16
  export class ManagedTemplateNotFoundError extends ManagedTemplateError {
17
17
  }
18
+ /**
19
+ * Raised when a send resolves a key that has versions, but none of them is `active`.
20
+ *
21
+ * A subclass of {@link ManagedTemplateNotFoundError} on purpose: from a send's point of view a
22
+ * key holding only drafts (or only retired versions) has nothing published to render, which is
23
+ * the same answer as a key with nothing stored. That is what lets a renderer's fallback treat a
24
+ * key nobody has published yet as "not customized", and what lets an API keep mapping it to 404.
25
+ * Catch this subclass to tell "exists but unpublished" apart from "never created".
26
+ */
27
+ export class ManagedTemplateNoActiveVersionError extends ManagedTemplateNotFoundError {
28
+ }
29
+ /**
30
+ * Raised when deleting a template version the deletion rule protects.
31
+ *
32
+ * Only a version that was never published can be deleted: one still in `draft` whose status
33
+ * history records nothing but its creation. Anything else may have rendered a notification that
34
+ * is pinned to it, and its status history is the record of who published it — retire it with
35
+ * `archive` instead. See `isTemplateVersionDeletable`.
36
+ */
37
+ export class ManagedTemplateDeletionNotAllowedError extends ManagedTemplateError {
38
+ }
18
39
  /** Raised when a filter is malformed or names a field the vocabulary does not have. */
19
40
  export class ManagedTemplateInvalidFilterError extends ManagedTemplateError {
20
41
  }
@@ -17,17 +17,28 @@ import type { ManagedTemplate, ManagedTemplateCreateInput, ManagedTemplateStatus
17
17
  export type InMemoryTemplateManagerBackendOptions = {
18
18
  /** Overridden in tests so timestamps are deterministic. */
19
19
  now?: () => Date;
20
+ /**
21
+ * When true, `deleteTemplate` removes a version whatever its status. Off by default: only a
22
+ * version that was never published can be deleted (see `isTemplateVersionDeletable`).
23
+ * `ManagedTemplateService` checks the rule too, under its own option of the same name.
24
+ */
25
+ allowDeletingPublishedVersions?: boolean;
20
26
  };
21
27
  export declare class InMemoryTemplateManagerBackend implements BaseTemplateManagerBackend {
22
28
  private templates;
23
29
  private tags;
24
30
  private history;
25
31
  private nextId;
32
+ /** The highest version number each key has had deleted, so it is never handed out again. */
33
+ private highestDeletedVersion;
26
34
  private readonly now;
35
+ private readonly allowDeletingPublishedVersions;
27
36
  constructor(options?: InMemoryTemplateManagerBackendOptions);
28
37
  createTemplate(data: ManagedTemplateCreateInput): Promise<ManagedTemplate>;
29
38
  getTemplate(templateKey: string, version?: number | null): Promise<ManagedTemplate>;
39
+ getActiveTemplate(templateKey: string): Promise<ManagedTemplate>;
30
40
  updateTemplate(templateKey: string, data: ManagedTemplateUpdateInput): Promise<ManagedTemplate>;
41
+ /** Delete one never-published version. Its status history is kept either way. */
31
42
  deleteTemplate(templateKey: string, version?: number | null): Promise<void>;
32
43
  createTemplateStatusUpdate(params: {
33
44
  templateKey: string;
@@ -59,6 +70,16 @@ export declare class InMemoryTemplateManagerBackend implements BaseTemplateManag
59
70
  getPaginatedTemplates(page: number, pageSize: number, orderBy?: ManagedTemplateOrderBy): Promise<ManagedTemplate[]>;
60
71
  getPaginatedFilteredTemplates(filters: ManagedTemplateFilter, page: number, pageSize: number, orderBy?: ManagedTemplateOrderBy): Promise<ManagedTemplate[]>;
61
72
  private deriveIsAbstract;
73
+ /**
74
+ * One above the highest version number the key has ever had — live, deleted or only in its
75
+ * status history.
76
+ *
77
+ * A number is never reused. Status history outlives a deleted version and is read by key and
78
+ * version, so a new version that took a deleted one's number would inherit its history: a
79
+ * publish that never happened, and a draft the deletion rule then refuses. A notification pinned
80
+ * to the deleted number would also start rendering the newcomer.
81
+ */
82
+ private nextVersion;
62
83
  private versionsOf;
63
84
  private find;
64
85
  private requireTag;
@@ -1 +1 @@
1
- {"version":3,"file":"in-memory-template-manager-backend.d.ts","sourceRoot":"","sources":["../src/in-memory-template-manager-backend.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"in-memory-template-manager-backend.d.ts","sourceRoot":"","sources":["../src/in-memory-template-manager-backend.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,7 @@ import { isAbstract } from './composition.js';
14
14
  import { ManagedTemplateInvalidTagError, ManagedTemplateNotFoundError, ManagedTemplateTagAlreadyExistsError, ManagedTemplateTagNotFoundError, } from './errors.js';
15
15
  import { matchesTemplateFilter, paginate, sortTemplates } from './filter-evaluation.js';
16
16
  import { MANAGED_TEMPLATE_ORDER_BY_FIELDS, orderByCapabilityKey, } from './filters.js';
17
+ import { assertTemplateVersionDeletable, newestActiveVersion, noActiveVersion, } from './lifecycle.js';
17
18
  import { nextAvailableSlug, normalizeTagText, slugifyTag } from './tags.js';
18
19
  export class InMemoryTemplateManagerBackend {
19
20
  constructor(options = {}) {
@@ -21,7 +22,10 @@ export class InMemoryTemplateManagerBackend {
21
22
  this.tags = [];
22
23
  this.history = [];
23
24
  this.nextId = 1;
25
+ /** The highest version number each key has had deleted, so it is never handed out again. */
26
+ this.highestDeletedVersion = new Map();
24
27
  this.now = options.now ?? (() => new Date());
28
+ this.allowDeletingPublishedVersions = options.allowDeletingPublishedVersions ?? false;
25
29
  }
26
30
  // -------------------------------------------------------------------------------------------
27
31
  // Templates
@@ -31,7 +35,8 @@ export class InMemoryTemplateManagerBackend {
31
35
  const template = {
32
36
  id: this.nextId++,
33
37
  key: data.key,
34
- version: 1,
38
+ // A key whose versions were all deleted starts above every number it used before.
39
+ version: this.versionsOf(data.key).length > 0 ? 1 : this.nextVersion(data.key),
35
40
  name: data.name,
36
41
  description: data.description,
37
42
  templateManagedBackend: data.templateManagedBackend,
@@ -55,6 +60,17 @@ export class InMemoryTemplateManagerBackend {
55
60
  }
56
61
  return structuredCloneTemplate(template);
57
62
  }
63
+ async getActiveTemplate(templateKey) {
64
+ const versions = this.versionsOf(templateKey);
65
+ if (versions.length === 0) {
66
+ throw new ManagedTemplateNotFoundError(describeMissing(templateKey, null));
67
+ }
68
+ const active = newestActiveVersion(versions);
69
+ if (active === undefined) {
70
+ throw noActiveVersion(templateKey);
71
+ }
72
+ return structuredCloneTemplate(active);
73
+ }
58
74
  async updateTemplate(templateKey, data) {
59
75
  const previous = this.find(templateKey, null);
60
76
  if (previous === undefined) {
@@ -74,7 +90,8 @@ export class InMemoryTemplateManagerBackend {
74
90
  const template = {
75
91
  id: this.nextId++,
76
92
  key: previous.key,
77
- version: previous.version + 1,
93
+ // Not `previous.version + 1`: a deleted version above it keeps its number.
94
+ version: this.nextVersion(previous.key),
78
95
  name: data.name || previous.name,
79
96
  description: data.description ?? previous.description,
80
97
  templateManagedBackend: previous.templateManagedBackend,
@@ -90,12 +107,17 @@ export class InMemoryTemplateManagerBackend {
90
107
  this.templates.push(template);
91
108
  return structuredCloneTemplate(template);
92
109
  }
110
+ /** Delete one never-published version. Its status history is kept either way. */
93
111
  async deleteTemplate(templateKey, version = null) {
94
112
  const template = this.find(templateKey, version);
95
113
  if (template === undefined) {
96
114
  throw new ManagedTemplateNotFoundError(describeMissing(templateKey, version));
97
115
  }
116
+ if (!this.allowDeletingPublishedVersions) {
117
+ assertTemplateVersionDeletable(template, this.history);
118
+ }
98
119
  this.templates = this.templates.filter((candidate) => candidate !== template);
120
+ this.highestDeletedVersion.set(templateKey, Math.max(this.highestDeletedVersion.get(templateKey) ?? 0, template.version));
99
121
  }
100
122
  async createTemplateStatusUpdate(params) {
101
123
  const template = this.find(params.templateKey, params.version);
@@ -234,6 +256,20 @@ export class InMemoryTemplateManagerBackend {
234
256
  return false;
235
257
  }
236
258
  }
259
+ /**
260
+ * One above the highest version number the key has ever had — live, deleted or only in its
261
+ * status history.
262
+ *
263
+ * A number is never reused. Status history outlives a deleted version and is read by key and
264
+ * version, so a new version that took a deleted one's number would inherit its history: a
265
+ * publish that never happened, and a draft the deletion rule then refuses. A notification pinned
266
+ * to the deleted number would also start rendering the newcomer.
267
+ */
268
+ nextVersion(templateKey) {
269
+ return (Math.max(0, this.highestDeletedVersion.get(templateKey) ?? 0, ...this.versionsOf(templateKey).map((template) => template.version), ...this.history
270
+ .filter((record) => record.templateKey === templateKey)
271
+ .map((record) => record.version)) + 1);
272
+ }
237
273
  versionsOf(templateKey) {
238
274
  return this.templates.filter((template) => template.key === templateKey);
239
275
  }
package/dist/index.d.ts CHANGED
@@ -3,14 +3,15 @@ export type { TemplateComposerOptions, TemplateReference, TemplateResolver, } fr
3
3
  export { DEFAULT_MAX_DEPTH, DEFAULT_TAG_PREFIX, isAbstract, TemplateComposer, } from './composition.js';
4
4
  export type { ManagedTemplateStatus, ManagedTemplateTagStatus, TemplateField, } from './constants.js';
5
5
  export { MANAGED_TEMPLATE_STATUSES, MANAGED_TEMPLATE_TAG_STATUSES, MOST_RECENT_ACTIVE_VERSION_STATUSES, TEMPLATE_FIELDS, } from './constants.js';
6
- export { isNotFoundError, ManagedTemplateChangeUserNotFoundError, ManagedTemplateCompositionCycleError, ManagedTemplateCompositionDepthError, ManagedTemplateCompositionError, ManagedTemplateCompositionReferenceError, ManagedTemplateCompositionSyntaxError, ManagedTemplateError, ManagedTemplateInvalidFilterError, ManagedTemplateInvalidTagError, ManagedTemplateNotFoundError, ManagedTemplateStatusTransitionError, ManagedTemplateTagAlreadyExistsError, ManagedTemplateTagNotFoundError, ManagedTemplateUnsupportedOrderingError, } from './errors.js';
6
+ export { isNotFoundError, ManagedTemplateChangeUserNotFoundError, ManagedTemplateCompositionCycleError, ManagedTemplateCompositionDepthError, ManagedTemplateCompositionError, ManagedTemplateCompositionReferenceError, ManagedTemplateCompositionSyntaxError, ManagedTemplateDeletionNotAllowedError, ManagedTemplateError, ManagedTemplateInvalidFilterError, ManagedTemplateInvalidTagError, ManagedTemplateNoActiveVersionError, ManagedTemplateNotFoundError, ManagedTemplateStatusTransitionError, ManagedTemplateTagAlreadyExistsError, ManagedTemplateTagNotFoundError, ManagedTemplateUnsupportedOrderingError, } from './errors.js';
7
7
  export type { FilterEvaluationContext } from './filter-evaluation.js';
8
8
  export { isMostRecentActiveVersion, matchesDateRange, matchesInteger, matchesStatus, matchesString, matchesTemplateFilter, normalizeSlugs, paginate, sortTemplates, } from './filter-evaluation.js';
9
9
  export type { DateRange, IntegerFieldFilter, ManagedTemplateFilter, ManagedTemplateFilterCapabilities, ManagedTemplateFilterFields, ManagedTemplateOrderBy, ManagedTemplateOrderByField, ManagedTemplateOrderDirection, ManagedTemplateStatusFilter, NumericFilterLookup, StringFieldFilter, StringFilterLookup, TagsFieldFilter, } from './filters.js';
10
10
  export { DEFAULT_TEMPLATE_BACKEND_FILTER_CAPABILITIES, FLAG_FILTER_FIELDS, isDateRange, isEmptyFilter, isFieldFilter, isNumericFilterLookup, isStatusExactLookup, isStatusInLookup, isStringFilterLookup, isTagsFilter, KNOWN_FILTER_FIELDS, MANAGED_TEMPLATE_ORDER_BY_FIELDS, orderByCapabilityKey, pruneUnsupportedFilters, supportsCapability, TAG_FILTER_FIELDS, } from './filters.js';
11
11
  export type { InMemoryTemplateManagerBackendOptions } from './in-memory-template-manager-backend.js';
12
12
  export { InMemoryTemplateManagerBackend } from './in-memory-template-manager-backend.js';
13
- export type { ManagedEmailTemplateContent, ManagedTemplateRendererOptions, ManagedTemplateRenderResult, TextTemplate, TextTemplateContent, VersionPinnedNotification, } from './managed-template-renderer.js';
13
+ export { assertTemplateVersionDeletable, isTemplateVersionDeletable, newestActiveVersion, noActiveVersion, resolveActiveTemplate, } from './lifecycle.js';
14
+ export type { ManagedEmailTemplateContent, ManagedTemplateFallbackOptions, ManagedTemplateFallbackTemplate, ManagedTemplateRendererOptions, ManagedTemplateRenderResult, TextTemplate, TextTemplateContent, VersionPinnedNotification, } from './managed-template-renderer.js';
14
15
  export { ManagedTemplateEmailRenderer, ManagedTemplateRenderer, ManagedTemplateTextRenderer, requestedTemplateVersion, } from './managed-template-renderer.js';
15
16
  export type { ManagedTemplateServiceOptions } from './managed-template-service.js';
16
17
  export { DEFAULT_STATUS_TRANSITIONS, ManagedTemplateService } from './managed-template-service.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AACrF,YAAY,EACV,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,qBAAqB,EACrB,wBAAwB,EACxB,aAAa,GACd,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,mCAAmC,EACnC,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,eAAe,EACf,sCAAsC,EACtC,oCAAoC,EACpC,oCAAoC,EACpC,+BAA+B,EAC/B,wCAAwC,EACxC,qCAAqC,EACrC,oBAAoB,EACpB,iCAAiC,EACjC,8BAA8B,EAC9B,4BAA4B,EAC5B,oCAAoC,EACpC,oCAAoC,EACpC,+BAA+B,EAC/B,uCAAuC,GACxC,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,OAAO,EACL,yBAAyB,EACzB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,cAAc,EACd,QAAQ,EACR,aAAa,GACd,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,SAAS,EACT,kBAAkB,EAClB,qBAAqB,EACrB,iCAAiC,EACjC,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,EAC3B,6BAA6B,EAC7B,2BAA2B,EAC3B,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,GAChB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,4CAA4C,EAC5C,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,gCAAgC,EAChC,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,qCAAqC,EAAE,MAAM,yCAAyC,CAAC;AAErG,OAAO,EAAE,8BAA8B,EAAE,MAAM,yCAAyC,CAAC;AACzF,YAAY,EACV,2BAA2B,EAC3B,8BAA8B,EAC9B,2BAA2B,EAC3B,YAAY,EACZ,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAEnF,OAAO,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEnG,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE7F,YAAY,EACV,eAAe,EACf,0BAA0B,EAC1B,iBAAiB,EACjB,4BAA4B,EAC5B,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AACrF,YAAY,EACV,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,qBAAqB,EACrB,wBAAwB,EACxB,aAAa,GACd,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,mCAAmC,EACnC,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,eAAe,EACf,sCAAsC,EACtC,oCAAoC,EACpC,oCAAoC,EACpC,+BAA+B,EAC/B,wCAAwC,EACxC,qCAAqC,EACrC,sCAAsC,EACtC,oBAAoB,EACpB,iCAAiC,EACjC,8BAA8B,EAC9B,mCAAmC,EACnC,4BAA4B,EAC5B,oCAAoC,EACpC,oCAAoC,EACpC,+BAA+B,EAC/B,uCAAuC,GACxC,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,OAAO,EACL,yBAAyB,EACzB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,cAAc,EACd,QAAQ,EACR,aAAa,GACd,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,SAAS,EACT,kBAAkB,EAClB,qBAAqB,EACrB,iCAAiC,EACjC,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,EAC3B,6BAA6B,EAC7B,2BAA2B,EAC3B,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,GAChB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,4CAA4C,EAC5C,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,gCAAgC,EAChC,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,qCAAqC,EAAE,MAAM,yCAAyC,CAAC;AAErG,OAAO,EAAE,8BAA8B,EAAE,MAAM,yCAAyC,CAAC;AAEzF,OAAO,EACL,8BAA8B,EAC9B,0BAA0B,EAC1B,mBAAmB,EACnB,eAAe,EACf,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,2BAA2B,EAC3B,8BAA8B,EAC9B,+BAA+B,EAC/B,8BAA8B,EAC9B,2BAA2B,EAC3B,YAAY,EACZ,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAEnF,OAAO,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEnG,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE7F,YAAY,EACV,eAAe,EACf,0BAA0B,EAC1B,iBAAiB,EACjB,4BAA4B,EAC5B,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -3,13 +3,15 @@ export { DEFAULT_MAX_DEPTH, DEFAULT_TAG_PREFIX, isAbstract, TemplateComposer, }
3
3
  // Constants
4
4
  export { MANAGED_TEMPLATE_STATUSES, MANAGED_TEMPLATE_TAG_STATUSES, MOST_RECENT_ACTIVE_VERSION_STATUSES, TEMPLATE_FIELDS, } from './constants.js';
5
5
  // Errors
6
- export { isNotFoundError, ManagedTemplateChangeUserNotFoundError, ManagedTemplateCompositionCycleError, ManagedTemplateCompositionDepthError, ManagedTemplateCompositionError, ManagedTemplateCompositionReferenceError, ManagedTemplateCompositionSyntaxError, ManagedTemplateError, ManagedTemplateInvalidFilterError, ManagedTemplateInvalidTagError, ManagedTemplateNotFoundError, ManagedTemplateStatusTransitionError, ManagedTemplateTagAlreadyExistsError, ManagedTemplateTagNotFoundError, ManagedTemplateUnsupportedOrderingError, } from './errors.js';
6
+ export { isNotFoundError, ManagedTemplateChangeUserNotFoundError, ManagedTemplateCompositionCycleError, ManagedTemplateCompositionDepthError, ManagedTemplateCompositionError, ManagedTemplateCompositionReferenceError, ManagedTemplateCompositionSyntaxError, ManagedTemplateDeletionNotAllowedError, ManagedTemplateError, ManagedTemplateInvalidFilterError, ManagedTemplateInvalidTagError, ManagedTemplateNoActiveVersionError, ManagedTemplateNotFoundError, ManagedTemplateStatusTransitionError, ManagedTemplateTagAlreadyExistsError, ManagedTemplateTagNotFoundError, ManagedTemplateUnsupportedOrderingError, } from './errors.js';
7
7
  // Filter evaluation, for a backend that has to finish a filter its query language cannot express
8
8
  export { isMostRecentActiveVersion, matchesDateRange, matchesInteger, matchesStatus, matchesString, matchesTemplateFilter, normalizeSlugs, paginate, sortTemplates, } from './filter-evaluation.js';
9
9
  // Filters and capabilities
10
10
  export { DEFAULT_TEMPLATE_BACKEND_FILTER_CAPABILITIES, FLAG_FILTER_FIELDS, isDateRange, isEmptyFilter, isFieldFilter, isNumericFilterLookup, isStatusExactLookup, isStatusInLookup, isStringFilterLookup, isTagsFilter, KNOWN_FILTER_FIELDS, MANAGED_TEMPLATE_ORDER_BY_FIELDS, orderByCapabilityKey, pruneUnsupportedFilters, supportsCapability, TAG_FILTER_FIELDS, } from './filters.js';
11
11
  // A backend to develop and test against
12
12
  export { InMemoryTemplateManagerBackend } from './in-memory-template-manager-backend.js';
13
+ // Lifecycle rules: which version a send renders, which versions may be deleted
14
+ export { assertTemplateVersionDeletable, isTemplateVersionDeletable, newestActiveVersion, noActiveVersion, resolveActiveTemplate, } from './lifecycle.js';
13
15
  // Renderers
14
16
  export { ManagedTemplateEmailRenderer, ManagedTemplateRenderer, ManagedTemplateTextRenderer, requestedTemplateVersion, } from './managed-template-renderer.js';
15
17
  // The service
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The two lifecycle rules every backend has to agree on: which version a send renders, and which
3
+ * versions may be deleted.
4
+ *
5
+ * Both live here rather than in one backend so the in-memory store, a FHIR store and the service
6
+ * apply the same rule, and so a backend author has a function to call instead of prose to
7
+ * re-derive.
8
+ *
9
+ * **Which version a send renders.** "The latest version" means two different things, and the
10
+ * seam keeps them apart:
11
+ *
12
+ * * The *editing view* — `getTemplate(key)` with no version — is the newest version whatever its
13
+ * status. An editor or an API listing a key's history wants the draft someone is working on.
14
+ * * The *send path* — {@link resolveActiveTemplate} — is the newest `active` version. A draft is
15
+ * unreviewed by definition, so publishing is the deliberate act that puts a version in front of
16
+ * recipients. When several versions are active at once (the lifecycle allows it), the
17
+ * highest-numbered one wins.
18
+ *
19
+ * **Which versions may be deleted.** Only one that was never published — see
20
+ * {@link isTemplateVersionDeletable}.
21
+ */
22
+ import type { BaseTemplateManagerBackend } from './base-template-manager-backend.js';
23
+ import { ManagedTemplateNoActiveVersionError } from './errors.js';
24
+ import type { ManagedTemplate, ManagedTemplateStatusHistory } from './types.js';
25
+ /**
26
+ * The highest-numbered `active` version among `versions`, or `undefined` when none is active.
27
+ *
28
+ * `versions` may hold several keys' rows; the caller narrows it to one key first.
29
+ */
30
+ export declare function newestActiveVersion(versions: readonly ManagedTemplate[]): ManagedTemplate | undefined;
31
+ /**
32
+ * The version an unpinned send of `templateKey` renders: its highest-numbered `active` version.
33
+ *
34
+ * Uses the backend's own `getActiveTemplate` when it has one. A backend written before that method
35
+ * existed is answered through the filter seam instead — an exact key match plus `status: 'active'`
36
+ * — which every backend supports by default.
37
+ *
38
+ * @throws ManagedTemplateNotFoundError if the key does not exist.
39
+ * @throws ManagedTemplateNoActiveVersionError if it exists but no version of it is active.
40
+ */
41
+ export declare function resolveActiveTemplate(backend: BaseTemplateManagerBackend, templateKey: string): Promise<ManagedTemplate>;
42
+ /** The error a backend throws for a key that exists but has no `active` version. */
43
+ export declare function noActiveVersion(templateKey: string): ManagedTemplateNoActiveVersionError;
44
+ /**
45
+ * Whether a template version may be hard-deleted: it was never published.
46
+ *
47
+ * That means it is still in `draft` and its status history records nothing but `draft` — a
48
+ * backend that writes a creation entry is fine, and one that writes none is too. A version that
49
+ * was ever `active`, `inactive` or `archived` may have rendered a notification that is pinned to
50
+ * it, and its history is the record of who published it; retire it with `archive` instead.
51
+ */
52
+ export declare function isTemplateVersionDeletable(template: ManagedTemplate, history: readonly ManagedTemplateStatusHistory[]): boolean;
53
+ /**
54
+ * Throw unless {@link isTemplateVersionDeletable} allows deleting `template`.
55
+ *
56
+ * @throws ManagedTemplateDeletionNotAllowedError
57
+ */
58
+ export declare function assertTemplateVersionDeletable(template: ManagedTemplate, history: readonly ManagedTemplateStatusHistory[]): void;
59
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,EAEL,mCAAmC,EACpC,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,eAAe,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAEhF;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,SAAS,eAAe,EAAE,GACnC,eAAe,GAAG,SAAS,CAQ7B;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,0BAA0B,EACnC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,CAAC,CAe1B;AAED,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,mCAAmC,CAIxF;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,SAAS,4BAA4B,EAAE,GAC/C,OAAO,CAST;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,SAAS,4BAA4B,EAAE,GAC/C,IAAI,CAQN"}