@directive-run/knowledge 0.2.0 → 0.4.2
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/README.md +3 -3
- package/ai/ai-adapters.md +7 -7
- package/ai/ai-agents-streaming.md +8 -8
- package/ai/ai-budget-resilience.md +5 -5
- package/ai/ai-communication.md +1 -1
- package/ai/ai-guardrails-memory.md +7 -7
- package/ai/ai-mcp-rag.md +5 -5
- package/ai/ai-multi-agent.md +14 -14
- package/ai/ai-orchestrator.md +8 -8
- package/ai/ai-security.md +2 -2
- package/ai/ai-tasks.md +9 -9
- package/ai/ai-testing-evals.md +2 -2
- package/core/anti-patterns.md +39 -39
- package/core/constraints.md +15 -15
- package/core/core-patterns.md +9 -9
- package/core/error-boundaries.md +7 -7
- package/core/multi-module.md +16 -16
- package/core/naming.md +21 -21
- package/core/plugins.md +14 -14
- package/core/react-adapter.md +13 -13
- package/core/resolvers.md +14 -14
- package/core/schema-types.md +22 -22
- package/core/system-api.md +16 -16
- package/core/testing.md +5 -5
- package/core/time-travel.md +20 -20
- package/dist/index.cjs +6 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -97
- package/dist/index.js.map +1 -1
- package/examples/ab-testing.ts +18 -90
- package/examples/ai-checkpoint.ts +68 -87
- package/examples/ai-guardrails.ts +20 -70
- package/examples/auth-flow.ts +2 -2
- package/examples/batch-resolver.ts +19 -59
- package/examples/contact-form.ts +220 -69
- package/examples/counter.ts +77 -95
- package/examples/dashboard-loader.ts +37 -55
- package/examples/debounce-constraints.ts +0 -2
- package/examples/dynamic-modules.ts +17 -20
- package/examples/error-boundaries.ts +30 -81
- package/examples/newsletter.ts +22 -49
- package/examples/notifications.ts +24 -23
- package/examples/optimistic-updates.ts +36 -41
- package/examples/pagination.ts +2 -2
- package/examples/permissions.ts +22 -32
- package/examples/provider-routing.ts +26 -83
- package/examples/shopping-cart.ts +8 -8
- package/examples/sudoku.ts +55 -62
- package/examples/theme-locale.ts +4 -7
- package/examples/time-machine.ts +12 -90
- package/examples/topic-guard.ts +30 -38
- package/examples/url-sync.ts +8 -8
- package/examples/websocket.ts +5 -5
- package/package.json +3 -3
package/examples/newsletter.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Example: newsletter
|
|
2
|
-
// Source: examples/newsletter/src/
|
|
3
|
-
//
|
|
2
|
+
// Source: examples/newsletter/src/module.ts
|
|
3
|
+
// Pure module file — no DOM wiring
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Newsletter Signup
|
|
6
|
+
* Newsletter Signup — Directive Module
|
|
7
7
|
*
|
|
8
8
|
* Demonstrates all six primitives with the simplest possible module:
|
|
9
9
|
* - Facts: email, touched, status, errorMessage, lastSubmittedAt
|
|
@@ -31,11 +31,22 @@ import { devtoolsPlugin } from "@directive-run/core/plugins";
|
|
|
31
31
|
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
32
32
|
const RATE_LIMIT_MS = 10_000; // 10 seconds (shorter for demo)
|
|
33
33
|
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Logs (external mutable array, same pattern as fraud-analysis)
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
export const logs: string[] = [];
|
|
39
|
+
|
|
40
|
+
export function addLog(msg: string): void {
|
|
41
|
+
console.log(`[newsletter] ${msg}`);
|
|
42
|
+
logs.push(`${new Date().toLocaleTimeString()}: ${msg}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
// ============================================================================
|
|
35
46
|
// Schema
|
|
36
47
|
// ============================================================================
|
|
37
48
|
|
|
38
|
-
const schema = {
|
|
49
|
+
export const schema = {
|
|
39
50
|
facts: {
|
|
40
51
|
email: t.string(),
|
|
41
52
|
touched: t.boolean(),
|
|
@@ -141,7 +152,7 @@ const newsletter = createModule("newsletter", {
|
|
|
141
152
|
subscribe: {
|
|
142
153
|
requirement: "SUBSCRIBE",
|
|
143
154
|
resolve: async (req, context) => {
|
|
144
|
-
|
|
155
|
+
addLog(`Subscribing: ${context.facts.email}`);
|
|
145
156
|
|
|
146
157
|
// Simulate network delay
|
|
147
158
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
@@ -151,27 +162,27 @@ const newsletter = createModule("newsletter", {
|
|
|
151
162
|
context.facts.status = "error";
|
|
152
163
|
context.facts.errorMessage =
|
|
153
164
|
"Simulated error — try again (20% failure rate for demo).";
|
|
154
|
-
|
|
165
|
+
addLog("Subscription failed (simulated)");
|
|
155
166
|
|
|
156
167
|
return;
|
|
157
168
|
}
|
|
158
169
|
|
|
159
170
|
context.facts.status = "success";
|
|
160
171
|
context.facts.lastSubmittedAt = Date.now();
|
|
161
|
-
|
|
172
|
+
addLog("Subscription succeeded");
|
|
162
173
|
},
|
|
163
174
|
},
|
|
164
175
|
|
|
165
176
|
resetAfterDelay: {
|
|
166
177
|
requirement: "RESET_AFTER_DELAY",
|
|
167
178
|
resolve: async (req, context) => {
|
|
168
|
-
|
|
179
|
+
addLog("Auto-resetting in 5 seconds...");
|
|
169
180
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
170
181
|
context.facts.email = "";
|
|
171
182
|
context.facts.touched = false;
|
|
172
183
|
context.facts.status = "idle";
|
|
173
184
|
context.facts.errorMessage = "";
|
|
174
|
-
|
|
185
|
+
addLog("Form reset");
|
|
175
186
|
},
|
|
176
187
|
},
|
|
177
188
|
},
|
|
@@ -185,7 +196,7 @@ const newsletter = createModule("newsletter", {
|
|
|
185
196
|
}
|
|
186
197
|
|
|
187
198
|
if (facts.status !== prev.status) {
|
|
188
|
-
|
|
199
|
+
addLog(`Status: ${prev.status} → ${facts.status}`);
|
|
189
200
|
}
|
|
190
201
|
},
|
|
191
202
|
},
|
|
@@ -196,46 +207,8 @@ const newsletter = createModule("newsletter", {
|
|
|
196
207
|
// System
|
|
197
208
|
// ============================================================================
|
|
198
209
|
|
|
199
|
-
const system = createSystem({
|
|
210
|
+
export const system = createSystem({
|
|
200
211
|
module: newsletter,
|
|
201
212
|
debug: { runHistory: true },
|
|
202
213
|
plugins: [devtoolsPlugin({ name: "newsletter" })],
|
|
203
214
|
});
|
|
204
|
-
system.start();
|
|
205
|
-
|
|
206
|
-
// ============================================================================
|
|
207
|
-
// Logging helper
|
|
208
|
-
// ============================================================================
|
|
209
|
-
|
|
210
|
-
function log(msg: string) {
|
|
211
|
-
console.log(`[newsletter] ${msg}`);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// ============================================================================
|
|
215
|
-
// DOM Bindings
|
|
216
|
-
// ============================================================================
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// ============================================================================
|
|
220
|
-
// Render
|
|
221
|
-
// ============================================================================
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// Subscribe to all relevant facts and derivations
|
|
225
|
-
system.subscribe(
|
|
226
|
-
[
|
|
227
|
-
"email",
|
|
228
|
-
"touched",
|
|
229
|
-
"status",
|
|
230
|
-
"errorMessage",
|
|
231
|
-
"lastSubmittedAt",
|
|
232
|
-
"emailError",
|
|
233
|
-
"isValid",
|
|
234
|
-
"canSubmit",
|
|
235
|
-
],
|
|
236
|
-
render,
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
// Initial render
|
|
240
|
-
render();
|
|
241
|
-
log("Newsletter signup ready. Enter an email and subscribe.");
|
|
@@ -30,13 +30,13 @@ export interface Notification {
|
|
|
30
30
|
|
|
31
31
|
export const notificationsSchema = {
|
|
32
32
|
facts: {
|
|
33
|
-
queue: t.
|
|
33
|
+
queue: t.array<Notification>(),
|
|
34
34
|
maxVisible: t.number(),
|
|
35
35
|
now: t.number(),
|
|
36
36
|
idCounter: t.number(),
|
|
37
37
|
},
|
|
38
38
|
derivations: {
|
|
39
|
-
visibleNotifications: t.
|
|
39
|
+
visibleNotifications: t.array<Notification>(),
|
|
40
40
|
hasNotifications: t.boolean(),
|
|
41
41
|
oldestExpired: t.object<Notification | null>(),
|
|
42
42
|
},
|
|
@@ -71,24 +71,20 @@ export const notificationsModule = createModule("notifications", {
|
|
|
71
71
|
|
|
72
72
|
derive: {
|
|
73
73
|
visibleNotifications: (facts) => {
|
|
74
|
-
return
|
|
75
|
-
0,
|
|
76
|
-
facts.maxVisible as number,
|
|
77
|
-
);
|
|
74
|
+
return facts.queue.slice(0, facts.maxVisible);
|
|
78
75
|
},
|
|
79
76
|
|
|
80
77
|
hasNotifications: (facts) => {
|
|
81
|
-
return
|
|
78
|
+
return facts.queue.length > 0;
|
|
82
79
|
},
|
|
83
80
|
|
|
84
81
|
oldestExpired: (facts) => {
|
|
85
|
-
const
|
|
86
|
-
const oldest = queue[0];
|
|
82
|
+
const oldest = facts.queue[0];
|
|
87
83
|
if (!oldest) {
|
|
88
84
|
return null;
|
|
89
85
|
}
|
|
90
86
|
|
|
91
|
-
if (
|
|
87
|
+
if (facts.now > oldest.createdAt + oldest.ttl) {
|
|
92
88
|
return oldest;
|
|
93
89
|
}
|
|
94
90
|
|
|
@@ -103,23 +99,28 @@ export const notificationsModule = createModule("notifications", {
|
|
|
103
99
|
constraints: {
|
|
104
100
|
autoDismiss: {
|
|
105
101
|
priority: 50,
|
|
106
|
-
when: (
|
|
107
|
-
|
|
102
|
+
when: (facts) => {
|
|
103
|
+
const oldest = facts.queue[0];
|
|
104
|
+
if (!oldest) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return facts.now > oldest.createdAt + oldest.ttl;
|
|
109
|
+
},
|
|
110
|
+
require: (facts) => ({
|
|
108
111
|
type: "DISMISS_NOTIFICATION" as const,
|
|
109
|
-
id:
|
|
112
|
+
id: facts.queue[0].id,
|
|
110
113
|
}),
|
|
111
114
|
},
|
|
112
115
|
|
|
113
116
|
overflow: {
|
|
114
117
|
priority: 60,
|
|
115
118
|
when: (facts) => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return queue.length > (facts.maxVisible as number) + 5;
|
|
119
|
+
return facts.queue.length > facts.maxVisible + 5;
|
|
119
120
|
},
|
|
120
121
|
require: (facts) => ({
|
|
121
122
|
type: "DISMISS_NOTIFICATION" as const,
|
|
122
|
-
id:
|
|
123
|
+
id: facts.queue[0].id,
|
|
123
124
|
}),
|
|
124
125
|
},
|
|
125
126
|
},
|
|
@@ -132,7 +133,7 @@ export const notificationsModule = createModule("notifications", {
|
|
|
132
133
|
dismiss: {
|
|
133
134
|
requirement: "DISMISS_NOTIFICATION",
|
|
134
135
|
resolve: async (req, context) => {
|
|
135
|
-
context.facts.queue =
|
|
136
|
+
context.facts.queue = context.facts.queue.filter(
|
|
136
137
|
(n) => n.id !== req.id,
|
|
137
138
|
);
|
|
138
139
|
},
|
|
@@ -154,7 +155,7 @@ export const notificationsModule = createModule("notifications", {
|
|
|
154
155
|
warning: 6000,
|
|
155
156
|
error: 10000,
|
|
156
157
|
};
|
|
157
|
-
const counter =
|
|
158
|
+
const counter = facts.idCounter + 1;
|
|
158
159
|
facts.idCounter = counter;
|
|
159
160
|
|
|
160
161
|
const notification: Notification = {
|
|
@@ -165,11 +166,11 @@ export const notificationsModule = createModule("notifications", {
|
|
|
165
166
|
ttl: payload.ttl ?? ttlMap[payload.level] ?? 4000,
|
|
166
167
|
};
|
|
167
168
|
|
|
168
|
-
facts.queue = [...
|
|
169
|
+
facts.queue = [...facts.queue, notification];
|
|
169
170
|
},
|
|
170
171
|
|
|
171
172
|
dismissNotification: (facts, { id }: { id: string }) => {
|
|
172
|
-
facts.queue =
|
|
173
|
+
facts.queue = facts.queue.filter((n) => n.id !== id);
|
|
173
174
|
},
|
|
174
175
|
|
|
175
176
|
tick: (facts) => {
|
|
@@ -188,7 +189,7 @@ export const notificationsModule = createModule("notifications", {
|
|
|
188
189
|
|
|
189
190
|
export const appSchema = {
|
|
190
191
|
facts: {
|
|
191
|
-
actionLog: t.
|
|
192
|
+
actionLog: t.array<string>(),
|
|
192
193
|
},
|
|
193
194
|
events: {
|
|
194
195
|
simulateAction: { message: t.string(), level: t.string() },
|
|
@@ -204,7 +205,7 @@ export const appModule = createModule("app", {
|
|
|
204
205
|
|
|
205
206
|
events: {
|
|
206
207
|
simulateAction: (facts, { message }: { message: string }) => {
|
|
207
|
-
facts.actionLog = [...
|
|
208
|
+
facts.actionLog = [...facts.actionLog, message];
|
|
208
209
|
},
|
|
209
210
|
},
|
|
210
211
|
});
|
|
@@ -51,15 +51,15 @@ let nextOpId = 1;
|
|
|
51
51
|
|
|
52
52
|
export const optimisticUpdatesSchema = {
|
|
53
53
|
facts: {
|
|
54
|
-
items: t.
|
|
55
|
-
syncQueue: t.
|
|
54
|
+
items: t.array<TodoItem>(),
|
|
55
|
+
syncQueue: t.array<SyncQueueEntry>(),
|
|
56
56
|
syncingOpId: t.string(),
|
|
57
57
|
newItemText: t.string(),
|
|
58
58
|
serverDelay: t.number(),
|
|
59
59
|
failRate: t.number(),
|
|
60
60
|
toastMessage: t.string(),
|
|
61
61
|
toastType: t.string(),
|
|
62
|
-
eventLog: t.
|
|
62
|
+
eventLog: t.array<EventLogEntry>(),
|
|
63
63
|
},
|
|
64
64
|
derivations: {
|
|
65
65
|
totalCount: t.number(),
|
|
@@ -127,16 +127,15 @@ export const optimisticUpdatesModule = createModule("optimistic-updates", {
|
|
|
127
127
|
// ============================================================================
|
|
128
128
|
|
|
129
129
|
derive: {
|
|
130
|
-
totalCount: (facts) =>
|
|
130
|
+
totalCount: (facts) => facts.items.length,
|
|
131
131
|
|
|
132
|
-
doneCount: (facts) =>
|
|
133
|
-
(facts.items as TodoItem[]).filter((i) => i.done).length,
|
|
132
|
+
doneCount: (facts) => facts.items.filter((i) => i.done).length,
|
|
134
133
|
|
|
135
|
-
pendingCount: (facts) =>
|
|
134
|
+
pendingCount: (facts) => facts.syncQueue.length,
|
|
136
135
|
|
|
137
|
-
canAdd: (facts) =>
|
|
136
|
+
canAdd: (facts) => facts.newItemText.trim() !== "",
|
|
138
137
|
|
|
139
|
-
isSyncing: (facts) =>
|
|
138
|
+
isSyncing: (facts) => facts.syncingOpId !== "",
|
|
140
139
|
},
|
|
141
140
|
|
|
142
141
|
// ============================================================================
|
|
@@ -145,15 +144,14 @@ export const optimisticUpdatesModule = createModule("optimistic-updates", {
|
|
|
145
144
|
|
|
146
145
|
events: {
|
|
147
146
|
toggleItem: (facts, { id }) => {
|
|
148
|
-
const
|
|
149
|
-
const undoItems = items.map((i) => ({ ...i }));
|
|
147
|
+
const undoItems = facts.items.map((i) => ({ ...i }));
|
|
150
148
|
|
|
151
|
-
facts.items = items.map((i) =>
|
|
149
|
+
facts.items = facts.items.map((i) =>
|
|
152
150
|
i.id === id ? { ...i, done: !i.done } : i,
|
|
153
151
|
);
|
|
154
152
|
|
|
155
153
|
const opId = String(nextOpId++);
|
|
156
|
-
const queue = [...
|
|
154
|
+
const queue = [...facts.syncQueue];
|
|
157
155
|
queue.push({ opId, itemId: id, op: "toggle", undoItems });
|
|
158
156
|
facts.syncQueue = queue;
|
|
159
157
|
|
|
@@ -161,13 +159,12 @@ export const optimisticUpdatesModule = createModule("optimistic-updates", {
|
|
|
161
159
|
},
|
|
162
160
|
|
|
163
161
|
deleteItem: (facts, { id }) => {
|
|
164
|
-
const
|
|
165
|
-
const undoItems = items.map((i) => ({ ...i }));
|
|
162
|
+
const undoItems = facts.items.map((i) => ({ ...i }));
|
|
166
163
|
|
|
167
|
-
facts.items = items.filter((i) => i.id !== id);
|
|
164
|
+
facts.items = facts.items.filter((i) => i.id !== id);
|
|
168
165
|
|
|
169
166
|
const opId = String(nextOpId++);
|
|
170
|
-
const queue = [...
|
|
167
|
+
const queue = [...facts.syncQueue];
|
|
171
168
|
queue.push({ opId, itemId: id, op: "delete", undoItems });
|
|
172
169
|
facts.syncQueue = queue;
|
|
173
170
|
|
|
@@ -175,20 +172,19 @@ export const optimisticUpdatesModule = createModule("optimistic-updates", {
|
|
|
175
172
|
},
|
|
176
173
|
|
|
177
174
|
addItem: (facts) => {
|
|
178
|
-
const text =
|
|
175
|
+
const text = facts.newItemText.trim();
|
|
179
176
|
if (!text) {
|
|
180
177
|
return;
|
|
181
178
|
}
|
|
182
179
|
|
|
183
|
-
const
|
|
184
|
-
const undoItems = items.map((i) => ({ ...i }));
|
|
180
|
+
const undoItems = facts.items.map((i) => ({ ...i }));
|
|
185
181
|
|
|
186
182
|
const itemId = String(nextId++);
|
|
187
|
-
facts.items = [...items, { id: itemId, text, done: false }];
|
|
183
|
+
facts.items = [...facts.items, { id: itemId, text, done: false }];
|
|
188
184
|
facts.newItemText = "";
|
|
189
185
|
|
|
190
186
|
const opId = String(nextOpId++);
|
|
191
|
-
const queue = [...
|
|
187
|
+
const queue = [...facts.syncQueue];
|
|
192
188
|
queue.push({ opId, itemId, op: "add", undoItems });
|
|
193
189
|
facts.syncQueue = queue;
|
|
194
190
|
|
|
@@ -221,17 +217,12 @@ export const optimisticUpdatesModule = createModule("optimistic-updates", {
|
|
|
221
217
|
needsSync: {
|
|
222
218
|
priority: 100,
|
|
223
219
|
when: (facts) => {
|
|
224
|
-
|
|
225
|
-
const syncingOpId = facts.syncingOpId as string;
|
|
226
|
-
|
|
227
|
-
return queue.length > 0 && syncingOpId === "";
|
|
220
|
+
return facts.syncQueue.length > 0 && facts.syncingOpId === "";
|
|
228
221
|
},
|
|
229
222
|
require: (facts) => {
|
|
230
|
-
const queue = facts.syncQueue as SyncQueueEntry[];
|
|
231
|
-
|
|
232
223
|
return {
|
|
233
224
|
type: "SYNC_TODO",
|
|
234
|
-
opId:
|
|
225
|
+
opId: facts.syncQueue[0].opId,
|
|
235
226
|
};
|
|
236
227
|
},
|
|
237
228
|
},
|
|
@@ -247,8 +238,7 @@ export const optimisticUpdatesModule = createModule("optimistic-updates", {
|
|
|
247
238
|
key: (req) => `sync-${req.opId}`,
|
|
248
239
|
timeout: 10000,
|
|
249
240
|
resolve: async (req, context) => {
|
|
250
|
-
const
|
|
251
|
-
const entry = queue.find((e) => e.opId === req.opId);
|
|
241
|
+
const entry = context.facts.syncQueue.find((e) => e.opId === req.opId);
|
|
252
242
|
if (!entry) {
|
|
253
243
|
return;
|
|
254
244
|
}
|
|
@@ -260,8 +250,8 @@ export const optimisticUpdatesModule = createModule("optimistic-updates", {
|
|
|
260
250
|
`Syncing ${entry.op} for item ${entry.itemId}...`,
|
|
261
251
|
);
|
|
262
252
|
|
|
263
|
-
const serverDelay = context.facts.serverDelay
|
|
264
|
-
const failRate = context.facts.failRate
|
|
253
|
+
const serverDelay = context.facts.serverDelay;
|
|
254
|
+
const failRate = context.facts.failRate;
|
|
265
255
|
|
|
266
256
|
try {
|
|
267
257
|
await mockServerSync(entry.op, entry.itemId, serverDelay, failRate);
|
|
@@ -285,8 +275,7 @@ export const optimisticUpdatesModule = createModule("optimistic-updates", {
|
|
|
285
275
|
}
|
|
286
276
|
|
|
287
277
|
// Remove entry from queue
|
|
288
|
-
|
|
289
|
-
context.facts.syncQueue = currentQueue.filter(
|
|
278
|
+
context.facts.syncQueue = context.facts.syncQueue.filter(
|
|
290
279
|
(e) => e.opId !== req.opId,
|
|
291
280
|
);
|
|
292
281
|
context.facts.syncingOpId = "";
|
|
@@ -303,12 +292,18 @@ export const optimisticUpdatesModule = createModule("optimistic-updates", {
|
|
|
303
292
|
deps: ["syncingOpId"],
|
|
304
293
|
run: (facts, prev) => {
|
|
305
294
|
if (prev) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
295
|
+
if (prev.syncingOpId === "" && facts.syncingOpId !== "") {
|
|
296
|
+
addLogEntry(
|
|
297
|
+
facts,
|
|
298
|
+
"status",
|
|
299
|
+
`Sync started: op ${facts.syncingOpId}`,
|
|
300
|
+
);
|
|
301
|
+
} else if (prev.syncingOpId !== "" && facts.syncingOpId === "") {
|
|
302
|
+
addLogEntry(
|
|
303
|
+
facts,
|
|
304
|
+
"status",
|
|
305
|
+
`Sync completed: op ${prev.syncingOpId}`,
|
|
306
|
+
);
|
|
312
307
|
}
|
|
313
308
|
}
|
|
314
309
|
},
|
package/examples/pagination.ts
CHANGED
|
@@ -56,7 +56,7 @@ export const filtersModule = createModule("filters", {
|
|
|
56
56
|
facts.search = value;
|
|
57
57
|
},
|
|
58
58
|
setSortBy: (facts, { value }) => {
|
|
59
|
-
facts.sortBy = value;
|
|
59
|
+
facts.sortBy = value as "newest" | "oldest" | "title";
|
|
60
60
|
},
|
|
61
61
|
setCategory: (facts, { value }) => {
|
|
62
62
|
facts.category = value;
|
|
@@ -70,7 +70,7 @@ export const filtersModule = createModule("filters", {
|
|
|
70
70
|
|
|
71
71
|
export const listSchema = {
|
|
72
72
|
facts: {
|
|
73
|
-
items: t.
|
|
73
|
+
items: t.array<ListItem>(),
|
|
74
74
|
cursor: t.string(),
|
|
75
75
|
hasMore: t.boolean(),
|
|
76
76
|
isLoadingMore: t.boolean(),
|
package/examples/permissions.ts
CHANGED
|
@@ -101,7 +101,7 @@ export const authModule = createModule("auth", {
|
|
|
101
101
|
|
|
102
102
|
export const permissionsSchema = {
|
|
103
103
|
facts: {
|
|
104
|
-
permissions: t.
|
|
104
|
+
permissions: t.array<string>(),
|
|
105
105
|
loaded: t.boolean(),
|
|
106
106
|
},
|
|
107
107
|
derivations: {
|
|
@@ -132,18 +132,14 @@ export const permissionsModule = createModule("permissions", {
|
|
|
132
132
|
},
|
|
133
133
|
|
|
134
134
|
derive: {
|
|
135
|
-
canEdit: (facts) =>
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
canDelete: (facts) =>
|
|
140
|
-
(facts.self.permissions as string[]).includes("content.delete"),
|
|
141
|
-
canManageUsers: (facts) =>
|
|
142
|
-
(facts.self.permissions as string[]).includes("users.manage"),
|
|
135
|
+
canEdit: (facts) => facts.self.permissions.includes("content.edit"),
|
|
136
|
+
canPublish: (facts) => facts.self.permissions.includes("content.publish"),
|
|
137
|
+
canDelete: (facts) => facts.self.permissions.includes("content.delete"),
|
|
138
|
+
canManageUsers: (facts) => facts.self.permissions.includes("users.manage"),
|
|
143
139
|
canViewAnalytics: (facts) =>
|
|
144
|
-
|
|
145
|
-
isAdmin: (_facts, derive) => derive.canManageUsers
|
|
146
|
-
permissionCount: (facts) =>
|
|
140
|
+
facts.self.permissions.includes("analytics.view"),
|
|
141
|
+
isAdmin: (_facts, derive) => derive.canManageUsers,
|
|
142
|
+
permissionCount: (facts) => facts.self.permissions.length,
|
|
147
143
|
},
|
|
148
144
|
|
|
149
145
|
events: {
|
|
@@ -156,13 +152,11 @@ export const permissionsModule = createModule("permissions", {
|
|
|
156
152
|
constraints: {
|
|
157
153
|
loadPermissions: {
|
|
158
154
|
when: (facts) => {
|
|
159
|
-
return
|
|
160
|
-
(facts.auth.token as string) !== "" && !(facts.self.loaded as boolean)
|
|
161
|
-
);
|
|
155
|
+
return facts.auth.token !== "" && !facts.self.loaded;
|
|
162
156
|
},
|
|
163
157
|
require: (facts) => ({
|
|
164
158
|
type: "FETCH_PERMISSIONS",
|
|
165
|
-
role: facts.auth.role
|
|
159
|
+
role: facts.auth.role,
|
|
166
160
|
}),
|
|
167
161
|
},
|
|
168
162
|
},
|
|
@@ -186,7 +180,7 @@ export const permissionsModule = createModule("permissions", {
|
|
|
186
180
|
|
|
187
181
|
export const contentSchema = {
|
|
188
182
|
facts: {
|
|
189
|
-
articles: t.
|
|
183
|
+
articles: t.array<Article>(),
|
|
190
184
|
loaded: t.boolean(),
|
|
191
185
|
publishRequested: t.string(),
|
|
192
186
|
deleteRequested: t.string(),
|
|
@@ -221,9 +215,7 @@ export const contentModule = createModule("content", {
|
|
|
221
215
|
constraints: {
|
|
222
216
|
loadContent: {
|
|
223
217
|
when: (facts) => {
|
|
224
|
-
return
|
|
225
|
-
(facts.auth.token as string) !== "" && !(facts.self.loaded as boolean)
|
|
226
|
-
);
|
|
218
|
+
return facts.auth.token !== "" && !facts.self.loaded;
|
|
227
219
|
},
|
|
228
220
|
require: { type: "LOAD_CONTENT" },
|
|
229
221
|
},
|
|
@@ -231,28 +223,26 @@ export const contentModule = createModule("content", {
|
|
|
231
223
|
publishArticle: {
|
|
232
224
|
when: (facts) => {
|
|
233
225
|
return (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
"content.publish",
|
|
237
|
-
)
|
|
226
|
+
facts.self.publishRequested !== "" &&
|
|
227
|
+
facts.permissions.permissions.includes("content.publish")
|
|
238
228
|
);
|
|
239
229
|
},
|
|
240
230
|
require: (facts) => ({
|
|
241
231
|
type: "PUBLISH_ARTICLE",
|
|
242
|
-
articleId: facts.self.publishRequested
|
|
232
|
+
articleId: facts.self.publishRequested,
|
|
243
233
|
}),
|
|
244
234
|
},
|
|
245
235
|
|
|
246
236
|
deleteArticle: {
|
|
247
237
|
when: (facts) => {
|
|
248
238
|
return (
|
|
249
|
-
|
|
250
|
-
|
|
239
|
+
facts.self.deleteRequested !== "" &&
|
|
240
|
+
facts.permissions.permissions.includes("content.delete")
|
|
251
241
|
);
|
|
252
242
|
},
|
|
253
243
|
require: (facts) => ({
|
|
254
244
|
type: "DELETE_ARTICLE",
|
|
255
|
-
articleId: facts.self.deleteRequested
|
|
245
|
+
articleId: facts.self.deleteRequested,
|
|
256
246
|
}),
|
|
257
247
|
},
|
|
258
248
|
},
|
|
@@ -275,8 +265,7 @@ export const contentModule = createModule("content", {
|
|
|
275
265
|
context.facts.actionStatus = "publishing";
|
|
276
266
|
await apiPublishArticle(req.articleId);
|
|
277
267
|
|
|
278
|
-
|
|
279
|
-
context.facts.articles = articles.map((a) => {
|
|
268
|
+
context.facts.articles = context.facts.articles.map((a) => {
|
|
280
269
|
if (a.id === req.articleId) {
|
|
281
270
|
return { ...a, status: "published" as const };
|
|
282
271
|
}
|
|
@@ -295,8 +284,9 @@ export const contentModule = createModule("content", {
|
|
|
295
284
|
context.facts.actionStatus = "deleting";
|
|
296
285
|
await apiDeleteArticle(req.articleId);
|
|
297
286
|
|
|
298
|
-
|
|
299
|
-
|
|
287
|
+
context.facts.articles = context.facts.articles.filter(
|
|
288
|
+
(a) => a.id !== req.articleId,
|
|
289
|
+
);
|
|
300
290
|
context.facts.deleteRequested = "";
|
|
301
291
|
context.facts.actionStatus = "done";
|
|
302
292
|
},
|