@hunsu/protocol 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.
Files changed (47) hide show
  1. package/LICENSE +157 -0
  2. package/dist/command-decoder.d.ts +5 -0
  3. package/dist/command-decoder.js +329 -0
  4. package/dist/constants.d.ts +15 -0
  5. package/dist/constants.js +15 -0
  6. package/dist/decoder-helpers.d.ts +58 -0
  7. package/dist/decoder-helpers.js +1757 -0
  8. package/dist/errors.d.ts +14 -0
  9. package/dist/errors.js +28 -0
  10. package/dist/event-decoder.d.ts +5 -0
  11. package/dist/event-decoder.js +232 -0
  12. package/dist/index.d.ts +10 -0
  13. package/dist/index.js +10 -0
  14. package/dist/lifecycle.d.ts +67 -0
  15. package/dist/lifecycle.js +271 -0
  16. package/dist/model.d.ts +1058 -0
  17. package/dist/model.js +1 -0
  18. package/dist/primitives.d.ts +59 -0
  19. package/dist/primitives.js +135 -0
  20. package/dist/projection.d.ts +8 -0
  21. package/dist/projection.js +875 -0
  22. package/dist/prompt-template.d.ts +12 -0
  23. package/dist/prompt-template.js +83 -0
  24. package/dist/protocol-validation.d.ts +36 -0
  25. package/dist/protocol-validation.js +1139 -0
  26. package/dist/result.d.ts +20 -0
  27. package/dist/result.js +21 -0
  28. package/dist/workflow-helpers.d.ts +31 -0
  29. package/dist/workflow-helpers.js +193 -0
  30. package/dist/workflow.d.ts +10 -0
  31. package/dist/workflow.js +594 -0
  32. package/package.json +30 -0
  33. package/src/command-decoder.ts +292 -0
  34. package/src/constants.ts +27 -0
  35. package/src/decoder-helpers.ts +1672 -0
  36. package/src/errors.ts +38 -0
  37. package/src/event-decoder.ts +225 -0
  38. package/src/index.ts +43 -0
  39. package/src/lifecycle.ts +403 -0
  40. package/src/model.ts +1233 -0
  41. package/src/primitives.ts +196 -0
  42. package/src/projection.ts +1081 -0
  43. package/src/prompt-template.ts +97 -0
  44. package/src/protocol-validation.ts +1252 -0
  45. package/src/result.ts +35 -0
  46. package/src/workflow-helpers.ts +239 -0
  47. package/src/workflow.ts +753 -0
