@raclettejs/workbench 0.1.34 → 0.1.36-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +18 -2
  2. package/config/compositions.js +18 -0
  3. package/config/interactionLinks.js +19 -0
  4. package/i18n/de-DE.json +15 -1
  5. package/i18n/en-EU.json +52 -1
  6. package/i18n/sk-SK.json +15 -1
  7. package/package.json +3 -4
  8. package/packages.json +3 -1
  9. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionConfiguration.vue +96 -14
  10. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/CompositionIntegration.vue +29 -1
  11. package/plugins/pacifico__compositions/frontend/components/compositionConfiguration/widgetsLayout/WidgetRenderer.vue +1 -6
  12. package/plugins/pacifico__compositions/frontend/composables/prefillInteractionLinkFromComposition.ts +54 -0
  13. package/plugins/pacifico__compositions/frontend/widgets/CompositionCreateWidget.vue +145 -9
  14. package/plugins/pacifico__compositions/frontend/widgets/CompositionEditWidget.vue +11 -2
  15. package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +26 -6
  16. package/plugins/pacifico__core/frontend/generated-config.ts +22 -0
  17. package/plugins/pacifico__interactionLinks/frontend/components/InteractionLinkConfiguration.vue +3 -65
  18. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +1 -1
  19. package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +21 -4
  20. package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +1 -1
  21. package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +1 -1
  22. package/plugins/raclette__cache/backend/helpers/cacheEntryBuilder.ts +94 -0
  23. package/plugins/raclette__cache/backend/helpers/cacheEntrySanitizer.ts +86 -0
  24. package/plugins/raclette__cache/backend/helpers/valkeyExplorer.ts +89 -0
  25. package/plugins/raclette__cache/backend/index.ts +21 -0
  26. package/plugins/raclette__cache/backend/routes/route.cache.cacheEntries.get.ts +118 -0
  27. package/plugins/raclette__cache/backend/routes/route.cache.cacheEntry.get.ts +91 -0
  28. package/plugins/raclette__cache/backend/routes.ts +8 -0
  29. package/plugins/raclette__cache/frontend/components/TableColumnConfigDialog.vue +95 -0
  30. package/plugins/raclette__cache/frontend/composables/useTableColumnConfig.ts +66 -0
  31. package/plugins/raclette__cache/frontend/generated-config.ts +33 -0
  32. package/plugins/raclette__cache/frontend/utils/cacheValueTableRows.ts +340 -0
  33. package/plugins/raclette__cache/frontend/widgets/CacheListWidget.vue +742 -0
  34. package/plugins/raclette__cache/raclette.plugin.ts +8 -0
  35. package/services/frontend/src/app/components/dynamicForm/DynamicForm.vue +3 -1
  36. package/services/frontend/src/app/components/interactionLink/InteractionLinkBehaviorForm.vue +99 -0
  37. package/services/frontend/src/app/components/stepNavigator/StepNavigator.vue +175 -24
  38. package/services/frontend/src/app/components/stepNavigator/StepNavigatorTypes.ts +18 -0
  39. package/services/frontend/src/app/composables/useInteractionLinkBehaviorFields.ts +83 -0
  40. package/services/frontend/src/app/composables/useRacletteUserIsAdmin.ts +25 -0
  41. package/services/frontend/src/orchestrator/assets/styles/themes/dark.ts +1 -0
  42. package/services/frontend/src/orchestrator/assets/styles/themes/light.ts +1 -1
