@mastra/factory 0.26.0-alpha.1 → 0.26.0-alpha.3
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 +74 -7
- package/dist/boards/define-board.d.ts +19 -0
- package/dist/boards/define-board.d.ts.map +1 -1
- package/dist/boards/define-board.js +41 -9
- package/dist/boards/define-board.js.map +1 -1
- package/dist/boards/project-boards.d.ts +78 -0
- package/dist/boards/project-boards.d.ts.map +1 -0
- package/dist/boards/project-boards.js +194 -0
- package/dist/boards/project-boards.js.map +1 -0
- package/dist/boards/review.js +2 -2
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/stored-board.d.ts +101 -0
- package/dist/boards/stored-board.d.ts.map +1 -0
- package/dist/boards/stored-board.js +139 -0
- package/dist/boards/stored-board.js.map +1 -0
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +6 -3
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/intake.d.ts +6 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/environment/build.d.ts +1 -1
- package/dist/environment/build.d.ts.map +1 -1
- package/dist/environment/build.js +2 -0
- package/dist/environment/build.js.map +1 -1
- package/dist/factory.d.ts +15 -2
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +51 -12
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/integration.d.ts +5 -4
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +10 -8
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -2
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +6 -1
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/jira/binding-scope.d.ts +30 -0
- package/dist/integrations/jira/binding-scope.d.ts.map +1 -0
- package/dist/integrations/jira/binding-scope.js +50 -0
- package/dist/integrations/jira/binding-scope.js.map +1 -0
- package/dist/integrations/jira/claim.d.ts +12 -0
- package/dist/integrations/jira/claim.d.ts.map +1 -1
- package/dist/integrations/jira/claim.js +24 -1
- package/dist/integrations/jira/claim.js.map +1 -1
- package/dist/integrations/jira/intake-jql.d.ts +32 -0
- package/dist/integrations/jira/intake-jql.d.ts.map +1 -0
- package/dist/integrations/jira/intake-jql.js +57 -0
- package/dist/integrations/jira/intake-jql.js.map +1 -0
- package/dist/integrations/jira/integration.d.ts +4 -3
- package/dist/integrations/jira/integration.d.ts.map +1 -1
- package/dist/integrations/jira/integration.js +14 -21
- package/dist/integrations/jira/integration.js.map +1 -1
- package/dist/integrations/jira/routes.d.ts +3 -0
- package/dist/integrations/jira/routes.d.ts.map +1 -1
- package/dist/integrations/jira/routes.js +16 -29
- package/dist/integrations/jira/routes.js.map +1 -1
- package/dist/integrations/jira/rules.d.ts.map +1 -1
- package/dist/integrations/jira/rules.js +8 -13
- package/dist/integrations/jira/rules.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts +5 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +7 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/jira/integration.d.ts +7 -1
- package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
- package/dist/integrations/platform/jira/integration.js +26 -24
- package/dist/integrations/platform/jira/integration.js.map +1 -1
- package/dist/integrations/platform/jira/routes.d.ts +3 -0
- package/dist/integrations/platform/jira/routes.d.ts.map +1 -1
- package/dist/integrations/platform/jira/routes.js +17 -28
- package/dist/integrations/platform/jira/routes.js.map +1 -1
- package/dist/routes/attention-providers.d.ts +2 -1
- package/dist/routes/attention-providers.d.ts.map +1 -1
- package/dist/routes/attention-providers.js +3 -2
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/attention.d.ts +2 -1
- package/dist/routes/attention.d.ts.map +1 -1
- package/dist/routes/attention.js.map +1 -1
- package/dist/routes/boards.d.ts +30 -0
- package/dist/routes/boards.d.ts.map +1 -0
- package/dist/routes/boards.js +272 -0
- package/dist/routes/boards.js.map +1 -0
- package/dist/routes/contracts.d.ts +249 -0
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/contracts.js +107 -35
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/intake.d.ts +15 -10
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +109 -22
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/projects.d.ts +3 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +10 -1
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/surface.d.ts +10 -3
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +13 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.d.ts +9 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -1
- package/dist/routes/tenant-credentials.js +24 -2
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.d.ts +3 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +64 -42
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +4 -0
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +7 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +70 -34
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts +3 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +11 -6
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/tools.d.ts +2 -1
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +2 -1
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +3 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +12 -12
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/sandbox/local-factory-sandbox.d.ts +4 -1
- package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -1
- package/dist/sandbox/local-factory-sandbox.js +1 -1
- package/dist/sandbox/local-factory-sandbox.js.map +1 -1
- package/dist/storage/domains/audit/actions.d.ts +1 -0
- package/dist/storage/domains/audit/actions.d.ts.map +1 -1
- package/dist/storage/domains/audit/actions.js +6 -1
- package/dist/storage/domains/audit/actions.js.map +1 -1
- package/dist/storage/domains/boards/base.d.ts +110 -0
- package/dist/storage/domains/boards/base.d.ts.map +1 -0
- package/dist/storage/domains/boards/base.js +292 -0
- package/dist/storage/domains/boards/base.js.map +1 -0
- package/dist/storage/domains/boards/schemas.d.ts +11 -0
- package/dist/storage/domains/boards/schemas.d.ts.map +1 -0
- package/dist/storage/domains/boards/schemas.js +106 -0
- package/dist/storage/domains/boards/schemas.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +4 -1
- package/dist/storage/domains/credentials/base.d.ts.map +1 -1
- package/dist/storage/domains/credentials/base.js +5 -2
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +86 -5
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +298 -59
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/projects/base.d.ts +2 -1
- package/dist/storage/domains/projects/base.d.ts.map +1 -1
- package/dist/storage/domains/projects/base.js +15 -10
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +16 -2
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +40 -11
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/tools/read-tools.d.ts +3 -1
- package/dist/tools/read-tools.d.ts.map +1 -1
- package/dist/tools/read-tools.js +3 -2
- package/dist/tools/read-tools.js.map +1 -1
- package/dist/tools/session-tools.d.ts +3 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +2 -1
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/tools/sessions-state-processor.d.ts +3 -1
- package/dist/tools/sessions-state-processor.d.ts.map +1 -1
- package/dist/tools/sessions-state-processor.js +2 -1
- package/dist/tools/sessions-state-processor.js.map +1 -1
- package/dist/tools/work-item-tools.d.ts +3 -1
- package/dist/tools/work-item-tools.d.ts.map +1 -1
- package/dist/tools/work-item-tools.js +3 -2
- package/dist/tools/work-item-tools.js.map +1 -1
- package/dist/workspace.d.ts +2 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +14 -5
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-plan/SKILL.md +1 -1
- package/factory-skills/factory-triage/SKILL.md +18 -7
- package/package.json +9 -9
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* every member reads the same one.
|
|
8
8
|
*/
|
|
9
9
|
import { FactoryStorageDomain } from '@mastra/core/storage';
|
|
10
|
-
import type { CollectionSchema } from '@mastra/core/storage';
|
|
10
|
+
import type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';
|
|
11
|
+
export declare class FactoryProjectNotFoundError extends Error {
|
|
12
|
+
constructor();
|
|
13
|
+
}
|
|
11
14
|
export interface IntakeSelection {
|
|
12
15
|
enabled: boolean;
|
|
13
16
|
/** Provider-owned source ids; `null` means nothing is selected. */
|
|
@@ -28,15 +31,52 @@ export declare function mergeIntakeSelections(configs: readonly IntakeConfig[]):
|
|
|
28
31
|
* ingested item should land. GitHub items are naturally scoped by their linked
|
|
29
32
|
* repository; providers without that link (Linear) need this explicit binding
|
|
30
33
|
* so viewing one project's board cannot materialize another project's items.
|
|
34
|
+
*
|
|
35
|
+
* Frozen: its unique index allows one binding per source, and indexes cannot be
|
|
36
|
+
* narrowed, so bindings moved to {@link INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA}. Rows
|
|
37
|
+
* are copied once at boot and never written again; the collection stays
|
|
38
|
+
* registered so an earlier build that is rolled back to still finds them.
|
|
31
39
|
*/
|
|
32
40
|
export declare const INTAKE_SOURCE_BINDINGS_SCHEMA: CollectionSchema;
|
|
41
|
+
/**
|
|
42
|
+
* Binds an intake source to a Factory project, one row per (source, Factory
|
|
43
|
+
* project). Most integrations keep one binding per source and rebinding moves
|
|
44
|
+
* it; integrations with `Intake.bindingLabelFilters` let several Factory
|
|
45
|
+
* projects share a source, each taking the issues carrying its own labels.
|
|
46
|
+
*/
|
|
47
|
+
export declare const INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA: CollectionSchema;
|
|
48
|
+
/** One-time data migrations that have run, so none re-runs over rows users have changed since. */
|
|
49
|
+
export declare const INTAKE_DATA_MIGRATIONS_SCHEMA: CollectionSchema;
|
|
33
50
|
export interface IntakeSourceBinding {
|
|
34
51
|
integrationId: string;
|
|
35
52
|
sourceId: string;
|
|
36
53
|
factoryProjectId: string;
|
|
37
54
|
/** Installed board the source feeds, or `null` for legacy source-type routing. */
|
|
38
55
|
board: string | null;
|
|
56
|
+
/** Labels whose issues this binding takes; `null` takes every issue no other binding's labels claim. */
|
|
57
|
+
labels: string[] | null;
|
|
58
|
+
createdAt: Date;
|
|
39
59
|
}
|
|
60
|
+
/** Another Factory project's binding that already takes some of the requested issues. */
|
|
61
|
+
export interface IntakeBindingConflict {
|
|
62
|
+
factoryProjectId: string;
|
|
63
|
+
/** The overlapping labels, or `null` when both bindings take all unclaimed issues. */
|
|
64
|
+
labels: string[] | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The slice of a shared source's issues `binding` takes, given every binding
|
|
68
|
+
* on that source (`siblings` may include `binding` itself). Label bindings beat
|
|
69
|
+
* the all-issues binding, and an issue carrying labels of two label bindings
|
|
70
|
+
* goes to the older one.
|
|
71
|
+
*/
|
|
72
|
+
export declare function bindingLabelSlice(binding: Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>, siblings: readonly Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>[]): {
|
|
73
|
+
include: string[] | null;
|
|
74
|
+
exclude: string[];
|
|
75
|
+
};
|
|
76
|
+
/** Whether an issue carrying `labels` falls in `binding`'s slice of its source. */
|
|
77
|
+
export declare function bindingTakesIssue(binding: Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>, siblings: readonly Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>[], labels: readonly string[]): boolean;
|
|
78
|
+
/** Trim and de-duplicate case-insensitively, keeping the first spelling the user typed. */
|
|
79
|
+
export declare function normalizeBindingLabels(labels: readonly string[]): string[];
|
|
40
80
|
/**
|
|
41
81
|
* Routes items carrying a label to an installed board, per Factory project.
|
|
42
82
|
*
|
|
@@ -82,7 +122,7 @@ export declare class IntakeStorage extends FactoryStorageDomain {
|
|
|
82
122
|
orgId: string;
|
|
83
123
|
config: IntakeConfig;
|
|
84
124
|
}): Promise<IntakeConfig>;
|
|
85
|
-
/** Every source binding in the org, optionally narrowed to one integration. */
|
|
125
|
+
/** Every source binding in the org, optionally narrowed to one integration, oldest first. */
|
|
86
126
|
listBindings({ orgId, integrationId, }: {
|
|
87
127
|
orgId: string;
|
|
88
128
|
integrationId?: string;
|
|
@@ -93,17 +133,40 @@ export declare class IntakeStorage extends FactoryStorageDomain {
|
|
|
93
133
|
integrationId: string;
|
|
94
134
|
factoryProjectId: string;
|
|
95
135
|
}): Promise<string[]>;
|
|
96
|
-
/**
|
|
97
|
-
|
|
136
|
+
/**
|
|
137
|
+
* One Factory project's binding of a source, or `null` when it has none.
|
|
138
|
+
* Without `factoryProjectId`, the source's oldest binding — for integrations
|
|
139
|
+
* that keep one binding per source.
|
|
140
|
+
*/
|
|
141
|
+
getBinding({ orgId, integrationId, sourceId, factoryProjectId, }: {
|
|
98
142
|
orgId: string;
|
|
99
143
|
integrationId: string;
|
|
100
144
|
sourceId: string;
|
|
145
|
+
factoryProjectId?: string;
|
|
101
146
|
}): Promise<IntakeSourceBinding | null>;
|
|
102
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Remove one Factory project's binding of a source, or — without
|
|
149
|
+
* `factoryProjectId` — every binding of it. Returns the (oldest) removed
|
|
150
|
+
* binding, or `null` when there was none.
|
|
151
|
+
*/
|
|
152
|
+
clearBinding({ orgId, integrationId, sourceId, factoryProjectId, }: {
|
|
103
153
|
orgId: string;
|
|
104
154
|
integrationId: string;
|
|
105
155
|
sourceId: string;
|
|
156
|
+
factoryProjectId?: string;
|
|
106
157
|
}): Promise<IntakeSourceBinding | null>;
|
|
158
|
+
/**
|
|
159
|
+
* Release every source a Factory project was bound to, so the others can take
|
|
160
|
+
* them. Pass the transaction deleting the project, so no binding lands in between.
|
|
161
|
+
*/
|
|
162
|
+
clearProjectBindings({ orgId, factoryProjectId }: {
|
|
163
|
+
orgId: string;
|
|
164
|
+
factoryProjectId: string;
|
|
165
|
+
}, ops?: FactoryStorageOps): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Bind a source to one Factory project, moving it away from any other: the
|
|
168
|
+
* source keeps a single binding, which takes all of its issues.
|
|
169
|
+
*/
|
|
107
170
|
setBinding({ orgId, integrationId, sourceId, factoryProjectId, board, userId, }: {
|
|
108
171
|
orgId: string;
|
|
109
172
|
integrationId: string;
|
|
@@ -112,6 +175,24 @@ export declare class IntakeStorage extends FactoryStorageDomain {
|
|
|
112
175
|
board?: string | null;
|
|
113
176
|
userId?: string;
|
|
114
177
|
}): Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Bind a source to one Factory project alongside other Factory projects'
|
|
180
|
+
* bindings of it, taking the issues carrying one of `labels` — or, with
|
|
181
|
+
* `labels: null`, every issue no label binding claims. Rejected with the
|
|
182
|
+
* overlapping bindings when another Factory project already takes some of
|
|
183
|
+
* those labels, or already takes all unclaimed issues.
|
|
184
|
+
*/
|
|
185
|
+
setSharedBinding({ orgId, integrationId, sourceId, factoryProjectId, board, labels, userId, }: {
|
|
186
|
+
orgId: string;
|
|
187
|
+
integrationId: string;
|
|
188
|
+
sourceId: string;
|
|
189
|
+
factoryProjectId: string;
|
|
190
|
+
board?: string | null;
|
|
191
|
+
labels: string[] | null;
|
|
192
|
+
userId?: string;
|
|
193
|
+
}): Promise<{
|
|
194
|
+
conflicts: IntakeBindingConflict[];
|
|
195
|
+
}>;
|
|
115
196
|
/** Atomically replace legacy provider source ids in both selection and routing state. */
|
|
116
197
|
migrateSourceIds({ orgId, integrationId, migrations, }: {
|
|
117
198
|
orgId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/intake/base.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,oBAAoB,EAAwB,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/intake/base.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,18 @@ import { FactoryStorageDomain, UniqueViolationError } from "@mastra/core/storage
|
|
|
8
8
|
* source ids the organization selected. The selection feeds shared boards, so
|
|
9
9
|
* every member reads the same one.
|
|
10
10
|
*/
|
|
11
|
+
var FactoryProjectNotFoundError = class extends Error {
|
|
12
|
+
constructor() {
|
|
13
|
+
super("Factory project not found for this organization.");
|
|
14
|
+
this.name = "FactoryProjectNotFoundError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
async function assertFactoryProject(ops, orgId, factoryProjectId) {
|
|
18
|
+
if (!await ops.findOne("factory_projects", {
|
|
19
|
+
org_id: orgId,
|
|
20
|
+
id: factoryProjectId
|
|
21
|
+
})) throw new FactoryProjectNotFoundError();
|
|
22
|
+
}
|
|
11
23
|
const DEFAULT_INTAKE_CONFIG = {};
|
|
12
24
|
const INTAKE_ORG_SETTINGS_SCHEMA = {
|
|
13
25
|
name: "intake_org_settings",
|
|
@@ -63,6 +75,11 @@ function mergeIntakeSelections(configs) {
|
|
|
63
75
|
* ingested item should land. GitHub items are naturally scoped by their linked
|
|
64
76
|
* repository; providers without that link (Linear) need this explicit binding
|
|
65
77
|
* so viewing one project's board cannot materialize another project's items.
|
|
78
|
+
*
|
|
79
|
+
* Frozen: its unique index allows one binding per source, and indexes cannot be
|
|
80
|
+
* narrowed, so bindings moved to {@link INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA}. Rows
|
|
81
|
+
* are copied once at boot and never written again; the collection stays
|
|
82
|
+
* registered so an earlier build that is rolled back to still finds them.
|
|
66
83
|
*/
|
|
67
84
|
const INTAKE_SOURCE_BINDINGS_SCHEMA = {
|
|
68
85
|
name: "intake_source_bindings",
|
|
@@ -98,6 +115,105 @@ const INTAKE_SOURCE_BINDINGS_SCHEMA = {
|
|
|
98
115
|
}]
|
|
99
116
|
};
|
|
100
117
|
/**
|
|
118
|
+
* Binds an intake source to a Factory project, one row per (source, Factory
|
|
119
|
+
* project). Most integrations keep one binding per source and rebinding moves
|
|
120
|
+
* it; integrations with `Intake.bindingLabelFilters` let several Factory
|
|
121
|
+
* projects share a source, each taking the issues carrying its own labels.
|
|
122
|
+
*/
|
|
123
|
+
const INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA = {
|
|
124
|
+
name: "intake_source_subscriptions",
|
|
125
|
+
columns: {
|
|
126
|
+
id: { type: "uuid-pk" },
|
|
127
|
+
org_id: { type: "text" },
|
|
128
|
+
integration_id: { type: "text" },
|
|
129
|
+
source_id: { type: "text" },
|
|
130
|
+
factory_project_id: { type: "text" },
|
|
131
|
+
/** Installed board the source feeds; `null` keeps the legacy source-type routing (issues → work, PRs → review). */
|
|
132
|
+
board: {
|
|
133
|
+
type: "text",
|
|
134
|
+
nullable: true
|
|
135
|
+
},
|
|
136
|
+
/** Labels whose issues this binding takes, as the user typed them; `null` takes every unclaimed issue. */
|
|
137
|
+
labels: {
|
|
138
|
+
type: "json",
|
|
139
|
+
nullable: true
|
|
140
|
+
},
|
|
141
|
+
created_by_user_id: {
|
|
142
|
+
type: "text",
|
|
143
|
+
nullable: true
|
|
144
|
+
},
|
|
145
|
+
created_at: { type: "timestamp" },
|
|
146
|
+
updated_at: { type: "timestamp" }
|
|
147
|
+
},
|
|
148
|
+
indexes: [{
|
|
149
|
+
name: "intake_source_subscriptions_project_idx",
|
|
150
|
+
columns: ["org_id", "factory_project_id"]
|
|
151
|
+
}],
|
|
152
|
+
uniqueIndexes: [{
|
|
153
|
+
name: "intake_source_subscriptions_org_integration_source_project_unique",
|
|
154
|
+
columns: [
|
|
155
|
+
"org_id",
|
|
156
|
+
"integration_id",
|
|
157
|
+
"source_id",
|
|
158
|
+
"factory_project_id"
|
|
159
|
+
]
|
|
160
|
+
}]
|
|
161
|
+
};
|
|
162
|
+
/** One-time data migrations that have run, so none re-runs over rows users have changed since. */
|
|
163
|
+
const INTAKE_DATA_MIGRATIONS_SCHEMA = {
|
|
164
|
+
name: "intake_data_migrations",
|
|
165
|
+
columns: {
|
|
166
|
+
name: {
|
|
167
|
+
type: "text",
|
|
168
|
+
primaryKey: true
|
|
169
|
+
},
|
|
170
|
+
completed_at: { type: "timestamp" }
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const COPY_LEGACY_BINDINGS_MIGRATION = "copy_intake_source_bindings_to_subscriptions";
|
|
174
|
+
/** Bindings on one source in precedence order: the oldest binding wins an issue two of them match. */
|
|
175
|
+
function inPrecedenceOrder(bindings) {
|
|
176
|
+
return [...bindings].sort((left, right) => left.createdAt.getTime() - right.createdAt.getTime() || left.factoryProjectId.localeCompare(right.factoryProjectId));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The slice of a shared source's issues `binding` takes, given every binding
|
|
180
|
+
* on that source (`siblings` may include `binding` itself). Label bindings beat
|
|
181
|
+
* the all-issues binding, and an issue carrying labels of two label bindings
|
|
182
|
+
* goes to the older one.
|
|
183
|
+
*/
|
|
184
|
+
function bindingLabelSlice(binding, siblings) {
|
|
185
|
+
const labelled = inPrecedenceOrder(siblings.filter((sibling) => sibling.labels !== null && sibling.factoryProjectId !== binding.factoryProjectId));
|
|
186
|
+
if (binding.labels === null) return {
|
|
187
|
+
include: null,
|
|
188
|
+
exclude: labelled.flatMap((sibling) => sibling.labels ?? [])
|
|
189
|
+
};
|
|
190
|
+
const older = labelled.filter((sibling) => inPrecedenceOrder([sibling, binding])[0] === sibling);
|
|
191
|
+
return {
|
|
192
|
+
include: binding.labels,
|
|
193
|
+
exclude: older.flatMap((sibling) => sibling.labels ?? [])
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/** Whether an issue carrying `labels` falls in `binding`'s slice of its source. */
|
|
197
|
+
function bindingTakesIssue(binding, siblings, labels) {
|
|
198
|
+
const carried = new Set(labels.map(normalizeIntakeLabel));
|
|
199
|
+
const { include, exclude } = bindingLabelSlice(binding, siblings);
|
|
200
|
+
if (exclude.some((label) => carried.has(normalizeIntakeLabel(label)))) return false;
|
|
201
|
+
return include === null || include.some((label) => carried.has(normalizeIntakeLabel(label)));
|
|
202
|
+
}
|
|
203
|
+
/** Trim and de-duplicate case-insensitively, keeping the first spelling the user typed. */
|
|
204
|
+
function normalizeBindingLabels(labels) {
|
|
205
|
+
const seen = /* @__PURE__ */ new Set();
|
|
206
|
+
const kept = [];
|
|
207
|
+
for (const label of labels) {
|
|
208
|
+
const trimmed = label.trim();
|
|
209
|
+
const key = normalizeIntakeLabel(trimmed);
|
|
210
|
+
if (!key || seen.has(key)) continue;
|
|
211
|
+
seen.add(key);
|
|
212
|
+
kept.push(trimmed);
|
|
213
|
+
}
|
|
214
|
+
return kept;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
101
217
|
* Routes items carrying a label to an installed board, per Factory project.
|
|
102
218
|
*
|
|
103
219
|
* Repository-scoped providers (GitHub) already know which project an item
|
|
@@ -163,9 +279,43 @@ function toIntakeSourceBinding(row) {
|
|
|
163
279
|
integrationId: row.integration_id,
|
|
164
280
|
sourceId: row.source_id,
|
|
165
281
|
factoryProjectId: row.factory_project_id,
|
|
166
|
-
board: row.board ?? null
|
|
282
|
+
board: row.board ?? null,
|
|
283
|
+
labels: row.labels ?? null,
|
|
284
|
+
createdAt: row.created_at
|
|
167
285
|
};
|
|
168
286
|
}
|
|
287
|
+
/** Overlap between a requested binding and the other Factory projects' bindings on the same source. */
|
|
288
|
+
function bindingConflicts(factoryProjectId, labels, rows) {
|
|
289
|
+
const requested = new Set(labels?.map(normalizeIntakeLabel));
|
|
290
|
+
return rows.flatMap((row) => {
|
|
291
|
+
if (row.factory_project_id === factoryProjectId) return [];
|
|
292
|
+
const theirs = row.labels ?? null;
|
|
293
|
+
if (labels === null || theirs === null) return labels === null && theirs === null ? [{
|
|
294
|
+
factoryProjectId: row.factory_project_id,
|
|
295
|
+
labels: null
|
|
296
|
+
}] : [];
|
|
297
|
+
const overlap = theirs.filter((label) => requested.has(normalizeIntakeLabel(label)));
|
|
298
|
+
return overlap.length > 0 ? [{
|
|
299
|
+
factoryProjectId: row.factory_project_id,
|
|
300
|
+
labels: overlap
|
|
301
|
+
}] : [];
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/** Update one (source, Factory project) binding in place, keeping its age, or insert it. */
|
|
305
|
+
async function upsertBinding(ops, key, patch, userId) {
|
|
306
|
+
const now = /* @__PURE__ */ new Date();
|
|
307
|
+
if (await ops.updateMany("intake_source_subscriptions", key, {
|
|
308
|
+
...patch,
|
|
309
|
+
updated_at: now
|
|
310
|
+
}) > 0) return;
|
|
311
|
+
await ops.insertOne("intake_source_subscriptions", {
|
|
312
|
+
...key,
|
|
313
|
+
...patch,
|
|
314
|
+
created_by_user_id: userId ?? null,
|
|
315
|
+
created_at: now,
|
|
316
|
+
updated_at: now
|
|
317
|
+
});
|
|
318
|
+
}
|
|
169
319
|
var IntakeStorage = class extends FactoryStorageDomain {
|
|
170
320
|
constructor() {
|
|
171
321
|
super("intake");
|
|
@@ -175,9 +325,49 @@ var IntakeStorage = class extends FactoryStorageDomain {
|
|
|
175
325
|
INTAKE_ORG_SETTINGS_SCHEMA,
|
|
176
326
|
LEGACY_INTAKE_USER_SETTINGS_SCHEMA,
|
|
177
327
|
INTAKE_SOURCE_BINDINGS_SCHEMA,
|
|
328
|
+
INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA,
|
|
329
|
+
INTAKE_DATA_MIGRATIONS_SCHEMA,
|
|
178
330
|
INTAKE_LABEL_ROUTES_SCHEMA
|
|
179
331
|
]);
|
|
180
332
|
await this.#foldLegacyUserSelections();
|
|
333
|
+
await this.#copyLegacyBindings();
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Copy `intake_source_bindings` into `intake_source_subscriptions` as
|
|
337
|
+
* all-issues bindings, once. The marker and the copy commit together, so a
|
|
338
|
+
* boot racing another cannot re-copy a binding removed after the first
|
|
339
|
+
* boot finished.
|
|
340
|
+
*/
|
|
341
|
+
async #copyLegacyBindings() {
|
|
342
|
+
try {
|
|
343
|
+
await this.storage.withTransaction(async (ops) => {
|
|
344
|
+
if (await ops.findOne("intake_data_migrations", { name: COPY_LEGACY_BINDINGS_MIGRATION })) return;
|
|
345
|
+
const legacyRows = await ops.findMany("intake_source_bindings", {});
|
|
346
|
+
for (const row of legacyRows) {
|
|
347
|
+
const key = {
|
|
348
|
+
org_id: row.org_id,
|
|
349
|
+
integration_id: row.integration_id,
|
|
350
|
+
source_id: row.source_id,
|
|
351
|
+
factory_project_id: row.factory_project_id
|
|
352
|
+
};
|
|
353
|
+
if (await ops.findOne("intake_source_subscriptions", key)) continue;
|
|
354
|
+
await ops.insertOne("intake_source_subscriptions", {
|
|
355
|
+
...key,
|
|
356
|
+
board: row.board ?? null,
|
|
357
|
+
labels: null,
|
|
358
|
+
created_by_user_id: row.created_by_user_id ?? null,
|
|
359
|
+
created_at: row.created_at,
|
|
360
|
+
updated_at: row.updated_at
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
await ops.insertOne("intake_data_migrations", {
|
|
364
|
+
name: COPY_LEGACY_BINDINGS_MIGRATION,
|
|
365
|
+
completed_at: /* @__PURE__ */ new Date()
|
|
366
|
+
});
|
|
367
|
+
}, { isolationLevel: "serializable" });
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (!(error instanceof UniqueViolationError)) throw error;
|
|
370
|
+
}
|
|
181
371
|
}
|
|
182
372
|
async #foldLegacyUserSelections() {
|
|
183
373
|
const legacyRows = await this.ops.findMany("intake_settings", {});
|
|
@@ -192,6 +382,8 @@ var IntakeStorage = class extends FactoryStorageDomain {
|
|
|
192
382
|
await this.ops.deleteMany("intake_org_settings", {});
|
|
193
383
|
await this.ops.deleteMany("intake_settings", {});
|
|
194
384
|
await this.ops.deleteMany("intake_source_bindings", {});
|
|
385
|
+
await this.ops.deleteMany("intake_source_subscriptions", {});
|
|
386
|
+
await this.ops.deleteMany("intake_data_migrations", {});
|
|
195
387
|
await this.ops.deleteMany("intake_label_routes", {});
|
|
196
388
|
}
|
|
197
389
|
get #db() {
|
|
@@ -243,69 +435,120 @@ var IntakeStorage = class extends FactoryStorageDomain {
|
|
|
243
435
|
throw error;
|
|
244
436
|
}
|
|
245
437
|
}
|
|
246
|
-
/** Every source binding in the org, optionally narrowed to one integration. */
|
|
438
|
+
/** Every source binding in the org, optionally narrowed to one integration, oldest first. */
|
|
247
439
|
async listBindings({ orgId, integrationId }) {
|
|
248
|
-
return (await this.#db.findMany("
|
|
440
|
+
return inPrecedenceOrder((await this.#db.findMany("intake_source_subscriptions", {
|
|
249
441
|
org_id: orgId,
|
|
250
442
|
...integrationId ? { integration_id: integrationId } : {}
|
|
251
|
-
})).map(toIntakeSourceBinding);
|
|
443
|
+
})).map(toIntakeSourceBinding));
|
|
252
444
|
}
|
|
253
445
|
/** Source ids bound to one Factory project. Empty means "nothing bound". */
|
|
254
446
|
async listBoundSourceIds({ orgId, integrationId, factoryProjectId }) {
|
|
255
|
-
return (await this.#db.findMany("
|
|
447
|
+
return (await this.#db.findMany("intake_source_subscriptions", {
|
|
256
448
|
org_id: orgId,
|
|
257
449
|
integration_id: integrationId,
|
|
258
450
|
factory_project_id: factoryProjectId
|
|
259
451
|
})).map((row) => row.source_id);
|
|
260
452
|
}
|
|
261
|
-
/**
|
|
262
|
-
|
|
263
|
-
|
|
453
|
+
/**
|
|
454
|
+
* One Factory project's binding of a source, or `null` when it has none.
|
|
455
|
+
* Without `factoryProjectId`, the source's oldest binding — for integrations
|
|
456
|
+
* that keep one binding per source.
|
|
457
|
+
*/
|
|
458
|
+
async getBinding({ orgId, integrationId, sourceId, factoryProjectId }) {
|
|
459
|
+
return inPrecedenceOrder((await this.#db.findMany("intake_source_subscriptions", {
|
|
264
460
|
org_id: orgId,
|
|
265
461
|
integration_id: integrationId,
|
|
266
|
-
source_id: sourceId
|
|
267
|
-
|
|
268
|
-
|
|
462
|
+
source_id: sourceId,
|
|
463
|
+
...factoryProjectId ? { factory_project_id: factoryProjectId } : {}
|
|
464
|
+
})).map(toIntakeSourceBinding))[0] ?? null;
|
|
269
465
|
}
|
|
270
|
-
|
|
466
|
+
/**
|
|
467
|
+
* Remove one Factory project's binding of a source, or — without
|
|
468
|
+
* `factoryProjectId` — every binding of it. Returns the (oldest) removed
|
|
469
|
+
* binding, or `null` when there was none.
|
|
470
|
+
*/
|
|
471
|
+
async clearBinding({ orgId, integrationId, sourceId, factoryProjectId }) {
|
|
271
472
|
const where = {
|
|
272
473
|
org_id: orgId,
|
|
273
474
|
integration_id: integrationId,
|
|
274
|
-
source_id: sourceId
|
|
475
|
+
source_id: sourceId,
|
|
476
|
+
...factoryProjectId ? { factory_project_id: factoryProjectId } : {}
|
|
275
477
|
};
|
|
276
478
|
return this.storage.withTransaction(async (ops) => {
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
await ops.deleteMany("
|
|
280
|
-
return toIntakeSourceBinding
|
|
479
|
+
const rows = await ops.findMany("intake_source_subscriptions", where);
|
|
480
|
+
if (rows.length === 0) return null;
|
|
481
|
+
await ops.deleteMany("intake_source_subscriptions", where);
|
|
482
|
+
return inPrecedenceOrder(rows.map(toIntakeSourceBinding))[0];
|
|
281
483
|
}, { isolationLevel: "serializable" });
|
|
282
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* Release every source a Factory project was bound to, so the others can take
|
|
487
|
+
* them. Pass the transaction deleting the project, so no binding lands in between.
|
|
488
|
+
*/
|
|
489
|
+
async clearProjectBindings({ orgId, factoryProjectId }, ops = this.#db) {
|
|
490
|
+
await ops.deleteMany("intake_source_subscriptions", {
|
|
491
|
+
org_id: orgId,
|
|
492
|
+
factory_project_id: factoryProjectId
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Bind a source to one Factory project, moving it away from any other: the
|
|
497
|
+
* source keeps a single binding, which takes all of its issues.
|
|
498
|
+
*/
|
|
283
499
|
async setBinding({ orgId, integrationId, sourceId, factoryProjectId, board = null, userId }) {
|
|
284
|
-
const
|
|
500
|
+
const source = {
|
|
285
501
|
org_id: orgId,
|
|
286
502
|
integration_id: integrationId,
|
|
287
503
|
source_id: sourceId
|
|
288
504
|
};
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
505
|
+
await this.storage.withTransaction(async (ops) => {
|
|
506
|
+
await assertFactoryProject(ops, orgId, factoryProjectId);
|
|
507
|
+
const rows = await ops.findMany("intake_source_subscriptions", source);
|
|
508
|
+
for (const row of rows) {
|
|
509
|
+
if (row.factory_project_id === factoryProjectId) continue;
|
|
510
|
+
await ops.deleteMany("intake_source_subscriptions", {
|
|
511
|
+
...source,
|
|
512
|
+
factory_project_id: row.factory_project_id
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
await upsertBinding(ops, {
|
|
516
|
+
...source,
|
|
517
|
+
factory_project_id: factoryProjectId
|
|
518
|
+
}, {
|
|
519
|
+
board,
|
|
520
|
+
labels: null
|
|
521
|
+
}, userId);
|
|
522
|
+
}, { isolationLevel: "serializable" });
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Bind a source to one Factory project alongside other Factory projects'
|
|
526
|
+
* bindings of it, taking the issues carrying one of `labels` — or, with
|
|
527
|
+
* `labels: null`, every issue no label binding claims. Rejected with the
|
|
528
|
+
* overlapping bindings when another Factory project already takes some of
|
|
529
|
+
* those labels, or already takes all unclaimed issues.
|
|
530
|
+
*/
|
|
531
|
+
async setSharedBinding({ orgId, integrationId, sourceId, factoryProjectId, board = null, labels, userId }) {
|
|
532
|
+
const source = {
|
|
533
|
+
org_id: orgId,
|
|
534
|
+
integration_id: integrationId,
|
|
535
|
+
source_id: sourceId
|
|
294
536
|
};
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
await
|
|
298
|
-
|
|
299
|
-
|
|
537
|
+
const kept = labels === null ? null : normalizeBindingLabels(labels);
|
|
538
|
+
return this.storage.withTransaction(async (ops) => {
|
|
539
|
+
await assertFactoryProject(ops, orgId, factoryProjectId);
|
|
540
|
+
const rows = await ops.findMany("intake_source_subscriptions", source);
|
|
541
|
+
const conflicts = bindingConflicts(factoryProjectId, kept, rows);
|
|
542
|
+
if (conflicts.length > 0) return { conflicts };
|
|
543
|
+
await upsertBinding(ops, {
|
|
544
|
+
...source,
|
|
545
|
+
factory_project_id: factoryProjectId
|
|
546
|
+
}, {
|
|
300
547
|
board,
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
} catch (error) {
|
|
306
|
-
if (!(error instanceof UniqueViolationError)) throw error;
|
|
307
|
-
await this.#db.updateMany("intake_source_bindings", where, patch);
|
|
308
|
-
}
|
|
548
|
+
labels: kept
|
|
549
|
+
}, userId);
|
|
550
|
+
return { conflicts: [] };
|
|
551
|
+
}, { isolationLevel: "serializable" });
|
|
309
552
|
}
|
|
310
553
|
/** Atomically replace legacy provider source ids in both selection and routing state. */
|
|
311
554
|
async migrateSourceIds({ orgId, integrationId, migrations }) {
|
|
@@ -314,17 +557,19 @@ var IntakeStorage = class extends FactoryStorageDomain {
|
|
|
314
557
|
conflicts: []
|
|
315
558
|
};
|
|
316
559
|
return this.storage.withTransaction(async (ops) => {
|
|
317
|
-
const bindings = await ops.findMany("
|
|
560
|
+
const bindings = await ops.findMany("intake_source_subscriptions", {
|
|
318
561
|
org_id: orgId,
|
|
319
562
|
integration_id: integrationId
|
|
320
563
|
});
|
|
321
|
-
const
|
|
564
|
+
const bindingsBySource = /* @__PURE__ */ new Map();
|
|
565
|
+
for (const binding of bindings) bindingsBySource.set(binding.source_id, [...bindingsBySource.get(binding.source_id) ?? [], binding]);
|
|
566
|
+
const destination = (rows) => rows.map((row) => `${row.factory_project_id}\0${row.board ?? ""}\0${JSON.stringify(row.labels ?? null)}`).sort().join("\n");
|
|
322
567
|
const grouped = /* @__PURE__ */ new Map();
|
|
323
568
|
for (const migration of migrations) grouped.set(migration.to, [...grouped.get(migration.to) ?? [], migration]);
|
|
324
569
|
const conflicts = [];
|
|
325
570
|
for (const [to, group] of grouped) {
|
|
326
|
-
const candidates = [
|
|
327
|
-
if (new Set(candidates.map(
|
|
571
|
+
const candidates = [bindingsBySource.get(to), ...group.map(({ from }) => bindingsBySource.get(from))].filter((rows) => rows !== void 0);
|
|
572
|
+
if (new Set(candidates.map(destination)).size > 1) conflicts.push(...group);
|
|
328
573
|
}
|
|
329
574
|
if (conflicts.length > 0) return {
|
|
330
575
|
migrations: [],
|
|
@@ -332,32 +577,26 @@ var IntakeStorage = class extends FactoryStorageDomain {
|
|
|
332
577
|
};
|
|
333
578
|
const now = /* @__PURE__ */ new Date();
|
|
334
579
|
for (const [to, group] of grouped) {
|
|
335
|
-
const
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}] : [];
|
|
342
|
-
});
|
|
343
|
-
if (!canonical && legacy.length > 0) {
|
|
580
|
+
const legacy = group.filter(({ from }) => bindingsBySource.has(from));
|
|
581
|
+
const scope = {
|
|
582
|
+
org_id: orgId,
|
|
583
|
+
integration_id: integrationId
|
|
584
|
+
};
|
|
585
|
+
if (!bindingsBySource.has(to) && legacy.length > 0) {
|
|
344
586
|
const [first, ...duplicates] = legacy;
|
|
345
|
-
await ops.updateMany("
|
|
346
|
-
|
|
347
|
-
integration_id: integrationId,
|
|
587
|
+
await ops.updateMany("intake_source_subscriptions", {
|
|
588
|
+
...scope,
|
|
348
589
|
source_id: first.from
|
|
349
590
|
}, {
|
|
350
591
|
source_id: to,
|
|
351
592
|
updated_at: now
|
|
352
593
|
});
|
|
353
|
-
for (const duplicate of duplicates) await ops.deleteMany("
|
|
354
|
-
|
|
355
|
-
integration_id: integrationId,
|
|
594
|
+
for (const duplicate of duplicates) await ops.deleteMany("intake_source_subscriptions", {
|
|
595
|
+
...scope,
|
|
356
596
|
source_id: duplicate.from
|
|
357
597
|
});
|
|
358
|
-
} else
|
|
359
|
-
|
|
360
|
-
integration_id: integrationId,
|
|
598
|
+
} else for (const entry of legacy) await ops.deleteMany("intake_source_subscriptions", {
|
|
599
|
+
...scope,
|
|
361
600
|
source_id: entry.from
|
|
362
601
|
});
|
|
363
602
|
}
|
|
@@ -434,6 +673,6 @@ var IntakeStorage = class extends FactoryStorageDomain {
|
|
|
434
673
|
}, { isolationLevel: "serializable" });
|
|
435
674
|
}
|
|
436
675
|
};
|
|
437
|
-
export { DEFAULT_INTAKE_CONFIG, INTAKE_LABEL_ROUTES_SCHEMA, INTAKE_ORG_SETTINGS_SCHEMA, INTAKE_SOURCE_BINDINGS_SCHEMA, IntakeStorage, LEGACY_INTAKE_USER_SETTINGS_SCHEMA, mergeIntakeSelections, normalizeIntakeLabel, resolveIntakeLabelRoute };
|
|
676
|
+
export { DEFAULT_INTAKE_CONFIG, FactoryProjectNotFoundError, INTAKE_DATA_MIGRATIONS_SCHEMA, INTAKE_LABEL_ROUTES_SCHEMA, INTAKE_ORG_SETTINGS_SCHEMA, INTAKE_SOURCE_BINDINGS_SCHEMA, INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA, IntakeStorage, LEGACY_INTAKE_USER_SETTINGS_SCHEMA, bindingLabelSlice, bindingTakesIssue, mergeIntakeSelections, normalizeBindingLabels, normalizeIntakeLabel, resolveIntakeLabelRoute };
|
|
438
677
|
|
|
439
678
|
//# sourceMappingURL=base.js.map
|