@terminalfour/terminalfour-js 1.0.0-rc.1

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.
Files changed (253) hide show
  1. package/LICENSE.md +106 -0
  2. package/README.md +169 -0
  3. package/dist/cjs/element-resolver.d.ts +115 -0
  4. package/dist/cjs/element-resolver.d.ts.map +1 -0
  5. package/dist/cjs/element-resolver.js +391 -0
  6. package/dist/cjs/element-resolver.js.map +1 -0
  7. package/dist/cjs/errors.d.ts +21 -0
  8. package/dist/cjs/errors.d.ts.map +1 -0
  9. package/dist/cjs/errors.js +21 -0
  10. package/dist/cjs/errors.js.map +1 -0
  11. package/dist/cjs/handlebars.d.ts +123 -0
  12. package/dist/cjs/handlebars.d.ts.map +1 -0
  13. package/dist/cjs/handlebars.js +306 -0
  14. package/dist/cjs/handlebars.js.map +1 -0
  15. package/dist/cjs/http-client.d.ts +21 -0
  16. package/dist/cjs/http-client.d.ts.map +1 -0
  17. package/dist/cjs/http-client.js +126 -0
  18. package/dist/cjs/http-client.js.map +1 -0
  19. package/dist/cjs/index.d.ts +37 -0
  20. package/dist/cjs/index.d.ts.map +1 -0
  21. package/dist/cjs/index.js +55 -0
  22. package/dist/cjs/index.js.map +1 -0
  23. package/dist/cjs/media-category-ref.d.ts +78 -0
  24. package/dist/cjs/media-category-ref.d.ts.map +1 -0
  25. package/dist/cjs/media-category-ref.js +184 -0
  26. package/dist/cjs/media-category-ref.js.map +1 -0
  27. package/dist/cjs/media-library.d.ts +30 -0
  28. package/dist/cjs/media-library.d.ts.map +1 -0
  29. package/dist/cjs/media-library.js +77 -0
  30. package/dist/cjs/media-library.js.map +1 -0
  31. package/dist/cjs/models/content-item.d.ts +80 -0
  32. package/dist/cjs/models/content-item.d.ts.map +1 -0
  33. package/dist/cjs/models/content-item.js +682 -0
  34. package/dist/cjs/models/content-item.js.map +1 -0
  35. package/dist/cjs/models/media-category-item.d.ts +29 -0
  36. package/dist/cjs/models/media-category-item.d.ts.map +1 -0
  37. package/dist/cjs/models/media-category-item.js +33 -0
  38. package/dist/cjs/models/media-category-item.js.map +1 -0
  39. package/dist/cjs/models/media-item.d.ts +74 -0
  40. package/dist/cjs/models/media-item.d.ts.map +1 -0
  41. package/dist/cjs/models/media-item.js +188 -0
  42. package/dist/cjs/models/media-item.js.map +1 -0
  43. package/dist/cjs/models/section-item.d.ts +58 -0
  44. package/dist/cjs/models/section-item.d.ts.map +1 -0
  45. package/dist/cjs/models/section-item.js +166 -0
  46. package/dist/cjs/models/section-item.js.map +1 -0
  47. package/dist/cjs/package.json +1 -0
  48. package/dist/cjs/resources/channel-resource.d.ts +112 -0
  49. package/dist/cjs/resources/channel-resource.d.ts.map +1 -0
  50. package/dist/cjs/resources/channel-resource.js +107 -0
  51. package/dist/cjs/resources/channel-resource.js.map +1 -0
  52. package/dist/cjs/resources/content-resource.d.ts +50 -0
  53. package/dist/cjs/resources/content-resource.d.ts.map +1 -0
  54. package/dist/cjs/resources/content-resource.js +286 -0
  55. package/dist/cjs/resources/content-resource.js.map +1 -0
  56. package/dist/cjs/resources/content-type-resource.d.ts +283 -0
  57. package/dist/cjs/resources/content-type-resource.d.ts.map +1 -0
  58. package/dist/cjs/resources/content-type-resource.js +970 -0
  59. package/dist/cjs/resources/content-type-resource.js.map +1 -0
  60. package/dist/cjs/resources/group-resource.d.ts +96 -0
  61. package/dist/cjs/resources/group-resource.d.ts.map +1 -0
  62. package/dist/cjs/resources/group-resource.js +213 -0
  63. package/dist/cjs/resources/group-resource.js.map +1 -0
  64. package/dist/cjs/resources/list-resource.d.ts +111 -0
  65. package/dist/cjs/resources/list-resource.d.ts.map +1 -0
  66. package/dist/cjs/resources/list-resource.js +179 -0
  67. package/dist/cjs/resources/list-resource.js.map +1 -0
  68. package/dist/cjs/resources/media-resource.d.ts +69 -0
  69. package/dist/cjs/resources/media-resource.d.ts.map +1 -0
  70. package/dist/cjs/resources/media-resource.js +210 -0
  71. package/dist/cjs/resources/media-resource.js.map +1 -0
  72. package/dist/cjs/resources/media-type-resource.d.ts +70 -0
  73. package/dist/cjs/resources/media-type-resource.d.ts.map +1 -0
  74. package/dist/cjs/resources/media-type-resource.js +195 -0
  75. package/dist/cjs/resources/media-type-resource.js.map +1 -0
  76. package/dist/cjs/resources/navigation-resource.d.ts +664 -0
  77. package/dist/cjs/resources/navigation-resource.d.ts.map +1 -0
  78. package/dist/cjs/resources/navigation-resource.js +2349 -0
  79. package/dist/cjs/resources/navigation-resource.js.map +1 -0
  80. package/dist/cjs/resources/page-layout-resource.d.ts +83 -0
  81. package/dist/cjs/resources/page-layout-resource.d.ts.map +1 -0
  82. package/dist/cjs/resources/page-layout-resource.js +214 -0
  83. package/dist/cjs/resources/page-layout-resource.js.map +1 -0
  84. package/dist/cjs/resources/user-resource.d.ts +126 -0
  85. package/dist/cjs/resources/user-resource.d.ts.map +1 -0
  86. package/dist/cjs/resources/user-resource.js +317 -0
  87. package/dist/cjs/resources/user-resource.js.map +1 -0
  88. package/dist/cjs/section-ref.d.ts +185 -0
  89. package/dist/cjs/section-ref.d.ts.map +1 -0
  90. package/dist/cjs/section-ref.js +813 -0
  91. package/dist/cjs/section-ref.js.map +1 -0
  92. package/dist/cjs/site-structure.d.ts +18 -0
  93. package/dist/cjs/site-structure.d.ts.map +1 -0
  94. package/dist/cjs/site-structure.js +57 -0
  95. package/dist/cjs/site-structure.js.map +1 -0
  96. package/dist/cjs/t4-client.d.ts +121 -0
  97. package/dist/cjs/t4-client.d.ts.map +1 -0
  98. package/dist/cjs/t4-client.js +190 -0
  99. package/dist/cjs/t4-client.js.map +1 -0
  100. package/dist/cjs/type-registry.d.ts +31 -0
  101. package/dist/cjs/type-registry.d.ts.map +1 -0
  102. package/dist/cjs/type-registry.js +76 -0
  103. package/dist/cjs/type-registry.js.map +1 -0
  104. package/dist/cjs/types.d.ts +211 -0
  105. package/dist/cjs/types.d.ts.map +1 -0
  106. package/dist/cjs/types.js +3 -0
  107. package/dist/cjs/types.js.map +1 -0
  108. package/dist/cjs/utils.d.ts +147 -0
  109. package/dist/cjs/utils.d.ts.map +1 -0
  110. package/dist/cjs/utils.js +408 -0
  111. package/dist/cjs/utils.js.map +1 -0
  112. package/dist/esm/element-resolver.d.ts +115 -0
  113. package/dist/esm/element-resolver.d.ts.map +1 -0
  114. package/dist/esm/element-resolver.js +387 -0
  115. package/dist/esm/element-resolver.js.map +1 -0
  116. package/dist/esm/errors.d.ts +21 -0
  117. package/dist/esm/errors.d.ts.map +1 -0
  118. package/dist/esm/errors.js +17 -0
  119. package/dist/esm/errors.js.map +1 -0
  120. package/dist/esm/handlebars.d.ts +123 -0
  121. package/dist/esm/handlebars.d.ts.map +1 -0
  122. package/dist/esm/handlebars.js +300 -0
  123. package/dist/esm/handlebars.js.map +1 -0
  124. package/dist/esm/http-client.d.ts +21 -0
  125. package/dist/esm/http-client.d.ts.map +1 -0
  126. package/dist/esm/http-client.js +122 -0
  127. package/dist/esm/http-client.js.map +1 -0
  128. package/dist/esm/index.d.ts +37 -0
  129. package/dist/esm/index.d.ts.map +1 -0
  130. package/dist/esm/index.js +26 -0
  131. package/dist/esm/index.js.map +1 -0
  132. package/dist/esm/media-category-ref.d.ts +78 -0
  133. package/dist/esm/media-category-ref.d.ts.map +1 -0
  134. package/dist/esm/media-category-ref.js +180 -0
  135. package/dist/esm/media-category-ref.js.map +1 -0
  136. package/dist/esm/media-library.d.ts +30 -0
  137. package/dist/esm/media-library.d.ts.map +1 -0
  138. package/dist/esm/media-library.js +73 -0
  139. package/dist/esm/media-library.js.map +1 -0
  140. package/dist/esm/models/content-item.d.ts +80 -0
  141. package/dist/esm/models/content-item.d.ts.map +1 -0
  142. package/dist/esm/models/content-item.js +676 -0
  143. package/dist/esm/models/content-item.js.map +1 -0
  144. package/dist/esm/models/media-category-item.d.ts +29 -0
  145. package/dist/esm/models/media-category-item.d.ts.map +1 -0
  146. package/dist/esm/models/media-category-item.js +29 -0
  147. package/dist/esm/models/media-category-item.js.map +1 -0
  148. package/dist/esm/models/media-item.d.ts +74 -0
  149. package/dist/esm/models/media-item.d.ts.map +1 -0
  150. package/dist/esm/models/media-item.js +184 -0
  151. package/dist/esm/models/media-item.js.map +1 -0
  152. package/dist/esm/models/section-item.d.ts +58 -0
  153. package/dist/esm/models/section-item.d.ts.map +1 -0
  154. package/dist/esm/models/section-item.js +162 -0
  155. package/dist/esm/models/section-item.js.map +1 -0
  156. package/dist/esm/resources/channel-resource.d.ts +112 -0
  157. package/dist/esm/resources/channel-resource.d.ts.map +1 -0
  158. package/dist/esm/resources/channel-resource.js +102 -0
  159. package/dist/esm/resources/channel-resource.js.map +1 -0
  160. package/dist/esm/resources/content-resource.d.ts +50 -0
  161. package/dist/esm/resources/content-resource.d.ts.map +1 -0
  162. package/dist/esm/resources/content-resource.js +282 -0
  163. package/dist/esm/resources/content-resource.js.map +1 -0
  164. package/dist/esm/resources/content-type-resource.d.ts +283 -0
  165. package/dist/esm/resources/content-type-resource.d.ts.map +1 -0
  166. package/dist/esm/resources/content-type-resource.js +964 -0
  167. package/dist/esm/resources/content-type-resource.js.map +1 -0
  168. package/dist/esm/resources/group-resource.d.ts +96 -0
  169. package/dist/esm/resources/group-resource.d.ts.map +1 -0
  170. package/dist/esm/resources/group-resource.js +208 -0
  171. package/dist/esm/resources/group-resource.js.map +1 -0
  172. package/dist/esm/resources/list-resource.d.ts +111 -0
  173. package/dist/esm/resources/list-resource.d.ts.map +1 -0
  174. package/dist/esm/resources/list-resource.js +174 -0
  175. package/dist/esm/resources/list-resource.js.map +1 -0
  176. package/dist/esm/resources/media-resource.d.ts +69 -0
  177. package/dist/esm/resources/media-resource.d.ts.map +1 -0
  178. package/dist/esm/resources/media-resource.js +206 -0
  179. package/dist/esm/resources/media-resource.js.map +1 -0
  180. package/dist/esm/resources/media-type-resource.d.ts +70 -0
  181. package/dist/esm/resources/media-type-resource.d.ts.map +1 -0
  182. package/dist/esm/resources/media-type-resource.js +190 -0
  183. package/dist/esm/resources/media-type-resource.js.map +1 -0
  184. package/dist/esm/resources/navigation-resource.d.ts +664 -0
  185. package/dist/esm/resources/navigation-resource.d.ts.map +1 -0
  186. package/dist/esm/resources/navigation-resource.js +2344 -0
  187. package/dist/esm/resources/navigation-resource.js.map +1 -0
  188. package/dist/esm/resources/page-layout-resource.d.ts +83 -0
  189. package/dist/esm/resources/page-layout-resource.d.ts.map +1 -0
  190. package/dist/esm/resources/page-layout-resource.js +209 -0
  191. package/dist/esm/resources/page-layout-resource.js.map +1 -0
  192. package/dist/esm/resources/user-resource.d.ts +126 -0
  193. package/dist/esm/resources/user-resource.d.ts.map +1 -0
  194. package/dist/esm/resources/user-resource.js +312 -0
  195. package/dist/esm/resources/user-resource.js.map +1 -0
  196. package/dist/esm/section-ref.d.ts +185 -0
  197. package/dist/esm/section-ref.d.ts.map +1 -0
  198. package/dist/esm/section-ref.js +808 -0
  199. package/dist/esm/section-ref.js.map +1 -0
  200. package/dist/esm/site-structure.d.ts +18 -0
  201. package/dist/esm/site-structure.d.ts.map +1 -0
  202. package/dist/esm/site-structure.js +53 -0
  203. package/dist/esm/site-structure.js.map +1 -0
  204. package/dist/esm/t4-client.d.ts +121 -0
  205. package/dist/esm/t4-client.d.ts.map +1 -0
  206. package/dist/esm/t4-client.js +186 -0
  207. package/dist/esm/t4-client.js.map +1 -0
  208. package/dist/esm/type-registry.d.ts +31 -0
  209. package/dist/esm/type-registry.d.ts.map +1 -0
  210. package/dist/esm/type-registry.js +72 -0
  211. package/dist/esm/type-registry.js.map +1 -0
  212. package/dist/esm/types.d.ts +211 -0
  213. package/dist/esm/types.d.ts.map +1 -0
  214. package/dist/esm/types.js +2 -0
  215. package/dist/esm/types.js.map +1 -0
  216. package/dist/esm/utils.d.ts +147 -0
  217. package/dist/esm/utils.d.ts.map +1 -0
  218. package/dist/esm/utils.js +355 -0
  219. package/dist/esm/utils.js.map +1 -0
  220. package/docs/channels.md +62 -0
  221. package/docs/content-types.md +313 -0
  222. package/docs/content.md +199 -0
  223. package/docs/error-handling.md +86 -0
  224. package/docs/getting-started.md +146 -0
  225. package/docs/groups-and-users.md +167 -0
  226. package/docs/handlebars.md +145 -0
  227. package/docs/lists.md +98 -0
  228. package/docs/media-types.md +111 -0
  229. package/docs/media.md +169 -0
  230. package/docs/navigation/a-to-z.md +62 -0
  231. package/docs/navigation/breadcrumbs.md +67 -0
  232. package/docs/navigation/css-selector.md +53 -0
  233. package/docs/navigation/generate-file.md +48 -0
  234. package/docs/navigation/keyword-search.md +134 -0
  235. package/docs/navigation/language-switcher.md +37 -0
  236. package/docs/navigation/link-menu.md +105 -0
  237. package/docs/navigation/pagination.md +80 -0
  238. package/docs/navigation/previous-next-fulltext.md +43 -0
  239. package/docs/navigation/publish-to-one-file.md +93 -0
  240. package/docs/navigation/related-content.md +79 -0
  241. package/docs/navigation/related-section-branch.md +31 -0
  242. package/docs/navigation/return-to-index.md +35 -0
  243. package/docs/navigation/section-details.md +51 -0
  244. package/docs/navigation/section-iterator.md +33 -0
  245. package/docs/navigation/section-meta-info.md +39 -0
  246. package/docs/navigation/site-map.md +58 -0
  247. package/docs/navigation/top-content.md +80 -0
  248. package/docs/navigation/top-stories.md +47 -0
  249. package/docs/navigation.md +134 -0
  250. package/docs/page-layouts.md +71 -0
  251. package/docs/sections.md +224 -0
  252. package/docs/typescript.md +124 -0
  253. package/package.json +64 -0
