@inlang/sdk 0.34.9 → 0.34.10

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 (45) hide show
  1. package/dist/adapter/solidAdapter.js +1 -1
  2. package/dist/adapter/solidAdapter.test.js +60 -23
  3. package/dist/api.d.ts +15 -7
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/createMessageLintReportsQuery.d.ts.map +1 -1
  6. package/dist/createMessageLintReportsQuery.js +158 -64
  7. package/dist/createMessagesQuery.d.ts.map +1 -1
  8. package/dist/createMessagesQuery.js +29 -11
  9. package/dist/lint/message/lintSingleMessage.d.ts.map +1 -1
  10. package/dist/lint/message/lintSingleMessage.js +3 -1
  11. package/dist/loadProject.d.ts.map +1 -1
  12. package/dist/loadProject.js +6 -2
  13. package/dist/loadProject.test.js +38 -25
  14. package/dist/reactivity/solid.d.ts +2 -1
  15. package/dist/reactivity/solid.d.ts.map +1 -1
  16. package/dist/reactivity/solid.js +3 -2
  17. package/dist/reactivity/solid.test.js +38 -1
  18. package/dist/v2/createMessageBundle.d.ts +25 -0
  19. package/dist/v2/createMessageBundle.d.ts.map +1 -0
  20. package/dist/v2/createMessageBundle.js +36 -0
  21. package/dist/v2/createMessageBundle.test.d.ts +2 -0
  22. package/dist/v2/createMessageBundle.test.d.ts.map +1 -0
  23. package/dist/v2/createMessageBundle.test.js +92 -0
  24. package/dist/v2/mocks/plural/bundle.d.ts +3 -0
  25. package/dist/v2/mocks/plural/bundle.d.ts.map +1 -0
  26. package/dist/v2/mocks/plural/bundle.js +140 -0
  27. package/dist/v2/mocks/plural/bundle.test.d.ts +2 -0
  28. package/dist/v2/mocks/plural/bundle.test.d.ts.map +1 -0
  29. package/dist/v2/mocks/plural/bundle.test.js +15 -0
  30. package/package.json +5 -5
  31. package/src/adapter/solidAdapter.test.ts +78 -33
  32. package/src/adapter/solidAdapter.ts +1 -1
  33. package/src/api.ts +14 -7
  34. package/src/createMessageLintReportsQuery.ts +183 -68
  35. package/src/createMessagesQuery.ts +32 -11
  36. package/src/createNodeishFsWithWatcher.ts +1 -1
  37. package/src/lint/message/lintSingleMessage.ts +4 -1
  38. package/src/loadProject.test.ts +45 -24
  39. package/src/loadProject.ts +7 -2
  40. package/src/reactivity/solid.test.ts +54 -1
  41. package/src/reactivity/solid.ts +3 -0
  42. package/src/v2/createMessageBundle.test.ts +95 -0
  43. package/src/v2/createMessageBundle.ts +43 -0
  44. package/src/v2/mocks/plural/bundle.test.ts +18 -0
  45. package/src/v2/mocks/plural/bundle.ts +142 -0
@@ -1,13 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
2
  import { describe, it, expect } from "vitest"
3
3
  import type { ImportFunction } from "../resolve-modules/index.js"
4
- import {
5
- createResource,
6
- createSignal,
7
- createEffect,
8
- from,
9
- createRoot,
10
- } from "../reactivity/solid.js"
4
+ import { createEffect, from, createRoot } from "../reactivity/solid.js"
11
5
  import { solidAdapter } from "./solidAdapter.js"
12
6
  import { loadProject } from "../loadProject.js"
13
7
  import { mockRepo } from "@lix-js/client"
@@ -19,10 +13,6 @@ import type {
19
13
  Text,
20
14
  } from "../versionedInterfaces.js"
21
15
 
22
- function sleep(ms: number) {
23
- return new Promise((resolve) => setTimeout(resolve, ms))
24
- }
25
-
26
16
  // ------------------------------------------------------------------------------------------------
27
17
 
