shikibu 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +487 -0
- data/lib/shikibu/activity.rb +135 -0
- data/lib/shikibu/app.rb +299 -0
- data/lib/shikibu/channels.rb +360 -0
- data/lib/shikibu/constants.rb +70 -0
- data/lib/shikibu/context.rb +208 -0
- data/lib/shikibu/errors.rb +137 -0
- data/lib/shikibu/integrations/active_job.rb +95 -0
- data/lib/shikibu/integrations/sidekiq.rb +104 -0
- data/lib/shikibu/locking.rb +110 -0
- data/lib/shikibu/middleware/rack_app.rb +197 -0
- data/lib/shikibu/notify/notify_base.rb +67 -0
- data/lib/shikibu/notify/pg_notify.rb +217 -0
- data/lib/shikibu/notify/wake_event.rb +56 -0
- data/lib/shikibu/outbox/relayer.rb +227 -0
- data/lib/shikibu/replay.rb +361 -0
- data/lib/shikibu/retry_policy.rb +81 -0
- data/lib/shikibu/storage/migrations.rb +179 -0
- data/lib/shikibu/storage/sequel_storage.rb +883 -0
- data/lib/shikibu/version.rb +5 -0
- data/lib/shikibu/worker.rb +389 -0
- data/lib/shikibu/workflow.rb +398 -0
- data/lib/shikibu.rb +152 -0
- data/schema/LICENSE +21 -0
- data/schema/README.md +57 -0
- data/schema/db/migrations/mysql/20251217000000_initial_schema.sql +284 -0
- data/schema/db/migrations/postgresql/20251217000000_initial_schema.sql +284 -0
- data/schema/db/migrations/sqlite/20251217000000_initial_schema.sql +284 -0
- data/schema/docs/column-values.md +91 -0
- metadata +231 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Schema version tracking
|
|
4
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
5
|
+
version INTEGER PRIMARY KEY,
|
|
6
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
7
|
+
description TEXT NOT NULL
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
-- Workflow definitions (source code storage)
|
|
11
|
+
CREATE TABLE IF NOT EXISTS workflow_definitions (
|
|
12
|
+
workflow_name TEXT NOT NULL,
|
|
13
|
+
source_hash TEXT NOT NULL,
|
|
14
|
+
source_code TEXT NOT NULL,
|
|
15
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
16
|
+
PRIMARY KEY (workflow_name, source_hash)
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_definitions_name ON workflow_definitions(workflow_name);
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_definitions_hash ON workflow_definitions(source_hash);
|
|
21
|
+
|
|
22
|
+
-- Workflow instances with distributed locking support
|
|
23
|
+
CREATE TABLE IF NOT EXISTS workflow_instances (
|
|
24
|
+
instance_id TEXT PRIMARY KEY,
|
|
25
|
+
workflow_name TEXT NOT NULL,
|
|
26
|
+
source_hash TEXT NOT NULL,
|
|
27
|
+
owner_service TEXT NOT NULL,
|
|
28
|
+
framework TEXT NOT NULL DEFAULT 'python',
|
|
29
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
30
|
+
current_activity_id TEXT,
|
|
31
|
+
continued_from TEXT,
|
|
32
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
33
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
34
|
+
input_data TEXT NOT NULL,
|
|
35
|
+
output_data TEXT,
|
|
36
|
+
locked_by TEXT,
|
|
37
|
+
locked_at TEXT,
|
|
38
|
+
lock_timeout_seconds INTEGER,
|
|
39
|
+
lock_expires_at TEXT,
|
|
40
|
+
CONSTRAINT valid_status CHECK (
|
|
41
|
+
status IN ('running', 'completed', 'failed', 'waiting_for_event', 'waiting_for_timer', 'waiting_for_message', 'compensating', 'cancelled', 'recurred')
|
|
42
|
+
),
|
|
43
|
+
FOREIGN KEY (continued_from) REFERENCES workflow_instances(instance_id),
|
|
44
|
+
FOREIGN KEY (workflow_name, source_hash) REFERENCES workflow_definitions(workflow_name, source_hash)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_instances_status ON workflow_instances(status);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_instances_workflow ON workflow_instances(workflow_name);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_instances_owner ON workflow_instances(owner_service);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_instances_framework ON workflow_instances(framework);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_instances_locked ON workflow_instances(locked_by, locked_at);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_instances_lock_expires ON workflow_instances(lock_expires_at);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_instances_updated ON workflow_instances(updated_at);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_instances_hash ON workflow_instances(source_hash);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_instances_continued_from ON workflow_instances(continued_from);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_instances_resumable ON workflow_instances(status, locked_by);
|
|
57
|
+
|
|
58
|
+
-- Workflow execution history (for deterministic replay)
|
|
59
|
+
CREATE TABLE IF NOT EXISTS workflow_history (
|
|
60
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
61
|
+
instance_id TEXT NOT NULL,
|
|
62
|
+
activity_id TEXT NOT NULL,
|
|
63
|
+
event_type TEXT NOT NULL,
|
|
64
|
+
data_type TEXT NOT NULL DEFAULT 'json',
|
|
65
|
+
event_data TEXT,
|
|
66
|
+
event_data_binary BLOB,
|
|
67
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
68
|
+
FOREIGN KEY (instance_id) REFERENCES workflow_instances(instance_id) ON DELETE CASCADE,
|
|
69
|
+
CONSTRAINT unique_instance_activity UNIQUE (instance_id, activity_id)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_history_instance ON workflow_history(instance_id, activity_id);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_history_created ON workflow_history(created_at);
|
|
74
|
+
|
|
75
|
+
-- Archived workflow history (for recur pattern)
|
|
76
|
+
CREATE TABLE IF NOT EXISTS workflow_history_archive (
|
|
77
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
78
|
+
instance_id TEXT NOT NULL,
|
|
79
|
+
activity_id TEXT NOT NULL,
|
|
80
|
+
event_type TEXT NOT NULL,
|
|
81
|
+
data_type TEXT NOT NULL DEFAULT 'json',
|
|
82
|
+
event_data TEXT,
|
|
83
|
+
event_data_binary BLOB,
|
|
84
|
+
created_at TEXT NOT NULL,
|
|
85
|
+
archived_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
86
|
+
FOREIGN KEY (instance_id) REFERENCES workflow_instances(instance_id) ON DELETE CASCADE
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_history_archive_instance ON workflow_history_archive(instance_id);
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_history_archive_archived ON workflow_history_archive(archived_at);
|
|
91
|
+
|
|
92
|
+
-- Compensation transactions (LIFO stack for Saga pattern)
|
|
93
|
+
CREATE TABLE IF NOT EXISTS workflow_compensations (
|
|
94
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
95
|
+
instance_id TEXT NOT NULL,
|
|
96
|
+
activity_id TEXT NOT NULL,
|
|
97
|
+
activity_name TEXT NOT NULL,
|
|
98
|
+
args TEXT NOT NULL,
|
|
99
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
100
|
+
FOREIGN KEY (instance_id) REFERENCES workflow_instances(instance_id) ON DELETE CASCADE
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_compensations_instance ON workflow_compensations(instance_id, created_at DESC);
|
|
104
|
+
|
|
105
|
+
-- Timer subscriptions (for wait_timer)
|
|
106
|
+
CREATE TABLE IF NOT EXISTS workflow_timer_subscriptions (
|
|
107
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
108
|
+
instance_id TEXT NOT NULL,
|
|
109
|
+
timer_id TEXT NOT NULL,
|
|
110
|
+
expires_at TEXT NOT NULL,
|
|
111
|
+
activity_id TEXT,
|
|
112
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
113
|
+
FOREIGN KEY (instance_id) REFERENCES workflow_instances(instance_id) ON DELETE CASCADE,
|
|
114
|
+
CONSTRAINT unique_instance_timer UNIQUE (instance_id, timer_id)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_timer_subscriptions_expires ON workflow_timer_subscriptions(expires_at);
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_timer_subscriptions_instance ON workflow_timer_subscriptions(instance_id);
|
|
119
|
+
|
|
120
|
+
-- Group memberships (Erlang pg style)
|
|
121
|
+
CREATE TABLE IF NOT EXISTS workflow_group_memberships (
|
|
122
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
123
|
+
instance_id TEXT NOT NULL,
|
|
124
|
+
group_name TEXT NOT NULL,
|
|
125
|
+
joined_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
126
|
+
FOREIGN KEY (instance_id) REFERENCES workflow_instances(instance_id) ON DELETE CASCADE,
|
|
127
|
+
CONSTRAINT unique_instance_group UNIQUE (instance_id, group_name)
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_group_memberships_group ON workflow_group_memberships(group_name);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_group_memberships_instance ON workflow_group_memberships(instance_id);
|
|
132
|
+
|
|
133
|
+
-- Transactional outbox pattern
|
|
134
|
+
CREATE TABLE IF NOT EXISTS outbox_events (
|
|
135
|
+
event_id TEXT PRIMARY KEY,
|
|
136
|
+
event_type TEXT NOT NULL,
|
|
137
|
+
event_source TEXT NOT NULL,
|
|
138
|
+
data_type TEXT NOT NULL DEFAULT 'json',
|
|
139
|
+
event_data TEXT,
|
|
140
|
+
event_data_binary BLOB,
|
|
141
|
+
content_type TEXT NOT NULL DEFAULT 'application/json',
|
|
142
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
143
|
+
published_at TEXT,
|
|
144
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
145
|
+
retry_count INTEGER DEFAULT 0,
|
|
146
|
+
last_error TEXT,
|
|
147
|
+
CONSTRAINT valid_outbox_status CHECK (status IN ('pending', 'processing', 'published', 'failed', 'invalid', 'expired'))
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
CREATE INDEX IF NOT EXISTS idx_outbox_status ON outbox_events(status, created_at);
|
|
151
|
+
CREATE INDEX IF NOT EXISTS idx_outbox_retry ON outbox_events(status, retry_count);
|
|
152
|
+
CREATE INDEX IF NOT EXISTS idx_outbox_published ON outbox_events(published_at);
|
|
153
|
+
|
|
154
|
+
-- Channel messages (persistent message queue)
|
|
155
|
+
CREATE TABLE IF NOT EXISTS channel_messages (
|
|
156
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
157
|
+
channel TEXT NOT NULL,
|
|
158
|
+
message_id TEXT NOT NULL UNIQUE,
|
|
159
|
+
data_type TEXT NOT NULL,
|
|
160
|
+
data TEXT,
|
|
161
|
+
data_binary BLOB,
|
|
162
|
+
metadata TEXT,
|
|
163
|
+
published_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
164
|
+
CONSTRAINT valid_data_type CHECK (data_type IN ('json', 'binary')),
|
|
165
|
+
CONSTRAINT data_type_consistency CHECK (
|
|
166
|
+
(data_type = 'json' AND data IS NOT NULL AND data_binary IS NULL) OR
|
|
167
|
+
(data_type = 'binary' AND data IS NULL AND data_binary IS NOT NULL)
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
CREATE INDEX IF NOT EXISTS idx_channel_messages_channel ON channel_messages(channel, published_at);
|
|
172
|
+
CREATE INDEX IF NOT EXISTS idx_channel_messages_id ON channel_messages(id);
|
|
173
|
+
|
|
174
|
+
-- Channel subscriptions
|
|
175
|
+
CREATE TABLE IF NOT EXISTS channel_subscriptions (
|
|
176
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
177
|
+
instance_id TEXT NOT NULL,
|
|
178
|
+
channel TEXT NOT NULL,
|
|
179
|
+
mode TEXT NOT NULL,
|
|
180
|
+
activity_id TEXT,
|
|
181
|
+
cursor_message_id INTEGER,
|
|
182
|
+
timeout_at TEXT,
|
|
183
|
+
subscribed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
184
|
+
FOREIGN KEY (instance_id) REFERENCES workflow_instances(instance_id) ON DELETE CASCADE,
|
|
185
|
+
CONSTRAINT valid_mode CHECK (mode IN ('broadcast', 'competing')),
|
|
186
|
+
CONSTRAINT unique_instance_channel UNIQUE (instance_id, channel)
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
CREATE INDEX IF NOT EXISTS idx_channel_subscriptions_channel ON channel_subscriptions(channel);
|
|
190
|
+
CREATE INDEX IF NOT EXISTS idx_channel_subscriptions_instance ON channel_subscriptions(instance_id);
|
|
191
|
+
CREATE INDEX IF NOT EXISTS idx_channel_subscriptions_waiting ON channel_subscriptions(channel, activity_id);
|
|
192
|
+
CREATE INDEX IF NOT EXISTS idx_channel_subscriptions_timeout ON channel_subscriptions(timeout_at);
|
|
193
|
+
|
|
194
|
+
-- Channel delivery cursors (broadcast mode: track who read what)
|
|
195
|
+
CREATE TABLE IF NOT EXISTS channel_delivery_cursors (
|
|
196
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
197
|
+
channel TEXT NOT NULL,
|
|
198
|
+
instance_id TEXT NOT NULL,
|
|
199
|
+
last_delivered_id INTEGER NOT NULL,
|
|
200
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
201
|
+
FOREIGN KEY (instance_id) REFERENCES workflow_instances(instance_id) ON DELETE CASCADE,
|
|
202
|
+
CONSTRAINT unique_channel_instance UNIQUE (channel, instance_id)
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
CREATE INDEX IF NOT EXISTS idx_channel_delivery_cursors_channel ON channel_delivery_cursors(channel);
|
|
206
|
+
|
|
207
|
+
-- Channel message claims (competing mode: who is processing what)
|
|
208
|
+
CREATE TABLE IF NOT EXISTS channel_message_claims (
|
|
209
|
+
message_id TEXT PRIMARY KEY,
|
|
210
|
+
instance_id TEXT NOT NULL,
|
|
211
|
+
claimed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
212
|
+
FOREIGN KEY (message_id) REFERENCES channel_messages(message_id) ON DELETE CASCADE,
|
|
213
|
+
FOREIGN KEY (instance_id) REFERENCES workflow_instances(instance_id) ON DELETE CASCADE
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
CREATE INDEX IF NOT EXISTS idx_channel_message_claims_instance ON channel_message_claims(instance_id);
|
|
217
|
+
|
|
218
|
+
-- System locks (for coordinating background tasks across pods)
|
|
219
|
+
CREATE TABLE IF NOT EXISTS system_locks (
|
|
220
|
+
lock_name TEXT PRIMARY KEY,
|
|
221
|
+
locked_by TEXT,
|
|
222
|
+
locked_at TEXT,
|
|
223
|
+
lock_expires_at TEXT
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
CREATE INDEX IF NOT EXISTS idx_system_locks_expires ON system_locks(lock_expires_at);
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
-- migrate:down
|
|
230
|
+
|
|
231
|
+
DROP INDEX IF EXISTS idx_channel_message_claims_instance;
|
|
232
|
+
DROP TABLE IF EXISTS channel_message_claims;
|
|
233
|
+
|
|
234
|
+
DROP INDEX IF EXISTS idx_channel_delivery_cursors_channel;
|
|
235
|
+
DROP TABLE IF EXISTS channel_delivery_cursors;
|
|
236
|
+
|
|
237
|
+
DROP INDEX IF EXISTS idx_channel_subscriptions_waiting;
|
|
238
|
+
DROP INDEX IF EXISTS idx_channel_subscriptions_instance;
|
|
239
|
+
DROP INDEX IF EXISTS idx_channel_subscriptions_channel;
|
|
240
|
+
DROP TABLE IF EXISTS channel_subscriptions;
|
|
241
|
+
|
|
242
|
+
DROP INDEX IF EXISTS idx_channel_messages_id;
|
|
243
|
+
DROP INDEX IF EXISTS idx_channel_messages_channel;
|
|
244
|
+
DROP TABLE IF EXISTS channel_messages;
|
|
245
|
+
|
|
246
|
+
DROP INDEX IF EXISTS idx_outbox_published;
|
|
247
|
+
DROP INDEX IF EXISTS idx_outbox_retry;
|
|
248
|
+
DROP INDEX IF EXISTS idx_outbox_status;
|
|
249
|
+
DROP TABLE IF EXISTS outbox_events;
|
|
250
|
+
|
|
251
|
+
DROP INDEX IF EXISTS idx_group_memberships_instance;
|
|
252
|
+
DROP INDEX IF EXISTS idx_group_memberships_group;
|
|
253
|
+
DROP TABLE IF EXISTS workflow_group_memberships;
|
|
254
|
+
|
|
255
|
+
DROP INDEX IF EXISTS idx_timer_subscriptions_instance;
|
|
256
|
+
DROP INDEX IF EXISTS idx_timer_subscriptions_expires;
|
|
257
|
+
DROP TABLE IF EXISTS workflow_timer_subscriptions;
|
|
258
|
+
|
|
259
|
+
DROP INDEX IF EXISTS idx_compensations_instance;
|
|
260
|
+
DROP TABLE IF EXISTS workflow_compensations;
|
|
261
|
+
|
|
262
|
+
DROP INDEX IF EXISTS idx_history_archive_archived;
|
|
263
|
+
DROP INDEX IF EXISTS idx_history_archive_instance;
|
|
264
|
+
DROP TABLE IF EXISTS workflow_history_archive;
|
|
265
|
+
|
|
266
|
+
DROP INDEX IF EXISTS idx_history_created;
|
|
267
|
+
DROP INDEX IF EXISTS idx_history_instance;
|
|
268
|
+
DROP TABLE IF EXISTS workflow_history;
|
|
269
|
+
|
|
270
|
+
DROP INDEX IF EXISTS idx_instances_continued_from;
|
|
271
|
+
DROP INDEX IF EXISTS idx_instances_hash;
|
|
272
|
+
DROP INDEX IF EXISTS idx_instances_updated;
|
|
273
|
+
DROP INDEX IF EXISTS idx_instances_locked;
|
|
274
|
+
DROP INDEX IF EXISTS idx_instances_framework;
|
|
275
|
+
DROP INDEX IF EXISTS idx_instances_owner;
|
|
276
|
+
DROP INDEX IF EXISTS idx_instances_workflow;
|
|
277
|
+
DROP INDEX IF EXISTS idx_instances_status;
|
|
278
|
+
DROP TABLE IF EXISTS workflow_instances;
|
|
279
|
+
|
|
280
|
+
DROP INDEX IF EXISTS idx_definitions_hash;
|
|
281
|
+
DROP INDEX IF EXISTS idx_definitions_name;
|
|
282
|
+
DROP TABLE IF EXISTS workflow_definitions;
|
|
283
|
+
|
|
284
|
+
DROP TABLE IF EXISTS schema_version;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Database Column Values Reference
|
|
2
|
+
|
|
3
|
+
This document defines the standard values for database columns that must be consistent across all Durax framework implementations (Edda/Python, Romancy/Go, etc.).
|
|
4
|
+
|
|
5
|
+
## workflow_instances.status
|
|
6
|
+
|
|
7
|
+
| Value | Description |
|
|
8
|
+
|-------|-------------|
|
|
9
|
+
| `running` | Workflow is actively executing |
|
|
10
|
+
| `completed` | Workflow finished successfully |
|
|
11
|
+
| `failed` | Workflow failed with an error |
|
|
12
|
+
| `cancelled` | Workflow was cancelled by user |
|
|
13
|
+
| `waiting_for_event` | Workflow is waiting for an external event (via `wait_event`) |
|
|
14
|
+
| `waiting_for_timer` | Workflow is waiting for a timer to expire (via `sleep`/`sleep_until`) |
|
|
15
|
+
| `waiting_for_message` | Workflow is waiting for a channel message (via `receive`) |
|
|
16
|
+
| `recurred` | Workflow completed one iteration and is ready to restart (Erlang-style tail recursion) |
|
|
17
|
+
| `compensating` | Workflow is executing compensation handlers |
|
|
18
|
+
|
|
19
|
+
## workflow_history.event_type
|
|
20
|
+
|
|
21
|
+
All event types use **PascalCase** for cross-language consistency.
|
|
22
|
+
|
|
23
|
+
| Value | Description |
|
|
24
|
+
|-------|-------------|
|
|
25
|
+
| `ActivityCompleted` | An activity finished successfully |
|
|
26
|
+
| `ActivityFailed` | An activity failed with an error |
|
|
27
|
+
| `EventReceived` | An external event was received (via `wait_event`) |
|
|
28
|
+
| `TimerExpired` | A timer expired (via `sleep`/`sleep_until`) |
|
|
29
|
+
| `ChannelMessageReceived` | A channel message was received (via `receive`) |
|
|
30
|
+
| `MessageTimeout` | A channel message receive timed out |
|
|
31
|
+
| `CompensationExecuted` | A compensation handler executed successfully |
|
|
32
|
+
| `CompensationFailed` | A compensation handler failed |
|
|
33
|
+
| `WorkflowFailed` | The workflow failed (recorded before status update) |
|
|
34
|
+
| `WorkflowCancelled` | The workflow was cancelled |
|
|
35
|
+
|
|
36
|
+
## workflow_history.data_type
|
|
37
|
+
|
|
38
|
+
| Value | Description |
|
|
39
|
+
|-------|-------------|
|
|
40
|
+
| `json` | Event data is stored as JSON text in `event_data` column |
|
|
41
|
+
| `binary` | Event data is stored as raw bytes in `event_data_binary` column |
|
|
42
|
+
|
|
43
|
+
## channel_subscriptions.mode
|
|
44
|
+
|
|
45
|
+
| Value | Description |
|
|
46
|
+
|-------|-------------|
|
|
47
|
+
| `broadcast` | All subscribers receive a copy of each message |
|
|
48
|
+
| `competing` | Only one subscriber receives each message (work queue pattern) |
|
|
49
|
+
|
|
50
|
+
## channel_messages.data_type
|
|
51
|
+
|
|
52
|
+
| Value | Description |
|
|
53
|
+
|-------|-------------|
|
|
54
|
+
| `json` | Message data is stored as JSON text in `data` column |
|
|
55
|
+
| `binary` | Message data is stored as raw bytes in `data_binary` column |
|
|
56
|
+
|
|
57
|
+
## outbox_events.status
|
|
58
|
+
|
|
59
|
+
| Value | Description |
|
|
60
|
+
|-------|-------------|
|
|
61
|
+
| `pending` | Event is waiting to be published |
|
|
62
|
+
| `processing` | Event is currently being processed by the relayer |
|
|
63
|
+
| `published` | Event was successfully published |
|
|
64
|
+
| `failed` | Event publishing failed after all retries |
|
|
65
|
+
| `invalid` | Event is malformed and cannot be published |
|
|
66
|
+
| `expired` | Event expired before it could be published |
|
|
67
|
+
|
|
68
|
+
## workflow_instances.framework
|
|
69
|
+
|
|
70
|
+
| Value | Description |
|
|
71
|
+
|-------|-------------|
|
|
72
|
+
| `python` | Workflow is managed by Edda (Python) |
|
|
73
|
+
| `go` | Workflow is managed by Romancy (Go) |
|
|
74
|
+
|
|
75
|
+
This column enables cross-language interoperability: any framework can deliver messages to workflows managed by other frameworks, while each framework only processes (replays/resumes) its own workflows.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Cross-Language Compatibility Notes
|
|
80
|
+
|
|
81
|
+
1. **Event type naming**: Always use PascalCase (e.g., `ActivityCompleted`, not `activity_completed`)
|
|
82
|
+
|
|
83
|
+
2. **Message delivery**: Any framework can deliver channel messages to any workflow, regardless of the target workflow's framework. The delivering framework:
|
|
84
|
+
- Acquires a lock on the target workflow
|
|
85
|
+
- Records the message in `workflow_history` with the correct `event_type`
|
|
86
|
+
- Updates the workflow status to `running`
|
|
87
|
+
- Releases the lock
|
|
88
|
+
|
|
89
|
+
3. **Workflow processing**: Each framework only processes workflows where `framework` matches its own identifier. This ensures correct replay behavior.
|
|
90
|
+
|
|
91
|
+
4. **Activity IDs**: Both frameworks use the format `{activity_name}:{counter}` for deterministic replay (e.g., `fetch_data:1`, `receive_orders:2`).
|
metadata
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: shikibu
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yasushi Itoh
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-21 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: concurrent-ruby
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.3'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.3'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: connection_pool
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.4'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.4'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rack
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.1'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.1'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: sequel
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '5.0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '5.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: sqlite3
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '2.0'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '2.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: mysql2
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0.5'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0.5'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: pg
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '1.5'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '1.5'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rake
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '13.0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '13.0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rspec
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '3.13'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '3.13'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: rubocop
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '1.68'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '1.68'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: testcontainers
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - "~>"
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '0.1'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - "~>"
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0.1'
|
|
167
|
+
description: Shikibu is a Ruby port of Edda, providing deterministic replay, saga
|
|
168
|
+
pattern, and channel-based messaging for durable workflows.
|
|
169
|
+
email:
|
|
170
|
+
executables: []
|
|
171
|
+
extensions: []
|
|
172
|
+
extra_rdoc_files: []
|
|
173
|
+
files:
|
|
174
|
+
- LICENSE
|
|
175
|
+
- README.md
|
|
176
|
+
- lib/shikibu.rb
|
|
177
|
+
- lib/shikibu/activity.rb
|
|
178
|
+
- lib/shikibu/app.rb
|
|
179
|
+
- lib/shikibu/channels.rb
|
|
180
|
+
- lib/shikibu/constants.rb
|
|
181
|
+
- lib/shikibu/context.rb
|
|
182
|
+
- lib/shikibu/errors.rb
|
|
183
|
+
- lib/shikibu/integrations/active_job.rb
|
|
184
|
+
- lib/shikibu/integrations/sidekiq.rb
|
|
185
|
+
- lib/shikibu/locking.rb
|
|
186
|
+
- lib/shikibu/middleware/rack_app.rb
|
|
187
|
+
- lib/shikibu/notify/notify_base.rb
|
|
188
|
+
- lib/shikibu/notify/pg_notify.rb
|
|
189
|
+
- lib/shikibu/notify/wake_event.rb
|
|
190
|
+
- lib/shikibu/outbox/relayer.rb
|
|
191
|
+
- lib/shikibu/replay.rb
|
|
192
|
+
- lib/shikibu/retry_policy.rb
|
|
193
|
+
- lib/shikibu/storage/migrations.rb
|
|
194
|
+
- lib/shikibu/storage/sequel_storage.rb
|
|
195
|
+
- lib/shikibu/version.rb
|
|
196
|
+
- lib/shikibu/worker.rb
|
|
197
|
+
- lib/shikibu/workflow.rb
|
|
198
|
+
- schema/LICENSE
|
|
199
|
+
- schema/README.md
|
|
200
|
+
- schema/db/migrations/mysql/20251217000000_initial_schema.sql
|
|
201
|
+
- schema/db/migrations/postgresql/20251217000000_initial_schema.sql
|
|
202
|
+
- schema/db/migrations/sqlite/20251217000000_initial_schema.sql
|
|
203
|
+
- schema/docs/column-values.md
|
|
204
|
+
homepage: https://github.com/durax-io/shikibu
|
|
205
|
+
licenses:
|
|
206
|
+
- MIT
|
|
207
|
+
metadata:
|
|
208
|
+
homepage_uri: https://github.com/durax-io/shikibu
|
|
209
|
+
source_code_uri: https://github.com/durax-io/shikibu
|
|
210
|
+
changelog_uri: https://github.com/durax-io/shikibu/blob/main/CHANGELOG.md
|
|
211
|
+
rubygems_mfa_required: 'true'
|
|
212
|
+
post_install_message:
|
|
213
|
+
rdoc_options: []
|
|
214
|
+
require_paths:
|
|
215
|
+
- lib
|
|
216
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
217
|
+
requirements:
|
|
218
|
+
- - ">="
|
|
219
|
+
- !ruby/object:Gem::Version
|
|
220
|
+
version: 3.3.0
|
|
221
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
222
|
+
requirements:
|
|
223
|
+
- - ">="
|
|
224
|
+
- !ruby/object:Gem::Version
|
|
225
|
+
version: '0'
|
|
226
|
+
requirements: []
|
|
227
|
+
rubygems_version: 3.5.22
|
|
228
|
+
signing_key:
|
|
229
|
+
specification_version: 4
|
|
230
|
+
summary: CloudEvents-native Durable Execution framework for Ruby
|
|
231
|
+
test_files: []
|