@@ -0,0 +1,146 @@
1
+ # Getting Started
2
+
3
+ Install and configure terminalfour-js in server-side TypeScript code, then verify the connection and choose language and cache settings.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @terminalfour/terminalfour-js
9
+ ```
10
+
11
+ The package includes TypeScript declarations and supports both ESM (`import`) and CommonJS (`require`). It requires **Node 18 or later** because it uses the built-in `fetch`, `FormData`, and `Error.cause`.
12
+
13
+ ## Create a client
14
+
15
+ ```typescript
16
+ import { T4Client } from '@terminalfour/terminalfour-js';
17
+
18
+ const t4 = new T4Client({
19
+ baseUrl: 'https://mysite.edu/terminalfour/rs',
20
+ apiToken: 'your-api-token',
21
+ });
22
+ ```
23
+
24
+ ### Configuration
25
+
26
+ | Option | Type | Default | Description |
27
+ |---|---|---|---|
28
+ | `baseUrl` | `string` | required | T4 instance REST API URL |
29
+ | `apiToken` | `string` | required | API authentication token |
30
+ | `language` | `string` | `'en'` | Default language for supported operations |
31
+ | `concurrency` | `number` | `10` | Maximum parallel HTTP requests |
32
+
33
+ ### `baseUrl` requirements
34
+
35
+ `baseUrl` must be an absolute `http` or `https` URL. The client strips trailing slashes, so these values are equivalent:
36
+
37
+ ```typescript
38
+ baseUrl: 'https://mysite.edu/terminalfour/rs'
39
+ baseUrl: 'https://mysite.edu/terminalfour/rs/'
40
+ ```
41
+
42
+ The constructor rejects a value that is not a parseable absolute URL or uses a protocol other than `http` or `https`:
43
+
44
+ ```typescript
45
+ new T4Client({ baseUrl: 'mysite.edu/terminalfour/rs', apiToken: '...' });
46
+ // Error: T4Client baseUrl "mysite.edu/terminalfour/rs" is not a valid absolute URL.
47
+ ```
48
+
49
+ **Important:** An `http://` URL logs a warning because every request sends the API token in an `Authorization` header, and plain HTTP transmits it in cleartext. Always use `https://` unless you are testing against a local instance.
50
+
51
+ ## Keep the client server-side
52
+
53
+ The `apiToken` grants full read/write access to your T4 instance. Browser bundles are publicly readable, so sending the token to front-end code exposes it to every visitor, regardless of your application's authentication.
54
+
55
+ The SDK prevents this by throwing when code constructs a client in a browser. You cannot disable this check.
56
+
57
+ ```typescript
58
+ // Browser code: throws
59
+ new T4Client({ baseUrl, apiToken });
60
+ // Error: T4Client cannot be used in a browser. The apiToken is a full-privilege
61
+ // credential and anything in front-end code is publicly readable...
62
+ ```
63
+
64
+ Construct the client in an API route, serverless function, or backend service. Return only the data the browser needs:
65
+
66
+ ```typescript
67
+ const t4 = new T4Client({
68
+ baseUrl: process.env.T4_BASE_URL!,
69
+ apiToken: process.env.T4_API_TOKEN!,
70
+ });
71
+
72
+ const items = await t4.section(482).content.list();
73
+ // Return `items` from your server endpoint. The token stays on the server.
74
+ ```
75
+
76
+ The guard checks for both `window` and `window.document`, so it blocks only browser environments. Node, Deno, Bun, Cloudflare Workers, similar edge runtimes, and Web Workers continue to work.
77
+
78
+ ## Set the language
79
+
80
+ Configure a client default when you need a language other than `en`:
81
+
82
+ ```typescript
83
+ const t4 = new T4Client({
84
+ baseUrl: 'https://mysite.edu/terminalfour/rs',
85
+ apiToken: 'your-api-token',
86
+ language: 'fr',
87
+ concurrency: 5,
88
+ });
89
+ ```
90
+
91
+ For sections, content, and lists, the SDK resolves language in this order:
92
+
93
+ 1. Per-call `{ language }` option
94
+ 2. Client `language`
95
+ 3. `'en'`
96
+
97
+ ```typescript
98
+ await t4.section(482).content.list(); // uses client default 'fr'
99
+ await t4.section(482).content.list({ language: 'de' }); // overrides this call
100
+ ```
101
+
102
+ Other resources use fixed languages:
103
+
104
+ | Resource | Language |
105
+ |---|---|
106
+ | Media items | `smxx` (language-independent) |
107
+ | Media categories and media library | `en` |
108
+ | Page layouts, content layouts, channels, groups, and users | `en` |
109
+
110
+ ## Inspect the T4 instance
111
+
112
+ The client provides four read-only platform methods:
113
+
114
+ | Method | Returned data | Example property |
115
+ |---|---|---|
116
+ | `about()` | T4 version, uptime, OS, Java, and servlet details | `info.t4.version` is `'8.4.2-FINAL'`; `info.t4.uptime` is a `Date`; `info.os.name` is `'Linux'`; `info.java.version` is `'11.0.18'` |
117
+ | `database()` | Database connection details | `db.name` is `'MySQL'`; `db.version` is `'8.0.32'` |
118
+ | `environment()` | Environment configuration | `env['max_upload_size']` is `'50000'` |
119
+ | `licence()` | Licence usage | `lic.remaining` may be `14121` content items |
120
+
121
+ ```typescript
122
+ const info = await t4.about();
123
+ const db = await t4.database();
124
+ const env = await t4.environment();
125
+ const lic = await t4.licence();
126
+ ```
127
+
128
+ ## Debug requests
129
+
130
+ Set `T4_DEBUG=1` to log HTTP requests and internal warnings:
131
+
132
+ ```bash
133
+ T4_DEBUG=1 node my-script.js
134
+ ```
135
+
136
+ ## Refresh cached data
137
+
138
+ The SDK caches content type templates, list values, element type definitions, and other lookup data for five minutes. You can clear every SDK cache after changing configuration during with:
139
+
140
+ ```typescript
141
+ t4.clearCache();
142
+ ```
143
+
144
+ ---
145
+
146
+ **Next:** [Sections](./sections.md)
@@ -0,0 +1,167 @@
1
+ # Groups and Users
2
+
3
+ Use `t4.groups` to manage memberships and `t4.users` to manage accounts, authentication methods, and custom fields.
4
+
5
+ ## Groups
6
+
7
+ ### List and read groups
8
+
9
+ ```typescript
10
+ const groups = await t4.groups.list();
11
+ // [{ id, name, description, membersCount, enabled, children, parentIds }]
12
+
13
+ const group = await t4.groups.get(1);
14
+ ```
15
+
16
+ A full group includes `name`, `description`, `enabled`, `emailAddress`, `children`, and `members`. `children` contains child group IDs. Each member includes `id`, `username`, `firstName`, `lastName`, `emailAddress`, and `userLevel`.
17
+
18
+ ```typescript
19
+ for (const member of group.members) {
20
+ console.log(
21
+ member.id,
22
+ member.username,
23
+ member.firstName,
24
+ member.lastName,
25
+ member.emailAddress,
26
+ member.userLevel,
27
+ );
28
+ }
29
+ ```
30
+
31
+ ### Create a group
32
+
33
+ ```typescript
34
+ await t4.groups.create({
35
+ name: 'Editors',
36
+ description: 'Content editors',
37
+ members: [38, 61], // user IDs; the SDK resolves full user objects
38
+ enabled: true,
39
+ });
40
+ ```
41
+
42
+ A group requires at least one member.
43
+
44
+ ### Update a group
45
+
46
+ #### Direct update
47
+
48
+ ```typescript
49
+ await t4.groups.update(1, { name: 'Renamed', enabled: false });
50
+ ```
51
+
52
+ #### Mutable item
53
+
54
+ ```typescript
55
+ const group = await t4.groups.get(1);
56
+ group.name = 'Renamed';
57
+ group.addMembers([62, 63]);
58
+ group.removeMembers([66]);
59
+ await group.save();
60
+ ```
61
+
62
+ After `save()`, the SDK refreshes `members` to reflect the changes.
63
+
64
+ ### Delete a group
65
+
66
+ ```typescript
67
+ await t4.groups.delete(42);
68
+ ```
69
+
70
+ ## Users
71
+
72
+ ### List users
73
+
74
+ ```typescript
75
+ const users = await t4.users.list();
76
+ // [{ id, username, firstName, lastName, emailAddress, userLevel, enabled, accountLocked, lastLogin }]
77
+
78
+ const admins = await t4.users.list({ userLevel: 'admin' });
79
+ ```
80
+
81
+ User levels are `'admin'`, `'power-user'`, `'moderator'`, `'contributor'`, and `'visitor'`.
82
+
83
+ ### Get a user
84
+
85
+ ```typescript
86
+ const user = await t4.users.get(30);
87
+ ```
88
+
89
+ | Property | Example or meaning |
90
+ |---|---|
91
+ | `username` | Account username |
92
+ | `firstName`, `lastName` | User's name |
93
+ | `emailAddress` | Email address |
94
+ | `userLevel` | `'contributor'` |
95
+ | `defaultLanguage` | `'en'` |
96
+ | `enabled` | Whether the account is enabled |
97
+ | `lastLogin` | `Date` or `null` when the user has never logged in |
98
+ | `groups` | `[{ id: 1, name: 'Editors' }]` |
99
+ | `customFields` | `{ Department: 'Engineering' }` or `null` |
100
+
101
+ ### Authentication methods
102
+
103
+ ```typescript
104
+ console.log(user.authMethods);
105
+ // {
106
+ // local: true,
107
+ // ldap: { enabled: true, identifier: 'uid=jsmith,ou=people,dc=example,dc=com' },
108
+ // saml: false,
109
+ // cas: false,
110
+ // remoteuser: false,
111
+ // }
112
+
113
+ user.authMethods.saml = { enabled: true, identifier: 'saml-user-id' };
114
+ await user.save();
115
+ ```
116
+
117
+ - `local` and `remoteuser` are booleans and never have identifiers.
118
+ - `ldap`, `saml`, and `cas` are `boolean | { enabled: boolean; identifier: string }`.
119
+
120
+ ### Create a user
121
+
122
+ ```typescript
123
+ await t4.users.create({
124
+ username: 'new.user',
125
+ firstName: 'New',
126
+ lastName: 'User',
127
+ emailAddress: 'new@example.com',
128
+ password: 'SecureP@ss123!',
129
+ userLevel: 'contributor', // optional; default: 'contributor'
130
+ defaultLanguage: 'en', // optional; default: 'en'
131
+ enabled: true, // optional; default: true
132
+ authMethods: { local: true }, // optional; default: { local: true }
133
+ });
134
+ ```
135
+
136
+ ### Update a user
137
+
138
+ #### Direct update
139
+
140
+ ```typescript
141
+ await t4.users.update(30, {
142
+ firstName: 'Updated',
143
+ userLevel: 'moderator',
144
+ });
145
+ ```
146
+
147
+ #### Mutable item
148
+
149
+ ```typescript
150
+ const user = await t4.users.get(30);
151
+ user.firstName = 'Updated';
152
+ user.password = 'NewP@ssword456!';
153
+ if (user.customFields) {
154
+ user.customFields['Department'] = 'Marketing';
155
+ }
156
+ await user.save();
157
+ ```
158
+
159
+ ### Delete a user
160
+
161
+ ```typescript
162
+ await t4.users.delete(68);
163
+ ```
164
+
165
+ ---
166
+
167
+ **Previous:** [Lists](./lists.md) · **Next:** [Page Layouts](./page-layouts.md)
@@ -0,0 +1,145 @@
1
+ # Handlebars
2
+
3
+ Use `t4.handlebars.helpers` for custom helper functions and `t4.handlebars.partials` for reusable template fragments.
4
+
5
+ ```typescript
6
+ t4.handlebars.helpers
7
+ t4.handlebars.partials
8
+ ```
9
+
10
+ Helpers and partials are stored as content items in hidden sections. Both resources expose `list()`, `get(name)`, `create()`, `update(name)`, `delete(name)`, and `purge(name)`. Any method that accepts a name also accepts a numeric ID.
11
+
12
+ ## Contents
13
+
14
+ - [Shared behavior](#shared-behavior)
15
+ - [Helpers](#helpers)
16
+ - [Partials](#partials)
17
+ - [Errors and operational notes](#errors-and-operational-notes)
18
+
19
+ ## Shared behavior
20
+
21
+ | Operation | Behavior |
22
+ |---|---|
23
+ | `list()` | Returns summary objects with `id`, `name`, and `lastModified` |
24
+ | `get(nameOrId)` | Returns the full mutable item, including `code` |
25
+ | `create({ name, code })` | Requires both values, enforces a unique name, and creates an approved item |
26
+ | `update(nameOrId, data)` | Applies a direct update and saves with approved status |
27
+ | `save()` | Saves a mutable item with approved status |
28
+ | `delete(nameOrId)` | Soft deletes the item |
29
+ | `purge(nameOrId)` | Permanently removes the item |
30
+
31
+ ## Helpers
32
+
33
+ Custom helpers are available in content layouts.
34
+
35
+ ### List and get helpers
36
+
37
+ ```typescript
38
+ const helpers = await t4.handlebars.helpers.list();
39
+ // [{ id: 100, name: 'formatDate', lastModified: Date }, ...]
40
+
41
+ const helper = await t4.handlebars.helpers.get('formatDate');
42
+ console.log(helper.name); // 'formatDate'
43
+ console.log(helper.code); // 'module.exports = function(date) { ... }'
44
+ console.log(helper.lastModified); // Date object
45
+ ```
46
+
47
+ `list()` returns summaries. Call `get(name)` for the code. You can also retrieve the same helper by ID with `t4.handlebars.helpers.get(100)`.
48
+
49
+ ### Create a helper
50
+
51
+ ```typescript
52
+ const helper = await t4.handlebars.helpers.create({
53
+ name: 'truncate',
54
+ code: 'function(context, options) { return context.substring(0, options.hash('len')); }',
55
+ });
56
+ ```
57
+
58
+ ### Direct update
59
+
60
+ ```typescript
61
+ const updated = await t4.handlebars.helpers.update('formatDate', {
62
+ code: 'function(context, options) { return new Date(context).toISOString(); }',
63
+ });
64
+ ```
65
+
66
+ ### Mutable item
67
+
68
+ ```typescript
69
+ const helper = await t4.handlebars.helpers.get('formatDate');
70
+ helper.name = 'formatDateISO';
71
+ helper.code = 'function(context, options) { return new Date(context).toISOString().split("T")[0]; }';
72
+ await helper.save();
73
+ ```
74
+
75
+ ### Delete or purge a helper
76
+
77
+ ```typescript
78
+ await t4.handlebars.helpers.delete('truncate'); // soft delete
79
+ await t4.handlebars.helpers.purge('truncate'); // permanent removal
80
+ ```
81
+
82
+ ## Partials
83
+
84
+ Partials are reusable template fragments included in content layouts with `{{> partialName}}`.
85
+
86
+ ### List and get partials
87
+
88
+ ```typescript
89
+ const partials = await t4.handlebars.partials.list();
90
+ // [{ id: 200, name: 'header', lastModified: Date }, ...]
91
+
92
+ const partial = await t4.handlebars.partials.get('header');
93
+ console.log(partial.name); // 'header'
94
+ console.log(partial.code); // '<header><h1>{{sectionName}}</h1></header>'
95
+ console.log(partial.lastModified); // Date object
96
+ ```
97
+
98
+ ### Create a partial
99
+
100
+ ```typescript
101
+ const partial = await t4.handlebars.partials.create({
102
+ name: 'footer',
103
+ code: '<footer><p>&copy; {{channelName}}</p></footer>',
104
+ });
105
+ ```
106
+
107
+ ### Direct update
108
+
109
+ ```typescript
110
+ const updated = await t4.handlebars.partials.update('header', {
111
+ code: '<header class="main"><h1>{{sectionName}}</h1></header>',
112
+ });
113
+ ```
114
+
115
+ ### Mutable item
116
+
117
+ ```typescript
118
+ const partial = await t4.handlebars.partials.get('header');
119
+ partial.name = 'site-header';
120
+ partial.code = '<header class="main"><h1>{{sectionName}}</h1></header>';
121
+ await partial.save();
122
+ ```
123
+
124
+ ### Delete or purge a partial
125
+
126
+ ```typescript
127
+ await t4.handlebars.partials.delete('footer'); // soft delete
128
+ await t4.handlebars.partials.purge('footer'); // permanent removal
129
+ ```
130
+
131
+ ## Errors and operational notes
132
+
133
+ - A duplicate name on create produces an error.
134
+ - A missing name produces an error that lists available names.
135
+ - If multiple items have the same name, the SDK reports their IDs so you can use a numeric ID.
136
+ - Names are the primary interface; IDs provide a fallback when a name is ambiguous.
137
+ - Every create, update, and save forces approved status so Handlebars can use the item immediately.
138
+ - Operations always use language `en`, regardless of the client's configured language. Helpers and partials are language-independent.
139
+ - Hidden section and content type IDs are read from configuration endpoints on first use and cached for five minutes.
140
+ - `t4.clearCache()` invalidates those cached configuration values.
141
+ - Helpers use a `Function Code` element; partials use a `Code` element. The SDK handles the difference.
142
+
143
+ ---
144
+
145
+ **Previous:** [Navigation](./navigation.md) · **Next:** [Error Handling](./error-handling.md)
package/docs/lists.md ADDED
@@ -0,0 +1,98 @@
1
+ # Lists
2
+
3
+ Use `t4.lists` to manage list definitions and their items.
4
+
5
+ ## List and read lists
6
+
7
+ ```typescript
8
+ const lists = await t4.lists.list();
9
+ // [{ id: 71, name: 'Sizes', description: 'Size options' }]
10
+
11
+ const list = await t4.lists.get(71);
12
+ ```
13
+
14
+ | Property | Example |
15
+ |---|---|
16
+ | `name` | `'Sizes'` |
17
+ | `description` | List description |
18
+ | `isForcedLanguage` | `false` |
19
+ | `isDefaultLanguage` | `false` |
20
+ | `primaryGroup` | `0` |
21
+ | `sharedGroups` | `[]` |
22
+ | `items` | Item definitions keyed by name |
23
+
24
+ Items are keyed by name:
25
+
26
+ ```typescript
27
+ console.log(list.items);
28
+ // {
29
+ // Large: { name: 'Large', value: 'lg', selected: true },
30
+ // Small: { name: 'Small', value: 'sm', selected: false },
31
+ // }
32
+
33
+ list.items['Large'].value = 'updated';
34
+ list.items['Large'].selected = false;
35
+ await list.save();
36
+ ```
37
+
38
+ ## Create a list
39
+
40
+ ```typescript
41
+ const newList = await t4.lists.create({
42
+ name: 'Priorities',
43
+ description: 'Priority levels',
44
+ items: [
45
+ { name: 'High', value: 'high', selected: true },
46
+ { name: 'Medium', value: 'medium' },
47
+ { name: 'Low', value: 'low' },
48
+ ],
49
+ });
50
+ ```
51
+
52
+ `isForcedLanguage` and `isDefaultLanguage` both default to `false`. They cannot both be `true`; the SDK validates this during create and save.
53
+
54
+ ## Update a list
55
+
56
+ ### Direct update
57
+
58
+ ```typescript
59
+ await t4.lists.update(71, {
60
+ name: 'Renamed',
61
+ description: 'Updated',
62
+ });
63
+ ```
64
+
65
+ ### Mutable item
66
+
67
+ ```typescript
68
+ const list = await t4.lists.get(71);
69
+ list.name = 'Renamed';
70
+ list.description = 'Updated';
71
+ await list.save();
72
+ ```
73
+
74
+ ## Add or remove items
75
+
76
+ ```typescript
77
+ const list = await t4.lists.get(71);
78
+
79
+ list.addItem({ name: 'Extra Large', value: 'xl', selected: false });
80
+ list.removeItem('Small');
81
+ await list.save();
82
+ ```
83
+
84
+ Add `sublistId` when an item opens a sublist:
85
+
86
+ ```typescript
87
+ list.addItem({ name: 'Soccer', value: 'soccer', sublistId: 72 });
88
+ ```
89
+
90
+ ## Delete a list
91
+
92
+ ```typescript
93
+ await t4.lists.delete(71);
94
+ ```
95
+
96
+ ---
97
+
98
+ **Previous:** [Content Types](./content-types.md) · **Next:** [Groups & Users](./groups-and-users.md)
@@ -0,0 +1,111 @@
1
+ # Media Types
2
+
3
+ Media types define permitted file extensions, whether files are binary or text-based, and which layouts can render them.
4
+
5
+ ## List and read media types
6
+
7
+ ```typescript
8
+ const types = await t4.mediaTypes.list();
9
+ for (const mediaType of types) {
10
+ console.log(mediaType.name, mediaType.extensions, mediaType.defaultLayout);
11
+ }
12
+
13
+ const images = await t4.mediaTypes.get(1);
14
+ ```
15
+
16
+ `get()` returns a mutable `MediaType`:
17
+
18
+ | Property | Type | Mutable | Description |
19
+ |---|---|---|---|
20
+ | `id` | `number` | no | Media type ID |
21
+ | `name` | `string` | yes | Display name, such as `'Image'` |
22
+ | `extensions` | `string[]` | yes | Permitted extensions, such as `['gif', 'jpg', 'jpeg', 'png', 'svg', 'webp']` |
23
+ | `binary` | `boolean` | yes | Whether files are binary rather than text-based |
24
+ | `parseForTags` | `boolean` | yes | Whether T4 parses tags; valid only when `binary` is `false` |
25
+ | `maxSize` | `string \| null` | yes | Formatted maximum size, such as `'5.0 KB'`, or `null` for unlimited |
26
+ | `layouts` | `MediaTypeLayout[]` | yes | Layouts with `name` and `default` |
27
+ | `defaultLayout` | `string` | yes | Default layout name, such as `'image/normal'` |
28
+
29
+ ## Create a media type
30
+
31
+ ```typescript
32
+ const video = await t4.mediaTypes.create({
33
+ name: 'Video',
34
+ extensions: ['mp4', 'webm', 'mov'],
35
+ binary: true,
36
+ maxSize: '50 MB', // also accepts 52428800 bytes or null for unlimited
37
+ layouts: [
38
+ { name: 'video/*', default: false },
39
+ { name: 'video/looping', default: true },
40
+ ],
41
+ });
42
+ ```
43
+
44
+ Instead of setting `default: true`, pass `defaultLayout`:
45
+
46
+ ```typescript
47
+ await t4.mediaTypes.create({
48
+ name: 'Video',
49
+ extensions: ['mp4', 'webm'],
50
+ binary: true,
51
+ layouts: [
52
+ { name: 'video/*', default: false },
53
+ { name: 'video/looping', default: false },
54
+ ],
55
+ defaultLayout: 'video/looping',
56
+ });
57
+ ```
58
+
59
+ ## Update a media type
60
+
61
+ ### Direct update
62
+
63
+ ```typescript
64
+ await t4.mediaTypes.update(1, {
65
+ name: 'Image Updated',
66
+ extensions: ['gif', 'jpg', 'jpeg', 'png', 'svg', 'webp', 'avif'],
67
+ maxSize: '10 MB',
68
+ });
69
+ ```
70
+
71
+ ### Mutable item
72
+
73
+ ```typescript
74
+ const mediaType = await t4.mediaTypes.get(1);
75
+ mediaType.name = 'Image Updated';
76
+ mediaType.extensions = [...mediaType.extensions, 'avif'];
77
+ mediaType.maxSize = '10 MB';
78
+ mediaType.defaultLayout = 'image/*';
79
+ await mediaType.save();
80
+ ```
81
+
82
+ ## Set the maximum size
83
+
84
+ `maxSize` accepts:
85
+
86
+ - A formatted string: `'2 KB'`, `'5 MB'`, or `'512 B'`
87
+ - A number of bytes: `2048` or `5242880`
88
+ - `null` for unlimited size
89
+
90
+ On read, `maxSize` is a formatted string or `null`.
91
+
92
+ ## Validation rules
93
+
94
+ 1. `parseForTags` cannot be `true` when `binary` is `true`. Tag parsing applies only to text-based media such as CSS, JavaScript, and PHP.
95
+ 2. Every media type requires at least one default layout.
96
+
97
+ For a non-binary type, enable `parseForTags` to let T4 process tags in the file content:
98
+
99
+ ```typescript
100
+ await t4.mediaTypes.create({
101
+ name: 'CSS Stylesheet',
102
+ extensions: ['css'],
103
+ binary: false,
104
+ parseForTags: true,
105
+ layouts: [{ name: 'css/*', default: true }],
106
+ });
107
+ ```
108
+
109
+ ---
110
+
111
+ **Previous:** [Media](./media.md) · **Next:** [Channels](./channels.md)