@@ -0,0 +1,340 @@
1
+ export type ValueTableRow = Record<string, string>
2
+
3
+ /** Minimal cache row fields needed to build the value table. */
4
+ export type CacheEntryValueSource = {
5
+ key: string
6
+ value: string
7
+ valueParsed?: unknown
8
+ }
9
+
10
+ export type ValueTableHeader = {
11
+ title: string
12
+ key: string
13
+ }
14
+
15
+ export const CACHE_KEY_COLUMN = "_cacheKey"
16
+ export const MAP_ENTRY_KEY_COLUMN = "entryKey"
17
+ const SCALAR_COLUMN = "value"
18
+ const RAW_COLUMN = "raw"
19
+
20
+ const stringifyCell = (value: unknown): string => {
21
+ if (value === null) {
22
+ return "null"
23
+ }
24
+ if (value === undefined) {
25
+ return ""
26
+ }
27
+ if (Array.isArray(value)) {
28
+ return value.length === 0 ? "" : JSON.stringify(value)
29
+ }
30
+ if (isPlainObject(value)) {
31
+ return Object.keys(value).length === 0 ? "" : JSON.stringify(value)
32
+ }
33
+ if (typeof value === "object") {
34
+ return JSON.stringify(value)
35
+ }
36
+ return String(value)
37
+ }
38
+
39
+ const tryParseJson = (value: string): unknown | undefined => {
40
+ try {
41
+ return JSON.parse(value)
42
+ } catch {
43
+ return undefined
44
+ }
45
+ }
46
+
47
+ const isPlainObject = (value: unknown): value is Record<string, unknown> =>
48
+ value !== null && typeof value === "object" && !Array.isArray(value)
49
+
50
+ const isScalarValue = (value: unknown): value is string | number | boolean | null =>
51
+ value === null ||
52
+ typeof value === "string" ||
53
+ typeof value === "number" ||
54
+ typeof value === "boolean"
55
+
56
+ const MAP_ENTRY_KEY_PATTERN =
57
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
58
+
59
+ const isMapEntryKey = (key: string): boolean => MAP_ENTRY_KEY_PATTERN.test(key)
60
+
61
+ const isHomogeneousObjectMap = (value: Record<string, unknown>): boolean => {
62
+ const objectValues = Object.values(value)
63
+ if (objectValues.length === 0) {
64
+ return false
65
+ }
66
+ return (
67
+ objectValues.every(isPlainObject) || objectValues.every(isScalarValue)
68
+ )
69
+ }
70
+
71
+ const headersFromKeys = (keys: string[]): ValueTableHeader[] =>
72
+ keys.map((key) => ({ title: key, key }))
73
+
74
+ const sortedKeys = (keys: Iterable<string>): string[] =>
75
+ [...keys].sort((a, b) => a.localeCompare(b))
76
+
77
+ const rowFromRecord = (
78
+ record: Record<string, unknown>,
79
+ columns: string[],
80
+ ): ValueTableRow => {
81
+ const row: ValueTableRow = {}
82
+ for (const column of columns) {
83
+ row[column] = column in record ? stringifyCell(record[column]) : ""
84
+ }
85
+ return row
86
+ }
87
+
88
+ const collectKeysFromObjects = (items: Record<string, unknown>[]): string[] => {
89
+ const columns = new Set<string>()
90
+ for (const item of items) {
91
+ for (const key of Object.keys(item)) {
92
+ columns.add(key)
93
+ }
94
+ }
95
+ return sortedKeys(columns)
96
+ }
97
+
98
+ /** Items inside one cache value that share the same object shape (array or object map). */
99
+ export const unwrapValueCollection = (
100
+ parsed: unknown | undefined,
101
+ raw?: string,
102
+ ): Record<string, unknown>[] | null => {
103
+ const value = parsed ?? (raw !== undefined ? tryParseJson(raw) : undefined)
104
+ if (value === undefined) {
105
+ return null
106
+ }
107
+
108
+ if (Array.isArray(value)) {
109
+ return value.every(isPlainObject) ? value : null
110
+ }
111
+
112
+ if (!isPlainObject(value)) {
113
+ return null
114
+ }
115
+
116
+ const objectKeys = Object.keys(value)
117
+ const objectValues = Object.values(value)
118
+ if (objectValues.length > 0 && objectValues.every(isPlainObject)) {
119
+ if (objectKeys.length > 0 && objectKeys.every(isMapEntryKey)) {
120
+ return Object.entries(value).map(([entryKey, entryValue]) => ({
121
+ ...(entryValue as Record<string, unknown>),
122
+ [MAP_ENTRY_KEY_COLUMN]: entryKey,
123
+ }))
124
+ }
125
+ return objectValues
126
+ }
127
+
128
+ if (
129
+ objectKeys.length > 0 &&
130
+ objectKeys.every(isMapEntryKey) &&
131
+ objectValues.every(isScalarValue)
132
+ ) {
133
+ return Object.entries(value).map(([entryKey, entryValue]) => ({
134
+ [MAP_ENTRY_KEY_COLUMN]: entryKey,
135
+ [SCALAR_COLUMN]: entryValue,
136
+ }))
137
+ }
138
+
139
+ if (objectKeys.length === 1) {
140
+ const nested = value[objectKeys[0]]
141
+ if (Array.isArray(nested) && nested.every(isPlainObject)) {
142
+ return nested
143
+ }
144
+ }
145
+
146
+ return null
147
+ }
148
+
149
+ const tableFromCollection = (
150
+ items: Record<string, unknown>[],
151
+ cacheKey?: string,
152
+ ): { rows: ValueTableRow[]; headers: ValueTableHeader[] } => {
153
+ const columns = collectKeysFromObjects(items)
154
+ if (columns.length === 0) {
155
+ return { rows: [], headers: [] }
156
+ }
157
+
158
+ const attributeHeaders = headersFromKeys(columns)
159
+ const headers = cacheKey
160
+ ? [
161
+ { title: "Key", key: CACHE_KEY_COLUMN },
162
+ ...attributeHeaders,
163
+ ]
164
+ : attributeHeaders
165
+
166
+ const rows = items.map((item, index) => {
167
+ const row: ValueTableRow = {
168
+ ...rowFromRecord(item, columns),
169
+ }
170
+ if (cacheKey) {
171
+ row[CACHE_KEY_COLUMN] = cacheKey
172
+ }
173
+ row._id = cacheKey ? `${cacheKey}:${index}` : String(index)
174
+ return row
175
+ })
176
+
177
+ return { rows, headers }
178
+ }
179
+
180
+ const scalarTable = (text: string): { rows: ValueTableRow[]; headers: ValueTableHeader[] } => ({
181
+ rows: [{ _id: "0", [SCALAR_COLUMN]: text }],
182
+ headers: [{ title: "(value)", key: SCALAR_COLUMN }],
183
+ })
184
+
185
+ const rawFallback = (value: unknown): { rows: ValueTableRow[]; headers: ValueTableHeader[] } => ({
186
+ rows: [{ _id: "0", [RAW_COLUMN]: JSON.stringify(value, null, 2) }],
187
+ headers: [{ title: "Raw", key: RAW_COLUMN }],
188
+ })
189
+
190
+ const parsedValueObject = (
191
+ entry: CacheEntryValueSource,
192
+ ): Record<string, unknown> | null => {
193
+ const parsed = entry.valueParsed ?? tryParseJson(entry.value)
194
+ return isPlainObject(parsed) ? parsed : null
195
+ }
196
+
197
+ /**
198
+ * Build a table from loaded cache entries: attribute keys become columns,
199
+ * each entry's parsed value object becomes one row.
200
+ */
201
+ export const cacheEntriesValueTableRows = (
202
+ entries: CacheEntryValueSource[],
203
+ schemaEntry?: CacheEntryValueSource,
204
+ ): { rows: ValueTableRow[]; headers: ValueTableHeader[] } => {
205
+ if (entries.length === 0) {
206
+ return { rows: [], headers: [] }
207
+ }
208
+
209
+ const focus = schemaEntry ?? entries[0]
210
+ const innerItems = unwrapValueCollection(focus.valueParsed, focus.value)
211
+ if (innerItems && innerItems.length > 0) {
212
+ const cacheKeyForCollection = entries.length > 1 ? focus.key : undefined
213
+ return tableFromCollection(innerItems, cacheKeyForCollection)
214
+ }
215
+
216
+ const schemaObject = schemaEntry ? parsedValueObject(schemaEntry) : null
217
+ if (schemaObject && !isHomogeneousObjectMap(schemaObject)) {
218
+ const fallback = schemaEntry ?? entries[0]
219
+ return cacheValueTableRows(fallback.valueParsed, fallback.value)
220
+ }
221
+
222
+ const rowObjects = entries
223
+ .map((entry) => ({ entry, data: parsedValueObject(entry) }))
224
+ .filter(
225
+ (row): row is { entry: CacheEntryValueSource; data: Record<string, unknown> } =>
226
+ row.data !== null,
227
+ )
228
+
229
+ if (rowObjects.length === 0) {
230
+ const fallback = schemaEntry ?? entries[0]
231
+ return cacheValueTableRows(fallback.valueParsed, fallback.value)
232
+ }
233
+
234
+ const columns = schemaObject
235
+ ? sortedKeys(Object.keys(schemaObject))
236
+ : collectKeysFromObjects(rowObjects.map((row) => row.data))
237
+
238
+ if (columns.length === 0) {
239
+ return { rows: [], headers: [] }
240
+ }
241
+
242
+ const headers: ValueTableHeader[] = [
243
+ { title: "Key", key: CACHE_KEY_COLUMN },
244
+ ...headersFromKeys(columns),
245
+ ]
246
+
247
+ const rows = rowObjects.map(({ entry, data }) => ({
248
+ _id: entry.key,
249
+ [CACHE_KEY_COLUMN]: entry.key,
250
+ ...rowFromRecord(data, columns),
251
+ }))
252
+
253
+ return { rows, headers }
254
+ }
255
+
256
+ /** Map one cache value into rows (collection items or single-object pivot). */
257
+ export const cacheValueTableRows = (
258
+ parsed: unknown | undefined,
259
+ raw?: string,
260
+ cacheKey?: string,
261
+ ): { rows: ValueTableRow[]; headers: ValueTableHeader[] } => {
262
+ const collection = unwrapValueCollection(parsed, raw)
263
+ if (collection && collection.length > 0) {
264
+ return tableFromCollection(collection, cacheKey)
265
+ }
266
+
267
+ if (parsed === undefined) {
268
+ return scalarTable(raw ?? "")
269
+ }
270
+
271
+ if (parsed === null) {
272
+ return scalarTable("null")
273
+ }
274
+
275
+ if (
276
+ typeof parsed === "string" ||
277
+ typeof parsed === "number" ||
278
+ typeof parsed === "boolean"
279
+ ) {
280
+ return scalarTable(String(parsed))
281
+ }
282
+
283
+ if (isPlainObject(parsed)) {
284
+ if (!isHomogeneousObjectMap(parsed)) {
285
+ return rawFallback(parsed)
286
+ }
287
+
288
+ const columns = sortedKeys(Object.keys(parsed))
289
+ if (columns.length === 0) {
290
+ return { rows: [], headers: [] }
291
+ }
292
+ const row: ValueTableRow = {
293
+ _id: cacheKey ?? "0",
294
+ ...rowFromRecord(parsed, columns),
295
+ }
296
+ if (cacheKey) {
297
+ row[CACHE_KEY_COLUMN] = cacheKey
298
+ return {
299
+ rows: [row],
300
+ headers: [
301
+ { title: "Key", key: CACHE_KEY_COLUMN },
302
+ ...headersFromKeys(columns),
303
+ ],
304
+ }
305
+ }
306
+ return {
307
+ rows: [row],
308
+ headers: headersFromKeys(columns),
309
+ }
310
+ }
311
+
312
+ if (Array.isArray(parsed)) {
313
+ return rawFallback(parsed)
314
+ }
315
+
316
+ return rawFallback(parsed)
317
+ }
318
+
319
+ /** True when tabular layout is not meaningful; use pretty-print instead. */
320
+ export const isScalarFallbackResult = (
321
+ rows: ValueTableRow[],
322
+ headers: ValueTableHeader[],
323
+ entry: CacheEntryValueSource,
324
+ ): boolean => {
325
+ if (rows.length === 0) {
326
+ return true
327
+ }
328
+
329
+ if (rows.length === 1 && headers.length === 1) {
330
+ const columnKey = headers[0].key
331
+ if (columnKey === RAW_COLUMN) {
332
+ return true
333
+ }
334
+ if (columnKey === SCALAR_COLUMN && entry.valueParsed === undefined) {
335
+ return true
336
+ }
337
+ }
338
+
339
+ return false
340
+ }