@lukso/transaction-view-headless 0.2.2-dev.a8c9315

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 (76) hide show
  1. package/.turbo/turbo-build.log +51 -0
  2. package/CHANGELOG.md +88 -0
  3. package/COMPONENT_ARCHITECTURE.md +190 -0
  4. package/COMPONENT_CONVERSION_ANALYSIS.md +371 -0
  5. package/CONTEXT_VARIANT_GUIDE.md +425 -0
  6. package/LAZY_LOADING_GUIDE.md +534 -0
  7. package/LICENSE +201 -0
  8. package/PLAYBACK_EXAMPLES.md +658 -0
  9. package/PLAYBACK_GUIDE.md +402 -0
  10. package/README.md +43 -0
  11. package/REGISTRY_SUMMARY.md +638 -0
  12. package/SUFFERING_ECONOMICS.md +346 -0
  13. package/SUFFERING_ECONOMICS.zip +0 -0
  14. package/VIEW_REGISTRATION_GUIDE.md +795 -0
  15. package/dist/composables/index.cjs +1321 -0
  16. package/dist/composables/index.cjs.map +1 -0
  17. package/dist/composables/index.d.cts +6 -0
  18. package/dist/composables/index.d.ts +6 -0
  19. package/dist/composables/index.js +1276 -0
  20. package/dist/composables/index.js.map +1 -0
  21. package/dist/index-CMLU1hKL.d.ts +374 -0
  22. package/dist/index-CSDjhiKV.d.cts +374 -0
  23. package/dist/index.cjs +1534 -0
  24. package/dist/index.cjs.map +1 -0
  25. package/dist/index.d.cts +93 -0
  26. package/dist/index.d.ts +93 -0
  27. package/dist/index.js +1463 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/registry/index.cjs +522 -0
  30. package/dist/registry/index.cjs.map +1 -0
  31. package/dist/registry/index.d.cts +46 -0
  32. package/dist/registry/index.d.ts +46 -0
  33. package/dist/registry/index.js +487 -0
  34. package/dist/registry/index.js.map +1 -0
  35. package/dist/types/index.cjs +19 -0
  36. package/dist/types/index.cjs.map +1 -0
  37. package/dist/types/index.d.cts +203 -0
  38. package/dist/types/index.d.ts +203 -0
  39. package/dist/types/index.js +1 -0
  40. package/dist/types/index.js.map +1 -0
  41. package/dist/utils/index.cjs +217 -0
  42. package/dist/utils/index.cjs.map +1 -0
  43. package/dist/utils/index.d.cts +47 -0
  44. package/dist/utils/index.d.ts +47 -0
  45. package/dist/utils/index.js +188 -0
  46. package/dist/utils/index.js.map +1 -0
  47. package/dist/view-loader-dD86QAlQ.d.cts +121 -0
  48. package/dist/view-loader-ii8AxiQR.d.ts +121 -0
  49. package/package.json +64 -0
  50. package/publish.log +0 -0
  51. package/src/composables/index.ts +59 -0
  52. package/src/composables/use-image-loader.ts +315 -0
  53. package/src/composables/use-transaction-playback.ts +396 -0
  54. package/src/composables/use-transaction-view.ts +309 -0
  55. package/src/config/lukso-config.ts +171 -0
  56. package/src/examples/context-aware-views.ts +300 -0
  57. package/src/examples/example-views.ts +197 -0
  58. package/src/examples/lit-plus-rn-strategy.ts +448 -0
  59. package/src/examples/lsp7-custom-matcher-examples.ts +174 -0
  60. package/src/examples/opt-in-context-examples.ts +372 -0
  61. package/src/examples/optimized-view-definitions.ts +408 -0
  62. package/src/examples/vue-to-lit-compilation.ts +358 -0
  63. package/src/hooks/useAddressQuery.ts +135 -0
  64. package/src/hooks/useAddressResolution.ts +437 -0
  65. package/src/index.ts +36 -0
  66. package/src/registry/index.ts +13 -0
  67. package/src/registry/view-loader.ts +305 -0
  68. package/src/registry/view-registry.ts +135 -0
  69. package/src/types/index.ts +16 -0
  70. package/src/types/view-contexts.ts +72 -0
  71. package/src/types/view-matching.ts +173 -0
  72. package/src/utils/context-matcher.ts +165 -0
  73. package/src/utils/index.ts +5 -0
  74. package/src/utils/view-matcher.ts +338 -0
  75. package/tsconfig.json +20 -0
  76. package/tsup.config.ts +34 -0
