@femtomc/mu-issue 26.2.69 → 26.2.70
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/dist/contracts.d.ts +33 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +93 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/issue_store.d.ts +10 -5
- package/dist/issue_store.d.ts.map +1 -1
- package/dist/issue_store.js +101 -26
- package/package.json +2 -2
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Issue } from "@femtomc/mu-core";
|
|
2
|
+
export declare const ISSUE_STATUS_VALUES: readonly ["open", "in_progress", "closed"];
|
|
3
|
+
export declare const DEFAULT_ISSUE_QUERY_LIMIT = 200;
|
|
4
|
+
export declare const MAX_ISSUE_QUERY_LIMIT = 200;
|
|
5
|
+
export declare class IssueStoreError extends Error {
|
|
6
|
+
constructor(message: string, opts?: {
|
|
7
|
+
cause?: unknown;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export declare class IssueStoreNotFoundError extends IssueStoreError {
|
|
11
|
+
readonly issueId: string;
|
|
12
|
+
constructor(issueId: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class IssueStoreValidationError extends IssueStoreError {
|
|
15
|
+
constructor(message: string, opts?: {
|
|
16
|
+
cause?: unknown;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export declare function normalizeIssueStatusFilter(status: unknown): Issue["status"] | undefined;
|
|
20
|
+
export declare function normalizeIssueTagFilter(tag: unknown): string | undefined;
|
|
21
|
+
export declare function normalizeIssueContainsFilter(contains: unknown): string | undefined;
|
|
22
|
+
export declare function normalizeIssueQueryLimit(limit: unknown, opts?: {
|
|
23
|
+
defaultLimit?: number | null;
|
|
24
|
+
max?: number;
|
|
25
|
+
}): number | null;
|
|
26
|
+
export declare function normalizeIssueDepInput(input: {
|
|
27
|
+
depType: unknown;
|
|
28
|
+
target: unknown;
|
|
29
|
+
}): {
|
|
30
|
+
depType: string;
|
|
31
|
+
target: string;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,eAAO,MAAM,mBAAmB,4CAAkF,CAAC;AACnH,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAIzC,qBAAa,eAAgB,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI9D;AAED,qBAAa,uBAAwB,SAAQ,eAAe;IAC3D,SAAgB,OAAO,EAAE,MAAM,CAAC;gBAEb,OAAO,EAAE,MAAM;CAKlC;AAED,qBAAa,yBAA0B,SAAQ,eAAe;gBAC1C,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI9D;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,SAAS,CAevF;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CASxE;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CASlF;AAED,wBAAgB,wBAAwB,CACvC,KAAK,EAAE,OAAO,EACd,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GACvD,MAAM,GAAG,IAAI,CAsBf;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IACrF,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CACf,CAUA"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export const ISSUE_STATUS_VALUES = ["open", "in_progress", "closed"];
|
|
2
|
+
export const DEFAULT_ISSUE_QUERY_LIMIT = 200;
|
|
3
|
+
export const MAX_ISSUE_QUERY_LIMIT = 200;
|
|
4
|
+
const ISSUE_STATUS_SET = new Set(ISSUE_STATUS_VALUES);
|
|
5
|
+
export class IssueStoreError extends Error {
|
|
6
|
+
constructor(message, opts) {
|
|
7
|
+
super(message, opts);
|
|
8
|
+
this.name = "IssueStoreError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class IssueStoreNotFoundError extends IssueStoreError {
|
|
12
|
+
issueId;
|
|
13
|
+
constructor(issueId) {
|
|
14
|
+
super(`issue not found: ${issueId}`);
|
|
15
|
+
this.name = "IssueStoreNotFoundError";
|
|
16
|
+
this.issueId = issueId;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class IssueStoreValidationError extends IssueStoreError {
|
|
20
|
+
constructor(message, opts) {
|
|
21
|
+
super(message, opts);
|
|
22
|
+
this.name = "IssueStoreValidationError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function normalizeIssueStatusFilter(status) {
|
|
26
|
+
if (status == null || status === "") {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
if (typeof status !== "string") {
|
|
30
|
+
throw new IssueStoreValidationError("invalid issue status filter: expected string");
|
|
31
|
+
}
|
|
32
|
+
const trimmed = status.trim();
|
|
33
|
+
if (trimmed.length === 0) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
if (!ISSUE_STATUS_SET.has(trimmed)) {
|
|
37
|
+
throw new IssueStoreValidationError(`invalid issue status filter: ${trimmed}`);
|
|
38
|
+
}
|
|
39
|
+
return trimmed;
|
|
40
|
+
}
|
|
41
|
+
export function normalizeIssueTagFilter(tag) {
|
|
42
|
+
if (tag == null || tag === "") {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (typeof tag !== "string") {
|
|
46
|
+
throw new IssueStoreValidationError("invalid issue tag filter: expected string");
|
|
47
|
+
}
|
|
48
|
+
const trimmed = tag.trim();
|
|
49
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
50
|
+
}
|
|
51
|
+
export function normalizeIssueContainsFilter(contains) {
|
|
52
|
+
if (contains == null || contains === "") {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
if (typeof contains !== "string") {
|
|
56
|
+
throw new IssueStoreValidationError("invalid issue contains filter: expected string");
|
|
57
|
+
}
|
|
58
|
+
const trimmed = contains.trim().toLowerCase();
|
|
59
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
60
|
+
}
|
|
61
|
+
export function normalizeIssueQueryLimit(limit, opts = {}) {
|
|
62
|
+
const defaultLimit = opts.defaultLimit ?? null;
|
|
63
|
+
const max = opts.max ?? MAX_ISSUE_QUERY_LIMIT;
|
|
64
|
+
if (limit == null || limit === "") {
|
|
65
|
+
return defaultLimit;
|
|
66
|
+
}
|
|
67
|
+
let value;
|
|
68
|
+
if (typeof limit === "number" && Number.isFinite(limit)) {
|
|
69
|
+
value = limit;
|
|
70
|
+
}
|
|
71
|
+
else if (typeof limit === "string" && /^\d+$/.test(limit.trim())) {
|
|
72
|
+
value = Number.parseInt(limit, 10);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
throw new IssueStoreValidationError("invalid issue query limit: expected positive integer");
|
|
76
|
+
}
|
|
77
|
+
const normalized = Math.trunc(value);
|
|
78
|
+
if (normalized < 1) {
|
|
79
|
+
throw new IssueStoreValidationError("invalid issue query limit: must be >= 1");
|
|
80
|
+
}
|
|
81
|
+
return Math.min(max, normalized);
|
|
82
|
+
}
|
|
83
|
+
export function normalizeIssueDepInput(input) {
|
|
84
|
+
const depType = typeof input.depType === "string" ? input.depType.trim() : "";
|
|
85
|
+
if (depType.length === 0) {
|
|
86
|
+
throw new IssueStoreValidationError("dependency type is required");
|
|
87
|
+
}
|
|
88
|
+
const target = typeof input.target === "string" ? input.target.trim() : "";
|
|
89
|
+
if (target.length === 0) {
|
|
90
|
+
throw new IssueStoreValidationError("dependency target is required");
|
|
91
|
+
}
|
|
92
|
+
return { depType, target };
|
|
93
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export type { CreateIssueOpts, ListIssueOpts } from "./issue_store.js";
|
|
1
|
+
export type { CreateIssueOpts, ListIssueOpts, ReadyIssueOpts } from "./issue_store.js";
|
|
2
2
|
export { IssueStore } from "./issue_store.js";
|
|
3
|
+
export { DEFAULT_ISSUE_QUERY_LIMIT, ISSUE_STATUS_VALUES, MAX_ISSUE_QUERY_LIMIT, IssueStoreError, IssueStoreNotFoundError, IssueStoreValidationError, normalizeIssueContainsFilter, normalizeIssueDepInput, normalizeIssueQueryLimit, normalizeIssueStatusFilter, normalizeIssueTagFilter, } from "./contracts.js";
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACN,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,uBAAuB,EACvB,yBAAyB,EACzB,4BAA4B,EAC5B,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,uBAAuB,GACvB,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
1
|
export { IssueStore } from "./issue_store.js";
|
|
2
|
+
export { DEFAULT_ISSUE_QUERY_LIMIT, ISSUE_STATUS_VALUES, MAX_ISSUE_QUERY_LIMIT, IssueStoreError, IssueStoreNotFoundError, IssueStoreValidationError, normalizeIssueContainsFilter, normalizeIssueDepInput, normalizeIssueQueryLimit, normalizeIssueStatusFilter, normalizeIssueTagFilter, } from "./contracts.js";
|
package/dist/issue_store.d.ts
CHANGED
|
@@ -6,8 +6,15 @@ export type CreateIssueOpts = {
|
|
|
6
6
|
priority?: number;
|
|
7
7
|
};
|
|
8
8
|
export type ListIssueOpts = {
|
|
9
|
-
status?: Issue["status"];
|
|
10
|
-
tag?: string;
|
|
9
|
+
status?: Issue["status"] | null;
|
|
10
|
+
tag?: string | null;
|
|
11
|
+
contains?: string | null;
|
|
12
|
+
limit?: number | null;
|
|
13
|
+
};
|
|
14
|
+
export type ReadyIssueOpts = {
|
|
15
|
+
tags?: readonly string[] | null;
|
|
16
|
+
contains?: string | null;
|
|
17
|
+
limit?: number | null;
|
|
11
18
|
};
|
|
12
19
|
export declare class IssueStore {
|
|
13
20
|
#private;
|
|
@@ -26,9 +33,7 @@ export declare class IssueStore {
|
|
|
26
33
|
remove_dep(srcId: string, depType: string, dstId: string): Promise<boolean>;
|
|
27
34
|
children(parentId: string): Promise<Issue[]>;
|
|
28
35
|
subtree_ids(rootId: string): Promise<string[]>;
|
|
29
|
-
ready(rootId?: string | null, opts?:
|
|
30
|
-
tags?: readonly string[] | null;
|
|
31
|
-
}): Promise<Issue[]>;
|
|
36
|
+
ready(rootId?: string | null, opts?: ReadyIssueOpts): Promise<Issue[]>;
|
|
32
37
|
collapsible(rootId: string): Promise<Issue[]>;
|
|
33
38
|
validate(rootId: string): Promise<ValidationResult>;
|
|
34
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issue_store.d.ts","sourceRoot":"","sources":["../src/issue_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAGN,QAAQ,EAOR,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"issue_store.d.ts","sourceRoot":"","sources":["../src/issue_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAGN,QAAQ,EAOR,MAAM,kBAAkB,CAAC;AAW1B,MAAM,MAAM,eAAe,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC3B,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAqDF,qBAAa,UAAU;;IACtB,SAAgB,MAAM,EAAE,QAAQ,CAAC;gBAGd,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,QAAQ,CAAA;KAAO;IA0CnE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,KAAK,CAAC;IA0BjE,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAM3C,IAAI,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IA0ChD,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;IA8ExE,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA+BxC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,MAAkB,GAAG,OAAO,CAAC,KAAK,CAAC;IAInE,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAmBpD,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuCrE,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAK5C,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAS9C,KAAK,CAAC,MAAM,GAAE,MAAM,GAAG,IAAW,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAgBhF,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAK7C,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAIhE"}
|
package/dist/issue_store.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { collapsible as dagCollapsible, subtreeIds as dagSubtreeIds, EventLog, IssueSchema, NullEventSink, nowTs, readyLeaves, shortId, validateDag, } from "@femtomc/mu-core";
|
|
2
|
+
import { IssueStoreNotFoundError, IssueStoreValidationError, normalizeIssueContainsFilter, normalizeIssueDepInput, normalizeIssueQueryLimit, normalizeIssueStatusFilter, normalizeIssueTagFilter, } from "./contracts.js";
|
|
2
3
|
function deepEqualJson(a, b) {
|
|
3
4
|
if (a === b) {
|
|
4
5
|
return true;
|
|
@@ -44,6 +45,10 @@ function deepEqualJson(a, b) {
|
|
|
44
45
|
}
|
|
45
46
|
return false;
|
|
46
47
|
}
|
|
48
|
+
function issueContainsText(issue, contains) {
|
|
49
|
+
const haystack = `${issue.title}\n${issue.body}`.toLowerCase();
|
|
50
|
+
return haystack.includes(contains);
|
|
51
|
+
}
|
|
47
52
|
export class IssueStore {
|
|
48
53
|
events;
|
|
49
54
|
#issues;
|
|
@@ -51,15 +56,30 @@ export class IssueStore {
|
|
|
51
56
|
this.#issues = issues;
|
|
52
57
|
this.events = opts.events ?? new EventLog(new NullEventSink());
|
|
53
58
|
}
|
|
59
|
+
#parseIssueRow(row, idx) {
|
|
60
|
+
const parsed = IssueSchema.safeParse(row);
|
|
61
|
+
if (!parsed.success) {
|
|
62
|
+
throw new Error(`invalid issue row ${idx}: ${parsed.error.message}`);
|
|
63
|
+
}
|
|
64
|
+
return parsed.data;
|
|
65
|
+
}
|
|
54
66
|
async #load() {
|
|
55
67
|
const rows = await this.#issues.read();
|
|
56
|
-
return rows.map((row, idx) =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
return rows.map((row, idx) => this.#parseIssueRow(row, idx));
|
|
69
|
+
}
|
|
70
|
+
async *#streamRows() {
|
|
71
|
+
if (this.#issues.stream) {
|
|
72
|
+
let idx = 0;
|
|
73
|
+
for await (const row of this.#issues.stream()) {
|
|
74
|
+
yield this.#parseIssueRow(row, idx);
|
|
75
|
+
idx += 1;
|
|
60
76
|
}
|
|
61
|
-
return
|
|
62
|
-
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const rows = await this.#issues.read();
|
|
80
|
+
for (let idx = 0; idx < rows.length; idx += 1) {
|
|
81
|
+
yield this.#parseIssueRow(rows[idx], idx);
|
|
82
|
+
}
|
|
63
83
|
}
|
|
64
84
|
async #save(rows) {
|
|
65
85
|
await this.#issues.write(rows);
|
|
@@ -82,9 +102,7 @@ export class IssueStore {
|
|
|
82
102
|
updated_at: now,
|
|
83
103
|
};
|
|
84
104
|
const issue = IssueSchema.parse(issueInput);
|
|
85
|
-
|
|
86
|
-
rows.push(issue);
|
|
87
|
-
await this.#save(rows);
|
|
105
|
+
await this.#issues.append(issue);
|
|
88
106
|
await this.events.emit("issue.create", {
|
|
89
107
|
source: "issue_store",
|
|
90
108
|
issueId: issue.id,
|
|
@@ -98,20 +116,52 @@ export class IssueStore {
|
|
|
98
116
|
return idx >= 0 ? rows[idx] : null;
|
|
99
117
|
}
|
|
100
118
|
async list(opts = {}) {
|
|
119
|
+
const status = normalizeIssueStatusFilter(opts.status);
|
|
120
|
+
const tag = normalizeIssueTagFilter(opts.tag);
|
|
121
|
+
const contains = normalizeIssueContainsFilter(opts.contains);
|
|
122
|
+
const limit = normalizeIssueQueryLimit(opts.limit, { defaultLimit: null });
|
|
123
|
+
if (limit != null && this.#issues.stream) {
|
|
124
|
+
const bounded = [];
|
|
125
|
+
for await (const row of this.#streamRows()) {
|
|
126
|
+
if (status && row.status !== status) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (tag && !row.tags.includes(tag)) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (contains && !issueContainsText(row, contains)) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
bounded.push(row);
|
|
136
|
+
if (bounded.length > limit) {
|
|
137
|
+
bounded.shift();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return bounded;
|
|
141
|
+
}
|
|
101
142
|
let rows = await this.#load();
|
|
102
|
-
if (
|
|
103
|
-
rows = rows.filter((row) => row.status ===
|
|
143
|
+
if (status) {
|
|
144
|
+
rows = rows.filter((row) => row.status === status);
|
|
145
|
+
}
|
|
146
|
+
if (tag) {
|
|
147
|
+
rows = rows.filter((row) => row.tags.includes(tag));
|
|
104
148
|
}
|
|
105
|
-
if (
|
|
106
|
-
rows = rows.filter((row) => row
|
|
149
|
+
if (contains) {
|
|
150
|
+
rows = rows.filter((row) => issueContainsText(row, contains));
|
|
151
|
+
}
|
|
152
|
+
if (limit != null) {
|
|
153
|
+
rows = rows.slice(-limit);
|
|
107
154
|
}
|
|
108
155
|
return rows;
|
|
109
156
|
}
|
|
110
157
|
async update(issueId, fields) {
|
|
158
|
+
if (!fields || typeof fields !== "object" || Array.isArray(fields)) {
|
|
159
|
+
throw new IssueStoreValidationError("update fields must be an object");
|
|
160
|
+
}
|
|
111
161
|
const rows = await this.#load();
|
|
112
162
|
const idx = this.#findIndex(rows, issueId);
|
|
113
163
|
if (idx < 0) {
|
|
114
|
-
throw new
|
|
164
|
+
throw new IssueStoreNotFoundError(issueId);
|
|
115
165
|
}
|
|
116
166
|
const issueBefore = rows[idx];
|
|
117
167
|
const before = JSON.parse(JSON.stringify(issueBefore));
|
|
@@ -221,35 +271,51 @@ export class IssueStore {
|
|
|
221
271
|
return reset;
|
|
222
272
|
}
|
|
223
273
|
async add_dep(srcId, depType, dstId) {
|
|
274
|
+
const normalizedSrcId = srcId.trim();
|
|
275
|
+
if (normalizedSrcId.length === 0) {
|
|
276
|
+
throw new IssueStoreValidationError("source issue id is required");
|
|
277
|
+
}
|
|
278
|
+
const normalizedDep = normalizeIssueDepInput({ depType, target: dstId });
|
|
279
|
+
if (normalizedDep.target === normalizedSrcId) {
|
|
280
|
+
throw new IssueStoreValidationError("dependency target cannot equal source issue id");
|
|
281
|
+
}
|
|
224
282
|
const rows = await this.#load();
|
|
225
|
-
const idx = this.#findIndex(rows,
|
|
283
|
+
const idx = this.#findIndex(rows, normalizedSrcId);
|
|
226
284
|
if (idx < 0) {
|
|
227
|
-
throw new
|
|
285
|
+
throw new IssueStoreNotFoundError(normalizedSrcId);
|
|
286
|
+
}
|
|
287
|
+
if (this.#findIndex(rows, normalizedDep.target) < 0) {
|
|
288
|
+
throw new IssueStoreNotFoundError(normalizedDep.target);
|
|
228
289
|
}
|
|
229
290
|
const issue = rows[idx];
|
|
230
|
-
const exists = issue.deps.some((dep) => dep.type === depType && dep.target ===
|
|
291
|
+
const exists = issue.deps.some((dep) => dep.type === normalizedDep.depType && dep.target === normalizedDep.target);
|
|
231
292
|
if (exists) {
|
|
232
293
|
return;
|
|
233
294
|
}
|
|
234
|
-
issue.deps.push({ type: depType, target:
|
|
295
|
+
issue.deps.push({ type: normalizedDep.depType, target: normalizedDep.target });
|
|
235
296
|
issue.updated_at = nowTs();
|
|
236
297
|
rows[idx] = IssueSchema.parse(issue);
|
|
237
298
|
await this.#save(rows);
|
|
238
299
|
await this.events.emit("issue.dep.add", {
|
|
239
300
|
source: "issue_store",
|
|
240
|
-
issueId:
|
|
241
|
-
payload: { type: depType, target:
|
|
301
|
+
issueId: normalizedSrcId,
|
|
302
|
+
payload: { type: normalizedDep.depType, target: normalizedDep.target },
|
|
242
303
|
});
|
|
243
304
|
}
|
|
244
305
|
async remove_dep(srcId, depType, dstId) {
|
|
306
|
+
const normalizedSrcId = srcId.trim();
|
|
307
|
+
if (normalizedSrcId.length === 0) {
|
|
308
|
+
throw new IssueStoreValidationError("source issue id is required");
|
|
309
|
+
}
|
|
310
|
+
const normalizedDep = normalizeIssueDepInput({ depType, target: dstId });
|
|
245
311
|
const rows = await this.#load();
|
|
246
|
-
const idx = this.#findIndex(rows,
|
|
312
|
+
const idx = this.#findIndex(rows, normalizedSrcId);
|
|
247
313
|
if (idx < 0) {
|
|
248
|
-
throw new
|
|
314
|
+
throw new IssueStoreNotFoundError(normalizedSrcId);
|
|
249
315
|
}
|
|
250
316
|
const issue = rows[idx];
|
|
251
317
|
const before = issue.deps.length;
|
|
252
|
-
issue.deps = issue.deps.filter((dep) => !(dep.type === depType && dep.target ===
|
|
318
|
+
issue.deps = issue.deps.filter((dep) => !(dep.type === normalizedDep.depType && dep.target === normalizedDep.target));
|
|
253
319
|
const changed = issue.deps.length !== before;
|
|
254
320
|
if (changed) {
|
|
255
321
|
issue.updated_at = nowTs();
|
|
@@ -258,8 +324,8 @@ export class IssueStore {
|
|
|
258
324
|
}
|
|
259
325
|
await this.events.emit("issue.dep.remove", {
|
|
260
326
|
source: "issue_store",
|
|
261
|
-
issueId:
|
|
262
|
-
payload: { type: depType, target:
|
|
327
|
+
issueId: normalizedSrcId,
|
|
328
|
+
payload: { type: normalizedDep.depType, target: normalizedDep.target, ok: changed },
|
|
263
329
|
});
|
|
264
330
|
return changed;
|
|
265
331
|
}
|
|
@@ -277,8 +343,17 @@ export class IssueStore {
|
|
|
277
343
|
async ready(rootId = null, opts = {}) {
|
|
278
344
|
const rows = await this.#load();
|
|
279
345
|
const tags = opts.tags ?? undefined;
|
|
346
|
+
const contains = normalizeIssueContainsFilter(opts.contains);
|
|
347
|
+
const limit = normalizeIssueQueryLimit(opts.limit, { defaultLimit: null });
|
|
280
348
|
const root_id = rootId ?? undefined;
|
|
281
|
-
|
|
349
|
+
let ready = readyLeaves(rows, { root_id, tags: tags ?? undefined });
|
|
350
|
+
if (contains) {
|
|
351
|
+
ready = ready.filter((issue) => issueContainsText(issue, contains));
|
|
352
|
+
}
|
|
353
|
+
if (limit != null) {
|
|
354
|
+
ready = ready.slice(0, limit);
|
|
355
|
+
}
|
|
356
|
+
return ready;
|
|
282
357
|
}
|
|
283
358
|
async collapsible(rootId) {
|
|
284
359
|
const rows = await this.#load();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-issue",
|
|
3
|
-
"version": "26.2.
|
|
3
|
+
"version": "26.2.70",
|
|
4
4
|
"description": "Work item store with dependency graph helpers for mu.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mu",
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
"dist/**"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@femtomc/mu-core": "26.2.
|
|
25
|
+
"@femtomc/mu-core": "26.2.70"
|
|
26
26
|
}
|
|
27
27
|
}
|