@sundaeswap/sprinkles 0.5.0 → 0.6.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 (112) hide show
  1. package/dist/cjs/Sprinkle/__tests__/encryption.test.js +3 -1
  2. package/dist/cjs/Sprinkle/__tests__/encryption.test.js.map +1 -1
  3. package/dist/cjs/Sprinkle/__tests__/enhancements.test.js +3 -37
  4. package/dist/cjs/Sprinkle/__tests__/enhancements.test.js.map +1 -1
  5. package/dist/cjs/Sprinkle/__tests__/field-utils.test.js +170 -0
  6. package/dist/cjs/Sprinkle/__tests__/field-utils.test.js.map +1 -0
  7. package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js +242 -87
  8. package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
  9. package/dist/cjs/Sprinkle/__tests__/formatting.test.js +97 -0
  10. package/dist/cjs/Sprinkle/__tests__/formatting.test.js.map +1 -0
  11. package/dist/cjs/Sprinkle/__tests__/show-menu.test.js +9 -5
  12. package/dist/cjs/Sprinkle/__tests__/show-menu.test.js.map +1 -1
  13. package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js +9 -0
  14. package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
  15. package/dist/cjs/Sprinkle/index.js +135 -91
  16. package/dist/cjs/Sprinkle/index.js.map +1 -1
  17. package/dist/cjs/Sprinkle/menus/array-menu.js +195 -0
  18. package/dist/cjs/Sprinkle/menus/array-menu.js.map +1 -0
  19. package/dist/cjs/Sprinkle/menus/field-menu.js +161 -0
  20. package/dist/cjs/Sprinkle/menus/field-menu.js.map +1 -0
  21. package/dist/cjs/Sprinkle/menus/index.js +33 -0
  22. package/dist/cjs/Sprinkle/menus/index.js.map +1 -0
  23. package/dist/cjs/Sprinkle/menus/object-menu.js +324 -0
  24. package/dist/cjs/Sprinkle/menus/object-menu.js.map +1 -0
  25. package/dist/cjs/Sprinkle/prompts.js +68 -2
  26. package/dist/cjs/Sprinkle/prompts.js.map +1 -1
  27. package/dist/cjs/Sprinkle/type-guards.js +48 -1
  28. package/dist/cjs/Sprinkle/type-guards.js.map +1 -1
  29. package/dist/cjs/Sprinkle/types.js +24 -0
  30. package/dist/cjs/Sprinkle/types.js.map +1 -1
  31. package/dist/cjs/Sprinkle/utils/field-utils.js +154 -0
  32. package/dist/cjs/Sprinkle/utils/field-utils.js.map +1 -0
  33. package/dist/cjs/Sprinkle/utils/formatting.js +126 -0
  34. package/dist/cjs/Sprinkle/utils/formatting.js.map +1 -0
  35. package/dist/cjs/Sprinkle/utils/index.js +56 -0
  36. package/dist/cjs/Sprinkle/utils/index.js.map +1 -0
  37. package/dist/esm/Sprinkle/__tests__/encryption.test.js +3 -1
  38. package/dist/esm/Sprinkle/__tests__/encryption.test.js.map +1 -1
  39. package/dist/esm/Sprinkle/__tests__/enhancements.test.js +3 -37
  40. package/dist/esm/Sprinkle/__tests__/enhancements.test.js.map +1 -1
  41. package/dist/esm/Sprinkle/__tests__/field-utils.test.js +168 -0
  42. package/dist/esm/Sprinkle/__tests__/field-utils.test.js.map +1 -0
  43. package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js +243 -88
  44. package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
  45. package/dist/esm/Sprinkle/__tests__/formatting.test.js +95 -0
  46. package/dist/esm/Sprinkle/__tests__/formatting.test.js.map +1 -0
  47. package/dist/esm/Sprinkle/__tests__/show-menu.test.js +9 -5
  48. package/dist/esm/Sprinkle/__tests__/show-menu.test.js.map +1 -1
  49. package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js +9 -0
  50. package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
  51. package/dist/esm/Sprinkle/index.js +102 -93
  52. package/dist/esm/Sprinkle/index.js.map +1 -1
  53. package/dist/esm/Sprinkle/menus/array-menu.js +190 -0
  54. package/dist/esm/Sprinkle/menus/array-menu.js.map +1 -0
  55. package/dist/esm/Sprinkle/menus/field-menu.js +155 -0
  56. package/dist/esm/Sprinkle/menus/field-menu.js.map +1 -0
  57. package/dist/esm/Sprinkle/menus/index.js +8 -0
  58. package/dist/esm/Sprinkle/menus/index.js.map +1 -0
  59. package/dist/esm/Sprinkle/menus/object-menu.js +318 -0
  60. package/dist/esm/Sprinkle/menus/object-menu.js.map +1 -0
  61. package/dist/esm/Sprinkle/prompts.js +59 -1
  62. package/dist/esm/Sprinkle/prompts.js.map +1 -1
  63. package/dist/esm/Sprinkle/type-guards.js +42 -0
  64. package/dist/esm/Sprinkle/type-guards.js.map +1 -1
  65. package/dist/esm/Sprinkle/types.js +24 -0
  66. package/dist/esm/Sprinkle/types.js.map +1 -1
  67. package/dist/esm/Sprinkle/utils/field-utils.js +145 -0
  68. package/dist/esm/Sprinkle/utils/field-utils.js.map +1 -0
  69. package/dist/esm/Sprinkle/utils/formatting.js +118 -0
  70. package/dist/esm/Sprinkle/utils/formatting.js.map +1 -0
  71. package/dist/esm/Sprinkle/utils/index.js +7 -0
  72. package/dist/esm/Sprinkle/utils/index.js.map +1 -0
  73. package/dist/types/Sprinkle/index.d.ts +9 -3
  74. package/dist/types/Sprinkle/index.d.ts.map +1 -1
  75. package/dist/types/Sprinkle/menus/array-menu.d.ts +31 -0
  76. package/dist/types/Sprinkle/menus/array-menu.d.ts.map +1 -0
  77. package/dist/types/Sprinkle/menus/field-menu.d.ts +34 -0
  78. package/dist/types/Sprinkle/menus/field-menu.d.ts.map +1 -0
  79. package/dist/types/Sprinkle/menus/index.d.ts +10 -0
  80. package/dist/types/Sprinkle/menus/index.d.ts.map +1 -0
  81. package/dist/types/Sprinkle/menus/object-menu.d.ts +34 -0
  82. package/dist/types/Sprinkle/menus/object-menu.d.ts.map +1 -0
  83. package/dist/types/Sprinkle/prompts.d.ts +25 -0
  84. package/dist/types/Sprinkle/prompts.d.ts.map +1 -1
  85. package/dist/types/Sprinkle/type-guards.d.ts +24 -1
  86. package/dist/types/Sprinkle/type-guards.d.ts.map +1 -1
  87. package/dist/types/Sprinkle/types.d.ts +53 -0
  88. package/dist/types/Sprinkle/types.d.ts.map +1 -1
  89. package/dist/types/Sprinkle/utils/field-utils.d.ts +47 -0
  90. package/dist/types/Sprinkle/utils/field-utils.d.ts.map +1 -0
  91. package/dist/types/Sprinkle/utils/formatting.d.ts +30 -0
  92. package/dist/types/Sprinkle/utils/formatting.d.ts.map +1 -0
  93. package/dist/types/tsconfig.build.tsbuildinfo +1 -1
  94. package/package.json +1 -1
  95. package/src/Sprinkle/__tests__/encryption.test.ts +2 -0
  96. package/src/Sprinkle/__tests__/enhancements.test.ts +3 -42
  97. package/src/Sprinkle/__tests__/field-utils.test.ts +191 -0
  98. package/src/Sprinkle/__tests__/fill-in-struct.test.ts +252 -103
  99. package/src/Sprinkle/__tests__/formatting.test.ts +115 -0
  100. package/src/Sprinkle/__tests__/show-menu.test.ts +14 -8
  101. package/src/Sprinkle/__tests__/tx-dialog.test.ts +9 -0
  102. package/src/Sprinkle/index.ts +131 -119
  103. package/src/Sprinkle/menus/array-menu.ts +191 -0
  104. package/src/Sprinkle/menus/field-menu.ts +145 -0
  105. package/src/Sprinkle/menus/index.ts +12 -0
  106. package/src/Sprinkle/menus/object-menu.ts +336 -0
  107. package/src/Sprinkle/prompts.ts +71 -1
  108. package/src/Sprinkle/type-guards.ts +42 -0
  109. package/src/Sprinkle/types.ts +43 -0
  110. package/src/Sprinkle/utils/field-utils.ts +158 -0
  111. package/src/Sprinkle/utils/formatting.ts +127 -0
  112. package/src/Sprinkle/utils/index.ts +17 -0
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Field utilities for menu-based struct editing.
3
+ * Provides required field counting and label building.
4
+ */
5
+
6
+ import type { TObject, TSchema } from "@sinclair/typebox";
7
+ import type { FieldState, RequiredFieldCount } from "../types.js";
8
+ import {
9
+ isOptional,
10
+ isNullable,
11
+ hasDefault,
12
+ getDefault,
13
+ isLiteral,
14
+ } from "../type-guards.js";
15
+ import { formatValuePreview } from "./formatting.js";
16
+
17
+ /**
18
+ * Count required fields in an object schema and how many are filled.
19
+ *
20
+ * @param type - The object schema
21
+ * @param state - Map of field names to their current state
22
+ * @returns Count of total required fields and how many are filled
23
+ */
24
+ export function countRequiredFields(
25
+ type: TObject,
26
+ state: Map<string, FieldState>,
27
+ ): RequiredFieldCount {
28
+ const fields = type.properties as Record<string, TSchema>;
29
+ let total = 0;
30
+ let filled = 0;
31
+
32
+ for (const [fieldName, fieldType] of Object.entries(fields)) {
33
+ // Skip optional fields - they're not required
34
+ if (isOptional(fieldType)) {
35
+ continue;
36
+ }
37
+
38
+ // Non-nullable fields with defaults don't require user input
39
+ if (hasDefault(fieldType) && !isNullable(fieldType)) {
40
+ continue;
41
+ }
42
+
43
+ // Literal fields auto-fill, so they're not required from the user
44
+ if (isLiteral(fieldType)) {
45
+ continue;
46
+ }
47
+
48
+ // This is a required field
49
+ total++;
50
+
51
+ // Check if it's filled
52
+ const fieldState = state.get(fieldName);
53
+ if (fieldState?.status === "set" || fieldState?.status === "null") {
54
+ filled++;
55
+ }
56
+ }
57
+
58
+ return { total, filled };
59
+ }
60
+
61
+ /**
62
+ * Determine if a field is required (must have a value to submit).
63
+ *
64
+ * @param fieldType - The field's schema
65
+ * @returns true if the field is required
66
+ */
67
+ export function isFieldRequired(fieldType: TSchema): boolean {
68
+ // Optional fields are not required
69
+ if (isOptional(fieldType)) {
70
+ return false;
71
+ }
72
+
73
+ // Literal fields auto-fill, so they're not required from the user
74
+ if (isLiteral(fieldType)) {
75
+ return false;
76
+ }
77
+
78
+ // Non-nullable fields with defaults are not required (default will be used)
79
+ if (hasDefault(fieldType) && !isNullable(fieldType)) {
80
+ return false;
81
+ }
82
+
83
+ return true;
84
+ }
85
+
86
+ /**
87
+ * Build a display label for a field in the menu.
88
+ *
89
+ * @param fieldName - The field name
90
+ * @param fieldType - The field's schema
91
+ * @param state - The current state of the field
92
+ * @returns Formatted label string
93
+ */
94
+ export function buildFieldLabel(
95
+ fieldName: string,
96
+ fieldType: TSchema,
97
+ state: FieldState,
98
+ ): string {
99
+ const required = isFieldRequired(fieldType);
100
+ const requiredMarker = required ? " *" : "";
101
+
102
+ switch (state.status) {
103
+ case "unset": {
104
+ // Check for default value
105
+ if (hasDefault(fieldType)) {
106
+ const defaultVal = getDefault(fieldType);
107
+ const preview = formatValuePreview(defaultVal, 30);
108
+ return `${fieldName}: [default: ${preview}]${requiredMarker}`;
109
+ }
110
+ return `${fieldName}: [not set]${requiredMarker}`;
111
+ }
112
+
113
+ case "null":
114
+ return `${fieldName}: null${requiredMarker}`;
115
+
116
+ case "set": {
117
+ const preview = formatValuePreview(state.value, 40);
118
+ return `${fieldName}: ${preview}${requiredMarker}`;
119
+ }
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Get the initial FieldState for a field based on its type and any existing value.
125
+ *
126
+ * @param _fieldType - The field's schema (reserved for future logic)
127
+ * @param existingValue - Existing value if editing
128
+ * @returns Initial FieldState
129
+ */
130
+ export function getInitialFieldState<T>(
131
+ _fieldType: TSchema,
132
+ existingValue?: T,
133
+ ): FieldState<T> {
134
+ if (existingValue === undefined) {
135
+ return { status: "unset" };
136
+ }
137
+
138
+ if (existingValue === null) {
139
+ return { status: "null" };
140
+ }
141
+
142
+ return { status: "set", value: existingValue };
143
+ }
144
+
145
+ /**
146
+ * Check if all required fields are filled in the state map.
147
+ *
148
+ * @param type - The object schema
149
+ * @param state - Map of field names to their current state
150
+ * @returns true if all required fields have values
151
+ */
152
+ export function allRequiredFieldsFilled(
153
+ type: TObject,
154
+ state: Map<string, FieldState>,
155
+ ): boolean {
156
+ const { total, filled } = countRequiredFields(type, state);
157
+ return filled >= total;
158
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Formatting utilities for menu-based struct editing.
3
+ * Provides value preview, path formatting, and breadcrumb generation.
4
+ */
5
+
6
+ /**
7
+ * Format a value for display in a menu item.
8
+ * Truncates long values and provides type-appropriate previews.
9
+ *
10
+ * @param value - The value to format
11
+ * @param maxLength - Maximum length before truncation (default 40)
12
+ * @returns Formatted string representation
13
+ */
14
+ export function formatValuePreview(value: unknown, maxLength = 40): string {
15
+ if (value === null) {
16
+ return "null";
17
+ }
18
+
19
+ if (value === undefined) {
20
+ return "[not set]";
21
+ }
22
+
23
+ if (typeof value === "string") {
24
+ const escaped = JSON.stringify(value);
25
+ if (escaped.length <= maxLength) {
26
+ return escaped;
27
+ }
28
+ // Truncate inside the quotes (guard against negative maxLength from recursion)
29
+ return `"${value.slice(0, Math.max(0, maxLength - 5))}..."`;
30
+ }
31
+
32
+ if (typeof value === "number" || typeof value === "bigint") {
33
+ return String(value);
34
+ }
35
+
36
+ if (typeof value === "boolean") {
37
+ return String(value);
38
+ }
39
+
40
+ if (Array.isArray(value)) {
41
+ return `[${value.length} item${value.length === 1 ? "" : "s"}]`;
42
+ }
43
+
44
+ if (typeof value === "object") {
45
+ // For objects, try to show a meaningful preview
46
+ const keys = Object.keys(value);
47
+ if (keys.length === 0) {
48
+ return "{}";
49
+ }
50
+
51
+ // If object has a single key (like union type), show it
52
+ if (keys.length === 1) {
53
+ const key = keys[0]!;
54
+ const innerValue = (value as Record<string, unknown>)[key];
55
+ const innerPreview = formatValuePreview(innerValue, maxLength - key.length - 6);
56
+ const preview = `{ ${key}: ${innerPreview} }`;
57
+ if (preview.length <= maxLength) {
58
+ return preview;
59
+ }
60
+ return `{ ${key}: ... }`;
61
+ }
62
+
63
+ // Multiple keys - show abbreviated
64
+ const firstKey = keys[0]!;
65
+ return `{ ${firstKey}: ..., +${keys.length - 1} }`;
66
+ }
67
+
68
+ return String(value).slice(0, maxLength);
69
+ }
70
+
71
+ /**
72
+ * Format a path array for display, stripping the "root" prefix.
73
+ *
74
+ * @param path - Array of path segments (e.g., ["root", "settings", "name"])
75
+ * @returns Formatted path string (e.g., "settings.name")
76
+ */
77
+ export function formatPath(path: string[]): string {
78
+ // Filter out "root" prefix
79
+ const filtered = path.filter((segment) => segment !== "root");
80
+ if (filtered.length === 0) {
81
+ return "";
82
+ }
83
+ return filtered.join(".");
84
+ }
85
+
86
+ /**
87
+ * Format a path as a breadcrumb trail for nested navigation.
88
+ * Truncates from the left if too long.
89
+ *
90
+ * @param path - Array of path segments
91
+ * @param maxLength - Maximum total length (default 50)
92
+ * @returns Breadcrumb string (e.g., "settings → permissions → mint")
93
+ */
94
+ export function formatBreadcrumb(path: string[], maxLength = 50): string {
95
+ // Filter out "root" prefix
96
+ const filtered = path.filter((segment) => segment !== "root");
97
+ if (filtered.length === 0) {
98
+ return "";
99
+ }
100
+
101
+ const separator = " \u2192 "; // Unicode right arrow
102
+ const joined = filtered.join(separator);
103
+
104
+ if (joined.length <= maxLength) {
105
+ return joined;
106
+ }
107
+
108
+ // Truncate from left, keeping most recent segments
109
+ const ellipsis = "...";
110
+ let result = "";
111
+ for (let i = filtered.length - 1; i >= 0; i--) {
112
+ const segment = filtered[i]!;
113
+ const candidate = i === filtered.length - 1 ? segment : segment + separator + result;
114
+ if (candidate.length + ellipsis.length + separator.length > maxLength && i < filtered.length - 1) {
115
+ return ellipsis + separator + result;
116
+ }
117
+ result = candidate;
118
+ }
119
+
120
+ // Safety clamp: if we still exceeded maxLength (e.g., last segment alone is too long),
121
+ // fall back to just the ellipsis to honor the maxLength contract.
122
+ if (result.length > maxLength) {
123
+ return ellipsis;
124
+ }
125
+
126
+ return result;
127
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Utility functions for menu-based struct editing.
3
+ */
4
+
5
+ export {
6
+ formatValuePreview,
7
+ formatPath,
8
+ formatBreadcrumb,
9
+ } from "./formatting.js";
10
+
11
+ export {
12
+ countRequiredFields,
13
+ isFieldRequired,
14
+ buildFieldLabel,
15
+ getInitialFieldState,
16
+ allRequiredFieldsFilled,
17
+ } from "./field-utils.js";