@useairfoil/producer-linear 0.0.1 → 0.0.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.
Files changed (51) hide show
  1. package/LICENSE.txt +201 -0
  2. package/README.md +71 -0
  3. package/dist/index.d.mts +4027 -0
  4. package/dist/index.mjs +3 -0
  5. package/dist/main.d.mts +1 -0
  6. package/dist/main.mjs +1305 -0
  7. package/dist/main.mjs.map +1 -0
  8. package/dist/manifest.d.mts +114 -0
  9. package/dist/manifest.mjs +161 -0
  10. package/dist/manifest.mjs.map +1 -0
  11. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  12. package/dist/src-BbzySPR7.mjs +1688 -0
  13. package/dist/src-BbzySPR7.mjs.map +1 -0
  14. package/package.json +46 -8
  15. package/src/client/client.ts +200 -0
  16. package/src/connector.ts +151 -0
  17. package/src/create-table.ts +10 -0
  18. package/src/demo/index.ts +137 -0
  19. package/src/demo/initiatives.ts +116 -0
  20. package/src/demo/issues.ts +320 -0
  21. package/src/demo/projects.ts +215 -0
  22. package/src/demo/setup.ts +151 -0
  23. package/src/demo/shared.ts +122 -0
  24. package/src/demo/teams.ts +173 -0
  25. package/src/index.ts +83 -0
  26. package/src/main.ts +28 -0
  27. package/src/manifest.ts +58 -0
  28. package/src/resources/comments.ts +32 -0
  29. package/src/resources/customers.ts +105 -0
  30. package/src/resources/documents.ts +31 -0
  31. package/src/resources/initiatives.ts +136 -0
  32. package/src/resources/issue-history.ts +119 -0
  33. package/src/resources/issues.ts +131 -0
  34. package/src/resources/pages.ts +264 -0
  35. package/src/resources/projects.ts +161 -0
  36. package/src/resources/releases.ts +114 -0
  37. package/src/resources/teams.ts +140 -0
  38. package/src/sandbox.ts +35 -0
  39. package/src/schemas/comments.ts +52 -0
  40. package/src/schemas/customers.ts +96 -0
  41. package/src/schemas/documents.ts +40 -0
  42. package/src/schemas/initiatives.ts +155 -0
  43. package/src/schemas/issues.ts +262 -0
  44. package/src/schemas/projects.ts +210 -0
  45. package/src/schemas/releases.ts +132 -0
  46. package/src/schemas/shared.ts +68 -0
  47. package/src/schemas/teams.ts +177 -0
  48. package/src/start.ts +47 -0
  49. package/src/tables.ts +83 -0
  50. package/src/webhook/route.ts +50 -0
  51. package/src/webhook/signature.ts +28 -0