28
18
  const config: ProjectSettings = {
@@ -170,8 +160,8 @@ describe("installed", () => {
170
160
  // TODO: how can we await `setConfig` correctly
171
161
  await new Promise((resolve) => setTimeout(resolve, 0))
172
162
 
173
- expect(counterPlugins).toBe(2) // 2 times because effect creation + set
174
- expect(counterLint).toBe(2) // 2 times because effect creation + set
163
+ expect(counterPlugins).toBe(3) // 3 times because effect creation + setSettings, setResolvedModules
164
+ expect(counterLint).toBe(3) // 3 times because effect creation + setSettings, setResolvedModules
175
165
  })
176
166
  })
177
167
 
@@ -219,7 +209,7 @@ describe("messages", () => {
219
209
  { from }
220
210
  )
221
211
 
222
- let effectOnMessagesCounter = 0
212
+ let effectOnMessagesCounter = -1
223
213
  createEffect(() => {
224
214
  project.query.messages.getAll()
225
215
  effectOnMessagesCounter += 1
@@ -232,7 +222,7 @@ describe("messages", () => {
232
222
  // TODO: how can we await `setConfig` correctly
233
223
  await new Promise((resolve) => setTimeout(resolve, 510))
234
224
 
235
- expect(effectOnMessagesCounter).toBe(7) // 7 = initial effect, setSetting, loadMessage (2x - one per message), setResolvedPlugins, loadMessages (2x - one per message)
225
+ expect(effectOnMessagesCounter).toBe(3) // 3 = setSetting, loadMessage (2x - one per message)
236
226
  expect(Object.values(project.query.messages.getAll()).length).toBe(2)
237
227
  })
238
228
 
@@ -363,6 +353,65 @@ describe("messages", () => {
363
353
  })
364
354
 
365
355
  describe("lint", () => {
356
+ it("should react to changes in config", async () => {
357
+ await createRoot(async () => {
358
+ const repo = await mockRepo()
359
+ const fs = repo.nodeishFs
360
+ await fs.mkdir("/user/project.inlang", { recursive: true })
361
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(config))
362
+ const project = solidAdapter(
363
+ await loadProject({
364
+ projectPath: "/user/project.inlang",
365
+ repo,
366
+ _import: $import,
367
+ }),
368
+ { from }
369
+ )
370
+
371
+ // set counter to -1 since creating effect will execute once
372
+ let counter = -1
373
+ createEffect(() => {
374
+ project.query.messageLintReports.getAll()
375
+ counter += 1
376
+ })
377
+
378
+ expect(counter).toBe(0)
379
+
380
+ // wait for all int reports beeing executed
381
+ await new Promise((resolve) => setTimeout(resolve, 510))
382
+
383
+ // 1 because we batch the two lint rules
384
+ expect(counter).toBe(1)
385
+
386
+ const currentSettings = project.settings()!
387
+ const newConfig = { ...currentSettings, languageTags: ["en", "de"] }
388
+ project.setSettings(newConfig)
389
+
390
+ // set settings trigges synchronous -> +1
391
+ expect(counter).toBe(2)
392
+
393
+ await new Promise((resolve) => setTimeout(resolve, 510))
394
+
395
+ // 4 = 2 + batched(1 (effect->settings) and 1 (effect->Resolved Plugin))
396
+ expect(counter).toBe(3)
397
+ expect(project.query.messageLintReports.getAll()).toEqual([])
398
+
399
+ await new Promise((resolve) => setTimeout(resolve, 510))
400
+
401
+ const newConfig2 = { ...project.settings()!, languageTags: ["en", "de", "fr"] }
402
+ project.setSettings(newConfig2)
403
+
404
+ // set settings trigges synchronous -> +1
405
+ expect(counter).toBe(4)
406
+
407
+ await new Promise((resolve) => setTimeout(resolve, 510))
408
+
409
+ // 5 -> 4 + 1 new lint report batch
410
+ expect(counter).toBe(5)
411
+ expect(project.query.messageLintReports.getAll()).toEqual([])
412
+ })
413
+ })
414
+
366
415
  it.todo("should react to changes to packages")
367
416
  it.todo("should react to changes to modules")
368
417
 
@@ -381,23 +430,16 @@ describe("lint", () => {
381
430
  { from }
382
431
  )
383
432
 
384
- const [messages, setMessages] = createSignal<readonly Message[]>()
385
-
386
- let counter = 0
387
-
433
+ let counter = -1 // -1 to start counting after the initial effect
388
434
  createEffect(() => {
389
- setMessages(project.query.messages.getAll())
390
- })
391
-
392
- const [lintReports] = createResource(messages, async () => {
393
- const reports = await project.query.messageLintReports.getAll()
435
+ project.query.messageLintReports.getAll()
394
436
  counter += 1
395
- return reports
396
437
  })
438
+ expect(counter).toBe(0)
397
439
 
398
- await sleep(10)
440
+ await new Promise((resolve) => setTimeout(resolve, 510))
441
+ // 1 -> lint rules are batched - we expect one signal on getAll
399
442
  expect(counter).toBe(1)
400
- expect(lintReports()).toStrictEqual([])
401
443
 
402
444
  project.query.messages.update({
403
445
  where: { id: "a" },
@@ -406,10 +448,11 @@ describe("lint", () => {
406
448
  variants: [{ languageTag: "en", match: [], pattern: [{ type: "Text", value: "new" }] }],
407
449
  },
408
450
  })
451
+ await new Promise((resolve) => setTimeout(resolve, 510))
409
452
 
410
- await sleep(10)
411
- expect(counter).toBe(2)
412
- expect(lintReports()).toStrictEqual([])
453
+ // 1 -> previous two messages + 0 - report results are the same - no effect
454
+ expect(counter).toBe(1)
455
+ expect(project.query.messageLintReports.getAll()).toEqual([])
413
456
 
414
457
  project.query.messages.update({
415
458
  where: { id: "a" },
@@ -419,9 +462,11 @@ describe("lint", () => {
419
462
  },
420
463
  })
421
464
 
422
- await sleep(10)
423
- expect(counter).toBe(3)
424
- expect(lintReports()).toStrictEqual([])
465
+ await new Promise((resolve) => setTimeout(resolve, 510))
466
+
467
+ // 2 -> the updated message does not update the lint rules - result is the same
468
+ expect(counter).toBe(1)
469
+ expect(project.query.messageLintReports.getAll()).toEqual([])
425
470
  })
426
471
  })
427
472
  })
@@ -39,7 +39,7 @@ export const solidAdapter = (
39
39
  },
40
40
  messageLintReports: {
41
41
  get: project.query.messageLintReports.get,
42
- getAll: project.query.messageLintReports.getAll,
42
+ getAll: convert(project.query.messageLintReports.getAll),
43
43
  },
44
44
  },
