@rsc-kit/press 0.0.0-stage → 0.30.42

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 (180) hide show
  1. package/README.md +117 -2
  2. package/dist/admin/actions.d.ts +30 -0
  3. package/dist/admin/actions.js +171 -0
  4. package/dist/admin/actions.js.map +1 -0
  5. package/dist/admin/index.d.ts +5 -0
  6. package/dist/admin/index.js +2 -0
  7. package/dist/admin/index.js.map +1 -0
  8. package/dist/admin/panel.d.ts +96 -0
  9. package/dist/admin/panel.js +354 -0
  10. package/dist/admin/panel.js.map +1 -0
  11. package/dist/admin/registry.d.ts +58 -0
  12. package/dist/admin/registry.js +90 -0
  13. package/dist/admin/registry.js.map +1 -0
  14. package/dist/admin/theme.d.ts +251 -0
  15. package/dist/admin/theme.js +2 -0
  16. package/dist/admin/theme.js.map +1 -0
  17. package/dist/admin/url.d.ts +24 -0
  18. package/dist/admin/url.js +45 -0
  19. package/dist/admin/url.js.map +1 -0
  20. package/dist/blocks.d.ts +46 -0
  21. package/dist/blocks.js +33 -0
  22. package/dist/blocks.js.map +1 -0
  23. package/dist/forms/actions.d.ts +7 -0
  24. package/dist/forms/actions.js +26 -0
  25. package/dist/forms/actions.js.map +1 -0
  26. package/dist/forms/form.d.ts +42 -0
  27. package/dist/forms/form.js +44 -0
  28. package/dist/forms/form.js.map +1 -0
  29. package/dist/forms/honeypot.d.ts +2 -0
  30. package/dist/forms/honeypot.js +3 -0
  31. package/dist/forms/honeypot.js.map +1 -0
  32. package/dist/forms/index.d.ts +3 -0
  33. package/dist/forms/index.js +2 -0
  34. package/dist/forms/index.js.map +1 -0
  35. package/dist/forms/registry.d.ts +10 -0
  36. package/dist/forms/registry.js +17 -0
  37. package/dist/forms/registry.js.map +1 -0
  38. package/dist/forms/theme.d.ts +29 -0
  39. package/dist/forms/theme.js +2 -0
  40. package/dist/forms/theme.js.map +1 -0
  41. package/dist/index.d.ts +12 -0
  42. package/dist/index.js +8 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/installable.d.ts +173 -0
  45. package/dist/installable.js +490 -0
  46. package/dist/installable.js.map +1 -0
  47. package/dist/memory.d.ts +7 -0
  48. package/dist/memory.js +50 -0
  49. package/dist/memory.js.map +1 -0
  50. package/dist/pages/index.d.ts +2 -0
  51. package/dist/pages/index.js +2 -0
  52. package/dist/pages/index.js.map +1 -0
  53. package/dist/pages/pages.d.ts +78 -0
  54. package/dist/pages/pages.js +183 -0
  55. package/dist/pages/pages.js.map +1 -0
  56. package/dist/plugin.d.ts +51 -0
  57. package/dist/plugin.js +20 -0
  58. package/dist/plugin.js.map +1 -0
  59. package/dist/resource.d.ts +320 -0
  60. package/dist/resource.js +415 -0
  61. package/dist/resource.js.map +1 -0
  62. package/dist/shadcn/breadcrumbs.d.ts +6 -0
  63. package/dist/shadcn/breadcrumbs.js +21 -0
  64. package/dist/shadcn/breadcrumbs.js.map +1 -0
  65. package/dist/shadcn/components/ui/alert-dialog.d.ts +18 -0
  66. package/dist/shadcn/components/ui/alert-dialog.js +44 -0
  67. package/dist/shadcn/components/ui/alert-dialog.js.map +1 -0
  68. package/dist/shadcn/components/ui/avatar.d.ts +11 -0
  69. package/dist/shadcn/components/ui/avatar.js +25 -0
  70. package/dist/shadcn/components/ui/avatar.js.map +1 -0
  71. package/dist/shadcn/components/ui/badge.d.ts +7 -0
  72. package/dist/shadcn/components/ui/badge.js +34 -0
  73. package/dist/shadcn/components/ui/badge.js.map +1 -0
  74. package/dist/shadcn/components/ui/breadcrumb.d.ts +10 -0
  75. package/dist/shadcn/components/ui/breadcrumb.js +38 -0
  76. package/dist/shadcn/components/ui/breadcrumb.js.map +1 -0
  77. package/dist/shadcn/components/ui/button.d.ts +8 -0
  78. package/dist/shadcn/components/ui/button.js +35 -0
  79. package/dist/shadcn/components/ui/button.js.map +1 -0
  80. package/dist/shadcn/components/ui/card.d.ts +11 -0
  81. package/dist/shadcn/components/ui/card.js +26 -0
  82. package/dist/shadcn/components/ui/card.js.map +1 -0
  83. package/dist/shadcn/components/ui/checkbox.d.ts +3 -0
  84. package/dist/shadcn/components/ui/checkbox.js +10 -0
  85. package/dist/shadcn/components/ui/checkbox.js.map +1 -0
  86. package/dist/shadcn/components/ui/dropdown-menu.d.ts +29 -0
  87. package/dist/shadcn/components/ui/dropdown-menu.js +53 -0
  88. package/dist/shadcn/components/ui/dropdown-menu.js.map +1 -0
  89. package/dist/shadcn/components/ui/field.d.ts +24 -0
  90. package/dist/shadcn/components/ui/field.js +69 -0
  91. package/dist/shadcn/components/ui/field.js.map +1 -0
  92. package/dist/shadcn/components/ui/input.d.ts +3 -0
  93. package/dist/shadcn/components/ui/input.js +9 -0
  94. package/dist/shadcn/components/ui/input.js.map +1 -0
  95. package/dist/shadcn/components/ui/label.d.ts +3 -0
  96. package/dist/shadcn/components/ui/label.js +9 -0
  97. package/dist/shadcn/components/ui/label.js.map +1 -0
  98. package/dist/shadcn/components/ui/select.d.ts +15 -0
  99. package/dist/shadcn/components/ui/select.js +36 -0
  100. package/dist/shadcn/components/ui/select.js.map +1 -0
  101. package/dist/shadcn/components/ui/separator.d.ts +3 -0
  102. package/dist/shadcn/components/ui/separator.js +9 -0
  103. package/dist/shadcn/components/ui/separator.js.map +1 -0
  104. package/dist/shadcn/components/ui/sheet.d.ts +14 -0
  105. package/dist/shadcn/components/ui/sheet.js +39 -0
  106. package/dist/shadcn/components/ui/sheet.js.map +1 -0
  107. package/dist/shadcn/components/ui/sidebar.d.ts +63 -0
  108. package/dist/shadcn/components/ui/sidebar.js +257 -0
  109. package/dist/shadcn/components/ui/sidebar.js.map +1 -0
  110. package/dist/shadcn/components/ui/skeleton.d.ts +2 -0
  111. package/dist/shadcn/components/ui/skeleton.js +7 -0
  112. package/dist/shadcn/components/ui/skeleton.js.map +1 -0
  113. package/dist/shadcn/components/ui/sonner.d.ts +3 -0
  114. package/dist/shadcn/components/ui/sonner.js +38 -0
  115. package/dist/shadcn/components/ui/sonner.js.map +1 -0
  116. package/dist/shadcn/components/ui/switch.d.ts +5 -0
  117. package/dist/shadcn/components/ui/switch.js +9 -0
  118. package/dist/shadcn/components/ui/switch.js.map +1 -0
  119. package/dist/shadcn/components/ui/table.d.ts +10 -0
  120. package/dist/shadcn/components/ui/table.js +30 -0
  121. package/dist/shadcn/components/ui/table.js.map +1 -0
  122. package/dist/shadcn/components/ui/textarea.d.ts +3 -0
  123. package/dist/shadcn/components/ui/textarea.js +8 -0
  124. package/dist/shadcn/components/ui/textarea.js.map +1 -0
  125. package/dist/shadcn/components/ui/tooltip.d.ts +6 -0
  126. package/dist/shadcn/components/ui/tooltip.js +18 -0
  127. package/dist/shadcn/components/ui/tooltip.js.map +1 -0
  128. package/dist/shadcn/data-table.d.ts +12 -0
  129. package/dist/shadcn/data-table.js +195 -0
  130. package/dist/shadcn/data-table.js.map +1 -0
  131. package/dist/shadcn/fields.d.ts +20 -0
  132. package/dist/shadcn/fields.js +179 -0
  133. package/dist/shadcn/fields.js.map +1 -0
  134. package/dist/shadcn/flash-toasts.d.ts +2 -0
  135. package/dist/shadcn/flash-toasts.js +11 -0
  136. package/dist/shadcn/flash-toasts.js.map +1 -0
  137. package/dist/shadcn/hooks/use-mobile.d.ts +1 -0
  138. package/dist/shadcn/hooks/use-mobile.js +16 -0
  139. package/dist/shadcn/hooks/use-mobile.js.map +1 -0
  140. package/dist/shadcn/index.d.ts +10 -0
  141. package/dist/shadcn/index.js +31 -0
  142. package/dist/shadcn/index.js.map +1 -0
  143. package/dist/shadcn/mode-toggle.d.ts +7 -0
  144. package/dist/shadcn/mode-toggle.js +52 -0
  145. package/dist/shadcn/mode-toggle.js.map +1 -0
  146. package/dist/shadcn/nav.d.ts +5 -0
  147. package/dist/shadcn/nav.js +19 -0
  148. package/dist/shadcn/nav.js.map +1 -0
  149. package/dist/shadcn/pages.d.ts +15 -0
  150. package/dist/shadcn/pages.js +59 -0
  151. package/dist/shadcn/pages.js.map +1 -0
  152. package/dist/shadcn/plugin-forms.d.ts +16 -0
  153. package/dist/shadcn/plugin-forms.js +25 -0
  154. package/dist/shadcn/plugin-forms.js.map +1 -0
  155. package/dist/shadcn/public-form.d.ts +8 -0
  156. package/dist/shadcn/public-form.js +26 -0
  157. package/dist/shadcn/public-form.js.map +1 -0
  158. package/dist/shadcn/resource-form.d.ts +19 -0
  159. package/dist/shadcn/resource-form.js +30 -0
  160. package/dist/shadcn/resource-form.js.map +1 -0
  161. package/dist/shadcn/styles.css +12 -0
  162. package/dist/shadcn/tenant-switcher.d.ts +3 -0
  163. package/dist/shadcn/tenant-switcher.js +17 -0
  164. package/dist/shadcn/tenant-switcher.js.map +1 -0
  165. package/dist/shadcn/theme-script.d.ts +7 -0
  166. package/dist/shadcn/theme-script.js +10 -0
  167. package/dist/shadcn/theme-script.js.map +1 -0
  168. package/dist/shadcn/user-menu.d.ts +3 -0
  169. package/dist/shadcn/user-menu.js +23 -0
  170. package/dist/shadcn/user-menu.js.map +1 -0
  171. package/dist/signing.d.ts +43 -0
  172. package/dist/signing.js +82 -0
  173. package/dist/signing.js.map +1 -0
  174. package/dist/tenant.d.ts +12 -0
  175. package/dist/tenant.js +29 -0
  176. package/dist/tenant.js.map +1 -0
  177. package/dist/uploads.d.ts +32 -0
  178. package/dist/uploads.js +28 -0
  179. package/dist/uploads.js.map +1 -0
  180. package/package.json +74 -4
