@raclettejs/workbench 0.1.36-canary.1 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.36] - 2026-06-22 <a href="https://gitlab.com/raclettejs/core-dev/-/compare/v0.1.35...v0.1.36" target="_blank" rel="noopener"><b>Overview of all changes</b></a>
11
+
12
+
13
+ ### Changed
14
+
15
+ - Improved UX of workbenchs "create composition" flow
16
+ - feat: Enhanced basedatatable to provide group by and footer slot abilities
17
+ - WidgetLayout: removed icon as fallback option after image -> only render image or fallback block, never icon in WidgetRenderer
18
+ - feat: added a workbench plugin which let's admins see all valkey entries in a datatable
19
+ - Adjusted package.json to improve monorepo handling
20
+ - Greatly imporoved the docs structure, hardened the code snipped interpolation logic
21
+
22
+ ### Updated
23
+
24
+ - Updated dependencies
25
+ - Updated dependencies
26
+ - Updated dependencies
27
+ - Updated dependencies
28
+ - Updated dependencies
29
+ - Updated dependencies
30
+ - @raclettejs/core@0.1.36
31
+
10
32
  ### Fixed
11
33
 
12
34
  - WidgetLayout: removed icon as fallback option after image -> only render image or fallback block, never icon in WidgetRenderer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raclettejs/workbench",
3
- "version": "0.1.36-canary.1",
3
+ "version": "0.1.36",
4
4
  "license": "MIT",
5
5
  "description": "racletteJS Workbench - used to configure your application, for dev and prod",
6
6
  "repository": "https://gitlab.com/raclettejs/workbench",
@@ -33,7 +33,7 @@
33
33
  ".": "./index.ts"
34
34
  },