45
45
  } satisfies InlangProjectWithSolidAdapter
package/src/api.ts CHANGED
@@ -82,9 +82,9 @@ export type Subscribable<Value> = {
82
82
  }
83
83
 
84
84
  export type MessageQueryDelegate = {
85
- onMessageCreate: (messageId: string, message: Message) => void
86
- onMessageUpdate: (messageId: string, message: Message) => void
87
- onMessageDelete: (messageId: string) => void
85
+ onMessageCreate: (messageId: string, message: Message, messages: Message[]) => void
86
+ onMessageUpdate: (messageId: string, message: Message, messages: Message[]) => void
87
+ onMessageDelete: (messageId: string, messages: Message[]) => void
88
88
  onLoaded: (messages: Message[]) => void
89
89
  onCleanup: () => void
90
90
  }
@@ -109,12 +109,19 @@ export type MessageQueryApi = {
109
109
  update: (args: { where: { id: Message["id"] }; data: Partial<Message> }) => boolean
110
110
  upsert: (args: { where: { id: Message["id"] }; data: Message }) => void
111
111
  delete: (args: { where: { id: Message["id"] } }) => boolean
112
- setDelegate: (delegate: MessageQueryDelegate) => void
112
+ setDelegate: (delegate: MessageQueryDelegate | undefined, callOnLoad: boolean) => void
113
113
  }
114
114
 