@@ -0,0 +1,165 @@
1
+ import type { DecoderResult } from '@lukso/transaction-decoder'
2
+ import type {
3
+ ViewContext,
4
+ ViewContextRequirements,
5
+ ViewVariant,
6
+ } from '../types/view-contexts'
7
+
8
+ /**
9
+ * Extended matching that includes context and variant awareness
10
+ */
11
+ export interface ContextAwareMatchOptions {
12
+ framework: 'vue' | 'react' | 'react-native' | 'lit'
13
+ context: ViewContext // WHERE to display
14
+ variant?: ViewVariant // HOW to display (optional)
15
+ contextRequirements?: Partial<ViewContextRequirements>
16
+ minScore?: number
17
+ maxMatches?: number
18
+ includeFrameworkConfig?: boolean
19
+ }
20
+
21
+ /**
22
+ * Extended criteria weights that include context and variant matching
23
+ */
24
+ export const CONTEXT_AWARE_WEIGHTS = {
25
+ recordType: 0.2, // What transaction type
26
+ context: 0.15, // WHERE to display (feed, approval, etc.)
27
+ variant: 0.15, // HOW to display (collapsed, minimal, etc.)
28
+ customMatcher: 0.15, // Custom logic (e.g., LSP7 decimals check)
29
+ functionName: 0.12,
30
+ standard: 0.1,
31
+ contractAddress: 0.08,
32
+ contextRequirements: 0.03, // Bonus for meeting specific requirements
33
+ chainId: 0.02,
34
+ } as const
35
+
36
+ /**
37
+ * Check if context criteria match
38
+ * Context = WHERE to display (feed, approval, list, etc.)
39
+ */
40
+ export function doesContextMatch(
41
+ requestedContext: ViewContext,
42
+ viewContext: ViewContext | ViewContext[] | undefined
43
+ ): boolean {
44
+ if (!viewContext) return false
45
+
46
+ if (Array.isArray(viewContext)) {
47
+ return viewContext.includes(requestedContext)
48
+ }
49
+
50
+ return viewContext === requestedContext
51
+ }
52
+
53
+ /**
54
+ * Check if variant criteria match
55
+ * Variant = HOW to display (collapsed, minimal, full, etc.)
56
+ */
57
+ export function doesVariantMatch(
58
+ requestedVariant: ViewVariant,
59
+ viewVariant: ViewVariant | ViewVariant[] | undefined
60
+ ): boolean {
61
+ if (!viewVariant) return false
62
+
63
+ if (Array.isArray(viewVariant)) {
64
+ return viewVariant.includes(requestedVariant)
65
+ }
66
+
67
+ return viewVariant === requestedVariant
68
+ }
69
+
70
+ /**
71
+ * Check if context requirements are compatible
72
+ */
73
+ export function areContextRequirementsCompatible(
74
+ requested: Partial<ViewContextRequirements> = {},
75
+ viewRequirements: Partial<ViewContextRequirements> = {}
76
+ ): boolean {
77
+ // Check detail level compatibility
78
+ if (requested.detailLevel && viewRequirements.detailLevel) {
79
+ const detailOrder = ['minimal', 'summary', 'full', 'debug']
80
+ const requestedLevel = detailOrder.indexOf(requested.detailLevel)
81
+ const viewLevel = detailOrder.indexOf(viewRequirements.detailLevel)
82
+
83
+ // View should provide at least the requested detail level
84
+ if (viewLevel < requestedLevel) return false
85
+ }
86
+
87
+ // Check height constraints
88
+ if (requested.maxHeight && viewRequirements.maxHeight) {
89
+ if (viewRequirements.maxHeight > requested.maxHeight) return false
90
+ }
91
+
92
+ // Check action requirements
93
+ if (
94
+ requested.showActions === true &&
95
+ viewRequirements.showActions === false
96
+ ) {
97
+ return false
98
+ }
99
+
100
+ // Check security emphasis
101
+ if (
102
+ requested.emphasizeSecurity === true &&
103
+ viewRequirements.emphasizeSecurity === false
104
+ ) {
105
+ return false
106
+ }
107
+
108
+ return true
109
+ }
110
+
111
+ /**
112
+ * Example usage in actual matching function
113
+ */
114
+ export function calculateContextAwareScore(
115
+ transaction: DecoderResult,
116
+ view: any, // Extended ViewDefinition with context
117
+ options: ContextAwareMatchOptions
118
+ ): { score: number; matchedCriteria: string[] } {
119
+ const { criteria } = view
120
+ const matchedCriteria: string[] = []
121
+ let totalWeight = 0
122
+ let matchedWeight = 0
123
+
124
+ // Standard criteria matching (existing logic)
125
+ // ... recordType, functionName, etc.
126
+
127
+ // Context matching (WHERE to display)
128
+ if (criteria.context !== undefined) {
129
+ totalWeight += CONTEXT_AWARE_WEIGHTS.context
130
+ if (doesContextMatch(options.context, criteria.context)) {
131
+ matchedCriteria.push('context')
132
+ matchedWeight += CONTEXT_AWARE_WEIGHTS.context
133
+ }
134
+ }
135
+
136
+ // Variant matching (HOW to display)
137
+ if (criteria.variant !== undefined && options.variant !== undefined) {
138
+ totalWeight += CONTEXT_AWARE_WEIGHTS.variant
139
+ if (doesVariantMatch(options.variant, criteria.variant)) {
140
+ matchedCriteria.push('variant')
141
+ matchedWeight += CONTEXT_AWARE_WEIGHTS.variant
142
+ }
143
+ }
144
+
145
+ // Context requirements matching
146
+ if (criteria.contextRequirements !== undefined) {
147
+ totalWeight += CONTEXT_AWARE_WEIGHTS.contextRequirements
148
+ if (
149
+ areContextRequirementsCompatible(
150
+ options.contextRequirements,
151
+ criteria.contextRequirements
152
+ )
153
+ ) {
154
+ matchedCriteria.push('contextRequirements')
155
+ matchedWeight += CONTEXT_AWARE_WEIGHTS.contextRequirements
156
+ }
157
+ }
158
+
159
+ if (totalWeight === 0) return { score: 0, matchedCriteria: [] }
160
+
161
+ return {
162
+ score: matchedWeight / totalWeight,
163
+ matchedCriteria,
164
+ }
165
+ }
@@ -0,0 +1,5 @@
1
+ export {
2
+ CRITERIA_WEIGHTS,
3
+ calculateMatchScore,
4
+ findMatchingViews,
5
+ } from './view-matcher'
@@ -0,0 +1,338 @@
1
+ import type { DecoderResult } from '@lukso/transaction-decoder'
2
+ import type {
3
+ DecoderRecordForSelection,
4
+ ViewDefinition,
5
+ ViewMatch,
6
+ ViewMatchCriteria,
7
+ ViewMatchOptions,
8
+ } from '../types/view-matching'
9
+
10
+ /**
11
+ * Weights for different matching criteria (higher = more important)
12
+ * Context is optional - only weighted when both view and request specify context
13
+ */
14
+ export const CRITERIA_WEIGHTS = {
15
+ recordType: 0.25, // Most specific to transaction type
16
+ functionName: 0.2, // Function-level specificity
17
+ context: 0.2, // Context specificity (when opt-in)
18
+ standard: 0.15, // LSP standard specificity
19
+ contractAddress: 0.1, // Contract-specific logic
20
+ contextRequirements: 0.05, // Context requirement matching
21
+ chainId: 0.03, // Network-specific behavior
22
+ customMatcher: 0.02, // Custom logic (lowest weight due to unpredictability)
23
+ } as const
24
+
25
+ /**
26
+ * Calculate how well a view matches a transaction and context
27
+ *
28
+ * Context matching is opt-in:
29
+ * - If view specifies context criteria, request MUST provide matching context
30
+ * - If view has no context criteria, it matches any context (universal view)
31
+ * - If request has no context, context-specific views are deprioritized
32
+ *
33
+ * Note: For batch operations (executeBatch, setDataBatch, etc.), this matches
34
+ * against the parent transaction. Future enhancement could include matching
35
+ * against child transactions for more granular view selection.
36
+ */
37
+ export function calculateMatchScore(
38
+ transaction: DecoderResult,
39
+ view: ViewDefinition,
40
+ options?: Pick<ViewMatchOptions, 'context' | 'contextRequirements'>
41
+ ): { score: number; matchedCriteria: (keyof ViewMatchCriteria)[] } {
42
+ const { criteria } = view
43
+ const matchedCriteria: (keyof ViewMatchCriteria)[] = []
44
+ let totalWeight = 0
45
+ let matchedWeight = 0
46
+
47
+ // Check each criterion and accumulate scores
48
+ for (const [criterion, weight] of Object.entries(CRITERIA_WEIGHTS)) {
49
+ const criterionKey = criterion as keyof ViewMatchCriteria
50
+ const criterionValue = criteria[criterionKey]
51
+
52
+ if (criterionValue === undefined) continue
53
+
54
+ totalWeight += weight
55
+
56
+ // Special handling for customMatcher - use match count
57
+ if (
58
+ criterionKey === 'customMatcher' &&
59
+ typeof criterionValue === 'function'
60
+ ) {
61
+ try {
62
+ const matchCount = criterionValue(transaction)
63
+ if (matchCount > 0) {
64
+ matchedCriteria.push(criterionKey)
65
+ // Normalize match count to 0-1 range (cap at 10 conditions)
66
+ const normalizedScore = Math.min(matchCount / 10, 1)
67
+ matchedWeight += weight * normalizedScore
68
+ }
69
+ } catch (error) {
70
+ console.warn('Custom matcher threw error:', error)
71
+ }
72
+ continue
73
+ }
74
+
75
+ if (
76
+ doesCriterionMatch(transaction, criterionKey, criterionValue, options)
77
+ ) {
78
+ matchedCriteria.push(criterionKey)
79
+ matchedWeight += weight
80
+ }
81
+ }
82
+
83
+ // Handle context opt-in logic
84
+ const viewHasContext = criteria.context !== undefined
85
+ const requestHasContext = options?.context !== undefined
86
+
87
+ if (viewHasContext && !requestHasContext) {
88
+ // View requires specific context, but none provided - penalize heavily
89
+ matchedWeight *= 0.1 // Reduce score by 90%
90
+ } else if (!viewHasContext && requestHasContext) {
91
+ // Universal view matching specific context - slight penalty to prefer context-specific views
92
+ matchedWeight *= 0.9 // Reduce score by 10%
93
+ }
94
+
95
+ // If no criteria are defined, this view doesn't match
96
+ if (totalWeight === 0) return { score: 0, matchedCriteria: [] }
97
+
98
+ // Calculate final score (0-1)
99
+ const score = matchedWeight / totalWeight
100
+
101
+ return { score, matchedCriteria }
102
+ }
103
+
104
+ /**
105
+ * Check if a specific criterion matches the transaction
106
+ */
107
+ function doesCriterionMatch(
108
+ transaction: DecoderResult,
109
+ criterion: keyof ViewMatchCriteria,
110
+ criterionValue: any,
111
+ options?: Pick<ViewMatchOptions, 'context' | 'contextRequirements'>
112
+ ): boolean {
113
+ switch (criterion) {
114
+ case 'recordType':
115
+ return matchesStringOrArray(
116
+ (transaction as DecoderRecordForSelection).recordType,
117
+ criterionValue
118
+ )
119
+
120
+ case 'functionName':
121
+ return matchesStringOrArray(
122
+ (transaction as DecoderRecordForSelection).functionName,
123
+ criterionValue
124
+ )
125
+
126
+ case 'standard':
127
+ return matchesStringOrArray(
128
+ (transaction as DecoderRecordForSelection).standard,
129
+ criterionValue
130
+ )
131
+
132
+ case 'contractAddress':
133
+ return matchesStringOrArray(
134
+ (transaction as DecoderRecordForSelection).contractAddress,
135
+ criterionValue
136
+ )
137
+
138
+ case 'chainId':
139
+ return matchesNumberOrArray(transaction.chainId, criterionValue)
140
+
141
+ case 'context':
142
+ if (!options?.context) return false // No context provided, can't match
143
+ return matchesStringOrArray(options.context, criterionValue)
144
+
145
+ case 'contextRequirements':
146
+ if (!options?.contextRequirements || !criterionValue) return false
147
+ return areContextRequirementsCompatible(
148
+ options.contextRequirements,
149
+ criterionValue
150
+ )
151
+
152
+ case 'customMatcher':
153
+ if (typeof criterionValue === 'function') {
154
+ try {
155
+ const matchCount = criterionValue(transaction)
156
+ // Match if any conditions matched (> 0)
157
+ // The actual count contributes to the score in calculateMatchScore
158
+ return matchCount > 0
159
+ } catch (error) {
160
+ console.warn('Custom matcher threw error:', error)
161
+ return false
162
+ }
163
+ }
164
+ return false
165
+
166
+ default:
167
+ return false
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Helper to match string values against string or array criteria
173
+ */
174
+ function matchesStringOrArray(
175
+ value: string | undefined,
176
+ criteria: string | string[]
177
+ ): boolean {
178
+ if (!value || !criteria) return false
179
+
180
+ if (Array.isArray(criteria)) {
181
+ return criteria.includes(value)
182
+ }
183
+
184
+ return value === criteria
185
+ }
186
+
187
+ /**
188
+ * Helper to match number values against number or array criteria
189
+ */
190
+ function matchesNumberOrArray(
191
+ value: number | undefined,
192
+ criteria: number | number[]
193
+ ): boolean {
194
+ if (value === undefined || criteria === undefined) return false
195
+
196
+ if (Array.isArray(criteria)) {
197
+ return criteria.includes(value)
198
+ }
199
+
200
+ return value === criteria
201
+ }
202
+
203
+ /**
204
+ * Check if context requirements are compatible
205
+ */
206
+ function areContextRequirementsCompatible(
207
+ requested: any,
208
+ viewRequirements: any
209
+ ): boolean {
210
+ // Simple compatibility check - can be extended
211
+ if (requested.detailLevel && viewRequirements.detailLevel) {
212
+ const detailOrder = ['minimal', 'summary', 'full', 'debug']
213
+ const requestedLevel = detailOrder.indexOf(requested.detailLevel)
214
+ const viewLevel = detailOrder.indexOf(viewRequirements.detailLevel)
215
+ if (viewLevel < requestedLevel) return false
216
+ }
217
+
218
+ if (requested.maxHeight && viewRequirements.maxHeight) {
219
+ if (viewRequirements.maxHeight > requested.maxHeight) return false
220
+ }
221
+
222
+ return true
223
+ }
224
+
225
+ /**
226
+ * Calculate native framework priority bonus
227
+ *
228
+ * Native framework implementations (Vue, React Native) get priority over Lit fallbacks:
229
+ * - Vue: +0.15 bonus when view has Vue implementation
230
+ * - React Native (rn): +0.15 bonus when view has RN implementation
231
+ * - Lit: NO bonus (Lit is the universal fallback)
232
+ *
233
+ * This ensures that when both native and Lit views exist for the same transaction:
234
+ * - Vue app prefers Vue view (Vue score + 0.15 > Lit score)
235
+ * - React Native app prefers RN view (RN score + 0.15 > Lit score)
236
+ * - Other frameworks use Lit view (universal compatibility)
237
+ */
238
+ function calculateNativeFrameworkBonus(
239
+ view: ViewDefinition,
240
+ requestedFramework: 'lit' | 'vue' | 'rn'
241
+ ): number {
242
+ // Lit gets no bonus - it's the universal fallback
243
+ if (requestedFramework === 'lit') {
244
+ return 0
245
+ }
246
+
247
+ // Vue gets bonus if view has Vue implementation
248
+ if (requestedFramework === 'vue' && view.frameworks.vue) {
249
+ return 0.15
250
+ }
251
+
252
+ // React Native gets bonus if view has RN implementation
253
+ if (requestedFramework === 'rn' && view.frameworks.rn) {
254
+ return 0.15
255
+ }
256
+
257
+ return 0
258
+ }
259
+
260
+ /**
261
+ * Find and score all matching views for a transaction
262
+ *
263
+ * Views are matched and sorted by:
264
+ * 1. Match score (how well criteria match the transaction)
265
+ * 2. Native framework bonus (Vue/RN get +0.15, Lit gets 0)
266
+ * 3. View priority (manually set priority)
267
+ *
268
+ * This ensures that native framework views are preferred when available,
269
+ * while Lit serves as a universal fallback.
270
+ */
271
+ export function findMatchingViews(
272
+ transaction: DecoderResult,
273
+ views: ViewDefinition[],
274
+ options: ViewMatchOptions
275
+ ): ViewMatch[] {
276
+ const matches: ViewMatch[] = []
277
+ const minScore = options.minScore ?? 0
278
+
279
+ for (const view of views) {
280
+ // Skip if framework is not supported
281
+ if (!view.frameworks[options.framework]) {
282
+ continue
283
+ }
284
+
285
+ const { score, matchedCriteria } = calculateMatchScore(transaction, view, {
286
+ context: options.context,
287
+ contextRequirements: options.contextRequirements,
288
+ })
289
+
290
+ // Skip if score is below threshold
291
+ if (score < minScore) {
292
+ continue
293
+ }
294
+
295
+ // Apply native framework bonus (Vue/RN only, NOT Lit)
296
+ const frameworkBonus = calculateNativeFrameworkBonus(
297
+ view,
298
+ options.framework
299
+ )
300
+ const finalScore = Math.min(1, score + frameworkBonus)
301
+
302
+ const match: ViewMatch = {
303
+ view,
304
+ score: finalScore,
305
+ matchedCriteria,
306
+ frameworkConfig: options.includeFrameworkConfig
307
+ ? view.frameworks[options.framework]
308
+ : undefined,
309
+ }
310
+
311
+ matches.push(match)
312
+ }
313
+
314
+ // Sort by score (descending), then by priority (descending)
315
+ matches.sort((a, b) => {
316
+ if (a.score !== b.score) {
317
+ return b.score - a.score
318
+ }
319
+ return b.view.priority - a.view.priority
320
+ })
321
+
322
+ // Limit results if requested
323
+ if (options.maxMatches) {
324
+ return matches.slice(0, options.maxMatches)
325
+ }
326
+
327
+ return matches
328
+ }
329
+
330
+ /**
331
+ * TODO: Future enhancement for batch operations
332
+ *
333
+ * For batch operations like executeBatch, setDataBatch, we might want to:
334
+ * 1. Match against the batch container (current behavior)
335
+ * 2. Also match against child transactions for more specific views
336
+ * 3. Allow views to specify whether they handle batches vs individual items
337
+ * 4. Support composite views that render both batch info + child items
338
+ */
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "node",
7
+ "allowSyntheticDefaultImports": true,
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "outDir": "./dist",
13
+ "rootDir": "./src",
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["dist", "node_modules", "src/examples"]
20
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig((options) => ({
4
+ entry: {
5
+ index: 'src/index.ts',
6
+ 'types/index': 'src/types/index.ts',
7
+ 'composables/index': 'src/composables/index.ts',
8
+ 'registry/index': 'src/registry/index.ts',
9
+ 'utils/index': 'src/utils/index.ts',
10
+ },
11
+ format: ['esm', 'cjs'],
12
+ target: 'es2022',
13
+ dts: true,
14
+ sourcemap: true,
15
+ // Only clean on initial build, not during watch mode
16
+ clean: !options?.watch,
17
+ splitting: false,
18
+ ignoreWatch: [
19
+ 'dist/**',
20
+ 'node_modules/**',
21
+ '**/.turbo/**',
22
+ '**/*.test.*',
23
+ '**/*.spec.*',
24
+ '**/__tests__/**',
25
+ '**/README.md',
26
+ '**/.git/**',
27
+ ],
28
+ external: ['@lukso/transaction-decoder', '@tanstack/query-core'],
29
+ onSuccess: async () => {
30
+ console.log(
31
+ `📦 Build complete ${options?.watch ? '(watch mode - DTS preserved)' : '(build mode)'}`
32
+ )
33
+ },
34
+ }))