@vira-ui/cli 0.3.3-alpha → 0.4.1-alpha

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/dist/go/appYaml.js +34 -0
  2. package/dist/go/backendEnvExample.js +21 -0
  3. package/dist/go/backendReadme.js +18 -0
  4. package/dist/go/channelHelpers.js +29 -0
  5. package/dist/go/configGo.js +262 -0
  6. package/dist/go/dbGo.js +47 -0
  7. package/dist/go/dbYaml.js +11 -0
  8. package/dist/go/dockerCompose.js +38 -0
  9. package/dist/go/dockerComposeProd.js +54 -0
  10. package/dist/go/dockerfile.js +19 -0
  11. package/dist/go/eventHandlerTemplate.js +34 -0
  12. package/dist/go/eventsAPI.js +414 -0
  13. package/dist/go/goMod.js +20 -0
  14. package/dist/go/kafkaGo.js +71 -0
  15. package/dist/go/kafkaYaml.js +10 -0
  16. package/dist/go/kanbanHandlers.js +221 -0
  17. package/dist/go/mainGo.js +527 -0
  18. package/dist/go/readme.js +14 -0
  19. package/dist/go/redisGo.js +35 -0
  20. package/dist/go/redisYaml.js +8 -0
  21. package/dist/go/registryGo.js +47 -0
  22. package/dist/go/sqlcYaml.js +17 -0
  23. package/dist/go/stateStore.js +119 -0
  24. package/dist/go/typesGo.js +15 -0
  25. package/dist/go/useViraState.js +160 -0
  26. package/dist/go/useViraStream.js +167 -0
  27. package/dist/index.js +644 -192
  28. package/dist/react/appTsx.js +52 -0
  29. package/dist/react/envExample.js +7 -0
  30. package/dist/react/envLocal.js +5 -0
  31. package/dist/react/indexCss.js +22 -0
  32. package/dist/react/indexHtml.js +16 -0
  33. package/dist/react/kanbanAppTsx.js +34 -0
  34. package/dist/react/kanbanBoard.js +63 -0
  35. package/dist/react/kanbanCard.js +65 -0
  36. package/dist/react/kanbanColumn.js +67 -0
  37. package/dist/react/kanbanModels.js +37 -0
  38. package/dist/react/kanbanService.js +119 -0
  39. package/dist/react/mainTsx.js +16 -0
  40. package/dist/react/tsconfig.js +25 -0
  41. package/dist/react/viteConfig.js +31 -0
  42. package/package.json +3 -4