@@ -0,0 +1,490 @@
1
+ /**
2
+ * Plugins installed while the panel runs, with no rebuild: a manifest that
3
+ * says what the plugin adds, and an endpoint of its own that answers for it.
4
+ *
5
+ * Nothing a plugin wrote runs here. Its resources are drawn by the panel's
6
+ * own components from data, its rows arrive as JSON and are shown as text,
7
+ * and every call to it is signed with a secret made at install and scoped to
8
+ * the tenant that installed it. What it may see of the app's own records is
9
+ * what its scopes say, granted by a person who read them on the review page:
10
+ *
11
+ * read:products told when a product changes, and which product one of
12
+ * its own records points to
13
+ *
14
+ * A build-time plugin (definePlugin) is code, trusted like the app's own.
15
+ * This is the other kind: installed by a person, trusted with what they
16
+ * granted and nothing more. design/installable-admin-plugins.md has the
17
+ * reasoning and what is left out.
18
+ */
19
+ import { ActionValidationError } from '@rsc-kit/core/action';
20
+ import { defineResource } from './resource.js';
21
+ import { newSecret, signedHeaders } from './signing.js';
22
+ /** Keyed by the tenant; on globalThis because a dev server can evaluate this module twice. */
23
+ const MEMORY = Symbol.for('@rsc-kit/press.installations');
24
+ /** Installations in memory: for a demo, a test, a single process. Gone on restart. */
25
+ export function memoryInstallations(name = 'default') {
26
+ const all = (globalThis[MEMORY] ??= new Map());
27
+ const kept = all.get(name) ?? new Map();
28
+ all.set(name, kept);
29
+ const of = (tenant) => tenant?.id ?? '';
30
+ return {
31
+ list: async (tenant) => [...(kept.get(of(tenant)) ?? [])],
32
+ add: async (tenant, installation) => void kept.set(of(tenant), [...(kept.get(of(tenant)) ?? []), installation]),
33
+ remove: async (tenant, id) => void kept.set(of(tenant), (kept.get(of(tenant)) ?? []).filter((candidate) => candidate.id !== id)),
34
+ seen: async (tenant, id) => void kept.set(of(tenant), (kept.get(of(tenant)) ?? []).map((candidate) => (candidate.id === id ? { ...candidate, secretSeen: true } : candidate))),
35
+ };
36
+ }
37
+ // ---------------------------------------------------------------- the manifest
38
+ const NAME = /^[a-z0-9][a-z0-9-]{0,39}$/;
39
+ const KEY = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
40
+ const FORMATS = ['text', 'money', 'badge', 'boolean', 'date'];
41
+ /** The panel's own url segments under its path, which no resource may take. */
42
+ export const RESERVED = ['plugins', 'new'];
43
+ const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
44
+ function text(value, max) {
45
+ return typeof value === 'string' && value.trim() !== '' && value.length <= max;
46
+ }
47
+ /** Keys a manifest object may have. One it does not know is refused: a newer field read as nothing could be a permission ignored. */
48
+ function only(where, value, keys, problems) {
49
+ for (const key of Object.keys(value))
50
+ if (!keys.includes(key))
51
+ problems.push(`${where}: unknown field ${JSON.stringify(key)}`);
52
+ }
53
+ /**
54
+ * Every problem with a manifest, or none. Checked when it is reviewed, when it
55
+ * is installed, and on every request after - the app's resources can change
56
+ * under an installed plugin, and one that no longer fits is left out.
57
+ */
58
+ export function manifestProblems(manifest, manifestUrl, host) {
59
+ const problems = [];
60
+ if (!isObject(manifest))
61
+ return ['the manifest is not a JSON object'];
62
+ only('manifest', manifest, ['name', 'label', 'description', 'version', 'endpoint', 'scopes', 'resources', 'events'], problems);
63
+ if (typeof manifest.name !== 'string' || !NAME.test(manifest.name))
64
+ problems.push('name: lowercase letters, digits and dashes, at most 40');
65
+ if (!text(manifest.label, 60))
66
+ problems.push('label: required, at most 60 characters');
67
+ if (manifest.description !== undefined && !text(manifest.description, 300))
68
+ problems.push('description: at most 300 characters');
69
+ if (manifest.version !== undefined && !text(manifest.version, 30))
70
+ problems.push('version: at most 30 characters');
71
+ try {
72
+ const endpoint = new URL(String(manifest.endpoint), manifestUrl);
73
+ if (endpoint.origin !== new URL(manifestUrl).origin)
74
+ problems.push('endpoint: must be on the same origin as the manifest');
75
+ }
76
+ catch {
77
+ problems.push('endpoint: not a url');
78
+ }
79
+ const scopes = Array.isArray(manifest.scopes) ? manifest.scopes : null;
80
+ const readable = [];
81
+ if (!scopes)
82
+ problems.push('scopes: a list, empty if it reads nothing of yours');
83
+ else {
84
+ for (const scope of scopes) {
85
+ const match = typeof scope === 'string' ? /^read:([a-z0-9][a-z0-9-]*)$/.exec(scope) : null;
86
+ if (!match)
87
+ problems.push(`scopes: ${JSON.stringify(scope)} is not read:<resource>`);
88
+ else if (!host.includes(match[1]))
89
+ problems.push(`scopes: ${scope} names a resource this panel does not have`);
90
+ else
91
+ readable.push(match[1]);
92
+ }
93
+ }
94
+ const resources = Array.isArray(manifest.resources) ? manifest.resources : null;
95
+ if (!resources || resources.length === 0 || resources.length > 20) {
96
+ problems.push('resources: a list of 1 to 20');
97
+ return problems;
98
+ }
99
+ const own = resources.flatMap((resource) => (isObject(resource) && typeof resource.name === 'string' ? [resource.name] : []));
100
+ const reachable = (name) => own.includes(name) || readable.includes(name);
101
+ for (const [index, resource] of resources.entries()) {
102
+ const where = `resources[${index}]`;
103
+ if (!isObject(resource)) {
104
+ problems.push(`${where}: not an object`);
105
+ continue;
106
+ }
107
+ only(where, resource, ['name', 'label', 'singular', 'key', 'title', 'description', 'columns', 'form', 'filters', 'perPage', 'mode', 'readOnly', 'bulkActions'], problems);
108
+ const name = typeof resource.name === 'string' && NAME.test(resource.name) ? resource.name : null;
109
+ if (!name)
110
+ problems.push(`${where}.name: lowercase letters, digits and dashes`);
111
+ else if (RESERVED.includes(name))
112
+ problems.push(`${where}.name: ${name} is the panel's own`);
113
+ else if (host.includes(name))
114
+ problems.push(`${where}.name: this panel already has a resource named ${name}`);
115
+ else if (own.indexOf(name) !== index)
116
+ problems.push(`${where}.name: two resources are named ${name}`);
117
+ for (const key of ['label', 'singular', 'description']) {
118
+ if (resource[key] !== undefined && !text(resource[key], key === 'description' ? 300 : 60))
119
+ problems.push(`${where}.${key}: too long, or empty`);
120
+ }
121
+ const form = resource.form;
122
+ const properties = isObject(form) && form.type === 'object' && isObject(form.properties) ? form.properties : null;
123
+ if (!properties || Object.keys(properties).length === 0 || Object.keys(properties).length > 50) {
124
+ problems.push(`${where}.form: a JSON Schema object with 1 to 50 properties`);
125
+ continue;
126
+ }
127
+ only(`${where}.form`, form, ['type', 'properties', 'required'], problems);
128
+ const fieldNames = Object.keys(properties);
129
+ if (form && form.required !== undefined) {
130
+ const required = form.required;
131
+ if (!Array.isArray(required) || required.some((field) => !fieldNames.includes(field))) {
132
+ problems.push(`${where}.form.required: names that are properties`);
133
+ }
134
+ }
135
+ for (const [field, property] of Object.entries(properties)) {
136
+ const at = `${where}.form.properties.${field}`;
137
+ if (!KEY.test(field))
138
+ problems.push(`${at}: not a field name`);
139
+ if (!isObject(property)) {
140
+ problems.push(`${at}: not an object`);
141
+ continue;
142
+ }
143
+ only(at, property, ['type', 'format', 'enum', 'minLength', 'maxLength', 'minimum', 'maximum', 'pattern', 'title', 'description', 'relation'], problems);
144
+ if (!['string', 'number', 'integer', 'boolean'].includes(property.type))
145
+ problems.push(`${at}.type: string, number, integer or boolean`);
146
+ if (property.format !== undefined && !['email', 'date', 'date-time', 'textarea'].includes(property.format)) {
147
+ problems.push(`${at}.format: email, date, date-time or textarea`);
148
+ }
149
+ if (property.enum !== undefined && (!Array.isArray(property.enum) || property.enum.length === 0 || property.enum.some((option) => !text(option, 60)))) {
150
+ problems.push(`${at}.enum: a list of strings`);
151
+ }
152
+ for (const bound of ['minLength', 'maxLength', 'minimum', 'maximum']) {
153
+ if (property[bound] !== undefined && typeof property[bound] !== 'number')
154
+ problems.push(`${at}.${bound}: a number`);
155
+ }
156
+ // Only the browser runs it - a pattern run on the server is a regular
157
+ // expression a stranger wrote, and one can be made to take minutes.
158
+ if (property.pattern !== undefined && !text(property.pattern, 200))
159
+ problems.push(`${at}.pattern: at most 200 characters`);
160
+ if (property.relation !== undefined && !(typeof property.relation === 'string' && reachable(property.relation))) {
161
+ problems.push(`${at}.relation: ${String(property.relation)} is neither its own resource nor one its scopes read`);
162
+ }
163
+ }
164
+ const fieldOf = (key) => typeof key === 'string' && (fieldNames.includes(key) || key === resource.key);
165
+ if (typeof resource.key !== 'string' || !KEY.test(resource.key))
166
+ problems.push(`${where}.key: a field name`);
167
+ if (!fieldOf(resource.title))
168
+ problems.push(`${where}.title: one of the form's fields, or the key`);
169
+ const columns = Array.isArray(resource.columns) ? resource.columns : null;
170
+ if (!columns || columns.length === 0 || columns.length > 30)
171
+ problems.push(`${where}.columns: a list of 1 to 30`);
172
+ else {
173
+ for (const [at, column] of columns.entries()) {
174
+ const where2 = `${where}.columns[${at}]`;
175
+ if (!isObject(column)) {
176
+ problems.push(`${where2}: not an object`);
177
+ continue;
178
+ }
179
+ only(where2, column, ['key', 'label', 'format', 'relation', 'sortable', 'searchable'], problems);
180
+ if (typeof column.key !== 'string' || !KEY.test(column.key))
181
+ problems.push(`${where2}.key: a field name`);
182
+ if (column.label !== undefined && !text(column.label, 60))
183
+ problems.push(`${where2}.label: at most 60 characters`);
184
+ if (column.format !== undefined && !FORMATS.includes(column.format))
185
+ problems.push(`${where2}.format: ${FORMATS.join(', ')}`);
186
+ if (column.relation !== undefined && !(typeof column.relation === 'string' && reachable(column.relation))) {
187
+ problems.push(`${where2}.relation: ${String(column.relation)} is neither its own resource nor one its scopes read`);
188
+ }
189
+ }
190
+ }
191
+ if (resource.filters !== undefined) {
192
+ const filters = Array.isArray(resource.filters) ? resource.filters : [];
193
+ for (const filter of filters) {
194
+ if (typeof filter !== 'string' || !properties[filter]?.enum)
195
+ problems.push(`${where}.filters: ${String(filter)} is not an enum field`);
196
+ }
197
+ }
198
+ if (resource.perPage !== undefined && !(Number.isInteger(resource.perPage) && resource.perPage >= 1 && resource.perPage <= 100)) {
199
+ problems.push(`${where}.perPage: 1 to 100`);
200
+ }
201
+ if (resource.mode !== undefined && resource.mode !== 'server' && resource.mode !== 'client')
202
+ problems.push(`${where}.mode: server or client`);
203
+ if (resource.readOnly !== undefined && typeof resource.readOnly !== 'boolean')
204
+ problems.push(`${where}.readOnly: true or false`);
205
+ if (resource.bulkActions !== undefined) {
206
+ const actions = Array.isArray(resource.bulkActions) ? resource.bulkActions : null;
207
+ if (!actions || actions.length > 10)
208
+ problems.push(`${where}.bulkActions: a list of at most 10`);
209
+ else if (resource.readOnly === true && actions.length > 0)
210
+ problems.push(`${where}.bulkActions: a read-only resource has none`);
211
+ else {
212
+ for (const action of actions) {
213
+ if (!isObject(action) || typeof action.name !== 'string' || !NAME.test(action.name) || action.name === 'delete' || !text(action.label, 40)) {
214
+ problems.push(`${where}.bulkActions: each needs a name (not delete) and a label`);
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+ if (manifest.events !== undefined) {
221
+ const events = Array.isArray(manifest.events) ? manifest.events : null;
222
+ if (!events)
223
+ problems.push('events: a list of resource names');
224
+ else {
225
+ for (const event of events) {
226
+ if (typeof event !== 'string' || !reachable(event))
227
+ problems.push(`events: ${String(event)} is neither its own resource nor one its scopes read`);
228
+ }
229
+ }
230
+ }
231
+ return problems;
232
+ }
233
+ /** The scopes, as the person installing reads them. */
234
+ export function scopeWords(manifest, labelOf) {
235
+ return manifest.scopes.map((scope) => {
236
+ const resource = labelOf(scope.slice('read:'.length)).toLowerCase();
237
+ return `See your ${resource}: be told when one changes, and which one its own records point to.`;
238
+ });
239
+ }
240
+ // ---------------------------------------------------------- reaching a plugin
241
+ /** Https to a name: not localhost, and not an address, which is how a private network is reached by url. */
242
+ export function publicHttps(url) {
243
+ if (url.protocol !== 'https:')
244
+ return false;
245
+ const host = url.hostname.replace(/^\[|\]$/g, '');
246
+ if (host === 'localhost' || host.endsWith('.localhost') || host.endsWith('.local') || host.endsWith('.internal'))
247
+ return false;
248
+ if (/^[\d.]+$/.test(host) || host.includes(':'))
249
+ return false;
250
+ return true;
251
+ }
252
+ const MANIFEST_BYTES = 256 * 1024;
253
+ const ANSWER_BYTES = 5 * 1024 * 1024;
254
+ /** A response's body, up to a limit: a plugin that answers with gigabytes is a plugin that is wrong. */
255
+ async function bounded(response, limit) {
256
+ const declared = Number(response.headers.get('content-length') ?? 0);
257
+ if (declared > limit)
258
+ throw new Error(`[admin] the answer is ${declared} bytes, over ${limit}`);
259
+ const body = await response.text();
260
+ if (body.length > limit)
261
+ throw new Error(`[admin] the answer is over ${limit} bytes`);
262
+ return body;
263
+ }
264
+ /** A manifest, fetched and checked. Redirects are refused: a url allowed once must not lead somewhere that was not. */
265
+ export async function fetchManifest(manifestUrl, allow, host) {
266
+ let url;
267
+ try {
268
+ url = new URL(manifestUrl);
269
+ }
270
+ catch {
271
+ throw new Error('That is not a url.');
272
+ }
273
+ if (!allow(url))
274
+ throw new Error('This panel does not install plugins from that address.');
275
+ const response = await fetch(url, { headers: { accept: 'application/json' }, redirect: 'error', signal: AbortSignal.timeout(5000) }).catch(() => null);
276
+ if (!response?.ok)
277
+ throw new Error(`The manifest could not be fetched${response ? ` (${response.status})` : ''}.`);
278
+ let manifest;
279
+ try {
280
+ manifest = JSON.parse(await bounded(response, MANIFEST_BYTES));
281
+ }
282
+ catch {
283
+ throw new Error('The manifest is not JSON.');
284
+ }
285
+ const problems = manifestProblems(manifest, url.href, host);
286
+ if (problems.length > 0)
287
+ throw new Error(`The manifest has problems: ${problems.join('; ')}.`);
288
+ const checked = manifest;
289
+ return { ...checked, endpoint: new URL(checked.endpoint, url).href };
290
+ }
291
+ /** A stable digest of a manifest: what was reviewed is what is installed, or the install is refused. */
292
+ export async function digestOf(manifest) {
293
+ const bytes = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(manifest))));
294
+ return [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('');
295
+ }
296
+ export function newInstallation(manifestUrl, manifest) {
297
+ return { id: crypto.randomUUID(), manifestUrl, manifest, secret: newSecret(), secretSeen: false, installedAt: new Date().toISOString() };
298
+ }
299
+ /**
300
+ * One signed call. A 404 is null; a 422 with `{ errors: { field: [message] } }`
301
+ * is the form's field errors, exactly as Laravel's are; anything else that is
302
+ * not a 2xx is a failure with the plugin's name on it.
303
+ */
304
+ export async function callPlugin(installation, call, context, allow, timeout = 10_000) {
305
+ const endpoint = new URL(installation.manifest.endpoint);
306
+ const name = installation.manifest.name;
307
+ // Checked again on every call: the rule can tighten after the install.
308
+ if (!allow(endpoint))
309
+ throw new Error(`[admin] the plugin ${name}'s endpoint is no longer allowed`);
310
+ const body = JSON.stringify({ ...call, tenant: context.tenant ? { id: context.tenant.id, name: context.tenant.name } : null });
311
+ const response = await fetch(endpoint, {
312
+ method: 'POST',
313
+ headers: { 'content-type': 'application/json', accept: 'application/json', ...(await signedHeaders(installation.id, installation.secret, body)) },
314
+ body,
315
+ redirect: 'error',
316
+ signal: AbortSignal.timeout(timeout),
317
+ });
318
+ if (response.status === 404)
319
+ return null;
320
+ const answer = await bounded(response, ANSWER_BYTES);
321
+ let json = null;
322
+ try {
323
+ json = answer === '' ? null : JSON.parse(answer);
324
+ }
325
+ catch {
326
+ throw new Error(`[admin] the plugin ${name} answered ${response.status} with something that is not JSON`);
327
+ }
328
+ if (response.status === 422 && isObject(json) && isObject(json.errors)) {
329
+ const errors = Object.fromEntries(Object.entries(json.errors).map(([field, messages]) => [field, [].concat(messages).map(String).slice(0, 5)]));
330
+ throw new ActionValidationError(errors);
331
+ }
332
+ if (!response.ok)
333
+ throw new Error(`[admin] the plugin ${name} answered ${response.status}`);
334
+ return json;
335
+ }
336
+ const row = (value, plugin) => {
337
+ if (!isObject(value))
338
+ throw new Error(`[admin] the plugin ${plugin} answered a row that is not an object`);
339
+ return value;
340
+ };
341
+ /** A resource's five calls, made to the plugin. */
342
+ function remoteSource(installation, resource, allow) {
343
+ const plugin = installation.manifest.name;
344
+ const call = (body, context) => callPlugin(installation, body, context, allow);
345
+ return {
346
+ async list(query, context) {
347
+ const answer = await call({ type: 'list', resource, query }, context);
348
+ if (!isObject(answer) || !Array.isArray(answer.rows) || typeof answer.total !== 'number') {
349
+ throw new Error(`[admin] the plugin ${plugin} answered a list without { rows, total }`);
350
+ }
351
+ return { rows: answer.rows.map((value) => row(value, plugin)), total: answer.total };
352
+ },
353
+ async find(id, context) {
354
+ const answer = await call({ type: 'find', resource, id }, context);
355
+ return isObject(answer) && answer.row !== undefined && answer.row !== null ? row(answer.row, plugin) : null;
356
+ },
357
+ async create(values, context) {
358
+ return row((await call({ type: 'create', resource, values }, context)), plugin).row;
359
+ },
360
+ async update(id, values, context) {
361
+ return row((await call({ type: 'update', resource, id, values }, context)), plugin).row;
362
+ },
363
+ async delete(id, context) {
364
+ await call({ type: 'delete', resource, id }, context);
365
+ },
366
+ };
367
+ }
368
+ // ------------------------------------------------------------------ the form
369
+ /**
370
+ * A Standard Schema from the manifest's JSON Schema, so a plugin's form goes
371
+ * through the same parse as any other. It checks what is cheap and safe to
372
+ * check here - types, required, enums, lengths, bounds - and never `pattern`;
373
+ * the plugin's endpoint checks everything again and answers 422.
374
+ */
375
+ export function jsonSchemaForm(form) {
376
+ const required = new Set(form.required ?? []);
377
+ const validate = (value) => {
378
+ if (!isObject(value))
379
+ return { issues: [{ message: 'Expected an object', path: [] }] };
380
+ const out = {};
381
+ const issues = [];
382
+ for (const [name, property] of Object.entries(form.properties)) {
383
+ const given = value[name];
384
+ const fail = (message) => issues.push({ message, path: [name] });
385
+ if (given === undefined || given === null || given === '') {
386
+ if (required.has(name) && property.type !== 'boolean')
387
+ fail('Required');
388
+ continue;
389
+ }
390
+ if (property.type === 'boolean') {
391
+ if (typeof given !== 'boolean')
392
+ fail('Expected true or false');
393
+ else
394
+ out[name] = given;
395
+ continue;
396
+ }
397
+ if (property.type === 'number' || property.type === 'integer') {
398
+ const number = typeof given === 'number' ? given : Number(given);
399
+ if (!Number.isFinite(number))
400
+ fail('Expected a number');
401
+ else if (property.type === 'integer' && !Number.isInteger(number))
402
+ fail('Expected a whole number');
403
+ else if (property.minimum !== undefined && number < property.minimum)
404
+ fail(`At least ${property.minimum}`);
405
+ else if (property.maximum !== undefined && number > property.maximum)
406
+ fail(`At most ${property.maximum}`);
407
+ else
408
+ out[name] = number;
409
+ continue;
410
+ }
411
+ const string = String(given);
412
+ if (property.enum && !property.enum.includes(string))
413
+ fail(`One of ${property.enum.join(', ')}`);
414
+ else if (property.minLength !== undefined && string.length < property.minLength)
415
+ fail(`At least ${property.minLength} characters`);
416
+ else if (property.maxLength !== undefined && string.length > property.maxLength)
417
+ fail(`At most ${property.maxLength} characters`);
418
+ else
419
+ out[name] = string;
420
+ }
421
+ return issues.length > 0 ? { issues } : { value: out };
422
+ };
423
+ const json = {
424
+ type: 'object',
425
+ required: form.required ?? [],
426
+ properties: Object.fromEntries(Object.entries(form.properties).map(([name, { relation: _relation, title: _title, format, ...property }]) => [
427
+ name,
428
+ { ...property, ...(format && format !== 'textarea' ? { format } : {}) },
429
+ ])),
430
+ };
431
+ return {
432
+ '~standard': {
433
+ version: 1,
434
+ vendor: '@rsc-kit/press/plugin',
435
+ validate,
436
+ jsonSchema: { input: () => json, output: () => json },
437
+ },
438
+ };
439
+ }
440
+ /** An installation's resources, as the panel's own. */
441
+ export function resourcesOf(installation, allow) {
442
+ return installation.manifest.resources.map((described) => defineResource()({
443
+ name: described.name,
444
+ ...(described.label ? { label: described.label } : {}),
445
+ ...(described.singular ? { singular: described.singular } : {}),
446
+ key: described.key,
447
+ title: described.title,
448
+ source: remoteSource(installation, described.name, allow),
449
+ table: (col) => described.columns.map((column) => {
450
+ const built = column.relation ? col.relation(column.key, column.relation) : col[column.format ?? 'text'](column.key);
451
+ if (column.label)
452
+ built.label(column.label);
453
+ if (column.sortable)
454
+ built.sortable();
455
+ if (column.searchable)
456
+ built.searchable();
457
+ return built;
458
+ }),
459
+ form: jsonSchemaForm(described.form),
460
+ fields: Object.fromEntries(Object.entries(described.form.properties).flatMap(([name, property]) => {
461
+ const override = {
462
+ ...(property.title ? { label: property.title } : {}),
463
+ ...(property.description ? { description: property.description } : {}),
464
+ ...(property.format === 'textarea' ? { type: 'textarea' } : {}),
465
+ ...(property.relation ? { relation: property.relation } : {}),
466
+ };
467
+ return Object.keys(override).length > 0 ? [[name, override]] : [];
468
+ })),
469
+ ...(described.filters ? { filters: described.filters } : {}),
470
+ ...(described.perPage ? { perPage: described.perPage } : {}),
471
+ ...(described.mode ? { mode: described.mode } : {}),
472
+ ...(described.readOnly ? { readOnly: true } : {}),
473
+ ...(described.description ? { description: described.description } : {}),
474
+ bulkActions: (described.bulkActions ?? []).map((action) => ({
475
+ name: action.name,
476
+ label: action.label,
477
+ ...(action.done ? { done: action.done } : {}),
478
+ ...(action.destructive ? { destructive: true } : {}),
479
+ run: async (rows, context) => {
480
+ const answer = await callPlugin(installation, { type: 'bulk', resource: described.name, action: action.name, ids: rows.map((found) => String(found[described.key])) }, context, allow);
481
+ return isObject(answer) && typeof answer.message === 'string' ? answer.message.slice(0, 200) : undefined;
482
+ },
483
+ })),
484
+ }));
485
+ }
486
+ /** Whether a plugin is told about a change: one of its events, which its scopes or ownership allow. */
487
+ export function wantsChange(installation, change) {
488
+ return installation.manifest.events?.includes(change.resource) ?? false;
489
+ }
490
+ //# sourceMappingURL=installable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installable.js","sourceRoot":"","sources":["../src/installable.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Plugins installed while the panel runs, with no rebuild: a manifest that\n * says what the plugin adds, and an endpoint of its own that answers for it.\n *\n * Nothing a plugin wrote runs here. Its resources are drawn by the panel's\n * own components from data, its rows arrive as JSON and are shown as text,\n * and every call to it is signed with a secret made at install and scoped to\n * the tenant that installed it. What it may see of the app's own records is\n * what its scopes say, granted by a person who read them on the review page:\n *\n * read:products told when a product changes, and which product one of\n * its own records points to\n *\n * A build-time plugin (definePlugin) is code, trusted like the app's own.\n * This is the other kind: installed by a person, trusted with what they\n * granted and nothing more. design/installable-admin-plugins.md has the\n * reasoning and what is left out.\n */\nimport { ActionValidationError } from '@rsc-kit/core/action'\nimport type { RecordChange } from './plugin.js'\nimport { defineResource } from './resource.js'\nimport type { DataSource, Format, FormSchema, ListQuery, Resource, SourceContext, Tenant } from './resource.js'\nimport { newSecret, signedHeaders } from './signing.js'\n\n/** A plugin, as it describes itself at its manifest url. */\nexport interface PluginManifest {\n /** Lowercase letters, digits and dashes; unique within a tenant. */\n name: string\n label: string\n description?: string\n version?: string\n /** Where the panel POSTs every call. Same origin as the manifest. */\n endpoint: string\n /** What it may see of the app's own resources: read:<resource>. */\n scopes: string[]\n resources: ManifestResource[]\n /** Resources whose changes it is told about: its own, or ones it may read. */\n events?: string[]\n}\n\nexport type ManifestFormat = Exclude<Format, 'relation'>\n\nexport interface ManifestColumn {\n key: string\n label?: string\n format?: ManifestFormat\n /** Shown by that resource's title: one of the plugin's own, or one it may read. */\n relation?: string\n sortable?: boolean\n searchable?: boolean\n}\n\n/** The JSON Schema a manifest's form is written in: flat, one level, scalar fields. */\nexport interface ManifestProperty {\n type: 'string' | 'number' | 'integer' | 'boolean'\n format?: 'email' | 'date' | 'date-time' | 'textarea'\n enum?: string[]\n minLength?: number\n maxLength?: number\n minimum?: number\n maximum?: number\n /** For the browser's pattern attribute only: the plugin checks it, not the panel. */\n pattern?: string\n title?: string\n description?: string\n /** A belongsTo, drawn as a select of that resource's rows. */\n relation?: string\n}\n\nexport interface ManifestResource {\n name: string\n label?: string\n singular?: string\n key: string\n title: string\n description?: string\n columns: ManifestColumn[]\n form: { type: 'object'; properties: Record<string, ManifestProperty>; required?: string[] }\n filters?: string[]\n perPage?: number\n mode?: 'server' | 'client'\n readOnly?: boolean\n bulkActions?: { name: string; label: string; done?: string; destructive?: boolean }[]\n}\n\n/** One plugin installed for one tenant. The secret signs every call to it. */\nexport interface Installation {\n /** Random; the plugin finds this installation's secret by it. */\n id: string\n manifestUrl: string\n manifest: PluginManifest\n secret: string\n /** Until someone confirms they copied the secret into the plugin, the plugins page shows it. */\n secretSeen: boolean\n installedAt: string\n}\n\n/**\n * Where installations are kept: the app's database, in a real app. Each call\n * names the tenant, and a store answers for that tenant alone - one team's\n * installs never reach another's panel. Keep `secret` encrypted at rest.\n */\nexport interface Installations {\n list(tenant: Tenant | null): Promise<Installation[]>\n add(tenant: Tenant | null, installation: Installation): Promise<void>\n remove(tenant: Tenant | null, id: string): Promise<void>\n /** The secret was copied into the plugin; stop showing it. */\n seen(tenant: Tenant | null, id: string): Promise<void>\n}\n\nexport interface Installs {\n store: Installations\n /**\n * Whether the panel may call this url: a manifest's, and so its endpoint's.\n * By default https to a public host name - not localhost, not an address.\n * The panel's server makes the request, so a url that reaches your own\n * network is the thing to refuse; allow what your plugins are, and in\n * development, localhost.\n */\n allow?: (url: URL) => boolean\n /** Who may install and uninstall. The panel's authorize by default; an owner, usually. */\n manage?: () => boolean | Promise<boolean>\n}\n\n/** Keyed by the tenant; on globalThis because a dev server can evaluate this module twice. */\nconst MEMORY = Symbol.for('@rsc-kit/press.installations')\n\n/** Installations in memory: for a demo, a test, a single process. Gone on restart. */\nexport function memoryInstallations(name = 'default'): Installations {\n const all = ((globalThis as Record<symbol, unknown>)[MEMORY] ??= new Map()) as Map<string, Map<string, Installation[]>>\n const kept = all.get(name) ?? new Map<string, Installation[]>()\n\n all.set(name, kept)\n\n const of = (tenant: Tenant | null) => tenant?.id ?? ''\n\n return {\n list: async (tenant) => [...(kept.get(of(tenant)) ?? [])],\n add: async (tenant, installation) => void kept.set(of(tenant), [...(kept.get(of(tenant)) ?? []), installation]),\n remove: async (tenant, id) => void kept.set(of(tenant), (kept.get(of(tenant)) ?? []).filter((candidate) => candidate.id !== id)),\n seen: async (tenant, id) =>\n void kept.set(\n of(tenant),\n (kept.get(of(tenant)) ?? []).map((candidate) => (candidate.id === id ? { ...candidate, secretSeen: true } : candidate)),\n ),\n }\n}\n\n// ---------------------------------------------------------------- the manifest\n\nconst NAME = /^[a-z0-9][a-z0-9-]{0,39}$/\nconst KEY = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/\nconst FORMATS: ManifestFormat[] = ['text', 'money', 'badge', 'boolean', 'date']\n/** The panel's own url segments under its path, which no resource may take. */\nexport const RESERVED = ['plugins', 'new']\n\nconst isObject = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value)\n\nfunction text(value: unknown, max: number): value is string {\n return typeof value === 'string' && value.trim() !== '' && value.length <= max\n}\n\n/** Keys a manifest object may have. One it does not know is refused: a newer field read as nothing could be a permission ignored. */\nfunction only(where: string, value: Record<string, unknown>, keys: string[], problems: string[]) {\n for (const key of Object.keys(value)) if (!keys.includes(key)) problems.push(`${where}: unknown field ${JSON.stringify(key)}`)\n}\n\n/**\n * Every problem with a manifest, or none. Checked when it is reviewed, when it\n * is installed, and on every request after - the app's resources can change\n * under an installed plugin, and one that no longer fits is left out.\n */\nexport function manifestProblems(manifest: unknown, manifestUrl: string, host: string[]): string[] {\n const problems: string[] = []\n\n if (!isObject(manifest)) return ['the manifest is not a JSON object']\n\n only('manifest', manifest, ['name', 'label', 'description', 'version', 'endpoint', 'scopes', 'resources', 'events'], problems)\n\n if (typeof manifest.name !== 'string' || !NAME.test(manifest.name)) problems.push('name: lowercase letters, digits and dashes, at most 40')\n if (!text(manifest.label, 60)) problems.push('label: required, at most 60 characters')\n if (manifest.description !== undefined && !text(manifest.description, 300)) problems.push('description: at most 300 characters')\n if (manifest.version !== undefined && !text(manifest.version, 30)) problems.push('version: at most 30 characters')\n\n try {\n const endpoint = new URL(String(manifest.endpoint), manifestUrl)\n\n if (endpoint.origin !== new URL(manifestUrl).origin) problems.push('endpoint: must be on the same origin as the manifest')\n } catch {\n problems.push('endpoint: not a url')\n }\n\n const scopes = Array.isArray(manifest.scopes) ? manifest.scopes : null\n const readable: string[] = []\n\n if (!scopes) problems.push('scopes: a list, empty if it reads nothing of yours')\n else {\n for (const scope of scopes) {\n const match = typeof scope === 'string' ? /^read:([a-z0-9][a-z0-9-]*)$/.exec(scope) : null\n\n if (!match) problems.push(`scopes: ${JSON.stringify(scope)} is not read:<resource>`)\n else if (!host.includes(match[1]!)) problems.push(`scopes: ${scope} names a resource this panel does not have`)\n else readable.push(match[1]!)\n }\n }\n\n const resources = Array.isArray(manifest.resources) ? manifest.resources : null\n\n if (!resources || resources.length === 0 || resources.length > 20) {\n problems.push('resources: a list of 1 to 20')\n\n return problems\n }\n\n const own = resources.flatMap((resource) => (isObject(resource) && typeof resource.name === 'string' ? [resource.name] : []))\n const reachable = (name: string) => own.includes(name) || readable.includes(name)\n\n for (const [index, resource] of resources.entries()) {\n const where = `resources[${index}]`\n\n if (!isObject(resource)) {\n problems.push(`${where}: not an object`)\n continue\n }\n\n only(\n where,\n resource,\n ['name', 'label', 'singular', 'key', 'title', 'description', 'columns', 'form', 'filters', 'perPage', 'mode', 'readOnly', 'bulkActions'],\n problems,\n )\n\n const name = typeof resource.name === 'string' && NAME.test(resource.name) ? resource.name : null\n\n if (!name) problems.push(`${where}.name: lowercase letters, digits and dashes`)\n else if (RESERVED.includes(name)) problems.push(`${where}.name: ${name} is the panel's own`)\n else if (host.includes(name)) problems.push(`${where}.name: this panel already has a resource named ${name}`)\n else if (own.indexOf(name) !== index) problems.push(`${where}.name: two resources are named ${name}`)\n\n for (const key of ['label', 'singular', 'description'] as const) {\n if (resource[key] !== undefined && !text(resource[key], key === 'description' ? 300 : 60)) problems.push(`${where}.${key}: too long, or empty`)\n }\n\n const form = resource.form\n const properties = isObject(form) && form.type === 'object' && isObject(form.properties) ? form.properties : null\n\n if (!properties || Object.keys(properties).length === 0 || Object.keys(properties).length > 50) {\n problems.push(`${where}.form: a JSON Schema object with 1 to 50 properties`)\n continue\n }\n\n only(`${where}.form`, form as Record<string, unknown>, ['type', 'properties', 'required'], problems)\n\n const fieldNames = Object.keys(properties)\n\n if (form && (form as { required?: unknown }).required !== undefined) {\n const required = (form as { required?: unknown }).required\n\n if (!Array.isArray(required) || required.some((field) => !fieldNames.includes(field as string))) {\n problems.push(`${where}.form.required: names that are properties`)\n }\n }\n\n for (const [field, property] of Object.entries(properties)) {\n const at = `${where}.form.properties.${field}`\n\n if (!KEY.test(field)) problems.push(`${at}: not a field name`)\n if (!isObject(property)) {\n problems.push(`${at}: not an object`)\n continue\n }\n\n only(at, property, ['type', 'format', 'enum', 'minLength', 'maxLength', 'minimum', 'maximum', 'pattern', 'title', 'description', 'relation'], problems)\n\n if (!['string', 'number', 'integer', 'boolean'].includes(property.type as string)) problems.push(`${at}.type: string, number, integer or boolean`)\n if (property.format !== undefined && !['email', 'date', 'date-time', 'textarea'].includes(property.format as string)) {\n problems.push(`${at}.format: email, date, date-time or textarea`)\n }\n if (property.enum !== undefined && (!Array.isArray(property.enum) || property.enum.length === 0 || property.enum.some((option) => !text(option, 60)))) {\n problems.push(`${at}.enum: a list of strings`)\n }\n for (const bound of ['minLength', 'maxLength', 'minimum', 'maximum'] as const) {\n if (property[bound] !== undefined && typeof property[bound] !== 'number') problems.push(`${at}.${bound}: a number`)\n }\n // Only the browser runs it - a pattern run on the server is a regular\n // expression a stranger wrote, and one can be made to take minutes.\n if (property.pattern !== undefined && !text(property.pattern, 200)) problems.push(`${at}.pattern: at most 200 characters`)\n if (property.relation !== undefined && !(typeof property.relation === 'string' && reachable(property.relation))) {\n problems.push(`${at}.relation: ${String(property.relation)} is neither its own resource nor one its scopes read`)\n }\n }\n\n const fieldOf = (key: unknown) => typeof key === 'string' && (fieldNames.includes(key) || key === resource.key)\n\n if (typeof resource.key !== 'string' || !KEY.test(resource.key)) problems.push(`${where}.key: a field name`)\n if (!fieldOf(resource.title)) problems.push(`${where}.title: one of the form's fields, or the key`)\n\n const columns = Array.isArray(resource.columns) ? resource.columns : null\n\n if (!columns || columns.length === 0 || columns.length > 30) problems.push(`${where}.columns: a list of 1 to 30`)\n else {\n for (const [at, column] of columns.entries()) {\n const where2 = `${where}.columns[${at}]`\n\n if (!isObject(column)) {\n problems.push(`${where2}: not an object`)\n continue\n }\n\n only(where2, column, ['key', 'label', 'format', 'relation', 'sortable', 'searchable'], problems)\n\n if (typeof column.key !== 'string' || !KEY.test(column.key)) problems.push(`${where2}.key: a field name`)\n if (column.label !== undefined && !text(column.label, 60)) problems.push(`${where2}.label: at most 60 characters`)\n if (column.format !== undefined && !FORMATS.includes(column.format as ManifestFormat)) problems.push(`${where2}.format: ${FORMATS.join(', ')}`)\n if (column.relation !== undefined && !(typeof column.relation === 'string' && reachable(column.relation))) {\n problems.push(`${where2}.relation: ${String(column.relation)} is neither its own resource nor one its scopes read`)\n }\n }\n }\n\n if (resource.filters !== undefined) {\n const filters = Array.isArray(resource.filters) ? resource.filters : []\n\n for (const filter of filters) {\n if (typeof filter !== 'string' || !(properties[filter] as ManifestProperty | undefined)?.enum) problems.push(`${where}.filters: ${String(filter)} is not an enum field`)\n }\n }\n\n if (resource.perPage !== undefined && !(Number.isInteger(resource.perPage) && (resource.perPage as number) >= 1 && (resource.perPage as number) <= 100)) {\n problems.push(`${where}.perPage: 1 to 100`)\n }\n if (resource.mode !== undefined && resource.mode !== 'server' && resource.mode !== 'client') problems.push(`${where}.mode: server or client`)\n if (resource.readOnly !== undefined && typeof resource.readOnly !== 'boolean') problems.push(`${where}.readOnly: true or false`)\n\n if (resource.bulkActions !== undefined) {\n const actions = Array.isArray(resource.bulkActions) ? resource.bulkActions : null\n\n if (!actions || actions.length > 10) problems.push(`${where}.bulkActions: a list of at most 10`)\n else if (resource.readOnly === true && actions.length > 0) problems.push(`${where}.bulkActions: a read-only resource has none`)\n else {\n for (const action of actions) {\n if (!isObject(action) || typeof action.name !== 'string' || !NAME.test(action.name) || action.name === 'delete' || !text(action.label, 40)) {\n problems.push(`${where}.bulkActions: each needs a name (not delete) and a label`)\n }\n }\n }\n }\n }\n\n if (manifest.events !== undefined) {\n const events = Array.isArray(manifest.events) ? manifest.events : null\n\n if (!events) problems.push('events: a list of resource names')\n else {\n for (const event of events) {\n if (typeof event !== 'string' || !reachable(event)) problems.push(`events: ${String(event)} is neither its own resource nor one its scopes read`)\n }\n }\n }\n\n return problems\n}\n\n/** The scopes, as the person installing reads them. */\nexport function scopeWords(manifest: PluginManifest, labelOf: (resource: string) => string): string[] {\n return manifest.scopes.map((scope) => {\n const resource = labelOf(scope.slice('read:'.length)).toLowerCase()\n\n return `See your ${resource}: be told when one changes, and which one its own records point to.`\n })\n}\n\n// ---------------------------------------------------------- reaching a plugin\n\n/** Https to a name: not localhost, and not an address, which is how a private network is reached by url. */\nexport function publicHttps(url: URL): boolean {\n if (url.protocol !== 'https:') return false\n\n const host = url.hostname.replace(/^\\[|\\]$/g, '')\n\n if (host === 'localhost' || host.endsWith('.localhost') || host.endsWith('.local') || host.endsWith('.internal')) return false\n if (/^[\\d.]+$/.test(host) || host.includes(':')) return false\n\n return true\n}\n\nconst MANIFEST_BYTES = 256 * 1024\nconst ANSWER_BYTES = 5 * 1024 * 1024\n\n/** A response's body, up to a limit: a plugin that answers with gigabytes is a plugin that is wrong. */\nasync function bounded(response: Response, limit: number): Promise<string> {\n const declared = Number(response.headers.get('content-length') ?? 0)\n\n if (declared > limit) throw new Error(`[admin] the answer is ${declared} bytes, over ${limit}`)\n\n const body = await response.text()\n\n if (body.length > limit) throw new Error(`[admin] the answer is over ${limit} bytes`)\n\n return body\n}\n\n/** A manifest, fetched and checked. Redirects are refused: a url allowed once must not lead somewhere that was not. */\nexport async function fetchManifest(manifestUrl: string, allow: (url: URL) => boolean, host: string[]): Promise<PluginManifest> {\n let url: URL\n\n try {\n url = new URL(manifestUrl)\n } catch {\n throw new Error('That is not a url.')\n }\n\n if (!allow(url)) throw new Error('This panel does not install plugins from that address.')\n\n const response = await fetch(url, { headers: { accept: 'application/json' }, redirect: 'error', signal: AbortSignal.timeout(5000) }).catch(() => null)\n\n if (!response?.ok) throw new Error(`The manifest could not be fetched${response ? ` (${response.status})` : ''}.`)\n\n let manifest: unknown\n\n try {\n manifest = JSON.parse(await bounded(response, MANIFEST_BYTES))\n } catch {\n throw new Error('The manifest is not JSON.')\n }\n\n const problems = manifestProblems(manifest, url.href, host)\n\n if (problems.length > 0) throw new Error(`The manifest has problems: ${problems.join('; ')}.`)\n\n const checked = manifest as PluginManifest\n\n return { ...checked, endpoint: new URL(checked.endpoint, url).href }\n}\n\n/** A stable digest of a manifest: what was reviewed is what is installed, or the install is refused. */\nexport async function digestOf(manifest: PluginManifest): Promise<string> {\n const bytes = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(manifest))))\n\n return [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\nexport function newInstallation(manifestUrl: string, manifest: PluginManifest): Installation {\n return { id: crypto.randomUUID(), manifestUrl, manifest, secret: newSecret(), secretSeen: false, installedAt: new Date().toISOString() }\n}\n\n/** What a plugin's endpoint is sent. `tenant` is the installation's, never one the browser named. */\nexport type PluginCall =\n | { type: 'list'; resource: string; query: ListQuery }\n | { type: 'find'; resource: string; id: string }\n | { type: 'create'; resource: string; values: Record<string, unknown> }\n | { type: 'update'; resource: string; id: string; values: Record<string, unknown> }\n | { type: 'delete'; resource: string; id: string }\n | { type: 'bulk'; resource: string; action: string; ids: string[] }\n | { type: 'change'; change: RecordChange }\n\nexport type PluginRequest = PluginCall & { tenant: Tenant | null }\n\n/**\n * One signed call. A 404 is null; a 422 with `{ errors: { field: [message] } }`\n * is the form's field errors, exactly as Laravel's are; anything else that is\n * not a 2xx is a failure with the plugin's name on it.\n */\nexport async function callPlugin(\n installation: Installation,\n call: PluginCall,\n context: SourceContext,\n allow: (url: URL) => boolean,\n timeout = 10_000,\n): Promise<unknown> {\n const endpoint = new URL(installation.manifest.endpoint)\n const name = installation.manifest.name\n\n // Checked again on every call: the rule can tighten after the install.\n if (!allow(endpoint)) throw new Error(`[admin] the plugin ${name}'s endpoint is no longer allowed`)\n\n const body = JSON.stringify({ ...call, tenant: context.tenant ? { id: context.tenant.id, name: context.tenant.name } : null })\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json', accept: 'application/json', ...(await signedHeaders(installation.id, installation.secret, body)) },\n body,\n redirect: 'error',\n signal: AbortSignal.timeout(timeout),\n })\n\n if (response.status === 404) return null\n\n const answer = await bounded(response, ANSWER_BYTES)\n let json: unknown = null\n\n try {\n json = answer === '' ? null : JSON.parse(answer)\n } catch {\n throw new Error(`[admin] the plugin ${name} answered ${response.status} with something that is not JSON`)\n }\n\n if (response.status === 422 && isObject(json) && isObject(json.errors)) {\n const errors = Object.fromEntries(\n Object.entries(json.errors).map(([field, messages]) => [field, ([] as unknown[]).concat(messages).map(String).slice(0, 5)]),\n )\n\n throw new ActionValidationError(errors)\n }\n\n if (!response.ok) throw new Error(`[admin] the plugin ${name} answered ${response.status}`)\n\n return json\n}\n\nconst row = (value: unknown, plugin: string): Record<string, unknown> => {\n if (!isObject(value)) throw new Error(`[admin] the plugin ${plugin} answered a row that is not an object`)\n\n return value\n}\n\n/** A resource's five calls, made to the plugin. */\nfunction remoteSource(installation: Installation, resource: string, allow: (url: URL) => boolean): DataSource<Record<string, unknown>, Record<string, unknown>> {\n const plugin = installation.manifest.name\n const call = (body: PluginCall, context: SourceContext) => callPlugin(installation, body, context, allow)\n\n return {\n async list(query, context) {\n const answer = await call({ type: 'list', resource, query }, context)\n\n if (!isObject(answer) || !Array.isArray(answer.rows) || typeof answer.total !== 'number') {\n throw new Error(`[admin] the plugin ${plugin} answered a list without { rows, total }`)\n }\n\n return { rows: answer.rows.map((value) => row(value, plugin)), total: answer.total }\n },\n async find(id, context) {\n const answer = await call({ type: 'find', resource, id }, context)\n\n return isObject(answer) && answer.row !== undefined && answer.row !== null ? row(answer.row, plugin) : null\n },\n async create(values, context) {\n return row((await call({ type: 'create', resource, values }, context)) as { row?: unknown } | null, plugin).row as Record<string, unknown>\n },\n async update(id, values, context) {\n return row((await call({ type: 'update', resource, id, values }, context)) as { row?: unknown } | null, plugin).row as Record<string, unknown>\n },\n async delete(id, context) {\n await call({ type: 'delete', resource, id }, context)\n },\n }\n}\n\n// ------------------------------------------------------------------ the form\n\n/**\n * A Standard Schema from the manifest's JSON Schema, so a plugin's form goes\n * through the same parse as any other. It checks what is cheap and safe to\n * check here - types, required, enums, lengths, bounds - and never `pattern`;\n * the plugin's endpoint checks everything again and answers 422.\n */\nexport function jsonSchemaForm(form: ManifestResource['form']): FormSchema {\n const required = new Set(form.required ?? [])\n\n const validate = (value: unknown): { value: Record<string, unknown> } | { issues: { message: string; path: string[] }[] } => {\n if (!isObject(value)) return { issues: [{ message: 'Expected an object', path: [] }] }\n\n const out: Record<string, unknown> = {}\n const issues: { message: string; path: string[] }[] = []\n\n for (const [name, property] of Object.entries(form.properties)) {\n const given = value[name]\n const fail = (message: string) => issues.push({ message, path: [name] })\n\n if (given === undefined || given === null || given === '') {\n if (required.has(name) && property.type !== 'boolean') fail('Required')\n continue\n }\n\n if (property.type === 'boolean') {\n if (typeof given !== 'boolean') fail('Expected true or false')\n else out[name] = given\n continue\n }\n\n if (property.type === 'number' || property.type === 'integer') {\n const number = typeof given === 'number' ? given : Number(given)\n\n if (!Number.isFinite(number)) fail('Expected a number')\n else if (property.type === 'integer' && !Number.isInteger(number)) fail('Expected a whole number')\n else if (property.minimum !== undefined && number < property.minimum) fail(`At least ${property.minimum}`)\n else if (property.maximum !== undefined && number > property.maximum) fail(`At most ${property.maximum}`)\n else out[name] = number\n continue\n }\n\n const string = String(given)\n\n if (property.enum && !property.enum.includes(string)) fail(`One of ${property.enum.join(', ')}`)\n else if (property.minLength !== undefined && string.length < property.minLength) fail(`At least ${property.minLength} characters`)\n else if (property.maxLength !== undefined && string.length > property.maxLength) fail(`At most ${property.maxLength} characters`)\n else out[name] = string\n }\n\n return issues.length > 0 ? { issues } : { value: out }\n }\n\n const json = {\n type: 'object',\n required: form.required ?? [],\n properties: Object.fromEntries(\n Object.entries(form.properties).map(([name, { relation: _relation, title: _title, format, ...property }]) => [\n name,\n { ...property, ...(format && format !== 'textarea' ? { format } : {}) },\n ]),\n ),\n }\n\n return {\n '~standard': {\n version: 1,\n vendor: '@rsc-kit/press/plugin',\n validate,\n jsonSchema: { input: () => json, output: () => json },\n },\n } as unknown as FormSchema\n}\n\n/** An installation's resources, as the panel's own. */\nexport function resourcesOf(installation: Installation, allow: (url: URL) => boolean): Resource[] {\n return installation.manifest.resources.map((described) =>\n defineResource<Record<string, unknown>>()({\n name: described.name,\n ...(described.label ? { label: described.label } : {}),\n ...(described.singular ? { singular: described.singular } : {}),\n key: described.key,\n title: described.title,\n source: remoteSource(installation, described.name, allow),\n table: (col) =>\n described.columns.map((column) => {\n const built = column.relation ? col.relation(column.key, column.relation) : col[column.format ?? 'text'](column.key)\n\n if (column.label) built.label(column.label)\n if (column.sortable) built.sortable()\n if (column.searchable) built.searchable()\n\n return built\n }),\n form: jsonSchemaForm(described.form),\n fields: Object.fromEntries(\n Object.entries(described.form.properties).flatMap(([name, property]) => {\n const override = {\n ...(property.title ? { label: property.title } : {}),\n ...(property.description ? { description: property.description } : {}),\n ...(property.format === 'textarea' ? { type: 'textarea' as const } : {}),\n ...(property.relation ? { relation: property.relation } : {}),\n }\n\n return Object.keys(override).length > 0 ? [[name, override]] : []\n }),\n ),\n ...(described.filters ? { filters: described.filters } : {}),\n ...(described.perPage ? { perPage: described.perPage } : {}),\n ...(described.mode ? { mode: described.mode } : {}),\n ...(described.readOnly ? { readOnly: true } : {}),\n ...(described.description ? { description: described.description } : {}),\n bulkActions: (described.bulkActions ?? []).map((action) => ({\n name: action.name,\n label: action.label,\n ...(action.done ? { done: action.done } : {}),\n ...(action.destructive ? { destructive: true } : {}),\n run: async (rows: Record<string, unknown>[], context: SourceContext) => {\n const answer = await callPlugin(\n installation,\n { type: 'bulk', resource: described.name, action: action.name, ids: rows.map((found) => String(found[described.key])) },\n context,\n allow,\n )\n\n return isObject(answer) && typeof answer.message === 'string' ? answer.message.slice(0, 200) : undefined\n },\n })),\n }),\n )\n}\n\n/** Whether a plugin is told about a change: one of its events, which its scopes or ownership allow. */\nexport function wantsChange(installation: Installation, change: RecordChange): boolean {\n return installation.manifest.events?.includes(change.resource) ?? false\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { DataSource } from './resource.js';
2
+ /**
3
+ * Rows held in memory, searched, filtered, sorted and paged the way a database
4
+ * adapter would. For a prototype, a demo or a test: the rows last as long as
5
+ * the process.
6
+ */
7
+ export declare function memorySource<Row extends object, Values>(rows: Row[], key: keyof Row & string, make: (id: number, values: Values) => Row): DataSource<Row, Values>;