@inlang/sdk 0.26.5 → 0.27.0

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 (60) hide show
  1. package/dist/adapter/solidAdapter.test.js +52 -4
  2. package/dist/api.d.ts +3 -0
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/createMessageLintReportsQuery.d.ts +1 -1
  5. package/dist/createMessageLintReportsQuery.d.ts.map +1 -1
  6. package/dist/createMessageLintReportsQuery.js +30 -22
  7. package/dist/createMessagesQuery.d.ts.map +1 -1
  8. package/dist/createMessagesQuery.js +24 -1
  9. package/dist/createMessagesQuery.test.js +46 -2
  10. package/dist/createNodeishFsWithAbsolutePaths.d.ts +3 -3
  11. package/dist/createNodeishFsWithAbsolutePaths.d.ts.map +1 -1
  12. package/dist/createNodeishFsWithAbsolutePaths.js +9 -1
  13. package/dist/createNodeishFsWithAbsolutePaths.test.js +7 -1
  14. package/dist/createNodeishFsWithWatcher.d.ts +3 -3
  15. package/dist/createNodeishFsWithWatcher.d.ts.map +1 -1
  16. package/dist/createNodeishFsWithWatcher.js +3 -0
  17. package/dist/errors.d.ts +14 -0
  18. package/dist/errors.d.ts.map +1 -1
  19. package/dist/errors.js +12 -0
  20. package/dist/loadProject.d.ts.map +1 -1
  21. package/dist/loadProject.js +472 -39
  22. package/dist/loadProject.test.js +91 -14
  23. package/dist/messages/variant.test.js +3 -0
  24. package/dist/resolve-modules/plugins/resolvePlugins.test.js +4 -2
  25. package/dist/storage/helper.d.ts +5 -0
  26. package/dist/storage/helper.d.ts.map +1 -0
  27. package/dist/storage/helper.js +35 -0
  28. package/dist/storage/human-id/human-readable-id.d.ts +3 -0
  29. package/dist/storage/human-id/human-readable-id.d.ts.map +1 -0
  30. package/dist/storage/human-id/human-readable-id.js +20 -0
  31. package/dist/storage/human-id/words.d.ts +5 -0
  32. package/dist/storage/human-id/words.d.ts.map +1 -0
  33. package/dist/storage/human-id/words.js +1032 -0
  34. package/dist/storage/human-id/words.test.d.ts +2 -0
  35. package/dist/storage/human-id/words.test.d.ts.map +1 -0
  36. package/dist/storage/human-id/words.test.js +25 -0
  37. package/dist/test-utilities/createMessage.d.ts +1 -0
  38. package/dist/test-utilities/createMessage.d.ts.map +1 -1
  39. package/dist/test-utilities/createMessage.js +1 -0
  40. package/dist/test-utilities/createMessage.test.js +70 -55
  41. package/package.json +12 -8
  42. package/src/adapter/solidAdapter.test.ts +76 -4
  43. package/src/api.ts +4 -0
  44. package/src/createMessageLintReportsQuery.ts +46 -34
  45. package/src/createMessagesQuery.test.ts +54 -2
  46. package/src/createMessagesQuery.ts +30 -1
  47. package/src/createNodeishFsWithAbsolutePaths.test.ts +10 -3
  48. package/src/createNodeishFsWithAbsolutePaths.ts +14 -5
  49. package/src/createNodeishFsWithWatcher.ts +6 -3
  50. package/src/errors.ts +20 -0
  51. package/src/loadProject.test.ts +106 -14
  52. package/src/loadProject.ts +655 -60
  53. package/src/messages/variant.test.ts +3 -0
  54. package/src/resolve-modules/plugins/resolvePlugins.test.ts +4 -2
  55. package/src/storage/helper.ts +48 -0
  56. package/src/storage/human-id/human-readable-id.ts +27 -0
  57. package/src/storage/human-id/words.test.ts +27 -0
  58. package/src/storage/human-id/words.ts +1035 -0
  59. package/src/test-utilities/createMessage.test.ts +72 -54
  60. package/src/test-utilities/createMessage.ts +1 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=words.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"words.test.d.ts","sourceRoot":"","sources":["../../../src/storage/human-id/words.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { adjectives, animals, adverbs, verbs } from "./words.js";
3
+ const wordlists = [adjectives, animals, adverbs, verbs];
4
+ const allwords = [...adjectives, ...animals, ...adverbs, ...verbs];
5
+ describe("wordlists", () => {
6
+ it("should have 256 words", () => {
7
+ for (const wordlist of wordlists) {
8
+ expect(wordlist.length).toBe(256);
9
+ }
10
+ });
11
+ it("words should be unique across lists", () => {
12
+ const unique = new Set(allwords);
13
+ expect(unique.size).toBe(256 * 4);
14
+ });
15
+ it("words should have < 10 characters", () => {
16
+ for (const word of allwords) {
17
+ expect(word.length).toBeLessThan(10);
18
+ }
19
+ });
20
+ it("words should match /^[a-z]+$/", () => {
21
+ for (const word of allwords) {
22
+ expect(word.match(/^[a-z]+$/) !== null).toBe(true);
23
+ }
24
+ });
25
+ });
@@ -1,6 +1,7 @@
1
1
  import type { Pattern } from "@inlang/message";
2
2
  export declare const createMessage: (id: string, patterns: Record<string, Pattern | string>) => {
3
3
  id: string;
4
+ alias: {};
4
5
  selectors: never[];
5
6
  variants: {
6
7
  languageTag: string;
@@ -1 +1 @@
1
- {"version":3,"file":"createMessage.d.ts","sourceRoot":"","sources":["../../src/test-utilities/createMessage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAEvD,eAAO,MAAM,aAAa,OAAQ,MAAM,YAAY,OAAO,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;;;;;;;;;;;;;;CAiB/D,CAAA"}
1
+ {"version":3,"file":"createMessage.d.ts","sourceRoot":"","sources":["../../src/test-utilities/createMessage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAEvD,eAAO,MAAM,aAAa,OAAQ,MAAM,YAAY,OAAO,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;;;;;;;;;;;;;;;CAkB/D,CAAA"}
@@ -1,5 +1,6 @@
1
1
  export const createMessage = (id, patterns) => ({
2
2
  id,
3
+ alias: {},
3
4
  selectors: [],
4
5
  variants: Object.entries(patterns).map(([languageTag, patterns]) => ({
5
6
  languageTag,
@@ -3,8 +3,25 @@ import { createMessage } from "./createMessage.js";
3
3
  test("should create a simple message", () => {
4
4
  expect(createMessage("welcome", {
5
5
  de: "Hallo inlang",
6
- })).toMatchInlineSnapshot(`
6
+ })).toMatchInlineSnapshot({
7
+ alias: {},
8
+ id: "welcome",
9
+ selectors: [],
10
+ variants: [
11
+ {
12
+ languageTag: "de",
13
+ match: [],
14
+ pattern: [
15
+ {
16
+ type: "Text",
17
+ value: "Hallo inlang",
18
+ },
19
+ ],
20
+ },
21
+ ],
22
+ }, `
7
23
  {
24
+ "alias": {},
8
25
  "id": "welcome",
9
26
  "selectors": [],
10
27
  "variants": [
@@ -29,63 +46,61 @@ test("should create a message with pattern", () => {
29
46
  { type: "VariableReference", name: "name" },
30
47
  { type: "Text", value: '"' },
31
48
  ],
32
- })).toMatchInlineSnapshot(`
33
- {
34
- "id": "greeting",
35
- "selectors": [],
36
- "variants": [
37
- {
38
- "languageTag": "en",
39
- "match": [],
40
- "pattern": [
41
- {
42
- "type": "Text",
43
- "value": "Hi ",
44
- },
45
- {
46
- "name": "name",
47
- "type": "VariableReference",
48
- },
49
- {
50
- "type": "Text",
51
- "value": "\\"",
52
- },
53
- ],
54
- },
55
- ],
56
- }
57
- `);
49
+ })).toStrictEqual({
50
+ alias: {},
51
+ id: "greeting",
52
+ selectors: [],
53
+ variants: [
54
+ {
55
+ languageTag: "en",
56
+ match: [],
57
+ pattern: [
58
+ {
59
+ type: "Text",
60
+ value: "Hi ",
61
+ },
62
+ {
63
+ name: "name",
64
+ type: "VariableReference",
65
+ },
66
+ {
67
+ type: "Text",
68
+ value: '"',
69
+ },
70
+ ],
71
+ },
72
+ ],
73
+ });
58
74
  });
59
75
  test("should create a message with a pattern", () => {
60
76
  expect(createMessage("welcome", {
61
77
  en: "hello inlang",
62
78
  de: [{ type: "Text", value: "Hallo inlang" }],
63
- })).toMatchInlineSnapshot(`
64
- {
65
- "id": "welcome",
66
- "selectors": [],
67
- "variants": [
68
- {
69
- "languageTag": "en",
70
- "match": [],
71
- "pattern": [
72
- {
73
- "type": "Text",
74
- "value": "hello inlang",
75
- },
76
- ],
77
- },
78
- {
79
- "languageTag": "de",
80
- "match": [],
81
- "pattern": [
82
- {
83
- "type": "Text",
84
- "value": "Hallo inlang",
85
- },
86
- ],
87
- },
88
- ],
89
- }
90
- `);
79
+ })).toStrictEqual({
80
+ alias: {},
81
+ id: "welcome",
82
+ selectors: [],
83
+ variants: [
84
+ {
85
+ languageTag: "en",
86
+ match: [],
87
+ pattern: [
88
+ {
89
+ type: "Text",
90
+ value: "hello inlang",
91
+ },
92
+ ],
93
+ },
94
+ {
95
+ languageTag: "de",
96
+ match: [],
97
+ pattern: [
98
+ {
99
+ type: "Text",
100
+ value: "Hallo inlang",
101
+ },
102
+ ],
103
+ },
104
+ ],
105
+ });
91
106
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inlang/sdk",
3
3
  "type": "module",
4
- "version": "0.26.5",
4
+ "version": "0.27.0",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -21,23 +21,27 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@sinclair/typebox": "^0.31.17",
24
+ "debug": "^4.3.4",
24
25
  "dedent": "1.5.1",
25
26
  "deepmerge-ts": "^5.1.0",
27
+ "murmurhash3js": "^3.0.1",
26
28
  "solid-js": "1.6.12",
27
29
  "throttle-debounce": "^5.0.0",
28
30
  "@inlang/json-types": "1.1.0",
29
- "@inlang/message": "2.0.3",
31
+ "@inlang/message": "2.1.0",
32
+ "@inlang/message-lint-rule": "1.4.5",
30
33
  "@inlang/language-tag": "1.5.1",
31
- "@inlang/message-lint-rule": "1.4.4",
32
- "@inlang/plugin": "2.4.7",
33
- "@inlang/module": "1.2.7",
34
- "@inlang/project-settings": "2.3.0",
34
+ "@inlang/module": "1.2.8",
35
+ "@inlang/plugin": "2.4.8",
36
+ "@inlang/project-settings": "2.4.0",
35
37
  "@inlang/result": "1.1.0",
36
38
  "@inlang/translatable": "1.3.1",
37
- "@lix-js/fs": "0.6.0",
38
- "@lix-js/client": "0.8.0"
39
+ "@lix-js/client": "0.9.0",
40
+ "@lix-js/fs": "0.6.0"
39
41
  },
40
42
  "devDependencies": {
43
+ "@types/debug": "^4.1.12",
44
+ "@types/murmurhash3js": "^3.0.7",
41
45
  "@types/throttle-debounce": "5.0.0",
42
46
  "@vitest/coverage-v8": "^0.33.0",
43
47
  "jsdom": "22.1.0",
@@ -28,6 +28,11 @@ const config: ProjectSettings = {
28
28
  },
29
29
  }
30
30
 
31
+ const configWithAliases: ProjectSettings = {
32
+ ...config,
33
+ experimental: { aliases: true },
34
+ }
35
+
31
36
  const mockPlugin: Plugin = {
32
37
  id: "plugin.project.i18next",
33
38
  description: { en: "Mock plugin description" },
@@ -41,6 +46,7 @@ const mockPlugin: Plugin = {
41
46
  const exampleMessages: Message[] = [
42
47
  {
43
48
  id: "a",
49
+ alias: {},
44
50
  selectors: [],
45
51
  variants: [
46
52
  {
@@ -57,6 +63,7 @@ const exampleMessages: Message[] = [
57
63
  },
58
64
  {
59
65
  id: "b",
66
+ alias: {},
60
67
  selectors: [],
61
68
  variants: [
62
69
  {
@@ -202,10 +209,10 @@ describe("messages", () => {
202
209
  { from }
203
210
  )
204
211
 
205
- let counter = 0
212
+ let effectOnMessagesCounter = 0
206
213
  createEffect(() => {
207
214
  project.query.messages.getAll()
208
- counter += 1
215
+ effectOnMessagesCounter += 1
209
216
  })
210
217
 
211
218
  expect(Object.values(project.query.messages.getAll()).length).toBe(2)
@@ -215,11 +222,11 @@ describe("messages", () => {
215
222
  // TODO: how can we await `setConfig` correctly
216
223
  await new Promise((resolve) => setTimeout(resolve, 510))
217
224
 
218
- expect(counter).toBe(1) // 2 times because effect creation + set
225
+ expect(effectOnMessagesCounter).toBe(1) // 2 times because effect creation + set
219
226
  expect(Object.values(project.query.messages.getAll()).length).toBe(2)
220
227
  })
221
228
 
222
- it("should react to changes in messages", async () => {
229
+ it("should react to message udpate", async () => {
223
230
  const repo = await mockRepo()
224
231
  const fs = repo.nodeishFs
225
232
  await fs.mkdir("/user/project.inlang.inlang", { recursive: true })
@@ -268,6 +275,71 @@ describe("messages", () => {
268
275
  },
269
276
  })
270
277
 
278
+ it("should react to message udpate (with aliases)", async () => {
279
+ const repo = await mockRepo()
280
+ const fs = repo.nodeishFs
281
+ await fs.mkdir("/user/project.inlang.inlang", { recursive: true })
282
+ await fs.writeFile(
283
+ "/user/project.inlang.inlang/settings.json",
284
+ JSON.stringify(configWithAliases)
285
+ )
286
+ const project = solidAdapter(
287
+ await loadProject({
288
+ projectPath: "/user/project.inlang.inlang",
289
+ repo,
290
+ _import: $import,
291
+ }),
292
+ { from }
293
+ )
294
+
295
+ let counter = 0
296
+ createEffect(() => {
297
+ project.query.messages.getAll()
298
+ counter += 1
299
+ })
300
+
301
+ const messagesBefore = project.query.messages.getAll
302
+ expect(Object.values(messagesBefore()).length).toBe(2)
303
+ expect(
304
+ (
305
+ Object.values(messagesBefore())[0]?.variants.find(
306
+ (variant) => variant.languageTag === "en"
307
+ )?.pattern[0] as Text
308
+ ).value
309
+ ).toBe("test")
310
+
311
+ project.query.messages.update({
312
+ where: { id: "raw_tapir_pause_grateful" },
313
+ // TODO: use `createMessage` utility
314
+ data: {
315
+ ...exampleMessages[0],
316
+ variants: [
317
+ {
318
+ languageTag: "en",
319
+ match: [],
320
+ pattern: [
321
+ {
322
+ type: "Text",
323
+ value: "test2",
324
+ },
325
+ ],
326
+ },
327
+ ],
328
+ },
329
+ })
330
+
331
+ expect(counter).toBe(2) // 2 times because effect creation + set
332
+ const messagesAfter = project.query.messages.getAll
333
+ expect(Object.values(messagesAfter()).length).toBe(2)
334
+ expect(
335
+ (
336
+ Object.values(messagesAfter())[0]?.variants.find(
337
+ (variant) => variant.languageTag === "en"
338
+ )?.pattern[0] as Text
339
+ ).value
340
+ ).toBe("test2")
341
+ })
342
+
271
343
  expect(counter).toBe(2) // 2 times because effect creation + set
272
344
  const messagesAfter = project.query.messages.getAll
273
345
  expect(Object.values(messagesAfter()).length).toBe(2)
package/src/api.ts CHANGED
@@ -72,6 +72,10 @@ export type MessageQueryApi = {
72
72
  callback: (message: Message) => void
73
73
  ) => void
74
74
  }
75
+ // use getByDefaultAlias() to resolve a message from its alias, not subscribable
76
+ getByDefaultAlias: ((alias: Message["alias"]["default"]) => Readonly<Message>) & {
77
+ subscribe: (alias: Message["alias"]["default"], callback: (message: Message) => void) => void
78
+ }
75
79
  includedMessageIds: Subscribable<Message["id"][]>
76
80
  /*
77
81
  * getAll is depricated do not use it
@@ -10,8 +10,7 @@ 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
  import { ReactiveMap } from "./reactivity/map.js"
13
- import { debounce } from "throttle-debounce"
14
- import { createEffect } from "./reactivity/solid.js"
13
+ import { createRoot, createEffect } from "./reactivity/solid.js"
15
14
 
16
15
  /**
17
16
  * Creates a reactive query API for messages.
@@ -21,7 +20,6 @@ export function createMessageLintReportsQuery(
21
20
  settings: () => ProjectSettings,
22
21
  installedMessageLintRules: () => Array<InstalledMessageLintRule>,
23
22
  resolvedModules: () => Awaited<ReturnType<typeof resolveModules>> | undefined,
24
- hasWatcher: boolean
25
23
  ): InlangProject["query"]["messageLintReports"] {
26
24
  // @ts-expect-error
27
25
  const index = new ReactiveMap<MessageLintReport["messageId"], MessageLintReport[]>()
@@ -41,41 +39,55 @@ export function createMessageLintReportsQuery(
41
39
 
42
40
  const messages = messagesQuery.getAll() as Message[]
43
41
 
42
+ const trackedMessages: Map<string, () => void> = new Map()
43
+
44
44
  createEffect(() => {
45
+ const currentMessageIds = new Set(messagesQuery.includedMessageIds())
46
+
47
+ const deletedTrackedMessages = [...trackedMessages].filter(
48
+ (tracked) => !currentMessageIds.has(tracked[0])
49
+ )
50
+
45
51
  if (rulesArray) {
46
- for (const messageId of messagesQuery.includedMessageIds()) {
47
- createEffect(() => {
48
- const message = messagesQuery.get({ where: { id: messageId } })
49
- if (hasWatcher) {
50
- lintSingleMessage({
51
- rules: rulesArray,
52
- settings: settingsObject(),
53
- messages: messages,
54
- message: message,
55
- }).then((report) => {
56
- if (report.errors.length === 0 && index.get(messageId) !== report.data) {
57
- index.set(messageId, report.data)
52
+ for (const messageId of currentMessageIds) {
53
+ if (!trackedMessages.has(messageId)) {
54
+ createRoot((dispose) => {
55
+ createEffect(() => {
56
+ const message = messagesQuery.get({ where: { id: messageId } })
57
+ if (!message) {
58
+ return
59
+ }
60
+ if (!trackedMessages?.has(messageId)) {
61
+ // initial effect execution - add dispose function
62
+ trackedMessages?.set(messageId, dispose)
58
63
  }
64
+
65
+ lintSingleMessage({
66
+ rules: rulesArray,
67
+ settings: settingsObject(),
68
+ messages: messages,
69
+ message: message,
70
+ }).then((report) => {
71
+ if (report.errors.length === 0 && index.get(messageId) !== report.data) {
72
+ index.set(messageId, report.data)
73
+ }
74
+ })
59
75
  })
60
- } else {
61
- debounce(
62
- 500,
63
- (message) => {
64
- lintSingleMessage({
65
- rules: rulesArray,
66
- settings: settingsObject(),
67
- messages: messages,
68
- message: message,
69
- }).then((report) => {
70
- if (report.errors.length === 0 && index.get(messageId) !== report.data) {
71
- index.set(messageId, report.data)
72
- }
73
- })
74
- },
75
- { atBegin: false }
76
- )(message)
77
- }
78
- })
76
+ })
77
+ }
78
+ }
79
+
80
+ for (const deletedMessage of deletedTrackedMessages) {
81
+ const deletedMessageId = deletedMessage[0]
82
+
83
+ // call dispose to cleanup the effect
84
+ const messageEffectDisposeFunction = trackedMessages.get(deletedMessageId)
85
+ if (messageEffectDisposeFunction) {
86
+ messageEffectDisposeFunction()
87
+ trackedMessages.delete(deletedMessageId)
88
+ // remove lint report result
89
+ index.delete(deletedMessageId)
90
+ }
79
91
  }
80
92
  }
81
93
  })
@@ -6,6 +6,7 @@ import type { Message, Text } from "@inlang/message"
6
6
  import { createMessage } from "./test-utilities/createMessage.js"
7
7
 
8
8
  const createChangeListener = async (cb: () => void) => createEffect(cb)
9
+ const nextTick = () => new Promise((resolve) => setTimeout(resolve, 0))
9
10
 
10
11
  describe("create", () => {
11
12
  it("should create a message", () => {
@@ -19,6 +20,19 @@ describe("create", () => {
19
20
  expect(created).toBe(true)
20
21
  })
21
22
 
23
+ it("query.getByDefaultAlias should return a message with a default alias", () => {
24
+ const query = createMessagesQuery(() => [])
25
+ expect(query.get({ where: { id: "first-message" } })).toBeUndefined()
26
+
27
+ const mockMessage = createMessage("first-message", { en: "Hello World" })
28
+ mockMessage.alias = { default: "first-message-alias" }
29
+ const created = query.create({ data: mockMessage })
30
+
31
+ expect(query.get({ where: { id: "first-message" } })).toEqual(mockMessage)
32
+ expect(query.getByDefaultAlias("first-message-alias")).toEqual(mockMessage)
33
+ expect(created).toBe(true)
34
+ })
35
+
22
36
  it("should return false if message with id already exists", () => {
23
37
  const query = createMessagesQuery(() => [createMessage("first-message", { en: "Hello World" })])
24
38
  expect(query.get({ where: { id: "first-message" } })).toBeDefined()
@@ -266,8 +280,46 @@ describe("reactivity", () => {
266
280
  })
267
281
  })
268
282
 
269
- describe.todo("subscribe", () => {
270
- // TODO: add tests for `subscribe`
283
+ describe("subscribe", () => {
284
+ describe("get", () => {
285
+ it("should subscribe to `create`", async () => {
286
+ await createRoot(async () => {
287
+ const query = createMessagesQuery(() => [])
288
+
289
+ // eslint-disable-next-line unicorn/no-null
290
+ let message: Message | undefined | null = null
291
+ query.get.subscribe({ where: { id: "1" } }, (v) => {
292
+ void (message = v)
293
+ })
294
+ await nextTick()
295
+ expect(message).toBeUndefined()
296
+
297
+ query.create({ data: createMessage("1", { en: "before" }) })
298
+ expect(message).toBeDefined()
299
+ })
300
+ })
301
+ })
302
+ describe("getByDefaultAlias", () => {
303
+ it("should subscribe to `create`", async () => {
304
+ await createRoot(async () => {
305
+ const query = createMessagesQuery(() => [])
306
+
307
+ // eslint-disable-next-line unicorn/no-null
308
+ let message: Message | undefined | null = null
309
+ query.getByDefaultAlias.subscribe("message-alias", (v) => {
310
+ void (message = v)
311
+ })
312
+ await nextTick() // required for effect to run on reactive map
313
+ expect(message).toBeUndefined()
314
+
315
+ const mockMessage = createMessage("1", { en: "before" })
316
+ mockMessage.alias = { default: "message-alias" }
317
+ query.create({ data: mockMessage })
318
+
319
+ expect(message).toBeDefined()
320
+ })
321
+ })
322
+ })
271
323
  })
272
324
 
273
325
  describe("getAll", () => {
@@ -13,19 +13,35 @@ export function createMessagesQuery(
13
13
  // @ts-expect-error
14
14
  const index = new ReactiveMap<string, Message>()
15
15
 
16
+ // Map default alias to message
17
+ // Assumes that aliases are only created and deleted, not updated
18
+ // TODO #2346 - handle updates to aliases
19
+ // TODO #2346 - refine to hold messageId[], if default alias is not unique
20
+ // @ts-expect-error
21
+ const defaultAliasIndex = new ReactiveMap<string, Message>()
22
+
16
23
  createEffect(() => {
17
24
  index.clear()
18
25
  for (const message of structuredClone(messages())) {
19
26
  index.set(message.id, message)
27
+ if ("default" in message.alias) {
28
+ defaultAliasIndex.set(message.alias.default, message)
29
+ }
20
30
  }
21
31
  })
22
32
 
23
33
  const get = (args: Parameters<MessageQueryApi["get"]>[0]) => index.get(args.where.id)
24
34
 
35
+ const getByDefaultAlias = (alias: Parameters<MessageQueryApi["getByDefaultAlias"]>[0]) =>
36
+ defaultAliasIndex.get(alias)
37
+
25
38
  return {
26
39
  create: ({ data }): boolean => {
27
40
  if (index.has(data.id)) return false
28
41
  index.set(data.id, data)
42
+ if ("default" in data.alias) {
43
+ defaultAliasIndex.set(data.alias.default, data)
44
+ }
29
45
  return true
30
46
  },
31
47
  get: Object.assign(get, {
@@ -34,6 +50,12 @@ export function createMessagesQuery(
34
50
  callback: Parameters<MessageQueryApi["get"]["subscribe"]>[1]
35
51
  ) => createSubscribable(() => get(args)).subscribe(callback),
36
52
  }) as any,
53
+ getByDefaultAlias: Object.assign(getByDefaultAlias, {
54
+ subscribe: (
55
+ alias: Parameters<MessageQueryApi["getByDefaultAlias"]["subscribe"]>[0],
56
+ callback: Parameters<MessageQueryApi["getByDefaultAlias"]["subscribe"]>[1]
57
+ ) => createSubscribable(() => getByDefaultAlias(alias)).subscribe(callback),
58
+ }) as any,
37
59
  includedMessageIds: createSubscribable(() => {
38
60
  return [...index.keys()]
39
61
  }),
@@ -50,13 +72,20 @@ export function createMessagesQuery(
50
72
  const message = index.get(where.id)
51
73
  if (message === undefined) {
52
74
  index.set(where.id, data)
75
+ if ("default" in data.alias) {
76
+ defaultAliasIndex.set(data.alias.default, data)
77
+ }
53
78
  } else {
54
79
  index.set(where.id, { ...message, ...data })
55
80
  }
56
81
  return true
57
82
  },
58
83
  delete: ({ where }): boolean => {
59
- if (!index.has(where.id)) return false
84
+ const message = index.get(where.id)
85
+ if (message === undefined) return false
86
+ if ("default" in message.alias) {
87
+ defaultAliasIndex.delete(message.alias.default)
88
+ }
60
89
  index.delete(where.id)
61
90
  return true
62
91
  },
@@ -1,6 +1,7 @@
1
1
  import { it, expect, vi } from "vitest"
2
2
  import { createNodeishFsWithAbsolutePaths } from "./createNodeishFsWithAbsolutePaths.js"
3
- import type { NodeishFilesystemSubset } from "./versionedInterfaces.js"
3
+ // import type { NodeishFilesystemSubset } from "./versionedInterfaces.js"
4
+ import type { NodeishFilesystem } from "@lix-js/fs"
4
5
 
5
6
  it("throws an error if projectPath is not an absolute path", () => {
6
7
  const relativePath = "relative/path"
@@ -27,9 +28,16 @@ it("intercepts paths correctly for readFile", async () => {
27
28
  readFile: vi.fn(),
28
29
  readdir: vi.fn(),
29
30
  mkdir: vi.fn(),
31
+ rmdir: vi.fn(),
30
32
  writeFile: vi.fn(),
31
33
  watch: vi.fn(),
32
- } satisfies Record<keyof NodeishFilesystemSubset, any>
34
+ rm: vi.fn(),
35
+ stat: vi.fn(),
36
+ lstat: vi.fn(),
37
+ symlink: vi.fn(),
38
+ unlink: vi.fn(),
39
+ readlink: vi.fn(),
40
+ } satisfies Record<keyof NodeishFilesystem, any>
33
41
 
34
42
  const interceptedFs = createNodeishFsWithAbsolutePaths({
35
43
  projectPath,
@@ -38,7 +46,6 @@ it("intercepts paths correctly for readFile", async () => {
38
46
 
39
47
  for (const [path, expectedPath] of filePaths) {
40
48
  for (const fn of Object.keys(mockNodeishFs)) {
41
- // @ts-expect-error
42
49
  await interceptedFs[fn](path)
43
50
  // @ts-expect-error
44
51
  // expect the first argument to be the expectedPath