@nestos/kanban 0.0.0-stage → 0.5.1
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 +121 -2
- package/dist/activity-C6xDM-cg.d.cts +232 -0
- package/dist/activity-Dw8DOw0L.d.ts +232 -0
- package/dist/activity.cjs +288 -0
- package/dist/activity.cjs.map +1 -0
- package/dist/activity.d.cts +34 -0
- package/dist/activity.d.ts +34 -0
- package/dist/activity.js +288 -0
- package/dist/activity.js.map +1 -0
- package/dist/chunk-2YFPV5GF.cjs +16 -0
- package/dist/chunk-2YFPV5GF.cjs.map +1 -0
- package/dist/chunk-4KF7KUAI.cjs +221 -0
- package/dist/chunk-4KF7KUAI.cjs.map +1 -0
- package/dist/chunk-4XF6LQSU.cjs +9 -0
- package/dist/chunk-4XF6LQSU.cjs.map +1 -0
- package/dist/chunk-5DLZWW5Y.js +148 -0
- package/dist/chunk-5DLZWW5Y.js.map +1 -0
- package/dist/chunk-5XKOCKY2.js +58 -0
- package/dist/chunk-5XKOCKY2.js.map +1 -0
- package/dist/chunk-6MN3H3YF.cjs +183 -0
- package/dist/chunk-6MN3H3YF.cjs.map +1 -0
- package/dist/chunk-ATUFFNLV.js +221 -0
- package/dist/chunk-ATUFFNLV.js.map +1 -0
- package/dist/chunk-DFTOTBGF.js +439 -0
- package/dist/chunk-DFTOTBGF.js.map +1 -0
- package/dist/chunk-G6FCDLDF.js +16 -0
- package/dist/chunk-G6FCDLDF.js.map +1 -0
- package/dist/chunk-IUI5CI2S.cjs +439 -0
- package/dist/chunk-IUI5CI2S.cjs.map +1 -0
- package/dist/chunk-KYNYPKKH.cjs +71 -0
- package/dist/chunk-KYNYPKKH.cjs.map +1 -0
- package/dist/chunk-L44TM4EE.cjs +1498 -0
- package/dist/chunk-L44TM4EE.cjs.map +1 -0
- package/dist/chunk-LSE6JG5I.cjs +20 -0
- package/dist/chunk-LSE6JG5I.cjs.map +1 -0
- package/dist/chunk-O4OGAAV4.js +71 -0
- package/dist/chunk-O4OGAAV4.js.map +1 -0
- package/dist/chunk-OHQTKGQN.js +1498 -0
- package/dist/chunk-OHQTKGQN.js.map +1 -0
- package/dist/chunk-PSKQAFDF.cjs +58 -0
- package/dist/chunk-PSKQAFDF.cjs.map +1 -0
- package/dist/chunk-QTMTBEBA.js +20 -0
- package/dist/chunk-QTMTBEBA.js.map +1 -0
- package/dist/chunk-RQP77ZTY.js +9 -0
- package/dist/chunk-RQP77ZTY.js.map +1 -0
- package/dist/chunk-T7P5EIC4.cjs +148 -0
- package/dist/chunk-T7P5EIC4.cjs.map +1 -0
- package/dist/chunk-TB2RZGDV.js +183 -0
- package/dist/chunk-TB2RZGDV.js.map +1 -0
- package/dist/content-CAIp1njn.d.cts +35 -0
- package/dist/content-CGMJdyH_.d.ts +35 -0
- package/dist/contracts/index.cjs +21 -0
- package/dist/contracts/index.cjs.map +1 -0
- package/dist/contracts/index.d.cts +4 -0
- package/dist/contracts/index.d.ts +4 -0
- package/dist/contracts/index.js +21 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/discovery-DxpirOz3.d.cts +61 -0
- package/dist/discovery-gSqWpuRm.d.ts +61 -0
- package/dist/discovery.cjs +379 -0
- package/dist/discovery.cjs.map +1 -0
- package/dist/discovery.d.cts +22 -0
- package/dist/discovery.d.ts +22 -0
- package/dist/discovery.js +379 -0
- package/dist/discovery.js.map +1 -0
- package/dist/execution.cjs +368 -0
- package/dist/execution.cjs.map +1 -0
- package/dist/execution.d.cts +89 -0
- package/dist/execution.d.ts +89 -0
- package/dist/execution.js +368 -0
- package/dist/execution.js.map +1 -0
- package/dist/index.cjs +76 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +61 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/module-BbvwdCDF.d.cts +11 -0
- package/dist/module-CWNK2BMj.d.ts +11 -0
- package/dist/planning-CcFZYdIL.d.cts +844 -0
- package/dist/planning-CcFZYdIL.d.ts +844 -0
- package/dist/ports-Bn6ro2FB.d.ts +142 -0
- package/dist/ports-CaL6geqR.d.cts +142 -0
- package/dist/storage.cjs +289 -0
- package/dist/storage.cjs.map +1 -0
- package/dist/storage.d.cts +71 -0
- package/dist/storage.d.ts +71 -0
- package/dist/storage.js +289 -0
- package/dist/storage.js.map +1 -0
- package/dist/store-Ct0gOAAg.d.ts +28 -0
- package/dist/store-q63eSMoT.d.cts +28 -0
- package/dist/ui/index.cjs +1961 -0
- package/dist/ui/index.cjs.map +1 -0
- package/dist/ui/index.d.cts +209 -0
- package/dist/ui/index.d.ts +209 -0
- package/dist/ui/index.js +1961 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/kanban.css +194 -0
- package/package.json +113 -4
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } async function _asyncOptionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = await fn(value); } else if (op === 'call' || op === 'optionalCall') { value = await fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
var _chunkL44TM4EEcjs = require('./chunk-L44TM4EE.cjs');
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
var _chunk4KF7KUAIcjs = require('./chunk-4KF7KUAI.cjs');
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
var _chunk2YFPV5GFcjs = require('./chunk-2YFPV5GF.cjs');
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
var _chunkKYNYPKKHcjs = require('./chunk-KYNYPKKH.cjs');
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
var _chunkPSKQAFDFcjs = require('./chunk-PSKQAFDF.cjs');
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
var _chunk6MN3H3YFcjs = require('./chunk-6MN3H3YF.cjs');
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
var _chunkLSE6JG5Icjs = require('./chunk-LSE6JG5I.cjs');
|
|
38
|
+
|
|
39
|
+
// src/references.ts
|
|
40
|
+
async function resolveReference(reference, store, read) {
|
|
41
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, typeof reference === "string" && reference.length <= 100, "INVALID_REFERENCE");
|
|
42
|
+
const match = /^KB-([1-9]\d*)(?:#([1-9]\d*)|\/(L|D)-([1-9]\d*))?$/i.exec(reference.trim());
|
|
43
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, match && [match[1], match[2], match[4]].every((value) => value === void 0 || Number.isSafeInteger(Number(value))), "INVALID_REFERENCE");
|
|
44
|
+
const id = await _optionalChain([store, 'access', _ => _.boardId, 'optionalCall', _2 => _2(Number(match[1]))]);
|
|
45
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, id, "REFERENCE_NOT_FOUND");
|
|
46
|
+
const view = await read(id), board = view.state.board;
|
|
47
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, board.number === Number(match[1]), "INVALID_NUMBERING");
|
|
48
|
+
const target = { boardId: id, scope: board.scope, reference: _chunkPSKQAFDFcjs.kanbanReference.call(void 0, board) };
|
|
49
|
+
if (match[2]) {
|
|
50
|
+
const item = [...view.items, ..._nullishCoalesce(view.state.archiveIndex, () => ( []))].find((item2) => item2.number === Number(match[2]));
|
|
51
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, item, "REFERENCE_NOT_FOUND");
|
|
52
|
+
target.itemId = item.id;
|
|
53
|
+
target.reference = _chunkPSKQAFDFcjs.kanbanReference.call(void 0, board, item);
|
|
54
|
+
} else if (match[3]) {
|
|
55
|
+
const record = view.records.find((record2) => record2.kind === (match[3].toUpperCase() === "L" ? "log" : "decision") && record2.number === Number(match[4]));
|
|
56
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, record, "REFERENCE_NOT_FOUND");
|
|
57
|
+
target.recordId = record.id;
|
|
58
|
+
target.reference = _chunkPSKQAFDFcjs.kanbanReference.call(void 0, board, record);
|
|
59
|
+
}
|
|
60
|
+
return target;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/suggestions.ts
|
|
64
|
+
function validateSuggestionQuery(query) {
|
|
65
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, query && ["item", "parent", "dependency", "area", "milestone", "decision", "session"].includes(query.kind), "INVALID_SUGGESTION_KIND");
|
|
66
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, query.search === void 0 || typeof query.search === "string" && query.search.length <= 256, "INVALID_FILTER");
|
|
67
|
+
const limit = _nullishCoalesce(query.limit, () => ( 20));
|
|
68
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, Number.isSafeInteger(limit) && limit > 0 && limit <= 50, "INVALID_LIMIT");
|
|
69
|
+
if (query.excludeItemId !== void 0) _chunk4KF7KUAIcjs.identity.call(void 0, query.excludeItemId);
|
|
70
|
+
if (query.excludeIds !== void 0) {
|
|
71
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, Array.isArray(query.excludeIds) && query.excludeIds.length <= 100, "INVALID_FILTER");
|
|
72
|
+
query.excludeIds.forEach(_chunk4KF7KUAIcjs.identity);
|
|
73
|
+
}
|
|
74
|
+
if (query.childKind !== void 0) _chunk4KF7KUAIcjs.identity.call(void 0, query.childKind);
|
|
75
|
+
return limit;
|
|
76
|
+
}
|
|
77
|
+
function suggestionPage(revision, rows, query) {
|
|
78
|
+
const limit = validateSuggestionQuery(query), search = _nullishCoalesce(_optionalChain([query, 'access', _3 => _3.search, 'optionalAccess', _4 => _4.trim, 'call', _5 => _5(), 'access', _6 => _6.toLocaleLowerCase, 'call', _7 => _7()]), () => ( ""));
|
|
79
|
+
const matched = rows.filter((row) => !_optionalChain([query, 'access', _8 => _8.excludeIds, 'optionalAccess', _9 => _9.includes, 'call', _10 => _10(row.id)])).filter((row) => `${row.id} ${row.label} ${_nullishCoalesce(row.detail, () => ( ""))}`.toLocaleLowerCase().includes(search)).sort((a, b) => score(a.label, search) - score(b.label, search) || a.label.localeCompare(b.label) || a.id.localeCompare(b.id));
|
|
80
|
+
return { revision, items: matched.slice(0, limit).map((row) => ({ id: row.id, label: row.label.slice(0, 256), ...row.detail ? { detail: row.detail.slice(0, 512) } : {}, ...row.status ? { status: row.status } : {}, ...row.archived !== void 0 ? { archived: row.archived } : {} })), hasMore: matched.length > limit };
|
|
81
|
+
}
|
|
82
|
+
var score = (label, query) => label.toLocaleLowerCase() === query ? 0 : label.toLocaleLowerCase().startsWith(query) ? 1 : 2;
|
|
83
|
+
function suggestFromState(state, query, items = _chunkL44TM4EEcjs.catalog.call(void 0, state), records = (_nullishCoalesce(state.records, () => ( []))).map(_chunkL44TM4EEcjs.recordSummary)) {
|
|
84
|
+
validateSuggestionQuery(query);
|
|
85
|
+
if (query.kind === "area" || query.kind === "milestone") {
|
|
86
|
+
const names = [...new Set(items.map((item) => item.planning[query.kind]).filter((name) => !!name))];
|
|
87
|
+
return suggestionPage(state.board.revision, names.map((name) => ({ id: name, label: name })), query);
|
|
88
|
+
}
|
|
89
|
+
if (query.kind === "decision") return suggestionPage(state.board.revision, records.filter((record) => record.kind === "decision" && record.status === "confirmed").map((record) => ({ id: record.id, label: record.title, detail: _chunkPSKQAFDFcjs.kanbanReference.call(void 0, state.board, record) })), query);
|
|
90
|
+
if (query.kind === "session") {
|
|
91
|
+
const ids = /* @__PURE__ */ new Set([
|
|
92
|
+
...state.board.participants.map((p) => p.sessionId),
|
|
93
|
+
...items.flatMap((item) => item.sessionIds),
|
|
94
|
+
...records.flatMap((record) => record.sessionIds)
|
|
95
|
+
]);
|
|
96
|
+
return suggestionPage(state.board.revision, [...ids].map((id) => ({ id, label: id })), query);
|
|
97
|
+
}
|
|
98
|
+
const blocked = query.kind === "dependency" && query.excludeItemId ? dependents(items, query.excludeItemId) : /* @__PURE__ */ new Set();
|
|
99
|
+
const rank = query.kind === "parent" ? _chunk4KF7KUAIcjs.workType.call(void 0, state.board, _nullishCoalesce(_nullishCoalesce(query.childKind, () => ( state.board.defaultWorkType)), () => ( "task"))).rank : void 0;
|
|
100
|
+
const rows = items.filter((item) => item.id !== query.excludeItemId && !blocked.has(item.id) && (query.kind !== "dependency" || item.status !== "cancelled") && (rank === void 0 || item.archivedAt === void 0 && !_chunkL44TM4EEcjs.terminal.call(void 0, item) && _chunk4KF7KUAIcjs.workType.call(void 0, state.board, item.planning.kind).rank < rank)).map((item) => ({
|
|
101
|
+
id: item.id,
|
|
102
|
+
label: item.title,
|
|
103
|
+
status: item.status,
|
|
104
|
+
archived: item.archivedAt !== void 0,
|
|
105
|
+
detail: [_chunkPSKQAFDFcjs.kanbanReference.call(void 0, state.board, item), item.planning.area, item.planning.milestone].filter(Boolean).join(" \xB7 ")
|
|
106
|
+
}));
|
|
107
|
+
return suggestionPage(state.board.revision, rows, query);
|
|
108
|
+
}
|
|
109
|
+
function dependents(items, id) {
|
|
110
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const item of items) for (const dependency of item.planning.dependencies) reverse.set(dependency, [..._nullishCoalesce(reverse.get(dependency), () => ( [])), item.id]);
|
|
112
|
+
const result = /* @__PURE__ */ new Set([id]), pending = [id];
|
|
113
|
+
while (pending.length) for (const next of _nullishCoalesce(reverse.get(pending.pop()), () => ( []))) if (!result.has(next)) {
|
|
114
|
+
result.add(next);
|
|
115
|
+
pending.push(next);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/queries.ts
|
|
121
|
+
function queryItems(state, query = {}, all = _chunkL44TM4EEcjs.catalog.call(void 0, state), now = Date.now()) {
|
|
122
|
+
const { cursor, limit = 50, ...filters } = query;
|
|
123
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, Number.isSafeInteger(limit) && limit >= 1 && limit <= 200, "INVALID_PAGE_SIZE");
|
|
124
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, ["ready", "backlog", "in-progress", "review", "closed", "proposed", "archive", "all"].includes(_nullishCoalesce(query.view, () => ( "ready"))), "INVALID_VIEW");
|
|
125
|
+
for (const field of ["area", "milestone", "parentId", "kind", "sessionId", "search"]) if (filters[field] !== void 0) _chunk4KF7KUAIcjs.requireValue.call(void 0, typeof filters[field] === "string" && filters[field].length <= 256, "INVALID_FILTER");
|
|
126
|
+
if (filters.reference) _chunk4KF7KUAIcjs.requireValue.call(void 0, typeof filters.reference.system === "string" && typeof filters.reference.id === "string" && filters.reference.system.length <= 256 && filters.reference.id.length <= 256, "INVALID_REFERENCE_FILTER");
|
|
127
|
+
const fingerprint = JSON.stringify(Object.fromEntries(Object.entries({ ...filters, view: _nullishCoalesce(filters.view, () => ( "ready")) }).sort(([a], [b]) => a.localeCompare(b)))), revision = state.board.revision;
|
|
128
|
+
all = all.map((item) => ({ ...item, task: _chunkKYNYPKKHcjs.projectKanbanTask.call(void 0, item, state.workflow, now, all) }));
|
|
129
|
+
const matches = all.filter((item) => matchesQuery(item, filters, state.board)).sort(filters.view === "archive" ? (a, b) => (_nullishCoalesce(b.archivedAt, () => ( 0))) - (_nullishCoalesce(a.archivedAt, () => ( 0))) || a.id.localeCompare(b.id) : order);
|
|
130
|
+
let start = 0;
|
|
131
|
+
if (cursor) {
|
|
132
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, typeof cursor === "string" && cursor.length <= 4e3, "INVALID_CURSOR");
|
|
133
|
+
let decoded;
|
|
134
|
+
try {
|
|
135
|
+
decoded = JSON.parse(cursor);
|
|
136
|
+
} catch (e) {
|
|
137
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, false, "INVALID_CURSOR");
|
|
138
|
+
}
|
|
139
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, decoded && decoded.filter === fingerprint, "CURSOR_SCOPE_MISMATCH");
|
|
140
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, decoded.revision === revision, "CURSOR_STALE");
|
|
141
|
+
start = matches.findIndex((i) => i.id === decoded.after) + 1;
|
|
142
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, start > 0, "INVALID_CURSOR");
|
|
143
|
+
}
|
|
144
|
+
const progress = progressByParent(state, all);
|
|
145
|
+
const items = matches.slice(start, start + limit).map((item) => ({ ...item, ...progress.has(item.id) ? { progress: progress.get(item.id) } : {} }));
|
|
146
|
+
return { revision, total: matches.length, items, ...start + items.length < matches.length ? {
|
|
147
|
+
nextCursor: JSON.stringify({ revision, after: items.at(-1).id, filter: fingerprint })
|
|
148
|
+
} : {} };
|
|
149
|
+
}
|
|
150
|
+
function order(a, b) {
|
|
151
|
+
return a.planning.priority - b.planning.priority || a.createdAt - b.createdAt || a.id.localeCompare(b.id);
|
|
152
|
+
}
|
|
153
|
+
function matchesQuery(item, query, board) {
|
|
154
|
+
if (query.phase && _optionalChain([item, 'access', _11 => _11.task, 'optionalAccess', _12 => _12.phase]) !== query.phase) return false;
|
|
155
|
+
if (!matchesView(item, _nullishCoalesce(query.view, () => ( "ready")))) return false;
|
|
156
|
+
for (const field of ["area", "milestone", "parentId", "kind"]) if (query[field] !== void 0 && item.planning[field] !== query[field]) return false;
|
|
157
|
+
if (query.reference && !_optionalChain([item, 'access', _13 => _13.externalRefs, 'optionalAccess', _14 => _14.some, 'call', _15 => _15((r) => r.system === query.reference.system && r.id === query.reference.id)])) return false;
|
|
158
|
+
if (query.sessionId && !item.sessionIds.includes(query.sessionId)) return false;
|
|
159
|
+
if (query.search && !`${_chunkPSKQAFDFcjs.kanbanReference.call(void 0, board, item)} ${item.id} ${item.title} ${_nullishCoalesce(item.planning.area, () => ( ""))}`.toLowerCase().includes(query.search.toLowerCase())) return false;
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
function matchesView(item, view) {
|
|
163
|
+
if (view === "archive") return item.archivedAt !== void 0;
|
|
164
|
+
if (view === "all") return true;
|
|
165
|
+
if (item.archivedAt !== void 0) return false;
|
|
166
|
+
if (view === "in-progress") return item.status === "claimed" || item.status === "review";
|
|
167
|
+
if (view === "closed") return _chunkL44TM4EEcjs.terminal.call(void 0, item);
|
|
168
|
+
return item.status === { ready: "open", backlog: "backlog", review: "review", proposed: "proposed" }[view];
|
|
169
|
+
}
|
|
170
|
+
function overview(state, now, items = state.board.items.map(_chunkL44TM4EEcjs.summary)) {
|
|
171
|
+
const count = (status) => items.filter((i) => i.status === status).length;
|
|
172
|
+
const age = state.board.policy.backlogReviewMs;
|
|
173
|
+
return {
|
|
174
|
+
revision: state.board.revision,
|
|
175
|
+
visible: items.length,
|
|
176
|
+
active: items.filter((i) => !_chunkL44TM4EEcjs.terminal.call(void 0, i)).length,
|
|
177
|
+
archived: _nullishCoalesce(_optionalChain([state, 'access', _16 => _16.archiveIndex, 'optionalAccess', _17 => _17.length]), () => ( 0)),
|
|
178
|
+
ready: count("open"),
|
|
179
|
+
inProgress: count("claimed") + count("review"),
|
|
180
|
+
backlog: count("backlog"),
|
|
181
|
+
proposed: count("proposed"),
|
|
182
|
+
closed: count("completed") + count("cancelled"),
|
|
183
|
+
dueForReview: age ? items.filter((i) => i.status === "backlog" && now - i.updatedAt >= age).length : 0,
|
|
184
|
+
limits: _chunkL44TM4EEcjs.limits.call(void 0, state)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function progressByParent(state, all) {
|
|
188
|
+
const byId = new Map(all.map((i) => [i.id, i]));
|
|
189
|
+
const result = /* @__PURE__ */ new Map();
|
|
190
|
+
for (const item of all.filter((i) => _chunk4KF7KUAIcjs.executable.call(void 0, state.board, i))) {
|
|
191
|
+
let parent = item.planning.parentId;
|
|
192
|
+
const seen = /* @__PURE__ */ new Set();
|
|
193
|
+
while (parent && !seen.has(parent)) {
|
|
194
|
+
seen.add(parent);
|
|
195
|
+
const progress = _nullishCoalesce(result.get(parent), () => ( { total: 0, completed: 0, cancelled: 0, inProgress: 0 }));
|
|
196
|
+
progress.total++;
|
|
197
|
+
if (item.status === "completed") progress.completed++;
|
|
198
|
+
if (item.status === "cancelled") progress.cancelled++;
|
|
199
|
+
if (item.status === "claimed" || item.status === "review") progress.inProgress++;
|
|
200
|
+
result.set(parent, progress);
|
|
201
|
+
parent = _optionalChain([byId, 'access', _18 => _18.get, 'call', _19 => _19(parent), 'optionalAccess', _20 => _20.planning, 'access', _21 => _21.parentId]);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/module.ts
|
|
208
|
+
function createKanbanModule(options) {
|
|
209
|
+
return new KanbanModule(options);
|
|
210
|
+
}
|
|
211
|
+
var KanbanModule = (_class = class {
|
|
212
|
+
constructor(options) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);_class.prototype.__init13.call(this);_class.prototype.__init14.call(this);_class.prototype.__init15.call(this);_class.prototype.__init16.call(this);_class.prototype.__init17.call(this);_class.prototype.__init18.call(this);_class.prototype.__init19.call(this);
|
|
213
|
+
this.options = options;
|
|
214
|
+
this.now = _nullishCoalesce(options.now, () => ( Date.now));
|
|
215
|
+
this.id = _nullishCoalesce(options.id, () => ( _chunk6MN3H3YFcjs.randomUUID));
|
|
216
|
+
this.actor = _chunk4KF7KUAIcjs.normalizeActor.call(void 0, options.actor);
|
|
217
|
+
}
|
|
218
|
+
__init() {this.stopping = false}
|
|
219
|
+
__init2() {this.pending = /* @__PURE__ */ new Set()}
|
|
220
|
+
async quiesce() {
|
|
221
|
+
this.stopping = true;
|
|
222
|
+
await Promise.allSettled([...this.pending]);
|
|
223
|
+
}
|
|
224
|
+
async dispose() {
|
|
225
|
+
await this.quiesce();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
__init3() {this.workflow = (boardId) => this.guard(this.id(), boardId, "workflow", async () => structuredClone(await _asyncNullishCoalesce((await this.readView(boardId)).state.workflow, async () => ( { plans: {}, dispatches: {}, ready: [], completedPlans: [] }))))}
|
|
231
|
+
__init4() {this.resolve = (reference) => this.guard(this.id(), void 0, "resolve", () => resolveReference(reference, this.options.store, (id) => this.readView(id)))}
|
|
232
|
+
__init5() {this.suggest = (boardId, query) => this.guard(this.id(), boardId, "suggest", async () => {
|
|
233
|
+
const snapshot = structuredClone(query), limit = validateSuggestionQuery(snapshot), view = await this.readView(boardId), state = view.state;
|
|
234
|
+
const suggestions = () => suggestFromState(state, snapshot, [...view.items, ..._nullishCoalesce(state.archiveIndex, () => ( []))], view.records);
|
|
235
|
+
if (!this.options.suggestions) return suggestions();
|
|
236
|
+
const rows = await this.options.suggestions(structuredClone(state.board), snapshot.kind, _nullishCoalesce(_optionalChain([snapshot, 'access', _47 => _47.search, 'optionalAccess', _48 => _48.trim, 'call', _49 => _49()]), () => ( "")), limit + 1);
|
|
237
|
+
if (rows === void 0) return suggestions();
|
|
238
|
+
const known = suggestions().items.filter((row) => !rows.some((entry) => entry.id === row.id));
|
|
239
|
+
return suggestionPage(state.board.revision, [...rows, ...known], snapshot);
|
|
240
|
+
})}
|
|
241
|
+
__init6() {this.records = (boardId, query) => this.guard(this.id(), boardId, "records", async () => {
|
|
242
|
+
const view = await this.readView(boardId);
|
|
243
|
+
return structuredClone(_chunkL44TM4EEcjs.queryRecords.call(void 0, view.state, query, view.records));
|
|
244
|
+
})}
|
|
245
|
+
__init7() {this.record = (boardId, recordId) => this.guard(this.id(), boardId, "record", async () => {
|
|
246
|
+
_chunk4KF7KUAIcjs.identity.call(void 0, recordId);
|
|
247
|
+
const record = await _asyncOptionalChain([(await this.readState(boardId)), 'access', async _50 => _50.records, 'optionalAccess', async _51 => _51.find, 'call', async _52 => _52((record2) => record2.id === recordId)]);
|
|
248
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, record, "RECORD_NOT_FOUND");
|
|
249
|
+
return structuredClone(record);
|
|
250
|
+
})}
|
|
251
|
+
__init8() {this.currentPrincipal = async () => structuredClone(_chunk4KF7KUAIcjs.principal.call(void 0, this.actor))}
|
|
252
|
+
__init9() {this.create = (input) => {
|
|
253
|
+
const snapshot = structuredClone(input);
|
|
254
|
+
return this.guard(this.id(), snapshot.id, "create", async () => {
|
|
255
|
+
const initial = _chunkL44TM4EEcjs.createBoardState.call(void 0, snapshot, this.actor, this.now()), board = initial.board;
|
|
256
|
+
await this.authorize(board, "create");
|
|
257
|
+
const state = await this.options.store.update(board.id, `create:${board.id}`, JSON.stringify({ snapshot, actor: this.actor }), (previous) => {
|
|
258
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, !previous, "BOARD_EXISTS");
|
|
259
|
+
return initial;
|
|
260
|
+
});
|
|
261
|
+
return structuredClone(state.board);
|
|
262
|
+
});
|
|
263
|
+
}}
|
|
264
|
+
__init10() {this.read = (boardId) => this.guard(
|
|
265
|
+
this.id(),
|
|
266
|
+
boardId,
|
|
267
|
+
"read",
|
|
268
|
+
async () => structuredClone((await this.readState(boardId)).board)
|
|
269
|
+
)}
|
|
270
|
+
__init11() {this.catalog = () => this.guard(this.id(), void 0, "catalog", async () => {
|
|
271
|
+
const boards = [];
|
|
272
|
+
for (const id of await this.options.store.ids()) {
|
|
273
|
+
try {
|
|
274
|
+
const view = await this.readView(id);
|
|
275
|
+
if (view.state.board.deletedAt !== void 0) continue;
|
|
276
|
+
boards.push({ ...view.state.board, executionPhase: _chunkLSE6JG5Icjs.boardControlPhase.call(void 0, { ...view.state.board, items: view.items }), items: view.items.map(({ id: id2, title, number }) => ({ id: id2, title, ...number === void 0 ? {} : { number } })) });
|
|
277
|
+
} catch (error) {
|
|
278
|
+
if (!(error instanceof _chunk2YFPV5GFcjs.KanbanError) || error.code !== "FORBIDDEN") throw error;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return structuredClone(boards);
|
|
282
|
+
})}
|
|
283
|
+
__init12() {this.list = () => this.guard(this.id(), void 0, "list", async () => {
|
|
284
|
+
const boards = [];
|
|
285
|
+
for (const boardId of await this.options.store.ids()) {
|
|
286
|
+
try {
|
|
287
|
+
const board = (await this.readState(boardId)).board;
|
|
288
|
+
if (board.deletedAt === void 0) boards.push(structuredClone(board));
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (!(error instanceof _chunk2YFPV5GFcjs.KanbanError) || error.code !== "FORBIDDEN") throw error;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return boards;
|
|
294
|
+
})}
|
|
295
|
+
__init13() {this.execute = (boardId, input) => this.guard(input.operationId, boardId, `execute:${input.command.type}`, async () => {
|
|
296
|
+
const request = structuredClone(input);
|
|
297
|
+
_chunk4KF7KUAIcjs.identity.call(void 0, request.operationId);
|
|
298
|
+
const state = await this.readState(boardId);
|
|
299
|
+
await this.authorize(state.board, request.command.type, request.command);
|
|
300
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, state.board.deletedAt === void 0, "BOARD_DELETED");
|
|
301
|
+
if (request.command.type === "delete-board") await _optionalChain([this, 'access', _53 => _53.options, 'access', _54 => _54.verifyDeletion, 'optionalCall', _55 => _55(structuredClone(state.board))]);
|
|
302
|
+
await this.verifyRelease(state.board, request.command);
|
|
303
|
+
const verifyStage = request.command.type === "observe-execution" && request.command.outcome === "succeeded" ? "observe" : request.command.type === "accept-execution" && request.command.accept ? "accept" : void 0;
|
|
304
|
+
if (verifyStage && "dispatchId" in request.command) {
|
|
305
|
+
const dispatch = _optionalChain([state, 'access', _56 => _56.workflow, 'optionalAccess', _57 => _57.dispatches, 'access', _58 => _58[request.command.dispatchId]]);
|
|
306
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, dispatch, "DISPATCH_NOT_FOUND");
|
|
307
|
+
const outputs = request.command.type === "observe-execution" ? request.command.outputs : dispatch.outputs;
|
|
308
|
+
if (_optionalChain([outputs, 'optionalAccess', _59 => _59.length])) _chunk4KF7KUAIcjs.requireValue.call(void 0, await _optionalChain([this, 'access', _60 => _60.options, 'access', _61 => _61.verifyOutputs, 'optionalCall', _62 => _62({
|
|
309
|
+
stage: verifyStage,
|
|
310
|
+
board: state.board,
|
|
311
|
+
itemId: dispatch.itemId,
|
|
312
|
+
dispatchId: dispatch.id,
|
|
313
|
+
outputs: structuredClone(outputs)
|
|
314
|
+
})]), "OUTPUT_VERIFICATION_REQUIRED");
|
|
315
|
+
}
|
|
316
|
+
const restore = request.command.type === "restore-item" ? await this.options.store.archived(boardId, request.command.itemId) : void 0;
|
|
317
|
+
const result = await this.options.store.update(boardId, request.operationId, JSON.stringify({ actor: this.actor, request }), (current) => {
|
|
318
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, current && current.board.revision === state.board.revision, "REVISION_CONFLICT");
|
|
319
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, current.board.revision === request.expectedRevision, "REVISION_CONFLICT");
|
|
320
|
+
if (restore) current.restore = restore;
|
|
321
|
+
return _chunkL44TM4EEcjs.reduce.call(void 0, current, request.command, this.actor, request.operationId, this.now(), this.id());
|
|
322
|
+
});
|
|
323
|
+
await this.publishNotifications(boardId, request.operationId);
|
|
324
|
+
return structuredClone(result.board);
|
|
325
|
+
})}
|
|
326
|
+
__init14() {this.events = (boardId, after = 0, limit = 100) => this.guard(this.id(), boardId, "events", async () => {
|
|
327
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, Number.isSafeInteger(after) && after >= 0 && Number.isSafeInteger(limit) && limit > 0 && limit <= 500, "INVALID_CURSOR");
|
|
328
|
+
const { state } = await this.readView(boardId);
|
|
329
|
+
return this.options.store.events(boardId, after, limit, state.sequence);
|
|
330
|
+
})}
|
|
331
|
+
__init15() {this.query = (boardId, query) => this.guard(this.id(), boardId, "query", async () => {
|
|
332
|
+
const view = await this.readView(boardId);
|
|
333
|
+
return structuredClone(queryItems(view.state, query, [...view.items, ..._nullishCoalesce(view.state.archiveIndex, () => ( []))], this.now()));
|
|
334
|
+
})}
|
|
335
|
+
__init16() {this.overview = (boardId) => this.guard(this.id(), boardId, "overview", async () => {
|
|
336
|
+
const view = await this.readView(boardId);
|
|
337
|
+
return overview(view.state, this.now(), view.items);
|
|
338
|
+
})}
|
|
339
|
+
__init17() {this.item = (boardId, itemId) => this.guard(this.id(), boardId, "item", async () => {
|
|
340
|
+
_chunk4KF7KUAIcjs.identity.call(void 0, itemId);
|
|
341
|
+
const state = await this.readState(boardId), item = state.board.items.find((i) => i.id === itemId);
|
|
342
|
+
if (item) return { item: structuredClone(item), task: _chunkKYNYPKKHcjs.projectKanbanTask.call(void 0, item, state.workflow, this.now(), [...state.board.items, ..._nullishCoalesce(state.archiveIndex, () => ( []))]) };
|
|
343
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, _optionalChain([state, 'access', _63 => _63.archiveIndex, 'optionalAccess', _64 => _64.some, 'call', _65 => _65((i) => i.id === itemId)]), "ITEM_NOT_FOUND");
|
|
344
|
+
const record = await this.options.store.archived(boardId, itemId);
|
|
345
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, record, "REVISION_CONFLICT");
|
|
346
|
+
return { item: structuredClone(record.item), task: _chunkKYNYPKKHcjs.projectKanbanTask.call(void 0, record.item, state.workflow, this.now(), state.archiveIndex), archive: { archivedAt: record.archivedAt, reason: record.reason } };
|
|
347
|
+
})}
|
|
348
|
+
__init18() {this.related = (sessionId) => this.guard(this.id(), void 0, "related", async () => {
|
|
349
|
+
_chunk4KF7KUAIcjs.identity.call(void 0, sessionId);
|
|
350
|
+
const result = [];
|
|
351
|
+
for (const board of await this.list()) {
|
|
352
|
+
const state = await this.readState(board.id);
|
|
353
|
+
if (_chunkL44TM4EEcjs.related.call(void 0, board).includes(sessionId) || _optionalChain([state, 'access', _66 => _66.records, 'optionalAccess', _67 => _67.some, 'call', _68 => _68((record) => record.sources.some((source) => source.sessionId === sessionId))]) || _optionalChain([state, 'access', _69 => _69.archiveIndex, 'optionalAccess', _70 => _70.some, 'call', _71 => _71((i) => i.sessionIds.includes(sessionId))])) result.push(board);
|
|
354
|
+
}
|
|
355
|
+
return result;
|
|
356
|
+
})}
|
|
357
|
+
__init19() {this.scan = () => this.guard(this.id(), void 0, "scan", async () => {
|
|
358
|
+
const errors = [];
|
|
359
|
+
for (const board of await this.list()) {
|
|
360
|
+
try {
|
|
361
|
+
await this.execute(board.id, { operationId: this.id(), expectedRevision: board.revision, command: { type: "scan" } });
|
|
362
|
+
} catch (error) {
|
|
363
|
+
if (!(error instanceof _chunk2YFPV5GFcjs.KanbanError) || error.code !== "REVISION_CONFLICT") errors.push(error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (errors.length) throw new AggregateError(errors, "KANBAN_SCAN_FAILED");
|
|
367
|
+
})}
|
|
368
|
+
async verifyRelease(board, command) {
|
|
369
|
+
if (command.type !== "release-item") return;
|
|
370
|
+
const item = board.items.find((i) => i.id === command.itemId);
|
|
371
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, _optionalChain([item, 'optionalAccess', _72 => _72.owner]) && item.owner.token === command.token, "OWNER_MISMATCH");
|
|
372
|
+
const own = _chunk4KF7KUAIcjs.sameOwner.call(void 0, item.owner, this.actor), actor = _chunk4KF7KUAIcjs.principal.call(void 0, this.actor);
|
|
373
|
+
const coordinator = _optionalChain([board, 'access', _73 => _73.members, 'optionalAccess', _74 => _74.some, 'call', _75 => _75((m) => _chunk4KF7KUAIcjs.principalKey.call(void 0, m) === _chunk4KF7KUAIcjs.principalKey.call(void 0, actor) && _optionalChain([m, 'access', _76 => _76.roles, 'optionalAccess', _77 => _77.includes, 'call', _78 => _78("coordinator")]))]);
|
|
374
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, own || coordinator, "OWNER_MISMATCH");
|
|
375
|
+
if (!(own && actor.kind === "human")) _chunk4KF7KUAIcjs.requireValue.call(void 0, await _optionalChain([this, 'access', _79 => _79.options, 'access', _80 => _80.verifyRelease, 'optionalCall', _81 => _81({ actor: this.actor, board: structuredClone(board), item: structuredClone(item) })]), "EXECUTION_NOT_SETTLED");
|
|
376
|
+
}
|
|
377
|
+
async publishNotifications(boardId, operationId) {
|
|
378
|
+
if (!this.options.onCommitted) return;
|
|
379
|
+
const started = this.now();
|
|
380
|
+
try {
|
|
381
|
+
await this.options.onCommitted(boardId, this, this.now());
|
|
382
|
+
this.log(operationId, boardId, "notify", started, "success");
|
|
383
|
+
} catch (error) {
|
|
384
|
+
this.log(operationId, boardId, "notify", started, "failure", safeCode(error, "NOTIFICATION_PROJECTION_FAILED"));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
async readView(boardId) {
|
|
388
|
+
_chunk4KF7KUAIcjs.identity.call(void 0, boardId);
|
|
389
|
+
if (!this.options.store.view) return _chunkL44TM4EEcjs.viewFromState.call(void 0, await this.readState(boardId));
|
|
390
|
+
const view = await this.options.store.view(boardId);
|
|
391
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, view, "BOARD_NOT_FOUND");
|
|
392
|
+
await this.authorize(view.state.board, "read");
|
|
393
|
+
return view;
|
|
394
|
+
}
|
|
395
|
+
async readState(boardId) {
|
|
396
|
+
_chunk4KF7KUAIcjs.identity.call(void 0, boardId);
|
|
397
|
+
const state = await this.options.store.read(boardId);
|
|
398
|
+
_chunk4KF7KUAIcjs.requireValue.call(void 0, state, "BOARD_NOT_FOUND");
|
|
399
|
+
await this.authorize(state.board, "read");
|
|
400
|
+
return state;
|
|
401
|
+
}
|
|
402
|
+
authorize(board, action, command) {
|
|
403
|
+
return this.options.authorize({ actor: structuredClone(this.actor), board: structuredClone(board), action, command: structuredClone(command) });
|
|
404
|
+
}
|
|
405
|
+
async guard(operationId, boardId, stage, run) {
|
|
406
|
+
const start = this.now();
|
|
407
|
+
if (this.stopping) {
|
|
408
|
+
this.log(operationId, boardId, stage, start, "failure", "SERVICE_STOPPING");
|
|
409
|
+
throw new (0, _chunk2YFPV5GFcjs.KanbanError)("SERVICE_STOPPING");
|
|
410
|
+
}
|
|
411
|
+
const operation = Promise.resolve().then(run);
|
|
412
|
+
this.pending.add(operation);
|
|
413
|
+
try {
|
|
414
|
+
const result = await operation;
|
|
415
|
+
this.log(operationId, boardId, stage, start, "success");
|
|
416
|
+
return result;
|
|
417
|
+
} catch (error) {
|
|
418
|
+
this.log(operationId, boardId, stage, start, "failure", safeCode(error, "PORT_FAILURE"));
|
|
419
|
+
throw error;
|
|
420
|
+
} finally {
|
|
421
|
+
this.pending.delete(operation);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
log(operationId, boardId, stage, start, result, code) {
|
|
425
|
+
try {
|
|
426
|
+
this.options.log({ operationId, boardId, stage, result, code, durationMs: this.now() - start });
|
|
427
|
+
} catch (e2) {
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}, _class);
|
|
431
|
+
function safeCode(error, fallback) {
|
|
432
|
+
const code = _optionalChain([error, 'optionalAccess', _82 => _82.code]);
|
|
433
|
+
return typeof code === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? code : fallback;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
exports.createKanbanModule = createKanbanModule;
|
|
439
|
+
//# sourceMappingURL=chunk-IUI5CI2S.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/li/share/prj/x1/release/local-libraries/work/1ca933a0-6fe7-4e9c-b13b-76d7f318e0fb/source/libraries/kanban/dist/chunk-IUI5CI2S.cjs","../src/references.ts","../src/suggestions.ts","../src/queries.ts","../src/module.ts"],"names":["item","record"],"mappings":"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","file":"/home/li/share/prj/x1/release/local-libraries/work/1ca933a0-6fe7-4e9c-b13b-76d7f318e0fb/source/libraries/kanban/dist/chunk-IUI5CI2S.cjs","sourcesContent":[null,"import {kanbanReference, type KanbanReferenceTarget} from './contracts';\nimport type {KanbanReadView, KanbanStore} from './ports';\nimport {requireValue} from './rules';\n\nexport async function resolveReference(reference: string, store: KanbanStore, read: (id: string) => Promise<KanbanReadView>): Promise<KanbanReferenceTarget> {\n requireValue(typeof reference === 'string' && reference.length <= 100, 'INVALID_REFERENCE');\n const match = /^KB-([1-9]\\d*)(?:#([1-9]\\d*)|\\/(L|D)-([1-9]\\d*))?$/i.exec(reference.trim());\n requireValue(match && [match[1], match[2], match[4]].every(value => value === undefined || Number.isSafeInteger(Number(value))), 'INVALID_REFERENCE');\n const id = await store.boardId?.(Number(match[1])); requireValue(id, 'REFERENCE_NOT_FOUND');\n const view = await read(id), board = view.state.board;\n requireValue(board.number === Number(match[1]), 'INVALID_NUMBERING');\n const target: KanbanReferenceTarget = {boardId: id, scope: board.scope, reference: kanbanReference(board)};\n if (match[2]) {\n const item = [...view.items, ...(view.state.archiveIndex ?? [])].find(item => item.number === Number(match[2]));\n requireValue(item, 'REFERENCE_NOT_FOUND'); target.itemId = item.id; target.reference = kanbanReference(board, item);\n } else if (match[3]) {\n const record = view.records.find(record => record.kind === (match[3].toUpperCase() === 'L' ? 'log' : 'decision') && record.number === Number(match[4]));\n requireValue(record, 'REFERENCE_NOT_FOUND'); target.recordId = record.id; target.reference = kanbanReference(board, record);\n }\n return target;\n}\n","import {kanbanReference} from './contracts';\nimport type {KanbanSuggestion, KanbanSuggestionPage, KanbanSuggestionQuery} from './contracts';\nimport type {KanbanState} from './ports';\nimport {recordSummary} from './project-records';\nimport {catalog, terminal} from './planning';\nimport {workType} from './configuration';\nimport {identity, requireValue} from './rules';\n\nexport function validateSuggestionQuery(query: KanbanSuggestionQuery): number {\n requireValue(query && ['item', 'parent', 'dependency', 'area', 'milestone', 'decision', 'session'].includes(query.kind), 'INVALID_SUGGESTION_KIND');\n requireValue(query.search === undefined || typeof query.search === 'string' && query.search.length <= 256, 'INVALID_FILTER');\n const limit = query.limit ?? 20; requireValue(Number.isSafeInteger(limit) && limit > 0 && limit <= 50, 'INVALID_LIMIT');\n if (query.excludeItemId !== undefined) identity(query.excludeItemId);\n if (query.excludeIds !== undefined) {requireValue(Array.isArray(query.excludeIds) && query.excludeIds.length <= 100, 'INVALID_FILTER'); query.excludeIds.forEach(identity);}\n if (query.childKind !== undefined) identity(query.childKind);\n return limit;\n}\nexport function suggestionPage(revision: number, rows: KanbanSuggestion[], query: KanbanSuggestionQuery): KanbanSuggestionPage {\n const limit = validateSuggestionQuery(query), search = query.search?.trim().toLocaleLowerCase() ?? '';\n const matched = rows.filter(row => !query.excludeIds?.includes(row.id)).filter(row => `${row.id} ${row.label} ${row.detail ?? ''}`.toLocaleLowerCase().includes(search))\n .sort((a, b) => score(a.label, search) - score(b.label, search) || a.label.localeCompare(b.label) || a.id.localeCompare(b.id));\n return {revision, items: matched.slice(0, limit).map(row => ({id: row.id, label: row.label.slice(0, 256), ...(row.detail ? {detail: row.detail.slice(0, 512)} : {}), ...(row.status ? {status: row.status} : {}), ...(row.archived !== undefined ? {archived: row.archived} : {})})), hasMore: matched.length > limit};\n}\nconst score = (label: string, query: string) => label.toLocaleLowerCase() === query ? 0 : label.toLocaleLowerCase().startsWith(query) ? 1 : 2;\nexport function suggestFromState(state: KanbanState, query: KanbanSuggestionQuery, items = catalog(state), records = (state.records ?? []).map(recordSummary)): KanbanSuggestionPage {\n validateSuggestionQuery(query);\n if (query.kind === 'area' || query.kind === 'milestone') {\n const names = [...new Set(items.map(item => item.planning[query.kind as 'area' | 'milestone']).filter((name): name is string => !!name))];\n return suggestionPage(state.board.revision, names.map(name => ({id: name, label: name})), query);\n }\n if (query.kind === 'decision') return suggestionPage(state.board.revision, records\n .filter(record => record.kind === 'decision' && record.status === 'confirmed').map(record => ({id: record.id, label: record.title, detail: kanbanReference(state.board, record)})), query);\n if (query.kind === 'session') {\n const ids = new Set([...state.board.participants.map(p => p.sessionId), ...items.flatMap(item => item.sessionIds),\n ...records.flatMap(record => record.sessionIds)]);\n return suggestionPage(state.board.revision, [...ids].map(id => ({id, label: id})), query);\n }\n const blocked = query.kind === 'dependency' && query.excludeItemId ? dependents(items, query.excludeItemId) : new Set<string>();\n const rank = query.kind === 'parent' ? workType(state.board, query.childKind ?? state.board.defaultWorkType ?? 'task').rank : undefined;\n const rows = items.filter(item => item.id !== query.excludeItemId && !blocked.has(item.id)\n && (query.kind !== 'dependency' || item.status !== 'cancelled')\n && (rank === undefined || item.archivedAt === undefined && !terminal(item) && workType(state.board, item.planning.kind).rank < rank))\n .map(item => ({id: item.id, label: item.title, status: item.status, archived: item.archivedAt !== undefined,\n detail: [kanbanReference(state.board, item), item.planning.area, item.planning.milestone].filter(Boolean).join(' · ')}));\n return suggestionPage(state.board.revision, rows, query);\n}\nfunction dependents(items: import('./contracts').KanbanItemSummary[], id: string): Set<string> {\n const reverse = new Map<string, string[]>();\n for (const item of items) for (const dependency of item.planning.dependencies) reverse.set(dependency, [...(reverse.get(dependency) ?? []), item.id]);\n const result = new Set([id]), pending = [id];\n while (pending.length) for (const next of reverse.get(pending.pop()!) ?? []) if (!result.has(next)) {result.add(next); pending.push(next);}\n return result;\n}\n","import {projectKanbanTask} from './contracts/task-projection';\nimport {kanbanReference} from './contracts';\nimport type {KanbanItemPage, KanbanItemQuery, KanbanItemSummary, KanbanOverview} from './contracts';\nimport type {KanbanState} from './ports';\nimport {requireValue} from './rules';\nimport {executable} from './configuration';\nimport {catalog, limits, summary, terminal} from './planning';\n\nexport function queryItems(state: KanbanState, query: KanbanItemQuery = {}, all = catalog(state), now = Date.now()): KanbanItemPage {\n const {cursor, limit = 50, ...filters} = query;\n requireValue(Number.isSafeInteger(limit) && limit >= 1 && limit <= 200, 'INVALID_PAGE_SIZE');\n requireValue(['ready', 'backlog', 'in-progress', 'review', 'closed', 'proposed', 'archive', 'all'].includes(query.view ?? 'ready'), 'INVALID_VIEW');\n for (const field of ['area', 'milestone', 'parentId', 'kind', 'sessionId', 'search'] as const) if (filters[field] !== undefined) requireValue(typeof filters[field] === 'string' && filters[field]!.length <= 256, 'INVALID_FILTER');\n if (filters.reference) requireValue(typeof filters.reference.system === 'string' && typeof filters.reference.id === 'string' && filters.reference.system.length <= 256 && filters.reference.id.length <= 256, 'INVALID_REFERENCE_FILTER');\n const fingerprint = JSON.stringify(Object.fromEntries(Object.entries({...filters, view: filters.view ?? 'ready'}).sort(([a], [b]) => a.localeCompare(b)))), revision = state.board.revision;\n all = all.map(item => ({...item, task: projectKanbanTask(item, state.workflow, now, all)}));\n const matches = all.filter(item => matchesQuery(item, filters, state.board)).sort(filters.view === 'archive'\n ? (a, b) => (b.archivedAt ?? 0) - (a.archivedAt ?? 0) || a.id.localeCompare(b.id) : order);\n let start = 0;\n if (cursor) {\n requireValue(typeof cursor === 'string' && cursor.length <= 4000, 'INVALID_CURSOR');\n let decoded: {revision: number; after: string; filter: string};\n try {decoded = JSON.parse(cursor);} catch {requireValue(false, 'INVALID_CURSOR');}\n requireValue(decoded && decoded.filter === fingerprint, 'CURSOR_SCOPE_MISMATCH');\n requireValue(decoded.revision === revision, 'CURSOR_STALE');\n start = matches.findIndex(i => i.id === decoded.after) + 1; requireValue(start > 0, 'INVALID_CURSOR');\n }\n const progress = progressByParent(state, all);\n const items = matches.slice(start, start + limit).map(item => ({...item, ...(progress.has(item.id) ? {progress: progress.get(item.id)} : {})}));\n return {revision, total: matches.length, items, ...(start + items.length < matches.length ? {\n nextCursor: JSON.stringify({revision, after: items.at(-1)!.id, filter: fingerprint}),\n } : {})};\n}\nfunction order(a: KanbanItemSummary, b: KanbanItemSummary): number {\n return a.planning.priority - b.planning.priority || a.createdAt - b.createdAt || a.id.localeCompare(b.id);\n}\nfunction matchesQuery(item: KanbanItemSummary, query: Omit<KanbanItemQuery, 'cursor' | 'limit'>, board: KanbanState['board']): boolean {\n if (query.phase && item.task?.phase !== query.phase) return false;\n if (!matchesView(item, query.view ?? 'ready')) return false;\n for (const field of ['area', 'milestone', 'parentId', 'kind'] as const) if (query[field] !== undefined && item.planning[field] !== query[field]) return false;\n if (query.reference && !item.externalRefs?.some(r => r.system === query.reference!.system && r.id === query.reference!.id)) return false;\n if (query.sessionId && !item.sessionIds.includes(query.sessionId)) return false;\n if (query.search && !`${kanbanReference(board, item)} ${item.id} ${item.title} ${item.planning.area ?? ''}`.toLowerCase().includes(query.search.toLowerCase())) return false;\n return true;\n}\nfunction matchesView(item: KanbanItemSummary, view: KanbanItemQuery['view']): boolean {\n if (view === 'archive') return item.archivedAt !== undefined;\n if (view === 'all') return true;\n if (item.archivedAt !== undefined) return false;\n if (view === 'in-progress') return item.status === 'claimed' || item.status === 'review';\n if (view === 'closed') return terminal(item);\n return item.status === ({ready: 'open', backlog: 'backlog', review: 'review', proposed: 'proposed'} as Record<string, string>)[view!];\n}\nexport function overview(state: KanbanState, now: number, items = state.board.items.map(summary)): KanbanOverview {\n const count = (status: string) => items.filter(i => i.status === status).length;\n const age = state.board.policy.backlogReviewMs;\n return {revision: state.board.revision, visible: items.length, active: items.filter(i => !terminal(i)).length, archived: state.archiveIndex?.length ?? 0, ready: count('open'),\n inProgress: count('claimed') + count('review'), backlog: count('backlog'), proposed: count('proposed'),\n closed: count('completed') + count('cancelled'), dueForReview: age ? items.filter(i => i.status === 'backlog' && now - i.updatedAt >= age).length : 0,\n limits: limits(state)};\n}\n\nfunction progressByParent(state: KanbanState, all: KanbanItemSummary[]): Map<string, NonNullable<KanbanItemSummary['progress']>> {\n const byId = new Map(all.map(i => [i.id, i]));\n const result = new Map<string, NonNullable<KanbanItemSummary['progress']>>();\n for (const item of all.filter(i => executable(state.board, i))) {\n let parent = item.planning.parentId; const seen = new Set<string>();\n while (parent && !seen.has(parent)) {\n seen.add(parent); const progress = result.get(parent) ?? {total: 0, completed: 0, cancelled: 0, inProgress: 0};\n progress.total++; if (item.status === 'completed') progress.completed++; if (item.status === 'cancelled') progress.cancelled++;\n if (item.status === 'claimed' || item.status === 'review') progress.inProgress++;\n result.set(parent, progress); parent = byId.get(parent)?.planning.parentId;\n }\n }\n return result;\n}\n","import {boardControlPhase} from './contracts/board-control';\nimport {projectKanbanTask} from './contracts/task-projection';\nimport {resolveReference} from './references';\nimport {suggestFromState, suggestionPage, validateSuggestionQuery} from './suggestions';\nimport {queryRecords} from './project-records';\nimport { randomUUID } from './utils';\nimport type { KanbanBoard, KanbanRequest, KanbanService, KanbanItemQuery, KanbanBoardInput } from './contracts';\nimport { KanbanError, type KanbanModuleOptions, type KanbanState } from './ports';\nimport { identity, requireValue } from './rules';\nimport {createBoardState} from './board';\nimport {normalizeActor, principal, principalKey, sameOwner} from './identity';\nimport {queryItems, overview} from './queries';\nimport {viewFromState} from './storage-codec';\nimport { reduce, related } from './reducer';\n\n/** Optional host-owned service; no timer or model calls are started implicitly. */\nexport function createKanbanModule(options: KanbanModuleOptions): KanbanModuleHandle { return new KanbanModule(options); }\nexport interface KanbanModuleHandle extends KanbanService {quiesce(): Promise<void>; dispose(): Promise<void>}\nclass KanbanModule implements KanbanService {\n private stopping = false;\n private readonly pending = new Set<Promise<unknown>>();\n async quiesce(): Promise<void> {this.stopping = true; await Promise.allSettled([...this.pending]);}\n async dispose(): Promise<void> {await this.quiesce();}\n private readonly now;\n private readonly id;\n private readonly actor;\n constructor(private readonly options: KanbanModuleOptions) {\n this.now = options.now ?? Date.now; this.id = options.id ?? randomUUID;\n this.actor = normalizeActor(options.actor);\n }\n workflow = (boardId: string) => this.guard(this.id(), boardId, 'workflow', async () => structuredClone((await this.readView(boardId)).state.workflow ?? {plans:{},dispatches:{},ready:[],completedPlans:[]}));\n resolve = (reference: string) => this.guard(this.id(), undefined, 'resolve', () => resolveReference(reference, this.options.store, id => this.readView(id)));\n suggest = (boardId: string, query: import('./contracts').KanbanSuggestionQuery) => this.guard(this.id(), boardId, 'suggest', async () => {\n const snapshot = structuredClone(query), limit = validateSuggestionQuery(snapshot), view = await this.readView(boardId), state = view.state;\n const suggestions = () => suggestFromState(state, snapshot, [...view.items, ...(state.archiveIndex ?? [])], view.records);\n if (!this.options.suggestions) return suggestions();\n const rows = await this.options.suggestions(structuredClone(state.board), snapshot.kind, snapshot.search?.trim() ?? '', limit + 1);\n if (rows === undefined) return suggestions();\n const known = suggestions().items.filter(row => !rows.some(entry => entry.id === row.id));\n return suggestionPage(state.board.revision, [...rows, ...known], snapshot);\n });\n records = (boardId: string, query?: import('./contracts').KanbanRecordQuery) => this.guard(this.id(), boardId, 'records', async () => {const view = await this.readView(boardId); return structuredClone(queryRecords(view.state, query, view.records));});\n record = (boardId: string, recordId: string) => this.guard(this.id(), boardId, 'record', async () => {\n identity(recordId); const record = (await this.readState(boardId)).records?.find(record => record.id === recordId);\n requireValue(record, 'RECORD_NOT_FOUND'); return structuredClone(record);\n });\n currentPrincipal = async () => structuredClone(principal(this.actor));\n create = (input: KanbanBoardInput): Promise<KanbanBoard> => {\n const snapshot = structuredClone(input);\n return this.guard(this.id(), snapshot.id, 'create', async () => {\n const initial = createBoardState(snapshot, this.actor, this.now()), board = initial.board;\n await this.authorize(board, 'create');\n const state = await this.options.store.update(board.id, `create:${board.id}`, JSON.stringify({ snapshot, actor: this.actor }), previous => {\n requireValue(!previous, 'BOARD_EXISTS'); return initial;\n });\n return structuredClone(state.board);\n });\n };\n read = (boardId: string): Promise<KanbanBoard> => this.guard(this.id(), boardId, 'read',\n async () => structuredClone((await this.readState(boardId)).board));\n catalog = () => this.guard(this.id(), undefined, 'catalog', async () => {\n const boards: import('./contracts').KanbanBoardSummary[] = [];\n for (const id of await this.options.store.ids()) {\n try {const view = await this.readView(id); if (view.state.board.deletedAt !== undefined) continue; boards.push({...view.state.board, executionPhase: boardControlPhase({...view.state.board, items: view.items}), items: view.items.map(({id, title, number}) => ({id, title, ...(number === undefined ? {} : {number})}))});}\n catch (error) {if (!(error instanceof KanbanError) || error.code !== 'FORBIDDEN') throw error;}\n }\n return structuredClone(boards);\n });\n list = (): Promise<KanbanBoard[]> => this.guard(this.id(), undefined, 'list', async () => {\n const boards: KanbanBoard[] = [];\n for (const boardId of await this.options.store.ids()) {\n try { const board = (await this.readState(boardId)).board; if (board.deletedAt === undefined) boards.push(structuredClone(board)); }\n catch (error) { if (!(error instanceof KanbanError) || error.code !== 'FORBIDDEN') throw error; }\n }\n return boards;\n });\n execute = (boardId: string, input: KanbanRequest): Promise<KanbanBoard> => this.guard(input.operationId, boardId, `execute:${input.command.type}`, async () => {\n const request = structuredClone(input); identity(request.operationId);\n const state = await this.readState(boardId);\n await this.authorize(state.board, request.command.type, request.command);\n requireValue(state.board.deletedAt === undefined, 'BOARD_DELETED');\n if (request.command.type === 'delete-board') await this.options.verifyDeletion?.(structuredClone(state.board));\n await this.verifyRelease(state.board, request.command);\n const verifyStage = request.command.type === 'observe-execution' && request.command.outcome === 'succeeded' ? 'observe'\n : request.command.type === 'accept-execution' && request.command.accept ? 'accept' : undefined;\n if (verifyStage && 'dispatchId' in request.command) {\n const dispatch = state.workflow?.dispatches[request.command.dispatchId]; requireValue(dispatch, 'DISPATCH_NOT_FOUND');\n const outputs = request.command.type === 'observe-execution' ? request.command.outputs : dispatch.outputs;\n if (outputs?.length) requireValue(await this.options.verifyOutputs?.({stage: verifyStage, board: state.board, itemId: dispatch.itemId,\n dispatchId: dispatch.id, outputs: structuredClone(outputs)}), 'OUTPUT_VERIFICATION_REQUIRED');\n }\n const restore = request.command.type === 'restore-item' ? await this.options.store.archived(boardId, request.command.itemId) : undefined;\n const result = await this.options.store.update(boardId, request.operationId, JSON.stringify({ actor: this.actor, request }), current => {\n requireValue(current && current.board.revision === state.board.revision, 'REVISION_CONFLICT');\n requireValue(current.board.revision === request.expectedRevision, 'REVISION_CONFLICT');\n if (restore) current.restore = restore;\n return reduce(current, request.command, this.actor, request.operationId, this.now(), this.id());\n });\n await this.publishNotifications(boardId, request.operationId);\n return structuredClone(result.board);\n });\n events = (boardId: string, after = 0, limit = 100) => this.guard(this.id(), boardId, 'events', async () => {\n requireValue(Number.isSafeInteger(after) && after >= 0 && Number.isSafeInteger(limit) && limit > 0 && limit <= 500, 'INVALID_CURSOR');\n const {state} = await this.readView(boardId);\n return this.options.store.events(boardId, after, limit, state.sequence);\n });\n query = (boardId: string, query?: KanbanItemQuery) => this.guard(this.id(), boardId, 'query', async () => {const view = await this.readView(boardId); return structuredClone(queryItems(view.state, query, [...view.items, ...(view.state.archiveIndex ?? [])], this.now()));});\n overview = (boardId: string) => this.guard(this.id(), boardId, 'overview', async () => {const view = await this.readView(boardId); return overview(view.state, this.now(), view.items);});\n item = (boardId: string, itemId: string) => this.guard(this.id(), boardId, 'item', async () => {\n identity(itemId); const state = await this.readState(boardId), item = state.board.items.find(i => i.id === itemId);\n if (item) return {item: structuredClone(item),task:projectKanbanTask(item,state.workflow,this.now(),[...state.board.items,...state.archiveIndex??[]])};\n requireValue(state.archiveIndex?.some(i => i.id === itemId), 'ITEM_NOT_FOUND');\n const record = await this.options.store.archived(boardId, itemId); requireValue(record, 'REVISION_CONFLICT');\n return {item: structuredClone(record.item), task:projectKanbanTask(record.item,state.workflow,this.now(),state.archiveIndex), archive: {archivedAt: record.archivedAt, reason: record.reason}};\n });\n related = (sessionId: string): Promise<KanbanBoard[]> => this.guard(this.id(), undefined, 'related', async () => {\n identity(sessionId); const result: KanbanBoard[] = [];\n for (const board of await this.list()) {\n const state = await this.readState(board.id);\n if (related(board).includes(sessionId) || state.records?.some(record => record.sources.some(source => source.sessionId === sessionId)) || state.archiveIndex?.some(i => i.sessionIds.includes(sessionId))) result.push(board);\n }\n return result;\n });\n scan = (): Promise<void> => this.guard(this.id(), undefined, 'scan', async () => {\n const errors: unknown[] = [];\n for (const board of await this.list()) {\n try { await this.execute(board.id, { operationId: this.id(), expectedRevision: board.revision, command: { type: 'scan' } }); }\n catch (error) {\n if (!(error instanceof KanbanError) || error.code !== 'REVISION_CONFLICT') errors.push(error);\n }\n }\n if (errors.length) throw new AggregateError(errors, 'KANBAN_SCAN_FAILED');\n });\n private async verifyRelease(board: KanbanBoard, command: KanbanRequest['command']): Promise<void> {\n if (command.type !== 'release-item') return;\n const item = board.items.find(i => i.id === command.itemId); requireValue(item?.owner && item.owner.token === command.token, 'OWNER_MISMATCH');\n const own = sameOwner(item.owner, this.actor), actor = principal(this.actor);\n const coordinator = board.members?.some(m => principalKey(m) === principalKey(actor) && m.roles?.includes('coordinator'));\n requireValue(own || coordinator, 'OWNER_MISMATCH');\n if (!(own && actor.kind === 'human')) requireValue(await this.options.verifyRelease?.({actor: this.actor, board: structuredClone(board), item: structuredClone(item)}), 'EXECUTION_NOT_SETTLED');\n }\n private async publishNotifications(boardId: string, operationId: string): Promise<void> {\n if (!this.options.onCommitted) return;\n const started = this.now();\n try {await this.options.onCommitted(boardId, this, this.now()); this.log(operationId, boardId, 'notify', started, 'success');}\n catch (error) {this.log(operationId, boardId, 'notify', started, 'failure', safeCode(error, 'NOTIFICATION_PROJECTION_FAILED'));}\n }\n private async readView(boardId: string) {\n identity(boardId);\n if (!this.options.store.view) return viewFromState(await this.readState(boardId));\n const view = await this.options.store.view(boardId); requireValue(view, 'BOARD_NOT_FOUND');\n await this.authorize(view.state.board, 'read'); return view;\n }\n private async readState(boardId: string): Promise<KanbanState> {\n identity(boardId); const state = await this.options.store.read(boardId); requireValue(state, 'BOARD_NOT_FOUND');\n await this.authorize(state.board, 'read'); return state;\n }\n private authorize(board: KanbanBoard, action: Parameters<KanbanModuleOptions['authorize']>[0]['action'], command?: KanbanRequest['command']) {\n return this.options.authorize({ actor: structuredClone(this.actor), board: structuredClone(board), action, command: structuredClone(command) });\n }\n private async guard<T>(operationId: string, boardId: string | undefined, stage: string, run: () => Promise<T>): Promise<T> {\n const start = this.now();\n if (this.stopping) {this.log(operationId,boardId,stage,start,'failure','SERVICE_STOPPING'); throw new KanbanError('SERVICE_STOPPING');}\n const operation = Promise.resolve().then(run); this.pending.add(operation);\n try { const result = await operation; this.log(operationId, boardId, stage, start, 'success'); return result; }\n catch (error) {\n this.log(operationId, boardId, stage, start, 'failure', safeCode(error, 'PORT_FAILURE')); throw error;\n } finally {this.pending.delete(operation);}\n }\n private log(operationId: string, boardId: string | undefined, stage: string, start: number, result: 'success' | 'failure', code?: string): void {\n try { this.options.log({ operationId, boardId, stage, result, code, durationMs: this.now() - start }); }\n catch { /* Diagnostics cannot undo a commit. */ }\n }\n}\n\nfunction safeCode(error: unknown, fallback: string): string {\n const code = (error as {code?: unknown})?.code;\n return typeof code === 'string' && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? code : fallback;\n}\n"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/contracts/task-projection.ts
|
|
2
|
+
var TASK_START_DELAY_MS = 3e5;
|
|
3
|
+
function taskStartAt(item) {
|
|
4
|
+
return _nullishCoalesce(_optionalChain([item, 'access', _ => _.launch, 'optionalAccess', _2 => _2.startAt]), () => ( item.createdAt + TASK_START_DELAY_MS));
|
|
5
|
+
}
|
|
6
|
+
function taskEditing(item, now) {
|
|
7
|
+
return (_nullishCoalesce(_optionalChain([item, 'access', _3 => _3.launch, 'optionalAccess', _4 => _4.editing, 'optionalAccess', _5 => _5.expiresAt]), () => ( 0))) > now;
|
|
8
|
+
}
|
|
9
|
+
function taskDue(item, now) {
|
|
10
|
+
return !taskEditing(item, now) && !item.blocked && _optionalChain([item, 'access', _6 => _6.control, 'optionalAccess', _7 => _7.action]) !== "pause" && (_optionalChain([item, 'access', _8 => _8.launch, 'optionalAccess', _9 => _9.requestedAt]) !== void 0 || _optionalChain([item, 'access', _10 => _10.launch, 'optionalAccess', _11 => _11.mode]) !== "manual" && taskStartAt(item) <= now);
|
|
11
|
+
}
|
|
12
|
+
function projectKanbanTask(item, workflow, now = Date.now(), all = []) {
|
|
13
|
+
const dispatch = Object.values(_nullishCoalesce(_optionalChain([workflow, 'optionalAccess', _12 => _12.dispatches]), () => ( {}))).filter((d) => d.itemId === item.id).sort((a, b) => b.attempt - a.attempt)[0];
|
|
14
|
+
const fact = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(terminalFact(item), () => ( issueFact(item, dispatch))), () => ( failureFact(item, dispatch))), () => ( controlFact(item))), () => ( executionFact(item, dispatch))), () => ( waitingFact(item, now, all)));
|
|
15
|
+
return {
|
|
16
|
+
...fact,
|
|
17
|
+
startAt: taskStartAt(item),
|
|
18
|
+
dispatchId: _optionalChain([dispatch, 'optionalAccess', _13 => _13.id]),
|
|
19
|
+
sessionId: _optionalChain([dispatch, 'optionalAccess', _14 => _14.executionSessionId]),
|
|
20
|
+
actions: [..._nullishCoalesce(fact.actions, () => ( [])), ...dispatch || _optionalChain([item, 'access', _15 => _15.launch, 'optionalAccess', _16 => _16.admittedAt]) !== void 0 || ["claimed", "review", "completed"].includes(item.status) ? ["fix"] : [], ..._optionalChain([dispatch, 'optionalAccess', _17 => _17.executionSessionId]) ? ["open-session"] : []]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function terminalFact(item) {
|
|
24
|
+
if (item.status === "completed" || item.status === "cancelled") return { phase: item.status };
|
|
25
|
+
if (item.status === "review") return { phase: "review", actions: ["review"] };
|
|
26
|
+
}
|
|
27
|
+
function issueFact(item, dispatch) {
|
|
28
|
+
const issue = _optionalChain([item, 'access', _18 => _18.issues, 'optionalAccess', _19 => _19.find, 'call', _20 => _20((i) => !i.resolvedBy && (!dispatch || i.attemptId === dispatch.id))]);
|
|
29
|
+
if (issue) return { phase: issue.type === "clarification" ? "waiting-input" : "error", reason: issue.id, actions: ["resolve"] };
|
|
30
|
+
}
|
|
31
|
+
function controlFact(item) {
|
|
32
|
+
const control = item.control;
|
|
33
|
+
if (!control) return;
|
|
34
|
+
const reason = control.code;
|
|
35
|
+
if (control.state === "unknown") return { phase: "unknown", reason };
|
|
36
|
+
if (control.state === "failed") return { phase: "error", reason, actions: ["resume"] };
|
|
37
|
+
if (control.action === "pause") return { phase: control.state === "paused" ? "paused" : "pausing", reason, actions: ["resume"] };
|
|
38
|
+
if (["requested", "accepted"].includes(control.state)) return { phase: "resuming", reason };
|
|
39
|
+
}
|
|
40
|
+
function failureFact(item, dispatch) {
|
|
41
|
+
if (_optionalChain([dispatch, 'optionalAccess', _21 => _21.outcome]) === "unknown" || _optionalChain([item, 'access', _22 => _22.analysis, 'optionalAccess', _23 => _23.state]) === "unknown") return { phase: "unknown", reason: _nullishCoalesce(_optionalChain([dispatch, 'optionalAccess', _24 => _24.code]), () => ( _optionalChain([item, 'access', _25 => _25.analysis, 'optionalAccess', _26 => _26.code]))) };
|
|
42
|
+
if (dispatch && ["failed", "cancelled", "rejected"].includes(dispatch.outcome)) return { phase: "error", reason: _nullishCoalesce(dispatch.code, () => ( dispatch.outcome)) };
|
|
43
|
+
if (_optionalChain([item, 'access', _27 => _27.analysis, 'optionalAccess', _28 => _28.state]) === "failed") return { phase: "error", reason: item.analysis.code };
|
|
44
|
+
}
|
|
45
|
+
function executionFact(item, dispatch) {
|
|
46
|
+
if (item.blocked) return { phase: item.blocked.reason === "WORKSPACE_INPUT_REQUIRED" ? "waiting-input" : "error", reason: item.blocked.reason };
|
|
47
|
+
if (_optionalChain([dispatch, 'optionalAccess', _29 => _29.outcome]) === "prepared") return { phase: "preparing" };
|
|
48
|
+
if (_optionalChain([dispatch, 'optionalAccess', _30 => _30.outcome]) === "running" || item.status === "claimed") return { phase: "running", actions: _optionalChain([dispatch, 'optionalAccess', _31 => _31.controlCapabilities, 'optionalAccess', _32 => _32.pause]) === "boundary" ? ["pause"] : [] };
|
|
49
|
+
}
|
|
50
|
+
function waitingFact(item, now, all) {
|
|
51
|
+
const analysis = analysisFact(item);
|
|
52
|
+
if (analysis) return analysis;
|
|
53
|
+
const dependency = _optionalChain([item, 'access', _33 => _33.planning, 'optionalAccess', _34 => _34.dependencies, 'access', _35 => _35.find, 'call', _36 => _36((id) => _optionalChain([all, 'access', _37 => _37.find, 'call', _38 => _38((i) => i.id === id), 'optionalAccess', _39 => _39.status]) !== "completed")]);
|
|
54
|
+
if (dependency) return { phase: "waiting-dependency", reason: "DEPENDENCY_UNFINISHED:" + dependency, actions: _optionalChain([item, 'access', _40 => _40.launch, 'optionalAccess', _41 => _41.admittedAt]) === void 0 ? ["edit", "cancel"] : [] };
|
|
55
|
+
if (taskEditing(item, now)) return { phase: "waiting-start", reason: "EDIT_LEASE_ACTIVE" };
|
|
56
|
+
if (_optionalChain([item, 'access', _42 => _42.launch, 'optionalAccess', _43 => _43.admittedAt]) !== void 0) return { phase: "queued" };
|
|
57
|
+
return { phase: "waiting-start", reason: _optionalChain([item, 'access', _44 => _44.launch, 'optionalAccess', _45 => _45.mode]) === "manual" ? "MANUAL_START_REQUIRED" : taskDue(item, now) ? "ADMISSION_PENDING" : "START_AT_PENDING", actions: ["run", "edit", "pause", "cancel"] };
|
|
58
|
+
}
|
|
59
|
+
function analysisFact(item) {
|
|
60
|
+
if (_optionalChain([item, 'access', _46 => _46.analysis, 'optionalAccess', _47 => _47.state]) === "clarification") return { phase: "waiting-input", reason: item.analysis.reason };
|
|
61
|
+
if (_optionalChain([item, 'access', _48 => _48.analysis, 'optionalAccess', _49 => _49.state]) === "analyzing") return { phase: "analyzing" };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
exports.TASK_START_DELAY_MS = TASK_START_DELAY_MS; exports.taskStartAt = taskStartAt; exports.taskEditing = taskEditing; exports.taskDue = taskDue; exports.projectKanbanTask = projectKanbanTask;
|
|
71
|
+
//# sourceMappingURL=chunk-KYNYPKKH.cjs.map
|