115
115
  export type MessageLintReportsQueryApi = {
116
- getAll: () => Promise<MessageLintReport[]>
117
- get: (args: {
116
+ getAll: Subscribable<MessageLintReport[]> & {
117
+ settled: () => Promise<MessageLintReport[]>
118
+ }
119
+ get: ((args: {
118
120
  where: { messageId: MessageLintReport["messageId"] }
119
- }) => Promise<Readonly<MessageLintReport[]>>
121
+ }) => Readonly<MessageLintReport[]>) & {
122
+ subscribe: (
123
+ args: { where: { messageId: MessageLintReport["messageId"] } },
124
+ callback: (MessageLintRules: Readonly<MessageLintReport[]>) => void
125
+ ) => void
126
+ }
120
127
  }
@@ -10,13 +10,13 @@ import type { resolveModules } from "./resolve-modules/index.js"
10
10
  import type { MessageLintReport, Message } from "./versionedInterfaces.js"
11
11
  import { lintSingleMessage } from "./lint/index.js"
12
12
 
13
+ import { ReactiveMap } from "./reactivity/map.js"
14
+ import { createMemo, onCleanup, untrack, batch } from "./reactivity/solid.js"
15
+ import { createSubscribable } from "./loadProject.js"
16
+
13
17
  import _debug from "debug"
14
18
  const debug = _debug("sdk:lintReports")
15
19
 