package/src/result.ts ADDED
@@ -0,0 +1,35 @@
1
+ export type Result<T, E> =
2
+ | { ok: true; value: T }
3
+ | { ok: false; error: E };
4
+
5
+ export type Option<T> =
6
+ | { type: "some"; value: T }
7
+ | { type: "none" };
8
+
9
+ export function ok<T>(value: T): Result<T, never> {
10
+ return { ok: true, value };
11
+ }
12
+
13
+ export function err<E>(error: E): Result<never, E> {
14
+ return { ok: false, error };
15
+ }
16
+
17
+ export function some<T>(value: T): Option<T> {
18
+ return { type: "some", value };
19
+ }
20
+
21
+ export function none<T = never>(): Option<T> {
22
+ return { type: "none" };
23
+ }
24
+
25
+ export function map<T, U, E>(result: Result<T, E>, transform: (value: T) => U): Result<U, E> {
26
+ return result.ok ? ok(transform(result.value)) : result;
27
+ }
28
+
29
+ export function flatMap<T, U, E>(result: Result<T, E>, transform: (value: T) => Result<U, E>): Result<U, E> {
30
+ return result.ok ? transform(result.value) : result;
31
+ }
32
+
33
+ export function fromNullable<T, E>(value: T | null | undefined, error: E): Result<T, E> {
34
+ return value === null || value === undefined ? err(error) : ok(value);
35
+ }
@@ -0,0 +1,239 @@
1
+ import { DomainInvariantError, unwrapDomainModelResult } from "./errors.ts";
2
+ import type {
3
+ BoardProjection,
4
+ Destination,
5
+ DestinationId,
6
+ TeamName,
7
+ HunsuId,
8
+ LineId,
9
+ LineRecord,
10
+ MoveRecord,
11
+ NodeId,
12
+ NodeRecord,
13
+ RequestId,
14
+ SkillDraftRecord
15
+ } from "./model.ts";
16
+ import {
17
+ makeLineId,
18
+ makeTeamName,
19
+ makeNodeId,
20
+ } from "./primitives.ts";
21
+
22
+ export const TEAM_NAME_POOL = [
23
+ "T1",
24
+ "Gen.G",
25
+ "Hanwha Life Esports",
26
+ "KT Rolster",
27
+ "Dplus KIA",
28
+ "DRX",
29
+ "Nongshim RedForce",
30
+ "BNK FearX",
31
+ "DN Freecs",
32
+ "OKSavingsBank BRION",
33
+ "Bilibili Gaming",
34
+ "JD Gaming",
35
+ "Top Esports",
36
+ "Anyone's Legend",
37
+ "Ninjas in Pyjamas",
38
+ "Weibo Gaming",
39
+ "Invictus Gaming",
40
+ "Team WE",
41
+ "LNG Esports",
42
+ "TT Gaming",
43
+ "LGD Gaming",
44
+ "EDward Gaming",
45
+ "Ultra Prime",
46
+ "Oh My God",
47
+ "Cloud9",
48
+ "Team Liquid",
49
+ "FlyQuest",
50
+ "Shopify Rebellion",
51
+ "100 Thieves",
52
+ "Dignitas",
53
+ "LYON",
54
+ "Sentinels",
55
+ "G2 Esports",
56
+ "Fnatic",
57
+ "Karmine Corp",
58
+ "Team Vitality",
59
+ "Team Heretics",
60
+ "GIANTX",
61
+ "Movistar KOI",
62
+ "SK Gaming",
63
+ "Natus Vincere",
64
+ "Rogue"
65
+ ];
66
+
67
+ export function upsertById<T extends { id: string }>(items: T[], item: T): T[] {
68
+ if (items.some(existing => existing.id === item.id)) {
69
+ return items.map(existing => (existing.id === item.id ? item : existing));
70
+ }
71
+ return [...items, item];
72
+ }
73
+
74
+ export function unique<T>(items: T[]): T[] {
75
+ return Array.from(new Set(items));
76
+ }
77
+
78
+ export function requestIdForLine(state: BoardProjection, lineId: LineId): RequestId {
79
+ return requireLine(state, lineId).requestId;
80
+ }
81
+
82
+ export function rootNodeId(requestId: RequestId): NodeId {
83
+ return unwrapDomainModelResult(makeNodeId(`${requestId}:root`));
84
+ }
85
+
86
+ export function nextNodeId(state: BoardProjection): NodeId {
87
+ const highest = state.nodes.reduce((max, node) => {
88
+ const match = node.id.match(/^N(\d+)$/i);
89
+ return match ? Math.max(max, Number(match[1])) : max;
90
+ }, 0);
91
+ return unwrapDomainModelResult(makeNodeId(`N${String(highest + 1).padStart(4, "0")}`));
92
+ }
93
+
94
+ export function nextTeamName(state: BoardProjection): TeamName {
95
+ const used = new Set<string>(state.lines.map(line => line.teamName).filter((name): name is TeamName => Boolean(name)));
96
+ const available = TEAM_NAME_POOL.find(name => !used.has(name));
97
+ if (available) {
98
+ return unwrapDomainModelResult(makeTeamName(available));
99
+ }
100
+ return unwrapDomainModelResult(makeTeamName(`${TEAM_NAME_POOL[state.lines.length % TEAM_NAME_POOL.length]}-${state.lines.length + 1}`));
101
+ }
102
+
103
+ export function nextForkLineId(lineId: LineId, hunsuId: HunsuId): LineId {
104
+ return unwrapDomainModelResult(makeLineId(`${lineId}/fork-${hunsuId}`));
105
+ }
106
+
107
+ export function requireRequest(state: BoardProjection, requestId: string): void {
108
+ if (!state.requests.some(request => request.id === requestId)) {
109
+ throw new DomainInvariantError(`Unknown request: ${requestId}`);
110
+ }
111
+ }
112
+
113
+ export function requireLine(state: BoardProjection, lineId: string): LineRecord {
114
+ const line = state.lines.find(candidate => candidate.id === lineId);
115
+ if (!line) {
116
+ throw new DomainInvariantError(`Unknown line: ${lineId}`);
117
+ }
118
+ return line;
119
+ }
120
+
121
+ export function requirePlayableLine(line: LineRecord): void {
122
+ if (line.status !== "active") {
123
+ throw new DomainInvariantError(`TEAM line ${line.id} cannot continue from status ${line.status}`);
124
+ }
125
+ }
126
+
127
+ export function requireNode(state: BoardProjection, nodeId: NodeId): NodeRecord {
128
+ const node = state.nodes.find(candidate => candidate.id === nodeId);
129
+ if (!node) {
130
+ throw new DomainInvariantError(`Unknown node: ${nodeId}`);
131
+ }
132
+ return node;
133
+ }
134
+
135
+ export function requireRootNode(state: BoardProjection, requestId: RequestId): NodeRecord {
136
+ return requireNode(state, rootNodeId(requestId));
137
+ }
138
+
139
+ export function requireCurrentNode(state: BoardProjection, line: LineRecord): NodeRecord {
140
+ return requireNode(state, line.currentNodeId);
141
+ }
142
+
143
+ export function requireMove(state: BoardProjection, moveId: string): MoveRecord {
144
+ const move = state.moves.find(candidate => candidate.id === moveId);
145
+ if (!move) {
146
+ throw new DomainInvariantError(`Unknown MOVE: ${moveId}`);
147
+ }
148
+ return move;
149
+ }
150
+
151
+ export function requireSkillDraft(state: BoardProjection, draftId: string): SkillDraftRecord {
152
+ const draft = state.skillDrafts.find(candidate => candidate.id === draftId);
153
+ if (!draft) {
154
+ throw new DomainInvariantError(`Unknown Skill Draft: ${draftId}`);
155
+ }
156
+ return draft;
157
+ }
158
+
159
+ export function nodeForMove(state: BoardProjection, move: MoveRecord): NodeRecord | undefined {
160
+ return move.toNodeId ? state.nodes.find(node => node.id === move.toNodeId) : undefined;
161
+ }
162
+
163
+ export function requireExistingDestination(state: BoardProjection, destinationId: DestinationId): Destination {
164
+ const destination = state.destinations.find(candidate => candidate.id === destinationId);
165
+ if (!destination) {
166
+ throw new DomainInvariantError(`Unknown Destination: ${destinationId}`);
167
+ }
168
+ return destination;
169
+ }
170
+
171
+ export function requireDestinationInNode(node: NodeRecord, destinationId: DestinationId): Destination {
172
+ const destination = node.destinations.find(candidate => candidate.id === destinationId);
173
+ if (!destination) {
174
+ throw new DomainInvariantError(`Unknown Destination in node ${node.id}: ${destinationId}`);
175
+ }
176
+ return destination;
177
+ }
178
+
179
+ export function requireClaimableDestination(state: BoardProjection, destinationId: DestinationId): Destination {
180
+ const destination = requireExistingDestination(state, destinationId);
181
+ if (destination.status !== "pending" && destination.status !== "blocked") {
182
+ throw new DomainInvariantError(`Destination ${destinationId} cannot be claimed from status ${destination.status}`);
183
+ }
184
+ return destination;
185
+ }
186
+
187
+ export function requireBlockableDestination(state: BoardProjection, destinationId: DestinationId): Destination {
188
+ const destination = requireExistingDestination(state, destinationId);
189
+ if (isClosedDestination(destination)) {
190
+ throw new DomainInvariantError(`Destination ${destinationId} cannot be blocked from status ${destination.status}`);
191
+ }
192
+ return destination;
193
+ }
194
+
195
+ export function requireBlockableDestinationInNode(node: NodeRecord, destinationId: DestinationId): Destination {
196
+ const destination = requireDestinationInNode(node, destinationId);
197
+ if (isClosedDestination(destination)) {
198
+ throw new DomainInvariantError(`Destination ${destinationId} cannot be blocked from status ${destination.status}`);
199
+ }
200
+ return destination;
201
+ }
202
+
203
+ export function requireBlockedDestinationInNode(node: NodeRecord, destinationId: DestinationId): Destination {
204
+ const destination = requireDestinationInNode(node, destinationId);
205
+ if (destination.status !== "blocked") {
206
+ throw new DomainInvariantError(`Destination ${destinationId} cannot be unblocked from status ${destination.status}`);
207
+ }
208
+ return destination;
209
+ }
210
+
211
+ export function requireReachibleDestinationInNode(node: NodeRecord, destinationId: DestinationId): Destination {
212
+ const destination = requireDestinationInNode(node, destinationId);
213
+ if (isClosedDestination(destination)) {
214
+ throw new DomainInvariantError(`Destination ${destinationId} cannot be reached from status ${destination.status}`);
215
+ }
216
+ return destination;
217
+ }
218
+
219
+ export function isClosedDestination(destination: Destination): boolean {
220
+ return destination.status === "reached" || destination.status === "canceled" || destination.status === "superseded";
221
+ }
222
+
223
+ export function assertNonEmpty<T>(items: T[], message: string): void {
224
+ if (items.length === 0) {
225
+ throw new DomainInvariantError(message);
226
+ }
227
+ }
228
+
229
+ export function assertSingleDestination<T>(items: T[], message: string): void {
230
+ if (items.length !== 1) {
231
+ throw new DomainInvariantError(message);
232
+ }
233
+ }
234
+
235
+ export function assertText(value: string, message: string): void {
236
+ if (value.trim() === "") {
237
+ throw new DomainInvariantError(message);
238
+ }
239
+ }