vintasend-managed-templates 1.0.0-alpha2
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 +529 -0
- package/dist/base-template-manager-backend.d.ts +202 -0
- package/dist/base-template-manager-backend.d.ts.map +1 -0
- package/dist/base-template-manager-backend.js +1 -0
- package/dist/composition.d.ts +238 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +0 -0
- package/dist/constants.d.ts +32 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +31 -0
- package/dist/errors.d.ts +80 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +86 -0
- package/dist/filter-evaluation.d.ts +69 -0
- package/dist/filter-evaluation.d.ts.map +1 -0
- package/dist/filter-evaluation.js +252 -0
- package/dist/filters.d.ts +192 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +252 -0
- package/dist/in-memory-template-manager-backend.d.ts +85 -0
- package/dist/in-memory-template-manager-backend.d.ts.map +1 -0
- package/dist/in-memory-template-manager-backend.js +323 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/managed-template-renderer.d.ts +153 -0
- package/dist/managed-template-renderer.d.ts.map +1 -0
- package/dist/managed-template-renderer.js +152 -0
- package/dist/managed-template-service.d.ts +415 -0
- package/dist/managed-template-service.d.ts.map +1 -0
- package/dist/managed-template-service.js +712 -0
- package/dist/tags.d.ts +54 -0
- package/dist/tags.d.ts.map +1 -0
- package/dist/tags.js +108 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { ManagedTemplateStatus, ManagedTemplateTagStatus } from './constants.js';
|
|
2
|
+
import type { ManagedTemplateFilter, ManagedTemplateFilterCapabilities, ManagedTemplateOrderBy } from './filters.js';
|
|
3
|
+
import type { ManagedTemplate, ManagedTemplateCreateInput, ManagedTemplateStatusHistory, ManagedTemplateTag, ManagedTemplateUpdateInput } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Where managed templates are stored, versioned, tagged and queried.
|
|
6
|
+
*
|
|
7
|
+
* One responsibility here is easy to miss because no method is named for it:
|
|
8
|
+
* **`ManagedTemplate.isAbstract` is the backend's to derive.** It is denormalized from the
|
|
9
|
+
* template's own source — `composition.isAbstract` computes it — and neither write input carries
|
|
10
|
+
* it, because it is a fact about the source rather than something a caller decides. Derive it on
|
|
11
|
+
* every write that touches a source field and store the answer, so the `isAbstract` filter can
|
|
12
|
+
* be a field lookup instead of a full-store parse. A backend that never sets it reports every
|
|
13
|
+
* template as concrete, and that filter quietly stops working.
|
|
14
|
+
*
|
|
15
|
+
* A source whose composition tags are malformed has no answer: store `false` rather than letting
|
|
16
|
+
* the syntax error out of the write. The flag is a search convenience, a template nobody can
|
|
17
|
+
* parse cannot be extended either, and a write is the wrong place to report a syntax error — the
|
|
18
|
+
* edit boundary already refuses it, and `compose` reports it in full at the point where it
|
|
19
|
+
* actually matters.
|
|
20
|
+
*
|
|
21
|
+
* Every method is asynchronous, unlike the Python sibling: a TypeScript store is a network call
|
|
22
|
+
* more often than not, and `ManagedTemplateService` awaits throughout.
|
|
23
|
+
*/
|
|
24
|
+
export interface BaseTemplateManagerBackend {
|
|
25
|
+
/**
|
|
26
|
+
* Which filters this backend can honour, declaring only what it *cannot* do.
|
|
27
|
+
*
|
|
28
|
+
* Optional. A backend that says nothing is taken at
|
|
29
|
+
* `DEFAULT_TEMPLATE_BACKEND_FILTER_CAPABILITIES` — fully capable — which is the same reading
|
|
30
|
+
* a backend returning `{}` gets. Callers merge the report over the default, so a capability
|
|
31
|
+
* added in a later release does not force every backend to re-declare it.
|
|
32
|
+
*/
|
|
33
|
+
getFilterCapabilities?(): ManagedTemplateFilterCapabilities;
|
|
34
|
+
/**
|
|
35
|
+
* Create a new template's first version.
|
|
36
|
+
*
|
|
37
|
+
* Derive `isAbstract` from the source being stored — see the interface docs.
|
|
38
|
+
*/
|
|
39
|
+
createTemplate(data: ManagedTemplateCreateInput): Promise<ManagedTemplate>;
|
|
40
|
+
/**
|
|
41
|
+
* One version of a template. `version` absent or `null` returns the latest version.
|
|
42
|
+
*
|
|
43
|
+
* @throws ManagedTemplateNotFoundError if the key (or that version of it) does not exist.
|
|
44
|
+
*/
|
|
45
|
+
getTemplate(templateKey: string, version?: number | null): Promise<ManagedTemplate>;
|
|
46
|
+
/**
|
|
47
|
+
* Create a new version of an existing template, copied forward from its latest one.
|
|
48
|
+
*
|
|
49
|
+
* A new version is a new row, and the version it was copied from is left exactly as it was —
|
|
50
|
+
* content, status and history. That is what versioning is for: a notification that already
|
|
51
|
+
* went out against v1 renders v1 forever, however many versions follow it, and several
|
|
52
|
+
* versions of one key are live at the same time as a matter of course.
|
|
53
|
+
*
|
|
54
|
+
* The new version starts in `draft` whatever its predecessor's status was, so a copy nobody
|
|
55
|
+
* has reviewed is never published by the act of creating it. Fields left absent on the input —
|
|
56
|
+
* tags included — carry forward from the version copied.
|
|
57
|
+
*
|
|
58
|
+
* `isAbstract` is re-derived from the new version's source rather than carried forward: an
|
|
59
|
+
* edit that adds or removes a `{% managed_children %}` hole changes what the template is.
|
|
60
|
+
*
|
|
61
|
+
* @throws ManagedTemplateNotFoundError if the key does not exist.
|
|
62
|
+
*/
|
|
63
|
+
updateTemplate(templateKey: string, data: ManagedTemplateUpdateInput): Promise<ManagedTemplate>;
|
|
64
|
+
/**
|
|
65
|
+
* Delete one version of a template, or its latest version when `version` is absent.
|
|
66
|
+
*
|
|
67
|
+
* @throws ManagedTemplateNotFoundError if the key (or that version of it) does not exist.
|
|
68
|
+
*/
|
|
69
|
+
deleteTemplate(templateKey: string, version?: number | null): Promise<void>;
|
|
70
|
+
/** Record a status change for one version in the audit trail. */
|
|
71
|
+
createTemplateStatusUpdate(params: {
|
|
72
|
+
templateKey: string;
|
|
73
|
+
version: number;
|
|
74
|
+
status: ManagedTemplateStatus;
|
|
75
|
+
changedBy?: string | null;
|
|
76
|
+
}): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* The status audit trail for a template.
|
|
79
|
+
*
|
|
80
|
+
* `version` absent asks for the whole key's history. Unlike everywhere else in this seam,
|
|
81
|
+
* `version` absent here does *not* mean "the latest version".
|
|
82
|
+
*/
|
|
83
|
+
getTemplateStatusHistory(templateKey: string, version?: number | null): Promise<ManagedTemplateStatusHistory[]>;
|
|
84
|
+
/**
|
|
85
|
+
* Resolve tag texts to tags, creating the ones that do not exist yet.
|
|
86
|
+
*
|
|
87
|
+
* This is the on-the-fly path every tagging call goes through: a caller tags a template with
|
|
88
|
+
* what a person typed and never has to check first whether that tag exists. Texts that slugify
|
|
89
|
+
* onto an existing tag resolve to it rather than creating a duplicate, and an existing tag is
|
|
90
|
+
* returned as it stands — its text and status are left alone, so re-using an archived tag does
|
|
91
|
+
* not quietly bring it back.
|
|
92
|
+
*
|
|
93
|
+
* @returns one tag per distinct text, in the order given.
|
|
94
|
+
* @throws ManagedTemplateInvalidTagError if a text has nothing that can be slugified.
|
|
95
|
+
*/
|
|
96
|
+
getOrCreateTags(texts: string[], tenant?: string | null): Promise<ManagedTemplateTag[]>;
|
|
97
|
+
/**
|
|
98
|
+
* Create a tag, failing if its text already slugs onto an existing one.
|
|
99
|
+
*
|
|
100
|
+
* Use `getOrCreateTags` when a duplicate should resolve to the existing tag; this is the
|
|
101
|
+
* explicit-create path, where a collision is worth reporting to the caller.
|
|
102
|
+
*
|
|
103
|
+
* @throws ManagedTemplateTagAlreadyExistsError if a tag with that slug exists.
|
|
104
|
+
* @throws ManagedTemplateInvalidTagError if the text has nothing that can be slugified.
|
|
105
|
+
*/
|
|
106
|
+
createTag(text: string, tenant?: string | null): Promise<ManagedTemplateTag>;
|
|
107
|
+
/**
|
|
108
|
+
* One tag by slug (or by the text it was created from).
|
|
109
|
+
*
|
|
110
|
+
* @throws ManagedTemplateTagNotFoundError if no tag has that slug.
|
|
111
|
+
*/
|
|
112
|
+
getTag(slug: string): Promise<ManagedTemplateTag>;
|
|
113
|
+
/**
|
|
114
|
+
* Rename a tag, regenerating its slug from the new text.
|
|
115
|
+
*
|
|
116
|
+
* The slug changes, so anything holding the old one — a bookmarked filter, a cached query —
|
|
117
|
+
* stops matching. The tag keeps its identity and its templates: only the strings change.
|
|
118
|
+
*
|
|
119
|
+
* @throws ManagedTemplateTagNotFoundError if no tag has that slug.
|
|
120
|
+
* @throws ManagedTemplateInvalidTagError if the new text has nothing to slugify.
|
|
121
|
+
*/
|
|
122
|
+
updateTag(slug: string, text: string): Promise<ManagedTemplateTag>;
|
|
123
|
+
/**
|
|
124
|
+
* Archive a tag, or bring an archived one back.
|
|
125
|
+
*
|
|
126
|
+
* Archiving keeps every link to a template: filtering by an archived tag still returns the
|
|
127
|
+
* templates carrying it. What archiving is for is dropping the tag out of the pickers a UI
|
|
128
|
+
* builds from the active list.
|
|
129
|
+
*
|
|
130
|
+
* @throws ManagedTemplateTagNotFoundError if no tag has that slug.
|
|
131
|
+
*/
|
|
132
|
+
setTagStatus(slug: string, status: ManagedTemplateTagStatus): Promise<ManagedTemplateTag>;
|
|
133
|
+
/**
|
|
134
|
+
* Delete a tag and remove it from every template carrying it.
|
|
135
|
+
*
|
|
136
|
+
* Unlike archiving, this is not reversible and the templates lose the label.
|
|
137
|
+
*
|
|
138
|
+
* @throws ManagedTemplateTagNotFoundError if no tag has that slug.
|
|
139
|
+
*/
|
|
140
|
+
deleteTag(slug: string): Promise<void>;
|
|
141
|
+
/**
|
|
142
|
+
* Tags, optionally narrowed by status, by a text search, or by tenant.
|
|
143
|
+
*
|
|
144
|
+
* @param status every status when absent.
|
|
145
|
+
* @param search a case-insensitive substring of the text or the slug.
|
|
146
|
+
* @param tenant every tenant when absent.
|
|
147
|
+
*/
|
|
148
|
+
getTags(status?: ManagedTemplateTagStatus[] | null, search?: string | null, tenant?: string | null): Promise<ManagedTemplateTag[]>;
|
|
149
|
+
/**
|
|
150
|
+
* The tags on one version of a template, or on its latest version.
|
|
151
|
+
*
|
|
152
|
+
* @throws ManagedTemplateNotFoundError if the key (or that version of it) does not exist.
|
|
153
|
+
*/
|
|
154
|
+
getTemplateTags(templateKey: string, version?: number | null): Promise<ManagedTemplateTag[]>;
|
|
155
|
+
/**
|
|
156
|
+
* Replace the tags on one version of a template, creating any that do not exist.
|
|
157
|
+
*
|
|
158
|
+
* This edits a version in place rather than creating a new one, which is the one thing about a
|
|
159
|
+
* template that does: tags are search metadata, not template content, so retagging for
|
|
160
|
+
* findability should not spawn a version and reset it to `draft`.
|
|
161
|
+
*
|
|
162
|
+
* @param tags tag texts (or slugs). Empty clears the version's tags.
|
|
163
|
+
* @throws ManagedTemplateNotFoundError if the key (or that version of it) does not exist.
|
|
164
|
+
* @throws ManagedTemplateInvalidTagError if a text has nothing that can be slugified.
|
|
165
|
+
*/
|
|
166
|
+
setTemplateTags(templateKey: string, tags: string[], version?: number | null): Promise<ManagedTemplate>;
|
|
167
|
+
/** Every version of every template in the store. */
|
|
168
|
+
getAllTemplates(): Promise<ManagedTemplate[]>;
|
|
169
|
+
/** Every template version in any of the given statuses. */
|
|
170
|
+
getTemplatesByStatus(status: ManagedTemplateStatus[]): Promise<ManagedTemplate[]>;
|
|
171
|
+
/**
|
|
172
|
+
* The templates matching `filters`.
|
|
173
|
+
*
|
|
174
|
+
* Every field of `ManagedTemplateFilterFields` tests an attribute of the row — `isAbstract`
|
|
175
|
+
* included, which is why it is stored rather than parsed here — with one exception:
|
|
176
|
+
* `mostRecentActiveVersion` is about the *key*. `true` keeps only the highest-numbered version
|
|
177
|
+
* of each key whose status is in `MOST_RECENT_ACTIVE_VERSION_STATUSES`, and `false` keeps every
|
|
178
|
+
* other row — so an implementation answers it by comparing the row against its key's other
|
|
179
|
+
* versions rather than by reading a field. It is what the service's listing methods apply by
|
|
180
|
+
* default, so a backend that cannot evaluate it cannot serve a default listing.
|
|
181
|
+
*/
|
|
182
|
+
getFilteredTemplates(filters: ManagedTemplateFilter): Promise<ManagedTemplate[]>;
|
|
183
|
+
/**
|
|
184
|
+
* One page of templates.
|
|
185
|
+
*
|
|
186
|
+
* @param page 1-indexed. `ManagedTemplateService` validates `page >= 1` before calling, so
|
|
187
|
+
* unlike `vintasend`'s notification backends there is no per-backend page numbering to
|
|
188
|
+
* negotiate.
|
|
189
|
+
*/
|
|
190
|
+
getPaginatedTemplates(page: number, pageSize: number, orderBy?: ManagedTemplateOrderBy): Promise<ManagedTemplate[]>;
|
|
191
|
+
/**
|
|
192
|
+
* One page of the templates matching `filters`. `page` is 1-indexed.
|
|
193
|
+
*
|
|
194
|
+
* `orderBy` is optional and every `orderBy.*` capability defaults to false, so a backend
|
|
195
|
+
* written before ordering existed keeps compiling and keeps its honest report. A backend that
|
|
196
|
+
* *does* accept it must apply the order to the whole result set before paging, and must
|
|
197
|
+
* declare the fields it can order by — a page sorted after it was chosen is sorted within
|
|
198
|
+
* itself and wrong across page boundaries.
|
|
199
|
+
*/
|
|
200
|
+
getPaginatedFilteredTemplates(filters: ManagedTemplateFilter, page: number, pageSize: number, orderBy?: ManagedTemplateOrderBy): Promise<ManagedTemplate[]>;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=base-template-manager-backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-template-manager-backend.d.ts","sourceRoot":"","sources":["../src/base-template-manager-backend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inheritance and inclusion for managed templates, resolved before the engine runs.
|
|
3
|
+
*
|
|
4
|
+
* A file-based renderer gets composition for free. Pug's `extends` and Nunjucks' `include` hand
|
|
5
|
+
* a *name* to a loader, and a loader reads files, so the header, the footer and the wrapper
|
|
6
|
+
* every email shares live in one file that every other file points at.
|
|
7
|
+
*
|
|
8
|
+
* Managed templates are not files. They reach the engine as source, pulled out of a store and
|
|
9
|
+
* handed to `renderFromTemplateContent` as a string, so a loader has nothing to resolve and
|
|
10
|
+
* those tags have nothing to load. Without this module the shared chrome would have to be
|
|
11
|
+
* pasted into every template in the store, and changing the footer would mean editing every row
|
|
12
|
+
* that has one.
|
|
13
|
+
*
|
|
14
|
+
* This module puts composition back where the store can serve it. It defines a small tag
|
|
15
|
+
* language, resolves it against the template store, and hands the engine one flat string with no
|
|
16
|
+
* trace of itself left in it. Whatever runs next — Pug, Nunjucks, React Email, anything — sees
|
|
17
|
+
* only its own syntax, and its own `extends` / `include` are left untouched for it to deal with.
|
|
18
|
+
*
|
|
19
|
+
* ## The tags
|
|
20
|
+
*
|
|
21
|
+
* Every tag carries the `managed_` prefix, so nothing here can be mistaken for an engine tag
|
|
22
|
+
* that is meant to survive composition. The prefix is configurable per composer, but it is
|
|
23
|
+
* reserved: an unknown `{% managed_* %}` tag is a syntax error rather than text passed through,
|
|
24
|
+
* which is what turns a typo into an error instead of into a broken email.
|
|
25
|
+
*
|
|
26
|
+
* - `{% managed_extends "base-email" %}` — this template is a child of `base-email`. At most one
|
|
27
|
+
* per template, at the top level (never inside a block). Takes an optional `version=2` to pin
|
|
28
|
+
* the parent.
|
|
29
|
+
* - `{% managed_children %}` — in a parent: where the child's content goes. This is what makes a
|
|
30
|
+
* template *abstract*: it is a layout with a hole in it, filled by whatever the child writes
|
|
31
|
+
* outside its blocks. Rendered on its own, with no child, the hole is simply empty.
|
|
32
|
+
* - `{% managed_block name %}...{% managed_endblock %}` — a named, overridable region. A
|
|
33
|
+
* parent's block renders its own content unless a child declares a block with the same name;
|
|
34
|
+
* the child's wins. Blocks may nest.
|
|
35
|
+
* - `{% managed_super %}` — inside a child's block: the content of the block it is overriding.
|
|
36
|
+
* Chains through as many levels of inheritance as there are.
|
|
37
|
+
* - `{% managed_include "footer" %}` — splice another template in at this point. The included
|
|
38
|
+
* template is composed in full first, so an include may itself extend and include.
|
|
39
|
+
*
|
|
40
|
+
* ## How a child fills a parent
|
|
41
|
+
*
|
|
42
|
+
* Everything the child writes *outside* a block is its children content, and it lands in the
|
|
43
|
+
* parent's `{% managed_children %}`. Blocks are pulled out of that content first, so the two
|
|
44
|
+
* mechanisms compose: a child can both fill the hole and override named regions.
|
|
45
|
+
*
|
|
46
|
+
* ```
|
|
47
|
+
* parent "base-email" <html><body>
|
|
48
|
+
* {% managed_block header %}<h1>Acme</h1>{% managed_endblock %}
|
|
49
|
+
* {% managed_children %}
|
|
50
|
+
* </body></html>
|
|
51
|
+
*
|
|
52
|
+
* child "welcome" {% managed_extends "base-email" %}
|
|
53
|
+
* {% managed_block header %}<h1>Welcome</h1>{% managed_endblock %}
|
|
54
|
+
* <p>Hi #{name}</p>
|
|
55
|
+
*
|
|
56
|
+
* composed <html><body>
|
|
57
|
+
* <h1>Welcome</h1>
|
|
58
|
+
* <p>Hi #{name}</p>
|
|
59
|
+
* </body></html>
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* `#{name}` is untouched: composition never looks at engine syntax, and the context is the
|
|
63
|
+
* engine's business.
|
|
64
|
+
*
|
|
65
|
+
* ## One field at a time
|
|
66
|
+
*
|
|
67
|
+
* A template carries three sources — body, subject and preheader — and each is composed against
|
|
68
|
+
* the *same* field of the template it references. A child's body extends the parent's body; its
|
|
69
|
+
* subject extends the parent's subject. So a base can define a subject prefix and a body wrapper
|
|
70
|
+
* at once, and neither leaks into the other. A field the referenced template leaves empty
|
|
71
|
+
* composes to nothing rather than to an error.
|
|
72
|
+
*
|
|
73
|
+
* ## Versions
|
|
74
|
+
*
|
|
75
|
+
* A reference with no `version=` resolves through the backend the same way any other read does:
|
|
76
|
+
* to whatever version that key currently is. Pin it when a template must keep composing against
|
|
77
|
+
* an exact parent — re-rendering an old notification resolves the child's version explicitly,
|
|
78
|
+
* but its unpinned parents still resolve to today's.
|
|
79
|
+
*
|
|
80
|
+
* ## Cycles and depth
|
|
81
|
+
*
|
|
82
|
+
* A reference chain that comes back to a template already being composed throws
|
|
83
|
+
* `ManagedTemplateCompositionCycleError` naming the chain, and a chain longer than `maxDepth`
|
|
84
|
+
* throws `ManagedTemplateCompositionDepthError`. Neither can be caught by the engine
|
|
85
|
+
* downstream, so both are found here rather than as a hang at send time.
|
|
86
|
+
*/
|
|
87
|
+
import { type TemplateField } from './constants.js';
|
|
88
|
+
import type { ManagedTemplate } from './types.js';
|
|
89
|
+
/**
|
|
90
|
+
* Prefixed so a composition tag can never be confused with an engine tag meant to survive into
|
|
91
|
+
* the rendered output. Configurable on a composer, but the whole prefix is reserved: unknown
|
|
92
|
+
* tags carrying it are rejected instead of passed through.
|
|
93
|
+
*/
|
|
94
|
+
export declare const DEFAULT_TAG_PREFIX = "managed_";
|
|
95
|
+
/**
|
|
96
|
+
* How many references deep a single chain may go — extends and include both count, since both
|
|
97
|
+
* resolve another template. High enough that no real layout hits it, low enough that a
|
|
98
|
+
* pathological store fails fast instead of exhausting the stack.
|
|
99
|
+
*/
|
|
100
|
+
export declare const DEFAULT_MAX_DEPTH = 25;
|
|
101
|
+
/**
|
|
102
|
+
* What `getTemplate` looks like from here: a key and an optional version in, one template out.
|
|
103
|
+
* `BaseTemplateManagerBackend.getTemplate` satisfies it as it stands.
|
|
104
|
+
*/
|
|
105
|
+
export type TemplateResolver = (key: string, version: number | null) => Promise<ManagedTemplate>;
|
|
106
|
+
/** One direct reference from a template's field to another template. */
|
|
107
|
+
export type TemplateReference = {
|
|
108
|
+
kind: 'extends' | 'include';
|
|
109
|
+
key: string;
|
|
110
|
+
version: number | null;
|
|
111
|
+
field: TemplateField;
|
|
112
|
+
};
|
|
113
|
+
export type TemplateComposerOptions = {
|
|
114
|
+
tagPrefix?: string;
|
|
115
|
+
maxDepth?: number;
|
|
116
|
+
};
|
|
117
|
+
/** Resolves `managed_*` composition tags against a template store. */
|
|
118
|
+
export declare class TemplateComposer {
|
|
119
|
+
private readonly resolveTemplate;
|
|
120
|
+
readonly tagPrefix: string;
|
|
121
|
+
readonly maxDepth: number;
|
|
122
|
+
/**
|
|
123
|
+
* @param resolveTemplate how a referenced key becomes a template — normally a backend's
|
|
124
|
+
* `getTemplate`. Left out, the composer still parses (so `references`, `isAbstract` and
|
|
125
|
+
* syntax checking work), but any template that actually references another throws
|
|
126
|
+
* `ManagedTemplateCompositionReferenceError`.
|
|
127
|
+
* @param options `tagPrefix` — the reserved prefix every composition tag carries; change it
|
|
128
|
+
* only if `managed_` collides with something the engine downstream must receive verbatim.
|
|
129
|
+
* `maxDepth` — how many references deep one chain may go before it is called runaway.
|
|
130
|
+
*/
|
|
131
|
+
constructor(resolveTemplate?: TemplateResolver | null, options?: TemplateComposerOptions);
|
|
132
|
+
/** A composer that resolves references through `backend.getTemplate`. */
|
|
133
|
+
static fromBackend(backend: {
|
|
134
|
+
getTemplate(key: string, version?: number | null): Promise<ManagedTemplate>;
|
|
135
|
+
}, options?: TemplateComposerOptions): TemplateComposer;
|
|
136
|
+
/**
|
|
137
|
+
* Resolve every composition tag in a template, returning the flattened result.
|
|
138
|
+
*
|
|
139
|
+
* Each of the three sources is composed against the same field of whatever it references. The
|
|
140
|
+
* template handed in is never mutated; a template with no composition tags in it is returned
|
|
141
|
+
* as it stands, same object and all.
|
|
142
|
+
*/
|
|
143
|
+
compose(template: ManagedTemplate): Promise<ManagedTemplate>;
|
|
144
|
+
/**
|
|
145
|
+
* Compose one source string that is not (yet) a stored template.
|
|
146
|
+
*
|
|
147
|
+
* This is the path for source in hand rather than in the store: an admin validating what was
|
|
148
|
+
* typed into a form before saving it, a preview of an unsaved draft.
|
|
149
|
+
*
|
|
150
|
+
* Pass `key` (and `version`) when the source belongs to a template that exists, so a chain
|
|
151
|
+
* that leads back to it is reported as the cycle it is instead of composing the stored — and
|
|
152
|
+
* by then stale — copy of the very row being edited.
|
|
153
|
+
*/
|
|
154
|
+
composeSource(source: string, options?: {
|
|
155
|
+
field?: TemplateField;
|
|
156
|
+
key?: string;
|
|
157
|
+
version?: number | null;
|
|
158
|
+
}): Promise<string>;
|
|
159
|
+
/** Compose a single field of a template, leaving the other two alone. */
|
|
160
|
+
composeField(template: ManagedTemplate, field: TemplateField): Promise<string | null>;
|
|
161
|
+
/**
|
|
162
|
+
* Compose the template and throw the result away, to surface any problem now.
|
|
163
|
+
*
|
|
164
|
+
* What an admin form or a deploy check calls: it throws exactly what rendering would have
|
|
165
|
+
* thrown, at a point where someone can still fix it.
|
|
166
|
+
*/
|
|
167
|
+
validate(template: ManagedTemplate): Promise<void>;
|
|
168
|
+
/**
|
|
169
|
+
* Every template this one directly names, across all three fields.
|
|
170
|
+
*
|
|
171
|
+
* Direct only: what the references themselves reference is not followed, so this needs no
|
|
172
|
+
* store and never throws for a missing template. Order is the order they appear, field by
|
|
173
|
+
* field.
|
|
174
|
+
*/
|
|
175
|
+
references(template: ManagedTemplate): TemplateReference[];
|
|
176
|
+
/** Every template one source string directly names. */
|
|
177
|
+
sourceReferences(source: string, field?: TemplateField): TemplateReference[];
|
|
178
|
+
/**
|
|
179
|
+
* Whether this template is a base to build on rather than one to send.
|
|
180
|
+
*
|
|
181
|
+
* True when any of its fields declares a `{% managed_children %}` hole, or declares blocks
|
|
182
|
+
* without extending anything — the two shapes that only make sense with a child underneath.
|
|
183
|
+
* Nothing is stored: abstractness is a property of the source, so a template becomes abstract
|
|
184
|
+
* the moment someone writes the hole into it and stops being abstract the moment they take it
|
|
185
|
+
* out.
|
|
186
|
+
*
|
|
187
|
+
* Composing an abstract template directly is allowed and yields the layout with an empty hole.
|
|
188
|
+
* Use this to keep one out of a picker where a *sendable* template is being chosen.
|
|
189
|
+
*/
|
|
190
|
+
isAbstract(template: ManagedTemplate): boolean;
|
|
191
|
+
/**
|
|
192
|
+
* Whether one source string is a base to build on rather than one to send.
|
|
193
|
+
*
|
|
194
|
+
* The per-field half of {@link isAbstract}, for source in hand rather than a whole template.
|
|
195
|
+
*/
|
|
196
|
+
sourceIsAbstract(source: string, field?: TemplateField): boolean;
|
|
197
|
+
/**
|
|
198
|
+
* Walk the inheritance chain from this source upwards, rendering once at the top.
|
|
199
|
+
*
|
|
200
|
+
* Overrides accumulate on the way up: each level's blocks are pushed *under* the ones already
|
|
201
|
+
* collected, so the most derived definition stays at the head of every chain and
|
|
202
|
+
* `{% managed_super %}` reaches the next one down it. The content each level writes outside its
|
|
203
|
+
* blocks is rendered as it is passed, becoming the children of the level above — which is what
|
|
204
|
+
* lets a middle template wrap its own child's content before handing it on.
|
|
205
|
+
*/
|
|
206
|
+
private composeInternal;
|
|
207
|
+
private render;
|
|
208
|
+
/**
|
|
209
|
+
* Fetch a referenced template and extend the chain with it.
|
|
210
|
+
*
|
|
211
|
+
* Cycles are compared on the *resolved* version rather than on the requested one, so a template
|
|
212
|
+
* reached once by name and once by an explicit `version=` is recognized as the same row.
|
|
213
|
+
*/
|
|
214
|
+
private resolve;
|
|
215
|
+
/**
|
|
216
|
+
* Turn a source string into a node tree, rejecting anything malformed.
|
|
217
|
+
*
|
|
218
|
+
* A tag sitting alone on its line takes the whole line with it — its indentation and the
|
|
219
|
+
* newline after it — so a layout written to be readable does not compose into one full of
|
|
220
|
+
* blank lines. A tag with content beside it is removed exactly, and the whitespace around it
|
|
221
|
+
* is the author's.
|
|
222
|
+
*/
|
|
223
|
+
private parse;
|
|
224
|
+
private tag;
|
|
225
|
+
private syntax;
|
|
226
|
+
private referenceArgs;
|
|
227
|
+
private blockName;
|
|
228
|
+
private expectNoArgs;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Whether a template is a base to build on rather than one to send.
|
|
232
|
+
*
|
|
233
|
+
* The store-free shortcut for `TemplateComposer.isAbstract`: abstractness is read off the
|
|
234
|
+
* source, so no backend is needed to answer it. This is what a backend calls on every write to
|
|
235
|
+
* keep `ManagedTemplate.isAbstract` in step with the sources.
|
|
236
|
+
*/
|
|
237
|
+
export declare function isAbstract(template: Pick<ManagedTemplate, TemplateField>, tagPrefix?: string): boolean;
|
|
238
|
+
//# sourceMappingURL=composition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary every other module in this package is written against.
|
|
3
|
+
*
|
|
4
|
+
* Statuses are string unions rather than enums, matching how `vintasend` spells
|
|
5
|
+
* `NotificationStatus`: the values travel over HTTP and through a database as strings, and a
|
|
6
|
+
* union keeps the stored form and the typed form the same thing.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MANAGED_TEMPLATE_STATUSES: readonly ["draft", "active", "inactive", "archived"];
|
|
9
|
+
export type ManagedTemplateStatus = (typeof MANAGED_TEMPLATE_STATUSES)[number];
|
|
10
|
+
/**
|
|
11
|
+
* The statuses a version has to be in to count as its key's current one for the
|
|
12
|
+
* `mostRecentActiveVersion` filter: what is published now, plus the draft on its way to
|
|
13
|
+
* replacing it. `inactive` and `archived` versions are history — a key whose versions are all
|
|
14
|
+
* retired has no current version at all and drops out of that filter entirely.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MOST_RECENT_ACTIVE_VERSION_STATUSES: readonly ManagedTemplateStatus[];
|
|
17
|
+
/**
|
|
18
|
+
* Whether a tag is still offered when tagging a template.
|
|
19
|
+
*
|
|
20
|
+
* `archived` retires a tag from the pickers and suggestion lists a UI builds without breaking
|
|
21
|
+
* the templates already carrying it: an archived tag keeps its links, and filtering by it keeps
|
|
22
|
+
* working. Deleting the tag is the operation that severs those links.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MANAGED_TEMPLATE_TAG_STATUSES: readonly ["active", "archived"];
|
|
25
|
+
export type ManagedTemplateTagStatus = (typeof MANAGED_TEMPLATE_TAG_STATUSES)[number];
|
|
26
|
+
/**
|
|
27
|
+
* The three sources a template carries, composed independently and each against the same field
|
|
28
|
+
* of whatever it references.
|
|
29
|
+
*/
|
|
30
|
+
export declare const TEMPLATE_FIELDS: readonly ["bodyTemplate", "subjectTemplate", "preheaderTemplate"];
|
|
31
|
+
export type TemplateField = (typeof TEMPLATE_FIELDS)[number];
|
|
32
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,yBAAyB,sDAAuD,CAAC;AAE9F,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/E;;;;;GAKG;AACH,eAAO,MAAM,mCAAmC,EAAE,SAAS,qBAAqB,EAG/E,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,iCAAkC,CAAC;AAE7E,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtF;;;GAGG;AACH,eAAO,MAAM,eAAe,mEAAoE,CAAC;AAEjG,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary every other module in this package is written against.
|
|
3
|
+
*
|
|
4
|
+
* Statuses are string unions rather than enums, matching how `vintasend` spells
|
|
5
|
+
* `NotificationStatus`: the values travel over HTTP and through a database as strings, and a
|
|
6
|
+
* union keeps the stored form and the typed form the same thing.
|
|
7
|
+
*/
|
|
8
|
+
export const MANAGED_TEMPLATE_STATUSES = ['draft', 'active', 'inactive', 'archived'];
|
|
9
|
+
/**
|
|
10
|
+
* The statuses a version has to be in to count as its key's current one for the
|
|
11
|
+
* `mostRecentActiveVersion` filter: what is published now, plus the draft on its way to
|
|
12
|
+
* replacing it. `inactive` and `archived` versions are history — a key whose versions are all
|
|
13
|
+
* retired has no current version at all and drops out of that filter entirely.
|
|
14
|
+
*/
|
|
15
|
+
export const MOST_RECENT_ACTIVE_VERSION_STATUSES = [
|
|
16
|
+
'active',
|
|
17
|
+
'draft',
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Whether a tag is still offered when tagging a template.
|
|
21
|
+
*
|
|
22
|
+
* `archived` retires a tag from the pickers and suggestion lists a UI builds without breaking
|
|
23
|
+
* the templates already carrying it: an archived tag keeps its links, and filtering by it keeps
|
|
24
|
+
* working. Deleting the tag is the operation that severs those links.
|
|
25
|
+
*/
|
|
26
|
+
export const MANAGED_TEMPLATE_TAG_STATUSES = ['active', 'archived'];
|
|
27
|
+
/**
|
|
28
|
+
* The three sources a template carries, composed independently and each against the same field
|
|
29
|
+
* of whatever it references.
|
|
30
|
+
*/
|
|
31
|
+
export const TEMPLATE_FIELDS = ['bodyTemplate', 'subjectTemplate', 'preheaderTemplate'];
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every failure this package reports, as a class a caller can branch on.
|
|
3
|
+
*
|
|
4
|
+
* `instanceof` works across the whole hierarchy because each constructor restores the prototype
|
|
5
|
+
* chain — TypeScript compiled to an ES5-era target breaks it otherwise, and this package is
|
|
6
|
+
* consumed by applications that pick their own target.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ManagedTemplateError extends Error {
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
/** Raised when a template is not found in the backend. */
|
|
12
|
+
export declare class ManagedTemplateNotFoundError extends ManagedTemplateError {
|
|
13
|
+
}
|
|
14
|
+
/** Raised when a filter is malformed or names a field the vocabulary does not have. */
|
|
15
|
+
export declare class ManagedTemplateInvalidFilterError extends ManagedTemplateError {
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Raised when a listing asks for an order the configured backend cannot apply.
|
|
19
|
+
*
|
|
20
|
+
* This throws rather than quietly dropping the order, which is the difference between ordering
|
|
21
|
+
* and filtering: a dropped filter returns more rows than asked for, which a caller can see, while
|
|
22
|
+
* a dropped order returns the same rows in an arbitrary sequence that looks sorted. Read
|
|
23
|
+
* `getBackendSupportedFilterCapabilities()` and offer only the fields it reports.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ManagedTemplateUnsupportedOrderingError extends ManagedTemplateError {
|
|
26
|
+
}
|
|
27
|
+
/** Raised when a status change is attributed to a user the backend cannot resolve. */
|
|
28
|
+
export declare class ManagedTemplateChangeUserNotFoundError extends ManagedTemplateError {
|
|
29
|
+
}
|
|
30
|
+
/** Raised when a status change is not allowed from the version's current status. */
|
|
31
|
+
export declare class ManagedTemplateStatusTransitionError extends ManagedTemplateError {
|
|
32
|
+
}
|
|
33
|
+
/** Raised when a tag is not found in the backend. */
|
|
34
|
+
export declare class ManagedTemplateTagNotFoundError extends ManagedTemplateError {
|
|
35
|
+
}
|
|
36
|
+
/** Raised when creating a tag whose text already slugs onto an existing tag. */
|
|
37
|
+
export declare class ManagedTemplateTagAlreadyExistsError extends ManagedTemplateError {
|
|
38
|
+
}
|
|
39
|
+
/** Raised when a tag's text is empty or has nothing that can be slugified. */
|
|
40
|
+
export declare class ManagedTemplateInvalidTagError extends ManagedTemplateError {
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Base class for every failure of {@link TemplateComposer}.
|
|
44
|
+
*
|
|
45
|
+
* Composition runs before the template engine does, so nothing here can be caught (or reported)
|
|
46
|
+
* by the engine downstream. Catch this to treat "the template could not be assembled" as one
|
|
47
|
+
* condition, or a subclass to tell a typo apart from a missing base.
|
|
48
|
+
*/
|
|
49
|
+
export declare class ManagedTemplateCompositionError extends ManagedTemplateError {
|
|
50
|
+
}
|
|
51
|
+
/** Raised when a `managed_*` composition tag is malformed, unknown or unbalanced. */
|
|
52
|
+
export declare class ManagedTemplateCompositionSyntaxError extends ManagedTemplateCompositionError {
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Raised when a template extends or includes a template that does not exist.
|
|
56
|
+
*
|
|
57
|
+
* The Python sibling makes this a `ManagedTemplateNotFoundError` as well, so code already
|
|
58
|
+
* handling a missing template keeps working. TypeScript has no multiple inheritance, so the
|
|
59
|
+
* relationship is carried by {@link isNotFoundError} instead — check with that rather than with
|
|
60
|
+
* a bare `instanceof ManagedTemplateNotFoundError`, and test for
|
|
61
|
+
* `ManagedTemplateCompositionError` *before* "not found" wherever the distinction matters: a
|
|
62
|
+
* base that does not exist is a broken composition of a template that does.
|
|
63
|
+
*/
|
|
64
|
+
export declare class ManagedTemplateCompositionReferenceError extends ManagedTemplateCompositionError {
|
|
65
|
+
}
|
|
66
|
+
/** Raised when a chain of extends/include references comes back to where it started. */
|
|
67
|
+
export declare class ManagedTemplateCompositionCycleError extends ManagedTemplateCompositionError {
|
|
68
|
+
}
|
|
69
|
+
/** Raised when a chain of references runs deeper than the composer's `maxDepth`. */
|
|
70
|
+
export declare class ManagedTemplateCompositionDepthError extends ManagedTemplateCompositionError {
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether an error means "the thing you asked for is not in the store".
|
|
74
|
+
*
|
|
75
|
+
* True for {@link ManagedTemplateNotFoundError} and for
|
|
76
|
+
* {@link ManagedTemplateCompositionReferenceError}, which is a missing template reached through
|
|
77
|
+
* a template that exists.
|
|
78
|
+
*/
|
|
79
|
+
export declare function isNotFoundError(error: unknown): boolean;
|
|
80
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +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"}
|