@@ -0,0 +1,200 @@
1
+ import { ConnectorError, Metrics, Telemetry } from "@useairfoil/connector-kit";
2
+ import {
3
+ Config,
4
+ Context,
5
+ Data,
6
+ Duration,
7
+ Effect,
8
+ Layer,
9
+ Option,
10
+ Redacted,
11
+ Schedule,
12
+ Schema,
13
+ } from "effect";
14
+ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http";
15
+
16
+ import { type LinearConfig, manifest } from "../manifest";
17
+
18
+ const endpoint = "https://api.linear.app/graphql";
19
+ const maxRetries = 5;
20
+ const retryBaseDelay = Duration.seconds(1);
21
+ const requestTimeout = Duration.minutes(2);
22
+ // Keep retrying rate limits; a failed source stays stopped until restart.
23
+ const limitRetry = Schedule.min([Schedule.exponential("1 minute"), Schedule.spaced("10 minutes")]);
24
+
25
+ type Variables = Readonly<Record<string, unknown>>;
26
+
27
+ export type LinearClientService = {
28
+ readonly query: <A>(
29
+ schema: Schema.Decoder<A>,
30
+ document: string,
31
+ variables?: Variables,
32
+ ) => Effect.Effect<A, ConnectorError>;
33
+ /** Like `query`, but "Entity not found" gives `None`. */
34
+ readonly find: <A>(
35
+ schema: Schema.Decoder<A>,
36
+ document: string,
37
+ variables?: Variables,
38
+ ) => Effect.Effect<Option.Option<A>, ConnectorError>;
39
+ };
40
+
41
+ export class LinearClient extends Context.Service<LinearClient, LinearClientService>()(
42
+ "@useairfoil/producer-linear/LinearClient",
43
+ ) {}
44
+
45
+ const ResponseSchema = Schema.Struct({
46
+ data: Schema.optional(Schema.Unknown),
47
+ errors: Schema.optional(
48
+ Schema.Array(
49
+ Schema.Struct({
50
+ message: Schema.String,
51
+ extensions: Schema.optional(Schema.Struct({ code: Schema.optional(Schema.String) })),
52
+ }),
53
+ ),
54
+ ),
55
+ });
56
+
57
+ class RateLimited extends Data.TaggedError("RateLimited")<{ readonly message: string }> {}
58
+
59
+ const isNotFound = (message: string) => message.startsWith("Entity not found");
60
+
61
+ const operationName = (document: string) => /^\s*query\s+(\w+)/.exec(document)?.[1] ?? "query";
62
+
63
+ export const make = Effect.fnUntraced(function* (config: Pick<LinearConfig, "apiKey">) {
64
+ const client = (yield* HttpClient.HttpClient).pipe(
65
+ HttpClient.mapRequest((request) =>
66
+ request.pipe(
67
+ HttpClientRequest.acceptJson,
68
+ // Personal API keys go in the header as they are, without `Bearer`.
69
+ HttpClientRequest.setHeader("Authorization", Redacted.value(config.apiKey)),
70
+ ),
71
+ ),
72
+ HttpClient.retryTransient({
73
+ schedule: Metrics.retrySchedule({
74
+ connector: manifest.name,
75
+ baseDelay: retryBaseDelay,
76
+ times: maxRetries,
77
+ }),
78
+ }),
79
+ );
80
+
81
+ const execute = <A>(
82
+ schema: Schema.Decoder<A>,
83
+ document: string,
84
+ variables: Variables = {},
85
+ ): Effect.Effect<Option.Option<A>, ConnectorError> => {
86
+ const operation = operationName(document);
87
+ return Effect.gen(function* () {
88
+ const response = yield* client
89
+ .execute(
90
+ HttpClientRequest.post(endpoint).pipe(
91
+ HttpClientRequest.bodyJsonUnsafe({ query: document, variables }),
92
+ ),
93
+ )
94
+ .pipe(
95
+ Effect.tapError((error) => Telemetry.annotateError("api_http", error)),
96
+ Effect.mapError(
97
+ (error) =>
98
+ new ConnectorError({
99
+ message: `Linear API request failed for ${operation}`,
100
+ cause: error,
101
+ }),
102
+ ),
103
+ );
104
+ // GraphQL errors come with 200 or 400, so read the body first.
105
+ const body = yield* HttpClientResponse.schemaBodyJson(ResponseSchema)(response).pipe(
106
+ Effect.tapError((error) => Telemetry.annotateError("api_status", error)),
107
+ Effect.mapError(
108
+ (error) =>
109
+ new ConnectorError({
110
+ message: `Linear API returned ${response.status} for ${operation}`,
111
+ cause: error,
112
+ }),
113
+ ),
114
+ );
115
+ const errors = body.errors ?? [];
116
+ if (errors.some((error) => error.extensions?.code === "RATELIMITED")) {
117
+ return yield* new RateLimited({ message: `Linear rate limit reached for ${operation}` });
118
+ }
119
+ if (response.status === 401) {
120
+ return yield* new ConnectorError({ message: "Linear rejected the API key" });
121
+ }
122
+ if (errors.length > 0 && errors.every((error) => isNotFound(error.message))) {
123
+ return Option.none();
124
+ }
125
+ const [first] = errors;
126
+ if (first !== undefined || response.status < 200 || response.status >= 300) {
127
+ const error = new ConnectorError({
128
+ message: `Linear API returned ${response.status} for ${operation}${first ? `: ${first.message}` : ""}`,
129
+ cause: errors,
130
+ });
131
+ yield* Telemetry.annotateError("api_graphql", error);
132
+ return yield* error;
133
+ }
134
+ return Option.some(
135
+ yield* Schema.decodeUnknownEffect(schema)(body.data).pipe(
136
+ Effect.tapError((error) => Telemetry.annotateError("api_decode", error)),
137
+ Effect.mapError(
138
+ (error) =>
139
+ new ConnectorError({
140
+ message: `Linear API response for ${operation} does not match the schema`,
141
+ cause: error,
142
+ }),
143
+ ),
144
+ ),
145
+ );
146
+ }).pipe(
147
+ Effect.timeoutOrElse({
148
+ duration: requestTimeout,
149
+ orElse: () =>
150
+ Effect.fail(
151
+ new ConnectorError({ message: `Linear API request timed out for ${operation}` }),
152
+ ),
153
+ }),
154
+ Effect.retry({
155
+ schedule: limitRetry.pipe(
156
+ Schedule.tap(({ duration }) =>
157
+ Effect.logWarning("Linear rate limit reached, waiting to try again").pipe(
158
+ Effect.annotateLogs({ operation, waitMillis: Duration.toMillis(duration) }),
159
+ ),
160
+ ),
161
+ ),
162
+ while: (error) => error._tag === "RateLimited",
163
+ }),
164
+ Effect.catchTag("RateLimited", (error) =>
165
+ Effect.fail(new ConnectorError({ message: error.message })),
166
+ ),
167
+ Effect.withSpan(Telemetry.SpanName.apiFetch, {
168
+ kind: "client",
169
+ attributes: { [Telemetry.Attr.apiPath]: `graphql:${operation}` },
170
+ }),
171
+ );
172
+ };
173
+
174
+ const query = <A>(schema: Schema.Decoder<A>, document: string, variables?: Variables) =>
175
+ execute(schema, document, variables).pipe(
176
+ Effect.flatMap(
177
+ Option.match({
178
+ onNone: () =>
179
+ Effect.fail(
180
+ new ConnectorError({
181
+ message: `Linear API found nothing for ${operationName(document)}`,
182
+ }),
183
+ ),
184
+ onSome: Effect.succeed,
185
+ }),
186
+ ),
187
+ );
188
+
189
+ return LinearClient.of({ query, find: execute });
190
+ });
191
+
192
+ export const layer = (
193
+ config: Pick<LinearConfig, "apiKey">,
194
+ ): Layer.Layer<LinearClient, never, HttpClient.HttpClient> =>
195
+ Layer.effect(LinearClient)(make(config));
196
+
197
+ export const layerConfig = (
198
+ config: Config.Wrap<LinearConfig>,
199
+ ): Layer.Layer<LinearClient, Config.ConfigError, HttpClient.HttpClient> =>
200
+ Layer.unwrap(Config.unwrap(config).pipe(Effect.map(layer)));
@@ -0,0 +1,151 @@
1
+ import { Connector, type ResourceDefinition } from "@useairfoil/connector-kit";
2
+ import { Config, Context, Effect, Layer, Redacted } from "effect";
3
+
4
+ import type { LinearConfig } from "./manifest";
5
+
6
+ import * as LinearClient from "./client/client";
7
+ import { makeComments } from "./resources/comments";
8
+ import {
9
+ makeCustomerNeeds,
10
+ makeCustomerStatuses,
11
+ makeCustomerTiers,
12
+ makeCustomers,
13
+ } from "./resources/customers";
14
+ import { makeDocuments } from "./resources/documents";
15
+ import {
16
+ makeInitiativeLabels,
17
+ makeInitiativeRelations,
18
+ makeInitiativeToProjects,
19
+ makeInitiativeUpdates,
20
+ makeInitiatives,
21
+ } from "./resources/initiatives";
22
+ import { makeIssueHistory } from "./resources/issue-history";
23
+ import {
24
+ makeAttachments,
25
+ makeIssueLabels,
26
+ makeIssueRelations,
27
+ makeIssues,
28
+ } from "./resources/issues";
29
+ import {
30
+ makeProjectLabels,
31
+ makeProjectMilestones,
32
+ makeProjectRelations,
33
+ makeProjectStatuses,
34
+ makeProjectUpdates,
35
+ makeProjects,
36
+ } from "./resources/projects";
37
+ import {
38
+ makeIssueToReleases,
39
+ makeReleaseNotes,
40
+ makeReleasePipelines,
41
+ makeReleaseStages,
42
+ makeReleases,
43
+ } from "./resources/releases";
44
+ import {
45
+ makeCycles,
46
+ makeExternalUsers,
47
+ makeTeamMemberships,
48
+ makeTeams,
49
+ makeUsers,
50
+ makeWorkflowStates,
51
+ } from "./resources/teams";
52
+ import { makeWebhookRoute } from "./webhook/route";
53
+ export { LinearConfigDef, manifest } from "./manifest";
54
+ export type { LinearConfig } from "./manifest";
55
+
56
+ export const make = Effect.fnUntraced(function* (config: LinearConfig) {
57
+ const client = yield* LinearClient.LinearClient;
58
+
59
+ const Issues = makeIssues(client);
60
+ const IssueHistory = makeIssueHistory(client);
61
+ const Comments = makeComments(client);
62
+ const Attachments = makeAttachments(client);
63
+ const IssueLabels = makeIssueLabels(client);
64
+ const Users = makeUsers(client);
65
+ const Cycles = makeCycles(client);
66
+ const Projects = makeProjects(client);
67
+ const ProjectUpdates = makeProjectUpdates(client);
68
+ const ProjectLabels = makeProjectLabels(client);
69
+ const Initiatives = makeInitiatives(client);
70
+ const InitiativeUpdates = makeInitiativeUpdates(client);
71
+ const Documents = makeDocuments(client);
72
+ const Customers = makeCustomers(client);
73
+ const CustomerNeeds = makeCustomerNeeds(client);
74
+
75
+ return Connector.define({
76
+ name: "producer-linear",
77
+ title: "Linear",
78
+ resources: [
79
+ Issues,
80
+ IssueHistory,
81
+ Comments,
82
+ makeIssueRelations(client),
83
+ Attachments,
84
+ IssueLabels,
85
+ makeWorkflowStates(client),
86
+ makeTeams(client),
87
+ makeTeamMemberships(client),
88
+ Users,
89
+ makeExternalUsers(client),
90
+ Cycles,
91
+ Projects,
92
+ makeProjectMilestones(client),
93
+ ProjectUpdates,
94
+ makeProjectStatuses(client),
95
+ ProjectLabels,
96
+ makeProjectRelations(client),
97
+ Initiatives,
98
+ InitiativeUpdates,
99
+ makeInitiativeLabels(client),
100
+ makeInitiativeRelations(client),
101
+ makeInitiativeToProjects(client),
102
+ Documents,
103
+ Customers,
104
+ CustomerNeeds,
105
+ makeCustomerStatuses(client),
106
+ makeCustomerTiers(client),
107
+ makeReleases(client),
108
+ makeReleaseNotes(client),
109
+ makeReleasePipelines(client),
110
+ makeReleaseStages(client),
111
+ makeIssueToReleases(client),
112
+ ],
113
+ webhooks: [
114
+ makeWebhookRoute({
115
+ secret: Redacted.value(config.webhookSecret),
116
+ issueHistory: IssueHistory,
117
+ // Release webhooks need the Business plan.
118
+ targets: new Map<string, ResourceDefinition>([
119
+ ["Issue", Issues],
120
+ ["Comment", Comments],
121
+ ["Attachment", Attachments],
122
+ ["IssueLabel", IssueLabels],
123
+ ["User", Users],
124
+ ["Cycle", Cycles],
125
+ ["Project", Projects],
126
+ ["ProjectUpdate", ProjectUpdates],
127
+ ["ProjectLabel", ProjectLabels],
128
+ ["Initiative", Initiatives],
129
+ ["InitiativeUpdate", InitiativeUpdates],
130
+ ["Document", Documents],
131
+ ["Customer", Customers],
132
+ ["CustomerNeed", CustomerNeeds],
133
+ ]),
134
+ }),
135
+ ],
136
+ });
137
+ });
138
+
139
+ export type LinearConnectorRuntime = Effect.Success<ReturnType<typeof make>>;
140
+
141
+ export class LinearConnector extends Context.Service<LinearConnector, LinearConnectorRuntime>()(
142
+ "@useairfoil/producer-linear/LinearConnector",
143
+ ) {}
144
+
145
+ export const layer = (config: LinearConfig) =>
146
+ Layer.effect(LinearConnector)(
147
+ make(config).pipe(Effect.annotateLogs({ component: "linear" })),
148
+ ).pipe(Layer.provide(LinearClient.layer(config)));
149
+
150
+ export const layerConfig = (config: Config.Wrap<LinearConfig>) =>
151
+ Layer.unwrap(Config.unwrap(config).pipe(Effect.map(layer)));
@@ -0,0 +1,10 @@
1
+ import { ConnectorApp, RuntimeConfig } from "@useairfoil/connector-kit";
2
+ import { Command } from "effect/cli";
3
+
4
+ import { tableSchemas } from "./tables";
5
+
6
+ export const createTableCommand = ConnectorApp.makeCreateTableCommand(tableSchemas);
7
+
8
+ export const setupCommand = ConnectorApp.makeSetupCommand(tableSchemas).pipe(
9
+ Command.provide(RuntimeConfig.layerHosted()),
10
+ );
@@ -0,0 +1,137 @@
1
+ import { ConnectorApp, Demo, RuntimeConfig } from "@useairfoil/connector-kit";
2
+ import { Command } from "effect/cli";
3
+
4
+ import { CommentSchema } from "../schemas/comments";
5
+ import {
6
+ CustomerNeedSchema,
7
+ CustomerSchema,
8
+ CustomerStatusSchema,
9
+ CustomerTierSchema,
10
+ } from "../schemas/customers";
11
+ import { DocumentSchema } from "../schemas/documents";
12
+ import {
13
+ InitiativeLabelSchema,
14
+ InitiativeRelationSchema,
15
+ InitiativeSchema,
16
+ InitiativeToProjectSchema,
17
+ InitiativeUpdateSchema,
18
+ } from "../schemas/initiatives";
19
+ import {
20
+ AttachmentSchema,
21
+ IssueHistorySchema,
22
+ IssueLabelSchema,
23
+ IssueRelationSchema,
24
+ IssueSchema,
25
+ } from "../schemas/issues";
26
+ import {
27
+ ProjectLabelSchema,
28
+ ProjectMilestoneSchema,
29
+ ProjectRelationSchema,
30
+ ProjectSchema,
31
+ ProjectStatusSchema,
32
+ ProjectUpdateSchema,
33
+ } from "../schemas/projects";
34
+ import {
35
+ IssueToReleaseSchema,
36
+ ReleaseNoteSchema,
37
+ ReleasePipelineSchema,
38
+ ReleaseSchema,
39
+ ReleaseStageSchema,
40
+ } from "../schemas/releases";
41
+ import {
42
+ CycleSchema,
43
+ ExternalUserSchema,
44
+ TeamMembershipSchema,
45
+ TeamSchema,
46
+ UserSchema,
47
+ WorkflowStateSchema,
48
+ } from "../schemas/teams";
49
+ import { issueRows } from "./issues";
50
+ import { setupRows } from "./setup";
51
+
52
+ export const demo = Demo.define({
53
+ name: "producer-linear",
54
+ setup: {
55
+ tables: [
56
+ Demo.table({ name: "teams", rowSchema: TeamSchema, version: "version" }),
57
+ Demo.table({ name: "workflow_states", rowSchema: WorkflowStateSchema, version: "version" }),
58
+ Demo.table({ name: "users", rowSchema: UserSchema, version: "version" }),
59
+ Demo.table({ name: "team_memberships", rowSchema: TeamMembershipSchema, version: "version" }),
60
+ Demo.table({ name: "external_users", rowSchema: ExternalUserSchema, version: "version" }),
61
+ Demo.table({ name: "issue_labels", rowSchema: IssueLabelSchema, version: "version" }),
62
+ Demo.table({ name: "cycles", rowSchema: CycleSchema, version: "version" }),
63
+ Demo.table({ name: "project_statuses", rowSchema: ProjectStatusSchema, version: "version" }),
64
+ Demo.table({ name: "project_labels", rowSchema: ProjectLabelSchema, version: "version" }),
65
+ Demo.table({ name: "projects", rowSchema: ProjectSchema, version: "version" }),
66
+ Demo.table({
67
+ name: "project_milestones",
68
+ rowSchema: ProjectMilestoneSchema,
69
+ version: "version",
70
+ }),
71
+ Demo.table({ name: "project_updates", rowSchema: ProjectUpdateSchema, version: "version" }),
72
+ Demo.table({
73
+ name: "project_relations",
74
+ rowSchema: ProjectRelationSchema,
75
+ version: "version",
76
+ }),
77
+ Demo.table({ name: "documents", rowSchema: DocumentSchema, version: "version" }),
78
+ Demo.table({ name: "initiatives", rowSchema: InitiativeSchema, version: "version" }),
79
+ Demo.table({
80
+ name: "initiative_labels",
81
+ rowSchema: InitiativeLabelSchema,
82
+ version: "version",
83
+ }),
84
+ Demo.table({
85
+ name: "initiative_updates",
86
+ rowSchema: InitiativeUpdateSchema,
87
+ version: "version",
88
+ }),
89
+ Demo.table({
90
+ name: "initiative_relations",
91
+ rowSchema: InitiativeRelationSchema,
92
+ version: "version",
93
+ }),
94
+ Demo.table({
95
+ name: "initiative_to_projects",
96
+ rowSchema: InitiativeToProjectSchema,
97
+ version: "version",
98
+ }),
99
+ Demo.table({
100
+ name: "customer_statuses",
101
+ rowSchema: CustomerStatusSchema,
102
+ version: "version",
103
+ }),
104
+ Demo.table({ name: "customer_tiers", rowSchema: CustomerTierSchema, version: "version" }),
105
+ Demo.table({ name: "customers", rowSchema: CustomerSchema, version: "version" }),
106
+ Demo.table({
107
+ name: "release_pipelines",
108
+ rowSchema: ReleasePipelineSchema,
109
+ version: "version",
110
+ }),
111
+ Demo.table({ name: "release_stages", rowSchema: ReleaseStageSchema, version: "version" }),
112
+ Demo.table({ name: "releases", rowSchema: ReleaseSchema, version: "version" }),
113
+ Demo.table({ name: "release_notes", rowSchema: ReleaseNoteSchema, version: "version" }),
114
+ ],
115
+ rows: setupRows,
116
+ },
117
+ group: {
118
+ tables: [
119
+ Demo.table({ name: "issues", rowSchema: IssueSchema, version: "version" }),
120
+ Demo.table({ name: "issue_history", rowSchema: IssueHistorySchema, version: "version" }),
121
+ Demo.table({ name: "comments", rowSchema: CommentSchema, version: "version" }),
122
+ Demo.table({ name: "issue_relations", rowSchema: IssueRelationSchema, version: "version" }),
123
+ Demo.table({ name: "attachments", rowSchema: AttachmentSchema, version: "version" }),
124
+ Demo.table({ name: "customer_needs", rowSchema: CustomerNeedSchema, version: "version" }),
125
+ Demo.table({
126
+ name: "issue_to_releases",
127
+ rowSchema: IssueToReleaseSchema,
128
+ version: "version",
129
+ }),
130
+ ],
131
+ rows: issueRows,
132
+ },
133
+ });
134
+
135
+ export const demoCommand = ConnectorApp.makeDemoCommand(demo).pipe(
136
+ Command.provide(RuntimeConfig.layerHosted()),
137
+ );
@@ -0,0 +1,116 @@
1
+ import type { Demo } from "@useairfoil/connector-kit";
2
+
3
+ import type {
4
+ Initiative,
5
+ InitiativeLabel,
6
+ InitiativeRelation,
7
+ InitiativeToProject,
8
+ InitiativeUpdate,
9
+ } from "../schemas/initiatives";
10
+
11
+ import {
12
+ dateOnly,
13
+ day,
14
+ initiativeId,
15
+ projectId,
16
+ projects,
17
+ stamp,
18
+ userId,
19
+ uuid,
20
+ workspace,
21
+ } from "./shared";
22
+
23
+ /** Initiatives with their labels, updates, relations, and projects. */
24
+ export const initiativeTables = ({ now }: Demo.DemoContext, founded: Date) => {
25
+ const set = stamp(founded, founded);
26
+
27
+ const initiatives = [
28
+ "Enterprise readiness",
29
+ "Self-serve growth",
30
+ "Platform reliability",
31
+ ] as const;
32
+
33
+ const initiativeRows: Array<Initiative> = initiatives.map((name, i) => ({
34
+ id: initiativeId(i),
35
+ ...stamp(founded, now),
36
+ _deleted: false,
37
+ name,
38
+ description: `${name} for this year`,
39
+ content: null,
40
+ url: `${workspace}/initiative/${name.toLowerCase().replaceAll(" ", "-")}`,
41
+ icon: null,
42
+ color: null,
43
+ status: "Active",
44
+ health: "onTrack",
45
+ health_updated_at: new Date(now.getTime() - 7 * day),
46
+ priority: BigInt(i + 1),
47
+ owner_id: userId(i),
48
+ creator_id: userId(0),
49
+ parent_initiative_id: null,
50
+ target_date: dateOnly(new Date(now.getTime() + 90 * day)),
51
+ started_at: founded,
52
+ completed_at: null,
53
+ canceled_at: null,
54
+ label_ids: [uuid(20, i % 2)],
55
+ visibility: "workspace",
56
+ trashed: false,
57
+ }));
58
+
59
+ const initiativeLabels: Array<InitiativeLabel> = ["Company goal", "Team goal"].map((name, l) => ({
60
+ id: uuid(20, l),
61
+ ...set,
62
+ name,
63
+ description: null,
64
+ color: "#5E6AD2",
65
+ is_group: false,
66
+ parent_id: null,
67
+ creator_id: userId(0),
68
+ retired_at: null,
69
+ }));
70
+
71
+ const initiativeUpdates: Array<InitiativeUpdate> = initiatives.flatMap((_, i) =>
72
+ Array.from({ length: 6 }, (_, k) => {
73
+ const posted = new Date(now.getTime() - (6 - k) * 30 * day);
74
+
75
+ return {
76
+ id: uuid(21, i, k),
77
+ ...stamp(posted, posted),
78
+ _deleted: false,
79
+ initiative_id: initiativeId(i),
80
+ user_id: userId(i),
81
+ body: "Monthly check-in: projects are moving and the main risks are covered.",
82
+ health: "onTrack",
83
+ url: `${workspace}/initiative/${i}/updates#${k}`,
84
+ edited_at: null,
85
+ comment_count: 0n,
86
+ };
87
+ }),
88
+ );
89
+
90
+ const initiativeRelations: Array<InitiativeRelation> = [
91
+ {
92
+ id: uuid(22, 0),
93
+ ...set,
94
+ initiative_id: initiativeId(0),
95
+ related_initiative_id: initiativeId(2),
96
+ sort_order: 0,
97
+ user_id: userId(0),
98
+ },
99
+ ];
100
+
101
+ const initiativeProjects: Array<InitiativeToProject> = projects.map((_, p) => ({
102
+ id: uuid(23, p),
103
+ ...set,
104
+ initiative_id: initiativeId(p % initiatives.length),
105
+ project_id: projectId(p),
106
+ sort_order: String(p),
107
+ }));
108
+
109
+ return {
110
+ initiatives: initiativeRows,
111
+ initiative_labels: initiativeLabels,
112
+ initiative_updates: initiativeUpdates,
113
+ initiative_relations: initiativeRelations,
114
+ initiative_to_projects: initiativeProjects,
115
+ };
116
+ };