16
- function sleep(ms: number) {
17
- return new Promise((resolve) => setTimeout(resolve, ms))
18
- }
19
-
20
20
  /**
21
21
  * Creates a non-reactive async query API for lint reports.
22
22
  * e.g. used in editor/.../Listheader.tsx
@@ -30,81 +30,196 @@ export function createMessageLintReportsQuery(
30
30
  installedMessageLintRules: () => Array<InstalledMessageLintRule>,
31
31
  resolvedModules: () => Awaited<ReturnType<typeof resolveModules>> | undefined
32
32
  ): InlangProject["query"]["messageLintReports"] {
33
- const index = new Map<MessageLintReport["messageId"], MessageLintReport[]>()
34
-
35
- const modules = resolvedModules()
36
-
37
- const rulesArray = modules?.messageLintRules
38
- const messageLintRuleLevels = Object.fromEntries(
39
- installedMessageLintRules().map((rule) => [rule.id, rule.level])
40
- )
41
- const settingsObject = () => {
42
- return {
43
- ...settings(),
44
- messageLintRuleLevels,
45
- }
46
- }
33
+ // @ts-expect-error Reactive map seems to have a problem with Type aliases here
34
+ const index = new ReactiveMap<MessageLintReport["messageId"], MessageLintReport[]>()
35
+
36
+ debug("resetting settledReports")
37
+ let settledReports = Promise.resolve()
38
+
39
+ let currentBatchEnd: undefined | (() => Promise<void>) = undefined
40
+
41
+ const updatedReports: { [messageId: string]: MessageLintReport[] } = {}
42
+
43
+ // triggered whenever settings or resolved modules changes
44
+ createMemo(() => {
45
+ // we clear the index independent from the change for
46
+ index.clear()
47
+
48
+ onCleanup(() => {
49
+ messagesQuery.setDelegate(undefined, false)
50
+ })
47
51
 
48
- const lintMessage = (message: Message, messages: Message[]) => {
49
- if (!rulesArray) {
50
- return
52
+ // settings is a signal - effect will be called whenever settings changes
53
+ const _settings = settings()
54
+ if (!_settings) return
55
+
56
+ const _resolvedModules = resolvedModules()
57
+ if (!_resolvedModules) return
58
+
59
+ // resolvedModules is a signal - effect will be called whenever resolvedModules changes
60
+ const rulesArray = _resolvedModules.messageLintRules
61
+
62
+ const messageLintRuleLevels = Object.fromEntries(
63
+ installedMessageLintRules().map((rule) => [rule.id, rule.level])
64
+ )
65
+ const settingsObject = () => {
66
+ return {
67
+ ...settings(),
68
+ messageLintRuleLevels,
69
+ }
51
70
  }
52
71
 
53
- // TODO unhandled promise rejection (as before the refactor) but won't tackle this in this pr
54
- lintSingleMessage({
55
- rules: rulesArray,
56
- settings: settingsObject(),
57
- messages: messages,
58
- message: message,
59
- }).then((report) => {
60
- if (report.errors.length === 0 && index.get(message.id) !== report.data) {
61
- debug("lintSingleMessage", message.id, report.data.length)
62
- index.set(message.id, report.data)
72
+ const sheduleLintMessage = (message: Message, messages: Message[]) => {
73
+ debug("shedule Lint for message:", message.id)
74
+
75
+ const updateOutstandingReportsOnLast = async () => {
76
+ if (currentBatchEnd !== updateOutstandingReportsOnLast) {
77
+ debug("skip triggering reactivy", message.id)
78
+ return
79
+ }
80
+ debug("finished queue - trigger reactivity", message.id)
81
+
82
+ batch(() => {
83
+ for (const [id, reports] of Object.entries(updatedReports)) {
84
+ const currentReports = index.get(id)
85
+ // we only update the report if it differs from the known one - to not trigger reactivity
86
+ if (!reportsEqual(currentReports, reports)) {
87
+ debug("lint reports for message: ", id, " now n:", reports.length)
88
+ index.set(id, reports)
89
+ }
90
+ }
91
+ })
63
92
  }
93
+
94
+ currentBatchEnd = updateOutstandingReportsOnLast
95
+
96
+ const scheduledLint = lintSingleMessage({
97
+ rules: rulesArray,
98
+ settings: settingsObject(),
99
+ messages: messages,
100
+ message: message,
101
+ }).then((reportsResult) => {
102
+ if (reportsResult.errors.length === 0) {
103
+ debug("lint reports for message: ", message.id, "n:", reportsResult.data.length)
104
+ updatedReports[message.id] = reportsResult.data
105
+ }
106
+
107
+ return updateOutstandingReportsOnLast()
108
+ })
109
+ settledReports = settledReports.then(() => scheduledLint)
110
+ }
111
+
112
+ // setup delegate of message query
113
+ const messageQueryChangeDelegate: MessageQueryDelegate = {
114
+ onCleanup: () => {
115
+ // NOTE: we could cancel all running lint rules - but results get overritten anyway
116
+ index.clear()
117
+ },
118
+ onLoaded: (messages: Message[]) => {
119
+ // for (const message of messages) {
120
+ // lintMessage(message, messages)
121
+ // }
122
+ debug("sheduluing Lint for all messages - on load")
123
+ batch(() => {
124
+ debug("sheduluing Lint for all messages - subsquencial call?")
125
+ for (const message of messages) {
126
+ // NOTE: this potentually creates thousands of promisses we could create a promise that batches linting
127
+ // NOTE: this produces a lot of signals - we could batch the
128
+ sheduleLintMessage(message, messages)
129
+ }
130
+ })
131
+ },
132
+ onMessageCreate: (messageId: string, message: Message, messages: Message[]) => {
133
+ // NOTE: unhandled promise rejection (as before the refactor) but won't tackle this in this pr
134
+ // TODO MESDK-105 reevaluate all lint's instead of those for the messsage that where created
135
+ debug("shedule Lint for message - onMessageCreate", message.id)
136
+ sheduleLintMessage(message, messages)
137
+ },
138
+ onMessageUpdate: (messageId: string, message: Message, messages: Message[]) => {
139
+ // NOTE: unhandled promise rejection (as before the refactor) but won't tackle this in this pr
140
+ // TODO MESDK-105 reevaluate all lint's instead of those for the messsage that changed
141
+ debug("shedule Lint for message - onMessageUpdate", message.id)
142
+ sheduleLintMessage(message, messages)
143
+ },
144
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO MESDK-105 we gonna need the mesage Property for evaluation
145
+ onMessageDelete: (messageId: string, _messages: Message[]) => {
146
+ index.delete(messageId)
147
+ },
148
+ }
149
+
150
+ untrack(() => {
151
+ messagesQuery.setDelegate(messageQueryChangeDelegate, true)
64
152
  })
153
+ })
154
+
155
+ const get = (args: Parameters<MessageLintReportsQueryApi["get"]>[0]) => {
156
+ debug("get", args.where.messageId)
157
+ return structuredClone(index.get(args.where.messageId))
65
158
  }
66
159
 
67
- const messages = messagesQuery.getAll() as Message[]
68
- // load report for all messages once
69
- for (const message of messages) {
70
- // NOTE: this potentually creates thousands of promisses we could create a promise that batches linting
71
- lintMessage(message, messages)
160
+ const getAll = () => {
161
+ const flatValues = [...index.values()].flat()
162
+ debug("getAll", flatValues.length)
163
+ return structuredClone(flatValues.length === 0 ? [] : flatValues) as MessageLintReport[]
72
164
  }
73
165
 
74
- const messageQueryChangeDelegate: MessageQueryDelegate = {
75
- onCleanup: () => {
76
- // NOTE: we could cancel all running lint rules - but results get overritten anyway
77
- index.clear()
78
- },
79
- onLoaded: (messages: Message[]) => {
80
- for (const message of messages) {
81
- lintMessage(message, messages)
82
- }
83
- },
84
- onMessageCreate: (messageId: string, message: Message) => {
85
- lintMessage(message, messages)
86
- },
87
- onMessageUpdate: (messageId: string, message: Message) => {
88
- lintMessage(message, messages)
89
- },
90
- onMessageDelete: (messageId: string) => {
91
- index.delete(messageId)
92
- },
166
+ return {
167
+ getAll: Object.assign(createSubscribable(getAll), {
168
+ settled: async () => {
169
+ await settledReports
170
+ return getAll()
171
+ },
172
+ }),
173
+ get: Object.assign(get, {
174
+ subscribe: (
175
+ args: Parameters<MessageLintReportsQueryApi["get"]["subscribe"]>[0],
176
+ callback: Parameters<MessageLintReportsQueryApi["get"]["subscribe"]>[1]
177
+ ) => createSubscribable(() => get(args)).subscribe(callback),
178
+ }) as any,
93
179
  }
180
+ }
94
181
 
95
- messagesQuery.setDelegate(messageQueryChangeDelegate)
182
+ function reportsEqual(
183
+ reportsA: MessageLintReport[] | undefined,
184
+ reportsB: MessageLintReport[] | undefined
185
+ ) {
186
+ if (reportsA === undefined && reportsB === undefined) {
187
+ return true
188
+ } else if (reportsA === undefined || reportsB === undefined) {
189
+ return false
190
+ }
96
191
 
97
- return {
98
- getAll: async () => {
99
- await sleep(0) // evaluate on next tick to allow for out-of-order effects
100
- const flatValues = [...index.values()].flat()
101
- debug("getAll", flatValues.length)
102
- return structuredClone(flatValues.length === 0 ? [] : flatValues)
103
- },
104
- get: async (args: Parameters<MessageLintReportsQueryApi["get"]>[0]) => {
105
- await sleep(0) // evaluate on next tick to allow for out-of-order effects
106
- debug("get", args.where.messageId)
107
- return structuredClone(index.get(args.where.messageId) ?? [])
108
- },
192
+ if (reportsA.length !== reportsB.length) {
193
+ return false
194
+ }
195
+
196
+ for (const [i, element] of reportsA.entries()) {
197
+ if (element?.languageTag !== reportsB[i]?.languageTag) {
198
+ return false
199
+ }
200
+
201
+ if (element?.level !== reportsB[i]?.level) {
202
+ return false
203
+ }
204
+
205
+ if (element?.ruleId !== reportsB[i]?.ruleId) {
206
+ return false
207
+ }
208
+
209
+ if (typeof element?.body !== typeof reportsB[i]?.body) {
210
+ return false
211
+ }
212
+
213
+ if (typeof element?.body === "string") {
214
+ if (reportsB[i]?.body !== reportsB[i]?.body) {
215
+ return false
216
+ }
217
+ } else {
218
+ // NOTE: this was the fastest way to check if both bodies are equal - we can optimize that later if needed
219
+ if (JSON.stringify(element?.body) !== JSON.stringify(element?.body)) {
220
+ return false
221
+ }
222
+ }
109
223
  }
224
+ return true
110
225
  }
@@ -1,13 +1,12 @@
1
1
  import type { Message } from "@inlang/message"
2
2
  import { ReactiveMap } from "./reactivity/map.js"
3
- import { createEffect } from "./reactivity/solid.js"
3
+ import { createEffect, onCleanup } from "./reactivity/solid.js"
4
4
  import { createSubscribable } from "./loadProject.js"
5
5
  import type { InlangProject, MessageQueryApi, MessageQueryDelegate } from "./api.js"
6
6
  import type { ResolvedPluginApi } from "./resolve-modules/plugins/types.js"
7
7
  import type { resolveModules } from "./resolve-modules/resolveModules.js"
8
8
  import { createNodeishFsWithWatcher } from "./createNodeishFsWithWatcher.js"
9
9
  import type { NodeishFilesystem } from "@lix-js/fs"
10
- import { onCleanup } from "solid-js"
11
10
  import { stringifyMessage } from "./storage/helper.js"
12
11
  import { acquireFileLock } from "./persistence/filelock/acquireFileLock.js"
13
12
  import _debug from "debug"
@@ -62,14 +61,18 @@ export function createMessagesQuery({
62
61
  }: createMessagesQueryParameters): InlangProject["query"]["messages"] {
63
62
  // @ts-expect-error
64
63
  const index = new ReactiveMap<string, Message>()
64
+ let loaded = false
65
65
 
66
66
  // filepath for the lock folder
67
67
  const messageLockDirPath = projectPath + "/messagelock"
68
68
 
69
69
  let delegate: MessageQueryDelegate | undefined = undefined
70
70
 
71
- const setDelegate = (newDelegate: MessageQueryDelegate) => {
71
+ const setDelegate = (newDelegate: MessageQueryDelegate | undefined, onLoad: boolean) => {
72
72
  delegate = newDelegate
73
+ if (newDelegate && loaded && onLoad) {
74
+ newDelegate.onLoaded([...index.values()] as Message[])
75
+ }
73
76
  }
74
77
 
75
78
  // Map default alias to message
@@ -94,6 +97,7 @@ export function createMessagesQuery({
94
97
  // we clear the index independent from the change for
95
98
  index.clear()
96
99
  defaultAliasIndex.clear()
100
+ loaded = false
97
101
 
98
102
  // Load messages -> use settings to subscribe to signals from the settings
99
103
  const _settings = settings()
@@ -143,7 +147,7 @@ export function createMessagesQuery({
143
147
  messageLockDirPath,
144
148
  messageStates,
145
149
  index,
146
- delegate,
150
+ undefined /* delegate - we don't pass it here since we will call onLoaded instead */,
147
151
  _settings, // NOTE we bang here - we don't expect the settings to become null during the livetime of a project