35
35
  "dependencies": {
36
- "@raclettejs/core": "0.1.36-canary.1",
36
+ "@raclettejs/core": "0.1.36",
37
37
  "@vueuse/integrations": "14.2.1",
38
38
  "fuse.js": "7.1.0",
39
39
  "three": "0.183.2",
@@ -43,7 +43,7 @@
43
43
  "@emnapi/core": "1.9.2",
44
44
  "@emnapi/runtime": "1.9.2",
45
45
  "@eslint/js": "9.35.0",
46
- "@raclettejs/types": "0.1.36-canary.1",
46
+ "@raclettejs/types": "0.1.36",
47
47
  "@sinclair/typebox": "0.34.48",
48
48
  "@types/ramda": "0.31.1",
49
49
  "@vueuse/core": "14.2.1",
@@ -11,6 +11,8 @@ export type CacheEntrySource = {
11
11
  valuePreview?: string
12
12
  truncated: boolean
13
13
  valueParsed?: unknown
14
+ /** True when `valueParsed` was built from the full stored value (not the transport slice). */
15
+ valueParsedFromFullValue?: boolean
14
16
  ttl: number
15
17
  ttlLabel: string
16
18
  }
@@ -51,6 +53,7 @@ export const buildCacheEntrySource = (
51
53
  const sanitized = sanitizeCacheEntryFields(truncatedFields)
52
54
 
53
55
  let valueParsed = sanitized.valueParsed
56
+ let valueParsedFromFullValue = false
54
57
 
55
58
  if (attachFullParsed && truncatedFields.truncated) {
56
59
  const fullSanitized = sanitizeCacheEntryFields({
@@ -59,6 +62,7 @@ export const buildCacheEntrySource = (
59
62
  })
60
63
  if (fullSanitized.valueParsed !== undefined) {
61
64
  valueParsed = fullSanitized.valueParsed
65
+ valueParsedFromFullValue = true
62
66
  }
63
67
  }
64
68
 
@@ -69,6 +73,7 @@ export const buildCacheEntrySource = (
69
73
  valuePreview: sanitized.valuePreview,
70
74
  truncated: sanitized.truncated,
71
75
  valueParsed,
76
+ valueParsedFromFullValue: valueParsedFromFullValue || undefined,
72
77
  ttl,
73
78
  ttlLabel: formatTtlLabel(ttl),
74
79
  }
@@ -0,0 +1,191 @@
1
+ /** Injected into repaired JSON so viewers know the payload is incomplete. */
2
+ export const TRUNCATION_MARKER = "__raclette_truncated__"
3
+
4
+ const TRUNCATION_NOTICE =
5
+ "Value was cut for transport; remainder omitted. Do not treat as complete source of truth."
6
+
7
+ type CacheEntryDisplaySource = {
8
+ value: string
9
+ truncated: boolean
10
+ valueParsed?: unknown
11
+ valueParsedFromFullValue?: boolean
12
+ }
13
+
14
+ const prettyJson = (value: unknown): string => JSON.stringify(value, null, 2)
15
+
16
+ const appendTruncationNotice = (body: string): string =>
17
+ `${body}\n\n/* ${TRUNCATION_NOTICE} */`
18
+
19
+ /**
20
+ * Close open strings/arrays/objects after an abrupt cut so JSON.parse may succeed
21
+ * on a prefix of the original document.
22
+ */
23
+ export const repairTruncatedJson = (raw: string): string => {
24
+ const text = raw.trimEnd()
25
+ if (!text.length) {
26
+ return text
27
+ }
28
+
29
+ try {
30
+ JSON.parse(text)
31
+ return text
32
+ } catch {
33
+ // continue with repair
34
+ }
35
+
36
+ const stack: ("{" | "[")[] = []
37
+ let inString = false
38
+ let escaped = false
39
+ let lastCompleteTopLevel = 0
40
+
41
+ for (let i = 0; i < text.length; i++) {
42
+ const char = text[i]
43
+
44
+ if (inString) {
45
+ if (escaped) {
46
+ escaped = false
47
+ } else if (char === "\\") {
48
+ escaped = true
49
+ } else if (char === '"') {
50
+ inString = false
51
+ }
52
+ continue
53
+ }
54
+
55
+ if (char === '"') {
56
+ inString = true
57
+ continue
58
+ }
59
+
60
+ if (char === "{") {
61
+ stack.push("{")
62
+ continue
63
+ }
64
+
65
+ if (char === "[") {
66
+ stack.push("[")
67
+ continue
68
+ }
69
+
70
+ if (char === "}") {
71
+ if (stack.at(-1) === "{") {
72
+ stack.pop()
73
+ if (stack.length === 0) {
74
+ lastCompleteTopLevel = i + 1
75
+ }
76
+ }
77
+ continue
78
+ }
79
+
80
+ if (char === "]") {
81
+ if (stack.at(-1) === "[") {
82
+ stack.pop()
83
+ if (stack.length === 0) {
84
+ lastCompleteTopLevel = i + 1
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ let candidate = text
91
+ if (inString || stack.length > 0) {
92
+ if (lastCompleteTopLevel > 0) {
93
+ candidate = text.slice(0, lastCompleteTopLevel)
94
+ }
95
+ }
96
+
97
+ candidate = candidate.replace(/,\s*$/, "")
98
+
99
+ const openStack: ("{" | "[")[] = []
100
+ inString = false
101
+ escaped = false
102
+
103
+ for (let i = 0; i < candidate.length; i++) {
104
+ const char = candidate[i]
105
+ if (inString) {
106
+ if (escaped) escaped = false
107
+ else if (char === "\\") escaped = true
108
+ else if (char === '"') inString = false
109
+ continue
110
+ }
111
+ if (char === '"') {
112
+ inString = true
113
+ continue
114
+ }
115
+ if (char === "{") openStack.push("{")
116
+ else if (char === "[") openStack.push("[")
117
+ else if (char === "}" && openStack.at(-1) === "{") openStack.pop()
118
+ else if (char === "]" && openStack.at(-1) === "[") openStack.pop()
119
+ }
120
+
121
+ if (inString) {
122
+ const lastQuote = candidate.lastIndexOf('"')
123
+ candidate = lastQuote > 0 ? candidate.slice(0, lastQuote) : candidate
124
+ candidate = candidate.replace(/,\s*"[^"]*$/, "")
125
+ candidate = candidate.replace(/:\s*$/, "")
126
+ }
127
+
128
+ while (openStack.length > 0) {
129
+ const open = openStack.pop()
130
+ candidate += open === "{" ? "}" : "]"
131
+ }
132
+
133
+ candidate = candidate.replace(/,\s*([}\]])/g, "$1")
134
+
135
+ return candidate
136
+ }
137
+
138
+ const withTruncationMarker = (parsed: unknown): unknown => {
139
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
140
+ return {
141
+ ...parsed,
142
+ [TRUNCATION_MARKER]: TRUNCATION_NOTICE,
143
+ }
144
+ }
145
+
146
+ return {
147
+ [TRUNCATION_MARKER]: TRUNCATION_NOTICE,
148
+ value: parsed,
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Pretty-print a cache entry for <pre> display.
154
+ * Recovers structure from truncated JSON when possible and always signals truncation.
155
+ */
156
+ export const formatCacheEntryDisplay = (entry: CacheEntryDisplaySource): string => {
157
+ if (!entry.truncated) {
158
+ if (entry.valueParsed !== undefined) {
159
+ return prettyJson(entry.valueParsed)
160
+ }
161
+ try {
162
+ return prettyJson(JSON.parse(entry.value))
163
+ } catch {
164
+ return entry.value
165
+ }
166
+ }
167
+
168
+ if (
169
+ entry.valueParsed !== undefined &&
170
+ entry.valueParsedFromFullValue
171
+ ) {
172
+ return prettyJson(entry.valueParsed)
173
+ }
174
+
175
+ if (entry.valueParsed !== undefined) {
176
+ return appendTruncationNotice(prettyJson(entry.valueParsed))
177
+ }
178
+
179
+ try {
180
+ return appendTruncationNotice(prettyJson(JSON.parse(entry.value)))
181
+ } catch {
182
+ const repaired = repairTruncatedJson(entry.value)
183
+ try {
184
+ return appendTruncationNotice(
185
+ prettyJson(withTruncationMarker(JSON.parse(repaired))),
186
+ )
187
+ } catch {
188
+ return `${entry.value}\n\n… [${TRUNCATION_NOTICE}]`
189
+ }
190
+ }
191
+ }
@@ -257,7 +257,11 @@
257
257
  </v-card-subtitle>
258
258
  <v-card-text>
259
259
  <v-alert
260
- v-if="selectedEntry.truncated"
260
+ v-if="
261
+ selectedEntry.truncated &&
262
+ !selectedEntry.valueParsedFromFullValue &&
263
+ selectedEntry.valueParsed === undefined
264
+ "
261
265
  type="warning"
262
266
  density="compact"
263
267
  class="tw:mb-3"
@@ -294,6 +298,7 @@ import {
294
298
  isScalarFallbackResult,
295
299
  type ValueTableHeader,
296
300
  } from "../utils/cacheValueTableRows"
301
+ import { formatCacheEntryDisplay } from "../utils/formatCacheEntryDisplay"
297
302
  import { computed, onMounted, ref } from "vue"
298
303
  import { useI18n } from "vue-i18n"
299
304
 
@@ -305,6 +310,7 @@ type CacheEntry = {
305
310
  valuePreview?: string
306
311
  truncated: boolean
307
312
  valueParsed?: unknown
313
+ valueParsedFromFullValue?: boolean
308
314
  ttl: number
309
315
  ttlLabel: string
310
316
  }
@@ -440,7 +446,7 @@ const showValueTruncatedWarning = computed(() => {
440
446
  if (!isValueView.value || !entry?.truncated) {
441
447
  return false
442
448
  }
443
- return entry.valueParsed === undefined
449
+ return !entry.valueParsedFromFullValue && entry.valueParsed === undefined
444
450
  })
445
451
 
446
452
  const valueTableDataName = computed(() => {
@@ -530,6 +536,7 @@ const toTableRow = (entry: Partial<CacheEntry> & { key?: string }): CacheEntry =
530
536
  valuePreview: entry.valuePreview,
531
537
  truncated: entry.truncated ?? false,
532
538
  valueParsed: entry.valueParsed,
539
+ valueParsedFromFullValue: entry.valueParsedFromFullValue,
533
540
  ttl: entry.ttl ?? -2,
534
541
  ttlLabel: entry.ttlLabel ?? "",
535
542
  }
@@ -638,12 +645,7 @@ const displayValue = (item: CacheEntry) => {
638
645
  return item.value
639
646
  }
640
647
 
641
- const formatDetailValue = (item: CacheEntry) => {
642
- if (item.valueParsed !== undefined) {
643
- return JSON.stringify(item.valueParsed, null, 2)
644
- }
645
- return item.value
646
- }
648
+ const formatDetailValue = (item: CacheEntry) => formatCacheEntryDisplay(item)
647
649
 
648
650
  const formatValueTableCell = (value: string | undefined) => value ?? ""
649
651