@terminalfour/terminalfour-js 1.0.2 → 1.1.0
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/dist/cjs/content-cache.d.ts +38 -0
- package/dist/cjs/content-cache.js +42 -0
- package/dist/cjs/element-resolver.d.ts +16 -0
- package/dist/cjs/element-resolver.js +77 -1
- package/dist/cjs/models/content-item.js +13 -1
- package/dist/cjs/resources/content-resource.d.ts +7 -11
- package/dist/cjs/resources/content-resource.js +36 -79
- package/dist/cjs/resources/content-type-resource.d.ts +8 -8
- package/dist/cjs/resources/content-type-resource.js +25 -6
- package/dist/cjs/resources/list-resource.d.ts +2 -6
- package/dist/cjs/resources/list-resource.js +8 -6
- package/dist/cjs/resources/navigation-resource.d.ts +17 -0
- package/dist/cjs/resources/navigation-resource.js +13 -2
- package/dist/cjs/resources/page-layout-resource.d.ts +11 -0
- package/dist/cjs/resources/page-layout-resource.js +12 -2
- package/dist/cjs/section-ref.d.ts +3 -1
- package/dist/cjs/section-ref.js +6 -5
- package/dist/cjs/t4-client.d.ts +1 -0
- package/dist/cjs/t4-client.js +6 -1
- package/dist/cjs/utils.d.ts +39 -0
- package/dist/cjs/utils.js +37 -0
- package/dist/esm/content-cache.d.ts +38 -0
- package/dist/esm/content-cache.js +38 -0
- package/dist/esm/element-resolver.d.ts +16 -0
- package/dist/esm/element-resolver.js +77 -1
- package/dist/esm/models/content-item.js +13 -1
- package/dist/esm/resources/content-resource.d.ts +7 -11
- package/dist/esm/resources/content-resource.js +37 -80
- package/dist/esm/resources/content-type-resource.d.ts +8 -8
- package/dist/esm/resources/content-type-resource.js +26 -7
- package/dist/esm/resources/list-resource.d.ts +2 -6
- package/dist/esm/resources/list-resource.js +9 -7
- package/dist/esm/resources/navigation-resource.d.ts +17 -0
- package/dist/esm/resources/navigation-resource.js +13 -2
- package/dist/esm/resources/page-layout-resource.d.ts +11 -0
- package/dist/esm/resources/page-layout-resource.js +13 -3
- package/dist/esm/section-ref.d.ts +3 -1
- package/dist/esm/section-ref.js +6 -5
- package/dist/esm/t4-client.d.ts +1 -0
- package/dist/esm/t4-client.js +6 -1
- package/dist/esm/utils.d.ts +39 -0
- package/dist/esm/utils.js +32 -0
- package/docs/content-types.md +20 -3
- package/docs/content.md +13 -0
- package/docs/error-handling.md +3 -1
- package/docs/getting-started.md +4 -0
- package/docs/lists.md +2 -2
- package/docs/navigation.md +30 -1
- package/docs/page-layouts.md +26 -0
- package/package.json +1 -1
package/dist/esm/t4-client.js
CHANGED
|
@@ -13,6 +13,7 @@ import { PageLayoutResource } from './resources/page-layout-resource.js';
|
|
|
13
13
|
import { MediaTypeResource } from './resources/media-type-resource.js';
|
|
14
14
|
import { NavigationResource } from './resources/navigation-resource.js';
|
|
15
15
|
import { Handlebars } from './handlebars.js';
|
|
16
|
+
import { ContentCache } from './content-cache.js';
|
|
16
17
|
import { invalidateAllCaches, normaliseBaseUrl, assertNotBrowser } from './utils.js';
|
|
17
18
|
/**
|
|
18
19
|
* Main entry point for the T4 SDK.
|
|
@@ -54,12 +55,16 @@ export class T4Client {
|
|
|
54
55
|
});
|
|
55
56
|
return item.id;
|
|
56
57
|
};
|
|
58
|
+
// Shared content caches (element TypeRegistry, content type templates).
|
|
59
|
+
// Threaded into every SectionRef/ContentResource so hierarchy traversal
|
|
60
|
+
// doesn't re-fetch instance-wide data on each t4.section(id) call.
|
|
61
|
+
this.contentCache = new ContentCache(this.httpClient, this.defaultLanguage, this.mediaCreateFn);
|
|
57
62
|
}
|
|
58
63
|
/**
|
|
59
64
|
* Returns a section reference scoped to the given section ID.
|
|
60
65
|
*/
|
|
61
66
|
section(id) {
|
|
62
|
-
return new SectionRef(this.httpClient, id, this.defaultLanguage, this.mediaCreateFn);
|
|
67
|
+
return new SectionRef(this.httpClient, id, this.defaultLanguage, this.mediaCreateFn, this.contentCache);
|
|
63
68
|
}
|
|
64
69
|
/**
|
|
65
70
|
* Returns a media category reference scoped to the given category ID.
|
package/dist/esm/utils.d.ts
CHANGED
|
@@ -68,6 +68,45 @@ export declare function flattenGroups(groups: Array<{
|
|
|
68
68
|
name: string;
|
|
69
69
|
groupChildren?: unknown[];
|
|
70
70
|
}>, map?: Map<number, string>): Map<number, string>;
|
|
71
|
+
/**
|
|
72
|
+
* Group/visibility mapping helpers.
|
|
73
|
+
*
|
|
74
|
+
* The T4 API represents an asset's owning group as `primaryGroup` (an object,
|
|
75
|
+
* where the id may be direct or nested under `group`) and its shared groups as
|
|
76
|
+
* `sharedGroups` (an array of `{ id }`). The SDK exposes these as a plain
|
|
77
|
+
* `primaryGroup: number` (0 = none) and `sharedGroups: number[]`. These helpers
|
|
78
|
+
* centralise the read/write mapping so content types, lists, page layouts, and
|
|
79
|
+
* navigation objects all handle groups identically.
|
|
80
|
+
*/
|
|
81
|
+
/** Raw shape of the API `primaryGroup` field. */
|
|
82
|
+
export interface RawPrimaryGroup {
|
|
83
|
+
id: number | null;
|
|
84
|
+
group?: {
|
|
85
|
+
id: number;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Reads the API `primaryGroup` object into a plain group id (0 = none). */
|
|
89
|
+
export declare function readPrimaryGroup(raw?: RawPrimaryGroup): number;
|
|
90
|
+
/** Reads the API `sharedGroups` array into a plain array of group ids. */
|
|
91
|
+
export declare function readSharedGroups(raw?: Array<{
|
|
92
|
+
id: number;
|
|
93
|
+
}>): number[];
|
|
94
|
+
/** Writes a plain group id back to the API `primaryGroup` shape (0/falsy = none). */
|
|
95
|
+
export declare function writePrimaryGroup(id: number): {
|
|
96
|
+
id: number | null;
|
|
97
|
+
};
|
|
98
|
+
/** Writes a plain array of group ids back to the API `sharedGroups` shape. */
|
|
99
|
+
export declare function writeSharedGroups(ids?: number[]): Array<{
|
|
100
|
+
id: number;
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* Validates that group/visibility values are acceptable to the T4 API before a
|
|
104
|
+
* write. The API returns an opaque 500 when `sharedGroups` contains the same id
|
|
105
|
+
* as `primaryGroup` (a group cannot be both the owner and a shared group), so we
|
|
106
|
+
* catch it here with a clear message. A `primaryGroup` of 0 means "no owning
|
|
107
|
+
* group" and is never a conflict.
|
|
108
|
+
*/
|
|
109
|
+
export declare function assertGroupsValid(primaryGroup: number, sharedGroups?: number[]): void;
|
|
71
110
|
/** Accepted file input: a file path, URL, Blob, ReadableStream, or { file, filename } object */
|
|
72
111
|
export type FileInput = string | Blob | NodeJS.ReadableStream | {
|
|
73
112
|
file: string | Blob | NodeJS.ReadableStream;
|
package/dist/esm/utils.js
CHANGED
|
@@ -170,6 +170,38 @@ export function flattenGroups(groups, map = new Map()) {
|
|
|
170
170
|
}
|
|
171
171
|
return map;
|
|
172
172
|
}
|
|
173
|
+
/** Reads the API `primaryGroup` object into a plain group id (0 = none). */
|
|
174
|
+
export function readPrimaryGroup(raw) {
|
|
175
|
+
return raw?.group?.id ?? raw?.id ?? 0;
|
|
176
|
+
}
|
|
177
|
+
/** Reads the API `sharedGroups` array into a plain array of group ids. */
|
|
178
|
+
export function readSharedGroups(raw) {
|
|
179
|
+
return (raw ?? []).map((g) => g.id);
|
|
180
|
+
}
|
|
181
|
+
/** Writes a plain group id back to the API `primaryGroup` shape (0/falsy = none). */
|
|
182
|
+
export function writePrimaryGroup(id) {
|
|
183
|
+
return { id: id || null };
|
|
184
|
+
}
|
|
185
|
+
/** Writes a plain array of group ids back to the API `sharedGroups` shape. */
|
|
186
|
+
export function writeSharedGroups(ids) {
|
|
187
|
+
return (ids ?? []).map((id) => ({ id }));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Validates that group/visibility values are acceptable to the T4 API before a
|
|
191
|
+
* write. The API returns an opaque 500 when `sharedGroups` contains the same id
|
|
192
|
+
* as `primaryGroup` (a group cannot be both the owner and a shared group), so we
|
|
193
|
+
* catch it here with a clear message. A `primaryGroup` of 0 means "no owning
|
|
194
|
+
* group" and is never a conflict.
|
|
195
|
+
*/
|
|
196
|
+
export function assertGroupsValid(primaryGroup, sharedGroups) {
|
|
197
|
+
if (!primaryGroup)
|
|
198
|
+
return;
|
|
199
|
+
if ((sharedGroups ?? []).includes(primaryGroup)) {
|
|
200
|
+
throw new Error(`sharedGroups cannot contain the primaryGroup id (${primaryGroup}). ` +
|
|
201
|
+
'A group cannot be both the primary (owning) group and a shared group. ' +
|
|
202
|
+
'Remove it from sharedGroups or choose a different primaryGroup.');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
173
205
|
/**
|
|
174
206
|
* Resolves a file input (path, URL, Blob, or ReadableStream) to a Blob.
|
|
175
207
|
*/
|
package/docs/content-types.md
CHANGED
|
@@ -36,8 +36,8 @@ The returned `ContentType` includes these main properties:
|
|
|
36
36
|
| `minUserLevel` | `'contributor'` |
|
|
37
37
|
| `workflow` | Assigned workflow |
|
|
38
38
|
| `directEdit` | Direct edit setting |
|
|
39
|
-
| `primaryGroup` |
|
|
40
|
-
| `sharedGroups` | Shared
|
|
39
|
+
| `primaryGroup` | Owning group ID (`0` = none) |
|
|
40
|
+
| `sharedGroups` | Shared group IDs. Cannot contain `primaryGroup`. |
|
|
41
41
|
| `fields` | Field definitions keyed by name |
|
|
42
42
|
|
|
43
43
|
Each entry in `news.fields` can include:
|
|
@@ -167,6 +167,8 @@ await t4.contentTypes.update(44, {
|
|
|
167
167
|
});
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
> **Warning:** Removing a field deletes that element's content from every content item using this content type, and it cannot be recovered. Only remove a field when you're certain the data is no longer needed. To change a field's length, description, or name, use `updateFields` rather than removing and re-adding.
|
|
171
|
+
|
|
170
172
|
Change properties of existing fields with `updateFields`. Fields are matched by `name`, and only the properties you supply are changed — anything you omit is left as-is. This is how you increase an element's length (`maxSize`):
|
|
171
173
|
|
|
172
174
|
```typescript
|
|
@@ -178,7 +180,20 @@ await t4.contentTypes.update(44, {
|
|
|
178
180
|
});
|
|
179
181
|
```
|
|
180
182
|
|
|
181
|
-
|
|
183
|
+
Rename an existing element with `newName`. The field is matched by its current `name`, and you can rename and change other properties in the same entry:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
await t4.contentTypes.update(44, {
|
|
187
|
+
updateFields: [
|
|
188
|
+
{ name: 'Title', newName: 'Headline' },
|
|
189
|
+
{ name: 'Summary', newName: 'Teaser', maxSize: 300 },
|
|
190
|
+
],
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
After a rename, the returned content type is addressable by the new name (`ct.fields['Headline']`); the old key no longer exists.
|
|
195
|
+
|
|
196
|
+
`updateFields` can change `maxSize`, `description`, `required`, `shown`, and `newName` (rename). It throws if a named field does not exist on the content type, and (like the other field operations) nothing is sent to T4 when it throws. Renaming an element on a system content type is blocked, except on the Section Meta Data and Extended User types (the same rule that applies to renames through the mutable pattern). It's currently not possible to change a field's *type*.
|
|
182
197
|
|
|
183
198
|
`updateFields`, `removeFields`, and `addFields` can be combined in a single call; updates are applied first, then removals, then additions.
|
|
184
199
|
|
|
@@ -240,6 +255,8 @@ contentType.removeField('Old Field');
|
|
|
240
255
|
await contentType.save();
|
|
241
256
|
```
|
|
242
257
|
|
|
258
|
+
> **Warning:** `removeField()` deletes the element's content from every content item using this content type once you `save()`, and it cannot be recovered. Prefer `updateFields` (or mutating the field directly) to change a field's length, description, or name.
|
|
259
|
+
|
|
243
260
|
Delete a content type through the resource:
|
|
244
261
|
|
|
245
262
|
```typescript
|
package/docs/content.md
CHANGED
|
@@ -113,6 +113,19 @@ await article.save();
|
|
|
113
113
|
|
|
114
114
|
`save()` defaults the status to `'pending'` to match T4's approval workflow. Set `item.status = 'approved'` before saving if the item must remain approved.
|
|
115
115
|
|
|
116
|
+
`save()` resolves every field type the same way `content.create()` and `content.update()` do — including Repeater fields. A repeater is read back as an array of `{ name, fields }` items (see [Values returned on read](#values-returned-on-read)), and you assign the same friendly shape back:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const deck = await t4.section(482).content.get(9200);
|
|
120
|
+
deck.fields['Slides'] = [
|
|
121
|
+
{ name: 'Slide 1', fields: { Heading: 'Welcome' } },
|
|
122
|
+
{ name: 'Slide 2', fields: { Heading: 'Agenda' } },
|
|
123
|
+
];
|
|
124
|
+
await deck.save();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Each repeater item's sub-fields are resolved into the API's element format automatically, so the mutable `get()` → modify → `save()` path and `content.update()` produce identical results.
|
|
128
|
+
|
|
116
129
|
## Approve, duplicate, move, or remove
|
|
117
130
|
|
|
118
131
|
### Approve one item
|
package/docs/error-handling.md
CHANGED
|
@@ -73,7 +73,9 @@ Graceful degradation paths, including failed media lookups and group name resolu
|
|
|
73
73
|
|
|
74
74
|
## Clear stale cache data
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
Content type writes through the SDK — `contentTypes.create()`, `contentTypes.update()`, `contentTypes.delete()`, and `ContentType.save()` (including `addField`/`removeField`) — clear the SDK's caches automatically, so a content type edit is reflected on the next read without any extra step.
|
|
77
|
+
|
|
78
|
+
You still need to clear the cache manually when configuration changes **outside** this SDK instance — for example content type, list, or other changes made in the T4 UI or by another process while your client is running:
|
|
77
79
|
|
|
78
80
|
```typescript
|
|
79
81
|
t4.clearCache();
|
package/docs/getting-started.md
CHANGED
|
@@ -141,6 +141,10 @@ The SDK caches content type templates, list values, element type definitions, an
|
|
|
141
141
|
t4.clearCache();
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
These caches are shared across the whole client, so reuse a single `T4Client` instance for the life of your task. Traversing the hierarchy — for example fetching and updating content across many sections with `t4.section(id)` — reuses cached element types and content type definitions instead of re-fetching them for each section. Creating a new `T4Client` per section would defeat this, so avoid that in loops.
|
|
145
|
+
|
|
146
|
+
Editing a content type through this client (`contentTypes.update()`, `ContentType.save()`, and so on) clears these caches for you, so your changes show up on the next read. Call `clearCache()` manually only for changes made outside this client instance.
|
|
147
|
+
|
|
144
148
|
---
|
|
145
149
|
|
|
146
150
|
**Next:** [Sections](./sections.md)
|
package/docs/lists.md
CHANGED
|
@@ -17,8 +17,8 @@ const list = await t4.lists.get(71);
|
|
|
17
17
|
| `description` | List description |
|
|
18
18
|
| `isForcedLanguage` | `false` |
|
|
19
19
|
| `isDefaultLanguage` | `false` |
|
|
20
|
-
| `primaryGroup` | `0` |
|
|
21
|
-
| `sharedGroups` | `
|
|
20
|
+
| `primaryGroup` | Owning group ID (`0` = none) |
|
|
21
|
+
| `sharedGroups` | Shared group IDs. Cannot contain `primaryGroup`. |
|
|
22
22
|
| `items` | Item definitions keyed by name |
|
|
23
23
|
|
|
24
24
|
Items are keyed by name:
|
package/docs/navigation.md
CHANGED
|
@@ -34,6 +34,8 @@ console.log(navigation.type); // 'a-to-z'
|
|
|
34
34
|
console.log(navigation.enabled); // true
|
|
35
35
|
console.log(navigation.cachingEnabled); // false
|
|
36
36
|
console.log(navigation.previewEnabled); // true
|
|
37
|
+
console.log(navigation.primaryGroup); // 1 (owning group ID; 0 = Global)
|
|
38
|
+
console.log(navigation.sharedGroups); // [34, 40]
|
|
37
39
|
console.log(navigation.properties); // properties vary by type
|
|
38
40
|
```
|
|
39
41
|
|
|
@@ -46,6 +48,8 @@ Type-specific properties use JavaScript booleans, numbers, and arrays. The SDK o
|
|
|
46
48
|
| `enabled` | yes | Whether the navigation is active |
|
|
47
49
|
| `cachingEnabled` | yes | Whether output caching is enabled |
|
|
48
50
|
| `previewEnabled` | yes | Whether preview mode is enabled |
|
|
51
|
+
| `primaryGroup` | yes | Owning group ID. `0` means no primary group (Global). |
|
|
52
|
+
| `sharedGroups` | yes | Array of group IDs the object is shared with |
|
|
49
53
|
| `properties` | yes | Type-specific configuration |
|
|
50
54
|
|
|
51
55
|
## Create a navigation object
|
|
@@ -63,7 +67,16 @@ await t4.navigation.create({
|
|
|
63
67
|
});
|
|
64
68
|
```
|
|
65
69
|
|
|
66
|
-
Only `type` and `name` are required. Properties have default values.
|
|
70
|
+
Only `type` and `name` are required. Properties have default values. `create()` also accepts optional `primaryGroup` and `sharedGroups`:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
await t4.navigation.create({
|
|
74
|
+
type: 'breadcrumbs',
|
|
75
|
+
name: 'Main Breadcrumbs',
|
|
76
|
+
primaryGroup: 35, // optional; owning group ID
|
|
77
|
+
sharedGroups: [34, 40], // optional; group IDs to share with
|
|
78
|
+
});
|
|
79
|
+
```
|
|
67
80
|
|
|
68
81
|
## Update a navigation object
|
|
69
82
|
|
|
@@ -99,6 +112,22 @@ navigation.properties.beforeHtml = '<div>';
|
|
|
99
112
|
await navigation.save();
|
|
100
113
|
```
|
|
101
114
|
|
|
115
|
+
## Group and visibility
|
|
116
|
+
|
|
117
|
+
`primaryGroup` is the object's owning group and `sharedGroups` are the groups it is shared with. Both are read on `get()` and written on `save()`, `update()`, and `create()`. Set `primaryGroup` to `0` to remove the owning group (Global):
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const navigation = await t4.navigation.get(181);
|
|
121
|
+
navigation.primaryGroup = 0; // remove from its group (Global)
|
|
122
|
+
navigation.sharedGroups = [];
|
|
123
|
+
await navigation.save();
|
|
124
|
+
|
|
125
|
+
// Or through update()
|
|
126
|
+
await t4.navigation.update(181, { primaryGroup: 35, sharedGroups: [34] });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`sharedGroups` cannot contain the `primaryGroup` id: a group cannot be both the owning group and a shared group. The SDK throws a clear error before sending the request (Terminalfour otherwise returns an opaque 500).
|
|
130
|
+
|
|
102
131
|
## Delete a navigation object
|
|
103
132
|
|
|
104
133
|
```typescript
|
package/docs/page-layouts.md
CHANGED
|
@@ -22,6 +22,8 @@ A full page layout includes:
|
|
|
22
22
|
| `fileExtension` | Output file extension |
|
|
23
23
|
| `syntax` | Syntax such as `'HTML/XML'` |
|
|
24
24
|
| `processor` | Processor such as `'handlebars'` |
|
|
25
|
+
| `primaryGroup` | Owning group ID. `0` means no primary group (Global). |
|
|
26
|
+
| `sharedGroups` | Array of group IDs the layout is shared with |
|
|
25
27
|
|
|
26
28
|
## Create a page layout
|
|
27
29
|
|
|
@@ -34,11 +36,35 @@ await t4.pageLayouts.create({
|
|
|
34
36
|
syntax: 'HTML/XML', // optional
|
|
35
37
|
processor: 'handlebars', // optional; default: 'handlebars'
|
|
36
38
|
fileExtension: 'html', // optional
|
|
39
|
+
primaryGroup: 35, // optional; owning group ID
|
|
40
|
+
sharedGroups: [34, 40], // optional; group IDs to share with
|
|
37
41
|
});
|
|
38
42
|
```
|
|
39
43
|
|
|
40
44
|
Processor options are `'handlebars'`, `'t4-tags'`, and `'programmable-layouts'`. The default is `'handlebars'`.
|
|
41
45
|
|
|
46
|
+
## Group and visibility
|
|
47
|
+
|
|
48
|
+
`primaryGroup` is the layout's owning group and `sharedGroups` are the groups it is shared with. Both are read on `get()` and written on `save()`, `update()`, and `create()`. Set `primaryGroup` to `0` to remove the owning group (Global):
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const layout = await t4.pageLayouts.get(5);
|
|
52
|
+
console.log(layout.primaryGroup); // 35
|
|
53
|
+
console.log(layout.sharedGroups); // [34, 40]
|
|
54
|
+
|
|
55
|
+
layout.primaryGroup = 0; // remove from its group (Global)
|
|
56
|
+
layout.sharedGroups = [];
|
|
57
|
+
await layout.save();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or through `update()`:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
await t4.pageLayouts.update(5, { primaryGroup: 35, sharedGroups: [34] });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`sharedGroups` cannot contain the `primaryGroup` id: a group cannot be both the owning group and a shared group. The SDK throws a clear error before sending the request (Terminalfour otherwise returns an opaque 500).
|
|
67
|
+
|
|
42
68
|
## Update a page layout
|
|
43
69
|
|
|
44
70
|
### Direct update
|