148
152
  resolvedPluginApi
149
153
  )
@@ -154,6 +158,7 @@ export function createMessagesQuery({
154
158
  .then(() => {
155
159
  onInitialMessageLoadResult()
156
160
  delegate?.onLoaded([...index.values()])
161
+ loaded = true
157
162
  })
158
163
  })
159
164
 
@@ -203,7 +208,7 @@ export function createMessagesQuery({
203
208
  }
204
209
 
205
210
  messageStates.messageDirtyFlags[data.id] = true
206
- delegate?.onMessageCreate(data.id, index.get(data.id))
211
+ delegate?.onMessageCreate(data.id, index.get(data.id), [...index.values()])
207
212
  scheduleSave()
208
213
  return true
209
214
  },
@@ -230,7 +235,7 @@ export function createMessagesQuery({
230
235
  if (message === undefined) return false
231
236
  index.set(where.id, { ...message, ...data })
232
237
  messageStates.messageDirtyFlags[where.id] = true
233
- delegate?.onMessageCreate(where.id, index.get(data.id))
238
+ delegate?.onMessageUpdate(where.id, index.get(data.id), [...index.values()])
234
239
  scheduleSave()
235
240
  return true
236
241
  },
@@ -242,11 +247,11 @@ export function createMessagesQuery({
242
247
  defaultAliasIndex.set(data.alias.default, data)
243
248
  }
244
249
  messageStates.messageDirtyFlags[where.id] = true
245
- delegate?.onMessageCreate(data.id, index.get(data.id))
250
+ delegate?.onMessageCreate(data.id, index.get(data.id), [...index.values()])
246
251
  } else {
247
252
  index.set(where.id, { ...message, ...data })
248
253
  messageStates.messageDirtyFlags[where.id] = true
249
- delegate?.onMessageUpdate(data.id, index.get(data.id))
254
+ delegate?.onMessageUpdate(data.id, index.get(data.id), [...index.values()])
250
255
  }
251
256
  scheduleSave()
252
257
  return true
@@ -259,7 +264,7 @@ export function createMessagesQuery({
259
264
  }
260
265
  index.delete(where.id)
261
266
  messageStates.messageDirtyFlags[where.id] = true
262
- delegate?.onMessageDelete(where.id)
267
+ delegate?.onMessageDelete(where.id, [...index.values()])
263
268
  scheduleSave()
264
269
  return true
265
270
  },
@@ -323,6 +328,8 @@ async function loadMessagesViaPlugin(
323
328
 
324
329
  let loadedMessageCount = 0
325
330
 
331
+ const deletedMessages = new Set(messages.keys())
332
+
326
333
  for (const loadedMessage of loadedMessages) {
327
334
  const loadedMessageClone = structuredClone(loadedMessage)
328
335
 
@@ -338,6 +345,8 @@ async function loadMessagesViaPlugin(
338
345
  // - this could be the case if one edits the aliase manualy
339
346
  throw new Error("more than one message with the same id or alias found ")
340
347
  } else if (currentMessages.length === 1) {
348
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length has checked beforhand
349
+ deletedMessages.delete(currentMessages[0]!.id)
341
350
  // update message in place - leave message id and alias untouched
342
351
  loadedMessageClone.alias = {} as any
343
352
 
@@ -364,7 +373,7 @@ async function loadMessagesViaPlugin(
364
373
  messages.set(loadedMessageClone.id, loadedMessageClone)
365
374
  // NOTE could use hash instead of the whole object JSON to save memory...
366
375
  messageState.messageLoadHash[loadedMessageClone.id] = importedEnecoded
367
- delegate?.onMessageUpdate(loadedMessageClone.id, loadedMessageClone)
376
+ delegate?.onMessageUpdate(loadedMessageClone.id, loadedMessageClone, [...messages.values()])
368
377
  loadedMessageCount++
369
378
  } else {
370
379
  // message with the given alias does not exist so far
@@ -392,7 +401,7 @@ async function loadMessagesViaPlugin(
392
401
  // we don't have to check - done before hand if (messages.has(loadedMessageClone.id)) return false
393
402
  messages.set(loadedMessageClone.id, loadedMessageClone)
394
403
  messageState.messageLoadHash[loadedMessageClone.id] = importedEnecoded
395
- delegate?.onMessageUpdate(loadedMessageClone.id, loadedMessageClone)
404
+ delegate?.onMessageUpdate(loadedMessageClone.id, loadedMessageClone, [...messages.values()])
396
405
  loadedMessageCount++
397
406
  }
398
407
  if (loadedMessageCount > maxMessagesPerTick) {
@@ -403,6 +412,18 @@ async function loadMessagesViaPlugin(
403
412
  loadedMessageCount = 0
404
413
  }
405
414
  }
415
+
416
+ loadedMessageCount = 0
417
+ for (const deletedMessageId of deletedMessages) {
418
+ messages.delete(deletedMessageId)
419
+ delegate?.onMessageDelete(deletedMessageId, [...messages.values()])
420
+ loadedMessageCount++
421
+ if (loadedMessageCount > maxMessagesPerTick) {
422
+ await sleep(0)
423
+ loadedMessageCount = 0
424
+ }
425
+ }
426
+
406
427
  await releaseLock(fs as NodeishFilesystem, lockDirPath, "loadMessage", lockTime)
407
428
  lockTime = undefined
408
429
 
@@ -20,7 +20,7 @@ export const createNodeishFsWithWatcher = (args: {
20
20
  ac.abort()
21
21
  }
22
22
  // release references
23
- abortControllers = [];
23
+ abortControllers = []
24
24
  }
25
25
 
26
26
  const makeWatcher = (path: string) => {
@@ -50,5 +50,8 @@ export const lintSingleMessage = async (args: {
50
50
 
51
51
  await Promise.all(promises)
52
52
 
53
- return { data: reports, errors }
53
+ // we sort the reports by rule id to allow us to easyly compare both
54
+ const sortedReports = reports.sort((r1, r2) => r1.ruleId.localeCompare(r2.ruleId))
55
+
56
+ return { data: sortedReports, errors }
54
57
  }