@@ -0,0 +1,414 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.eventsAPI = void 0;
4
+ exports.eventsAPI = `package events
5
+
6
+ import (
7
+ "context"
8
+ "encoding/json"
9
+ "sync"
10
+ "time"
11
+
12
+ jsonpatch "github.com/evanphx/json-patch/v5"
13
+ "github.com/gorilla/websocket"
14
+ )
15
+
16
+ // VRP_VERSION is the current protocol version.
17
+ const VRP_VERSION = "0.1"
18
+
19
+ // WSMessage matches protocol message schema.
20
+ type WSMessage struct {
21
+ Type string \`json:"type"\`
22
+ Name string \`json:"name,omitempty"\`
23
+ Channel string \`json:"channel,omitempty"\`
24
+ Channels []string \`json:"channels,omitempty"\`
25
+ Data json.RawMessage \`json:"data,omitempty"\`
26
+ Patch json.RawMessage \`json:"patch,omitempty"\`
27
+ Ts int64 \`json:"ts,omitempty"\`
28
+ Client string \`json:"client,omitempty"\`
29
+ Version string \`json:"version,omitempty"\`
30
+ Auth string \`json:"authToken,omitempty"\`
31
+ Session string \`json:"session,omitempty"\`
32
+ Interval int64 \`json:"interval,omitempty"\`
33
+ VersionNo int64 \`json:"versionNo,omitempty"\`
34
+ MsgID string \`json:"msgId,omitempty"\` // for idempotency
35
+ Code string \`json:"code,omitempty"\` // error code
36
+ Message string \`json:"message,omitempty"\` // error message
37
+ Retry bool \`json:"retry,omitempty"\` // error retry flag
38
+ }
39
+
40
+ // EventHandler signature for domain events.
41
+ type EventHandler func(ctx context.Context, hub EventEmitter, conn *websocket.Conn, msg WSMessage)
42
+
43
+ // EventEmitter exposes server-side emit/update/diff for handlers.
44
+ type EventEmitter interface {
45
+ Emit(channel string, payload any)
46
+ Update(channel string, payload any)
47
+ Diff(channel string, patch any)
48
+ Snapshot(channel string) (json.RawMessage, int64, bool)
49
+ }
50
+
51
+ // DiffMode controls how diffs are generated.
52
+ type DiffMode int
53
+
54
+ const (
55
+ DiffModeMerge DiffMode = iota // JSON Merge Patch (RFC 7396)
56
+ DiffModePatch // JSON Patch (RFC 6902)
57
+ )
58
+
59
+ // Hub is an in-memory event hub with state and versions.
60
+ type Hub struct {
61
+ mu sync.Mutex
62
+ clients map[*websocket.Conn]bool
63
+ subs map[string]map[*websocket.Conn]bool
64
+ sessions map[*websocket.Conn]string
65
+ state map[string]json.RawMessage
66
+ versions map[string]int64
67
+ events map[string]EventHandler
68
+ diffMode DiffMode
69
+ history map[string][]StateSnapshot // for replay
70
+ maxHistory int
71
+ store StateStore
72
+ ttlSec int
73
+ }
74
+
75
+ // StateSnapshot stores a versioned state snapshot.
76
+ type StateSnapshot struct {
77
+ Data json.RawMessage
78
+ VersionNo int64
79
+ Ts int64
80
+ }
81
+
82
+ func NewHub() *Hub {
83
+ return &Hub{
84
+ clients: make(map[*websocket.Conn]bool),
85
+ subs: make(map[string]map[*websocket.Conn]bool),
86
+ sessions: make(map[*websocket.Conn]string),
87
+ state: make(map[string]json.RawMessage),
88
+ versions: make(map[string]int64),
89
+ events: make(map[string]EventHandler),
90
+ diffMode: DiffModeMerge,
91
+ history: make(map[string][]StateSnapshot),
92
+ maxHistory: 100, // keep last 100 versions per channel
93
+ store: MemoryStore{},
94
+ ttlSec: 0,
95
+ msgIDs: make(map[string]int64),
96
+ maxMsgIDs: 1000, // keep last 1000 msgIds for dedup
97
+ }
98
+ }
99
+
100
+ // SetDiffMode sets the diff generation mode.
101
+ func (h *Hub) SetDiffMode(mode DiffMode) {
102
+ h.mu.Lock()
103
+ defer h.mu.Unlock()
104
+ h.diffMode = mode
105
+ }
106
+
107
+ // SetHistoryLimit limits how many snapshots are stored per channel.
108
+ func (h *Hub) SetHistoryLimit(limit int) {
109
+ h.mu.Lock()
110
+ defer h.mu.Unlock()
111
+ if limit > 0 {
112
+ h.maxHistory = limit
113
+ }
114
+ }
115
+
116
+ // SetStore sets the state store (memory/redis).
117
+ func (h *Hub) SetStore(store StateStore) {
118
+ h.mu.Lock()
119
+ defer h.mu.Unlock()
120
+ if store != nil {
121
+ h.store = store
122
+ }
123
+ }
124
+
125
+ // SetTTL sets TTL for persisted entries.
126
+ func (h *Hub) SetTTL(ttlSec int) {
127
+ h.mu.Lock()
128
+ defer h.mu.Unlock()
129
+ if ttlSec >= 0 {
130
+ h.ttlSec = ttlSec
131
+ }
132
+ }
133
+
134
+ // CheckMsgID returns true if msgId was already seen (and records it), false otherwise.
135
+ // Used for idempotency: duplicate messages with same msgId are ignored.
136
+ func (h *Hub) CheckMsgID(msgID string) bool {
137
+ if msgID == "" {
138
+ return false
139
+ }
140
+ h.mu.Lock()
141
+ defer h.mu.Unlock()
142
+ now := time.Now().UnixMilli()
143
+ // Clean old entries (older than 5 minutes)
144
+ cutoff := now - 5*60*1000
145
+ for id, ts := range h.msgIDs {
146
+ if ts < cutoff {
147
+ delete(h.msgIDs, id)
148
+ }
149
+ }
150
+ // Check if exists
151
+ if _, exists := h.msgIDs[msgID]; exists {
152
+ return true // duplicate
153
+ }
154
+ // Record
155
+ h.msgIDs[msgID] = now
156
+ // Trim if too many
157
+ if len(h.msgIDs) > h.maxMsgIDs {
158
+ // Remove oldest 100 entries
159
+ type entry struct {
160
+ id string
161
+ ts int64
162
+ }
163
+ var entries []entry
164
+ for id, ts := range h.msgIDs {
165
+ entries = append(entries, entry{id, ts})
166
+ }
167
+ // Sort by timestamp (oldest first)
168
+ for i := 0; i < len(entries)-1; i++ {
169
+ for j := i + 1; j < len(entries); j++ {
170
+ if entries[i].ts > entries[j].ts {
171
+ entries[i], entries[j] = entries[j], entries[i]
172
+ }
173
+ }
174
+ }
175
+ // Remove oldest 100
176
+ for i := 0; i < 100 && i < len(entries); i++ {
177
+ delete(h.msgIDs, entries[i].id)
178
+ }
179
+ }
180
+ return false // new message
181
+ }
182
+
183
+ // Emit aliases Update with force update.
184
+ func (h *Hub) Emit(channel string, payload any) {
185
+ h.applyUpdate(channel, payload, true)
186
+ }
187
+
188
+ // Update applies merge-patch optimization when possible.
189
+ func (h *Hub) Update(channel string, payload any) {
190
+ h.applyUpdate(channel, payload, false)
191
+ }
192
+
193
+ // Diff applies a raw patch and broadcasts.
194
+ func (h *Hub) Diff(channel string, patch any) {
195
+ h.applyDiff(channel, patch)
196
+ }
197
+
198
+ func (h *Hub) Snapshot(channel string) (json.RawMessage, int64, bool) {
199
+ h.mu.Lock()
200
+ snap, ok := h.state[channel]
201
+ v := h.versions[channel]
202
+ store := h.store
203
+ h.mu.Unlock()
204
+
205
+ if ok {
206
+ return snap, v, true
207
+ }
208
+
209
+ // Try loading from store if available
210
+ if store != nil {
211
+ if snap, ok, _ := store.LoadSnapshot(context.Background(), channel); ok {
212
+ // Update in-memory cache
213
+ h.mu.Lock()
214
+ h.state[channel] = snap.Data
215
+ h.versions[channel] = snap.VersionNo
216
+ h.mu.Unlock()
217
+ return snap.Data, snap.VersionNo, true
218
+ }
219
+ }
220
+
221
+ return nil, 0, false
222
+ }
223
+
224
+ // Replay returns state snapshots for a channel from a given version.
225
+ func (h *Hub) Replay(channel string, fromVersion int64) []StateSnapshot {
226
+ h.mu.Lock()
227
+ hist := h.history[channel]
228
+ store := h.store
229
+ h.mu.Unlock()
230
+
231
+ var result []StateSnapshot
232
+ for _, snap := range hist {
233
+ if snap.VersionNo > fromVersion {
234
+ result = append(result, snap)
235
+ }
236
+ }
237
+
238
+ // Try loading from store if available and in-memory is empty/incomplete
239
+ if store != nil && len(result) == 0 {
240
+ if stored, err := store.LoadHistory(context.Background(), channel, fromVersion); err == nil {
241
+ result = stored
242
+ }
243
+ }
244
+
245
+ return result
246
+ }
247
+
248
+ // internal helpers
249
+ func (h *Hub) applyUpdate(channel string, payload any, force bool) {
250
+ newData, err := json.Marshal(payload)
251
+ if err != nil {
252
+ return
253
+ }
254
+ var prev []byte
255
+ h.mu.Lock()
256
+ if s, ok := h.state[channel]; ok {
257
+ prev = append([]byte{}, s...)
258
+ }
259
+ h.state[channel] = newData
260
+ h.versions[channel]++
261
+ version := h.versions[channel]
262
+
263
+ // Save snapshot for replay
264
+ snap := StateSnapshot{
265
+ Data: append([]byte{}, newData...),
266
+ VersionNo: version,
267
+ Ts: time.Now().UnixMilli(),
268
+ }
269
+ hist := h.history[channel]
270
+ hist = append(hist, snap)
271
+ if len(hist) > h.maxHistory {
272
+ hist = hist[len(hist)-h.maxHistory:]
273
+ }
274
+ h.history[channel] = hist
275
+ store := h.store
276
+ ttl := h.ttlSec
277
+
278
+ h.mu.Unlock()
279
+
280
+ if store != nil {
281
+ _ = store.SaveSnapshot(context.Background(), channel, snap, ttl)
282
+ _ = store.AppendHistory(context.Background(), channel, snap, h.maxHistory, ttl)
283
+ }
284
+
285
+ if !force && prev != nil {
286
+ var patch json.RawMessage
287
+ var err error
288
+ if h.diffMode == DiffModePatch {
289
+ // Generate RFC 6902 JSON Patch
290
+ patchOps, err := jsonpatch.CreatePatch(prev, newData)
291
+ if err == nil {
292
+ patch, _ = json.Marshal(patchOps)
293
+ }
294
+ } else {
295
+ // Generate RFC 7396 JSON Merge Patch
296
+ patch, err = jsonpatch.CreateMergePatch(prev, newData)
297
+ }
298
+ if err == nil && len(patch) > 2 {
299
+ h.applyDiff(channel, patch)
300
+ return
301
+ }
302
+ }
303
+
304
+ msg := WSMessage{
305
+ Type: "update",
306
+ Channel: channel,
307
+ Data: newData,
308
+ VersionNo: version,
309
+ Ts: time.Now().UnixMilli(),
310
+ }
311
+ raw, err := json.Marshal(msg)
312
+ if err != nil {
313
+ return
314
+ }
315
+ h.broadcast(channel, raw)
316
+ }
317
+
318
+ func (h *Hub) applyDiff(channel string, patch any) {
319
+ data, err := json.Marshal(patch)
320
+ if err != nil {
321
+ return
322
+ }
323
+ h.mu.Lock()
324
+ prev := h.state[channel]
325
+ merged := prev
326
+ if prev != nil {
327
+ if h.diffMode == DiffModePatch {
328
+ // Apply RFC 6902 JSON Patch
329
+ ops, err := jsonpatch.DecodePatch(data)
330
+ if err == nil {
331
+ applied, err := ops.Apply(prev)
332
+ if err == nil {
333
+ merged = applied
334
+ }
335
+ }
336
+ } else {
337
+ // Apply RFC 7396 JSON Merge Patch
338
+ if applied, err := jsonpatch.MergePatch(prev, data); err == nil {
339
+ merged = applied
340
+ }
341
+ }
342
+ } else {
343
+ merged = data
344
+ }
345
+ h.state[channel] = merged
346
+ h.versions[channel]++
347
+ version := h.versions[channel]
348
+
349
+ // Save snapshot
350
+ snap := StateSnapshot{
351
+ Data: append([]byte{}, merged...),
352
+ VersionNo: version,
353
+ Ts: time.Now().UnixMilli(),
354
+ }
355
+ hist := h.history[channel]
356
+ hist = append(hist, snap)
357
+ if len(hist) > h.maxHistory {
358
+ hist = hist[len(hist)-h.maxHistory:]
359
+ }
360
+ h.history[channel] = hist
361
+ store := h.store
362
+ ttl := h.ttlSec
363
+
364
+ h.mu.Unlock()
365
+
366
+ if store != nil {
367
+ _ = store.SaveSnapshot(context.Background(), channel, snap, ttl)
368
+ _ = store.AppendHistory(context.Background(), channel, snap, h.maxHistory, ttl)
369
+ }
370
+
371
+ msg := WSMessage{
372
+ Type: "diff",
373
+ Channel: channel,
374
+ Patch: data,
375
+ Ts: time.Now().UnixMilli(),
376
+ VersionNo: version,
377
+ }
378
+ raw, err := json.Marshal(msg)
379
+ if err != nil {
380
+ return
381
+ }
382
+ h.broadcast(channel, raw)
383
+ }
384
+
385
+ // Broadcast sends a message to all subscribers of a channel.
386
+ // This is called by applyUpdate/applyDiff after creating the message.
387
+ func (h *Hub) Broadcast(channel string, raw json.RawMessage) {
388
+ // This will be implemented by wsHub wrapper in main.go
389
+ // Hub only manages state, wsHub manages connections
390
+ }
391
+
392
+ // Get returns an event handler by name.
393
+ func (h *Hub) Get(name string) (EventHandler, bool) {
394
+ h.mu.Lock()
395
+ defer h.mu.Unlock()
396
+ handler, ok := h.events[name]
397
+ return handler, ok
398
+ }
399
+
400
+ // SetBroadcaster sets a custom broadcast function (used by wsHub).
401
+ type Broadcaster func(channel string, raw json.RawMessage)
402
+
403
+ var globalBroadcaster Broadcaster
404
+
405
+ func SetBroadcaster(fn Broadcaster) {
406
+ globalBroadcaster = fn
407
+ }
408
+
409
+ func (h *Hub) broadcast(channel string, raw json.RawMessage) {
410
+ if globalBroadcaster != nil {
411
+ globalBroadcaster(channel, raw)
412
+ }
413
+ }
414
+ `;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.goMod = void 0;
4
+ exports.goMod = `module vira-engine-backend
5
+
6
+ go 1.21
7
+
8
+ require (
9
+ github.com/go-chi/chi/v5 v5.0.10
10
+ github.com/go-chi/cors v1.2.1
11
+ github.com/gorilla/websocket v1.5.1
12
+ github.com/google/uuid v1.5.0
13
+ github.com/rs/zerolog v1.32.0
14
+ github.com/jackc/pgx/v5 v5.5.4
15
+ github.com/redis/go-redis/v9 v9.5.1
16
+ github.com/segmentio/kafka-go v0.4.45
17
+ github.com/evanphx/json-patch/v5 v5.9.0
18
+ gopkg.in/yaml.v3 v3.0.1
19
+ )
20
+ `;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.kafkaGo = void 0;
4
+ exports.kafkaGo = `package events
5
+
6
+ import (
7
+ "context"
8
+ "fmt"
9
+ "time"
10
+
11
+ "github.com/segmentio/kafka-go"
12
+ "github.com/rs/zerolog"
13
+
14
+ "vira-engine-backend/internal/config"
15
+ )
16
+
17
+ type Client struct {
18
+ dialer *kafka.Dialer
19
+ cfg config.Config
20
+ logger zerolog.Logger
21
+ }
22
+
23
+ func NewKafka(cfg config.Config, logger zerolog.Logger) (*Client, error) {
24
+ d := &kafka.Dialer{
25
+ Timeout: 5 * time.Second,
26
+ DualStack: true,
27
+ }
28
+ c := &Client{dialer: d, cfg: cfg, logger: logger}
29
+ if err := c.Ping(context.Background()); err != nil {
30
+ return nil, err
31
+ }
32
+ return c, nil
33
+ }
34
+
35
+ func (c *Client) Ping(ctx context.Context) error {
36
+ if len(c.cfg.Kafka.Brokers) == 0 {
37
+ return fmt.Errorf("no kafka brokers configured")
38
+ }
39
+ conn, err := c.dialer.DialContext(ctx, "tcp", c.cfg.Kafka.Brokers[0])
40
+ if err != nil {
41
+ return fmt.Errorf("dial kafka: %w", err)
42
+ }
43
+ _ = conn.Close()
44
+ return nil
45
+ }
46
+
47
+ func (c *Client) NewWriter(topic string) *kafka.Writer {
48
+ return &kafka.Writer{
49
+ Addr: kafka.TCP(c.cfg.Kafka.Brokers...),
50
+ Topic: topic,
51
+ RequiredAcks: kafka.RequireAll,
52
+ BatchTimeout: 10 * time.Millisecond,
53
+ }
54
+ }
55
+
56
+ func (c *Client) NewReader(topic string) *kafka.Reader {
57
+ return kafka.NewReader(kafka.ReaderConfig{
58
+ Brokers: c.cfg.Kafka.Brokers,
59
+ GroupID: c.cfg.Kafka.GroupID,
60
+ Topic: topic,
61
+ StartOffset: kafka.FirstOffset,
62
+ SessionTimeout: 10 * time.Second,
63
+ HeartbeatInterval: 3 * time.Second,
64
+ CommitInterval: 5 * time.Second,
65
+ })
66
+ }
67
+
68
+ func (c *Client) Close() error {
69
+ return nil
70
+ }
71
+ `;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.kafkaYaml = void 0;
4
+ exports.kafkaYaml = `brokers:
5
+ - localhost:9092
6
+ groupId: vira-engine
7
+ topics:
8
+ events: vira.events
9
+ dlq: vira.events.dlq
10
+ `;