@inlang/sdk 0.1.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 (170) hide show
  1. package/README.md +25 -0
  2. package/dist/adapter/solidAdapter.d.ts +32 -0
  3. package/dist/adapter/solidAdapter.d.ts.map +1 -0
  4. package/dist/adapter/solidAdapter.js +39 -0
  5. package/dist/adapter/solidAdapter.test.d.ts +2 -0
  6. package/dist/adapter/solidAdapter.test.d.ts.map +1 -0
  7. package/dist/adapter/solidAdapter.test.js +284 -0
  8. package/dist/api.d.ts +88 -0
  9. package/dist/api.d.ts.map +1 -0
  10. package/dist/api.js +1 -0
  11. package/dist/createMessageLintReportsQuery.d.ts +9 -0
  12. package/dist/createMessageLintReportsQuery.d.ts.map +1 -0
  13. package/dist/createMessageLintReportsQuery.js +48 -0
  14. package/dist/createMessagesQuery.d.ts +7 -0
  15. package/dist/createMessagesQuery.d.ts.map +1 -0
  16. package/dist/createMessagesQuery.js +57 -0
  17. package/dist/createMessagesQuery.test.d.ts +2 -0
  18. package/dist/createMessagesQuery.test.d.ts.map +1 -0
  19. package/dist/createMessagesQuery.test.js +304 -0
  20. package/dist/errors.d.ts +22 -0
  21. package/dist/errors.d.ts.map +1 -0
  22. package/dist/errors.js +39 -0
  23. package/dist/index.d.ts +15 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +13 -0
  26. package/dist/lint/index.d.ts +3 -0
  27. package/dist/lint/index.d.ts.map +1 -0
  28. package/dist/lint/index.js +2 -0
  29. package/dist/lint/message/errors.d.ts +7 -0
  30. package/dist/lint/message/errors.d.ts.map +1 -0
  31. package/dist/lint/message/errors.js +9 -0
  32. package/dist/lint/message/lintMessages.d.ts +17 -0
  33. package/dist/lint/message/lintMessages.d.ts.map +1 -0
  34. package/dist/lint/message/lintMessages.js +12 -0
  35. package/dist/lint/message/lintMessages.test.d.ts +2 -0
  36. package/dist/lint/message/lintMessages.test.d.ts.map +1 -0
  37. package/dist/lint/message/lintMessages.test.js +105 -0
  38. package/dist/lint/message/lintSingleMessage.d.ts +23 -0
  39. package/dist/lint/message/lintSingleMessage.d.ts.map +1 -0
  40. package/dist/lint/message/lintSingleMessage.js +36 -0
  41. package/dist/lint/message/lintSingleMessage.test.d.ts +2 -0
  42. package/dist/lint/message/lintSingleMessage.test.d.ts.map +1 -0
  43. package/dist/lint/message/lintSingleMessage.test.js +155 -0
  44. package/dist/messages/errors.d.ts +13 -0
  45. package/dist/messages/errors.d.ts.map +1 -0
  46. package/dist/messages/errors.js +18 -0
  47. package/dist/messages/index.d.ts +3 -0
  48. package/dist/messages/index.d.ts.map +1 -0
  49. package/dist/messages/index.js +2 -0
  50. package/dist/messages/variant.d.ts +46 -0
  51. package/dist/messages/variant.d.ts.map +1 -0
  52. package/dist/messages/variant.js +177 -0
  53. package/dist/messages/variant.test.d.ts +2 -0
  54. package/dist/messages/variant.test.d.ts.map +1 -0
  55. package/dist/messages/variant.test.js +379 -0
  56. package/dist/openInlangProject.d.ts +18 -0
  57. package/dist/openInlangProject.d.ts.map +1 -0
  58. package/dist/openInlangProject.js +226 -0
  59. package/dist/openInlangProject.test.d.ts +2 -0
  60. package/dist/openInlangProject.test.d.ts.map +1 -0
  61. package/dist/openInlangProject.test.js +627 -0
  62. package/dist/parseConfig.d.ts +8 -0
  63. package/dist/parseConfig.d.ts.map +1 -0
  64. package/dist/parseConfig.js +26 -0
  65. package/dist/reactivity/map.d.ts +66 -0
  66. package/dist/reactivity/map.d.ts.map +1 -0
  67. package/dist/reactivity/map.js +143 -0
  68. package/dist/reactivity/solid.d.ts +12 -0
  69. package/dist/reactivity/solid.d.ts.map +1 -0
  70. package/dist/reactivity/solid.js +13 -0
  71. package/dist/reactivity/trigger.d.ts +11 -0
  72. package/dist/reactivity/trigger.d.ts.map +1 -0
  73. package/dist/reactivity/trigger.js +46 -0
  74. package/dist/resolve-modules/errors.d.ts +34 -0
  75. package/dist/resolve-modules/errors.d.ts.map +1 -0
  76. package/dist/resolve-modules/errors.js +35 -0
  77. package/dist/resolve-modules/import.d.ts +35 -0
  78. package/dist/resolve-modules/import.d.ts.map +1 -0
  79. package/dist/resolve-modules/import.js +40 -0
  80. package/dist/resolve-modules/import.test.d.ts +2 -0
  81. package/dist/resolve-modules/import.test.d.ts.map +1 -0
  82. package/dist/resolve-modules/import.test.js +45 -0
  83. package/dist/resolve-modules/index.d.ts +3 -0
  84. package/dist/resolve-modules/index.d.ts.map +1 -0
  85. package/dist/resolve-modules/index.js +2 -0
  86. package/dist/resolve-modules/message-lint-rules/errors.d.ts +8 -0
  87. package/dist/resolve-modules/message-lint-rules/errors.d.ts.map +1 -0
  88. package/dist/resolve-modules/message-lint-rules/errors.js +8 -0
  89. package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts +9 -0
  90. package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts.map +1 -0
  91. package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.js +21 -0
  92. package/dist/resolve-modules/plugins/errors.d.ts +28 -0
  93. package/dist/resolve-modules/plugins/errors.d.ts.map +1 -0
  94. package/dist/resolve-modules/plugins/errors.js +44 -0
  95. package/dist/resolve-modules/plugins/resolvePlugins.d.ts +3 -0
  96. package/dist/resolve-modules/plugins/resolvePlugins.d.ts.map +1 -0
  97. package/dist/resolve-modules/plugins/resolvePlugins.js +108 -0
  98. package/dist/resolve-modules/plugins/resolvePlugins.test.d.ts +2 -0
  99. package/dist/resolve-modules/plugins/resolvePlugins.test.d.ts.map +1 -0
  100. package/dist/resolve-modules/plugins/resolvePlugins.test.js +289 -0
  101. package/dist/resolve-modules/plugins/types.d.ts +60 -0
  102. package/dist/resolve-modules/plugins/types.d.ts.map +1 -0
  103. package/dist/resolve-modules/plugins/types.js +1 -0
  104. package/dist/resolve-modules/plugins/types.test.d.ts +2 -0
  105. package/dist/resolve-modules/plugins/types.test.d.ts.map +1 -0
  106. package/dist/resolve-modules/plugins/types.test.js +49 -0
  107. package/dist/resolve-modules/resolveModules.d.ts +3 -0
  108. package/dist/resolve-modules/resolveModules.d.ts.map +1 -0
  109. package/dist/resolve-modules/resolveModules.js +70 -0
  110. package/dist/resolve-modules/resolveModules.test.d.ts +2 -0
  111. package/dist/resolve-modules/resolveModules.test.d.ts.map +1 -0
  112. package/dist/resolve-modules/resolveModules.test.js +143 -0
  113. package/dist/resolve-modules/types.d.ts +62 -0
  114. package/dist/resolve-modules/types.d.ts.map +1 -0
  115. package/dist/resolve-modules/types.js +1 -0
  116. package/dist/test-utilities/createMessage.d.ts +17 -0
  117. package/dist/test-utilities/createMessage.d.ts.map +1 -0
  118. package/dist/test-utilities/createMessage.js +16 -0
  119. package/dist/test-utilities/createMessage.test.d.ts +2 -0
  120. package/dist/test-utilities/createMessage.test.d.ts.map +1 -0
  121. package/dist/test-utilities/createMessage.test.js +91 -0
  122. package/dist/test-utilities/index.d.ts +2 -0
  123. package/dist/test-utilities/index.d.ts.map +1 -0
  124. package/dist/test-utilities/index.js +1 -0
  125. package/dist/versionedInterfaces.d.ts +8 -0
  126. package/dist/versionedInterfaces.d.ts.map +1 -0
  127. package/dist/versionedInterfaces.js +8 -0
  128. package/package.json +58 -0
  129. package/src/adapter/solidAdapter.test.ts +363 -0
  130. package/src/adapter/solidAdapter.ts +77 -0
  131. package/src/api.ts +86 -0
  132. package/src/createMessageLintReportsQuery.ts +77 -0
  133. package/src/createMessagesQuery.test.ts +435 -0
  134. package/src/createMessagesQuery.ts +64 -0
  135. package/src/errors.ts +46 -0
  136. package/src/index.ts +29 -0
  137. package/src/lint/index.ts +2 -0
  138. package/src/lint/message/errors.ts +9 -0
  139. package/src/lint/message/lintMessages.test.ts +122 -0
  140. package/src/lint/message/lintMessages.ts +33 -0
  141. package/src/lint/message/lintSingleMessage.test.ts +183 -0
  142. package/src/lint/message/lintSingleMessage.ts +62 -0
  143. package/src/messages/errors.ts +25 -0
  144. package/src/messages/index.ts +2 -0
  145. package/src/messages/variant.test.ts +444 -0
  146. package/src/messages/variant.ts +242 -0
  147. package/src/openInlangProject.test.ts +734 -0
  148. package/src/openInlangProject.ts +337 -0
  149. package/src/parseConfig.ts +33 -0
  150. package/src/reactivity/map.ts +135 -0
  151. package/src/reactivity/solid.ts +36 -0
  152. package/src/reactivity/trigger.ts +46 -0
  153. package/src/resolve-modules/errors.ts +39 -0
  154. package/src/resolve-modules/import.test.ts +58 -0
  155. package/src/resolve-modules/import.ts +69 -0
  156. package/src/resolve-modules/index.ts +2 -0
  157. package/src/resolve-modules/message-lint-rules/errors.ts +9 -0
  158. package/src/resolve-modules/message-lint-rules/resolveMessageLintRules.ts +24 -0
  159. package/src/resolve-modules/plugins/errors.ts +57 -0
  160. package/src/resolve-modules/plugins/resolvePlugins.test.ts +340 -0
  161. package/src/resolve-modules/plugins/resolvePlugins.ts +170 -0
  162. package/src/resolve-modules/plugins/types.test.ts +57 -0
  163. package/src/resolve-modules/plugins/types.ts +77 -0
  164. package/src/resolve-modules/resolveModules.test.ts +176 -0
  165. package/src/resolve-modules/resolveModules.ts +97 -0
  166. package/src/resolve-modules/types.ts +71 -0
  167. package/src/test-utilities/createMessage.test.ts +100 -0
  168. package/src/test-utilities/createMessage.ts +20 -0
  169. package/src/test-utilities/index.ts +1 -0
  170. package/src/versionedInterfaces.ts +9 -0
@@ -0,0 +1,435 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ import { describe, it, expect } from "vitest"
3
+ import { createMessagesQuery } from "./createMessagesQuery.js"
4
+ import { createEffect, createRoot, createSignal } from "./reactivity/solid.js"
5
+ import type { Message, Text } from "@inlang/message"
6
+ import { createMessage } from "./test-utilities/createMessage.js"
7
+
8
+ const createChangeListener = async (cb: () => void) => createEffect(cb)
9
+
10
+ describe("create", () => {
11
+ it("should create a message", () => {
12
+ const query = createMessagesQuery(() => [])
13
+ expect(query.get({ where: { id: "first-message" } })).toBeUndefined()
14
+
15
+ const mockMessage = createMessage("first-message", { en: "Hello World" })
16
+ const created = query.create({ data: mockMessage })
17
+
18
+ expect(query.get({ where: { id: "first-message" } })).toEqual(mockMessage)
19
+ expect(created).toBe(true)
20
+ })
21
+
22
+ it("should return false if message with id already exists", () => {
23
+ const query = createMessagesQuery(() => [createMessage("first-message", { en: "Hello World" })])
24
+ expect(query.get({ where: { id: "first-message" } })).toBeDefined()
25
+
26
+ const mockMessage = createMessage("first-message", { en: "Some Text" })
27
+ query.create({ data: mockMessage })
28
+
29
+ const created = query.create({ data: mockMessage })
30
+ expect(created).toBe(false)
31
+ })
32
+ })
33
+
34
+ describe("get", () => {
35
+ it("should return undefined if a message does not exist", () => {
36
+ const query = createMessagesQuery(() => [createMessage("first-message", { en: "Hello World" })])
37
+ const message = query.get({ where: { id: "none-existent-message" } })
38
+ expect(message).toBeUndefined()
39
+ })
40
+
41
+ it("should return an object, not an array", () => {
42
+ const query = createMessagesQuery(() => [createMessage("first-message", { en: "Hello World" })])
43
+ const message = query.get({ where: { id: "first-message" } })
44
+ expect(message).toBeDefined()
45
+ expect(Array.isArray(message)).toBe(false)
46
+ })
47
+
48
+ // todo: improve the readonly type
49
+ it.skip("mutating the returned value should not affect subsequent return values", () => {
50
+ const query = createMessagesQuery(() => [createMessage("first-message", { en: "Hello World" })])
51
+ const message1 = query.get({ where: { id: "first-message" } })!
52
+ ;(message1.variants.find((v) => v.languageTag === "en")!.pattern![0]! as Text).value =
53
+ "Hello World 2"
54
+ const message2 = query.get({ where: { id: "first-message" } })!
55
+
56
+ expect(
57
+ (message1.variants.find((v) => v.languageTag === "en")!.pattern![0]! as Text).value,
58
+ ).toBe("Hello World 2")
59
+ expect(
60
+ (message2.variants.find((v) => v.languageTag === "en")!.pattern![0]! as Text).value,
61
+ ).toBe("Hello World")
62
+ })
63
+ })
64
+
65
+ describe("getAll", () => {
66
+ it("should return an empty array if no messages exist", () => {
67
+ const query = createMessagesQuery(() => [])
68
+ const messages = query.getAll()
69
+
70
+ expect(Object.values(messages!)).toEqual([])
71
+ })
72
+
73
+ it("should return all message objects", () => {
74
+ const query = createMessagesQuery(() => [])
75
+ const mockMessage1 = createMessage("first-message", { en: "Hello World" })
76
+ const mockMessage2 = createMessage("second-message", { en: "Hello World 2" })
77
+ query.create({ data: mockMessage1 })
78
+ query.create({ data: mockMessage2 })
79
+
80
+ const messages = query.getAll()
81
+ expect(Object.values(messages!)).toEqual([mockMessage1, mockMessage2])
82
+ })
83
+
84
+ // todo: improve the readonly type
85
+ it.skip("mutating the returned value should not affect subsequent return values", () => {
86
+ const query = createMessagesQuery(() => [createMessage("first-message", { en: "Hello World" })])
87
+ const messages1 = query.getAll()
88
+ ;(
89
+ Object.values(messages1!)[0]!.variants.find((v) => v.languageTag === "en")!
90
+ .pattern![0]! as Text
91
+ ).value = "Hello World 2"
92
+
93
+ expect(
94
+ (
95
+ Object.values(query.getAll()!)[0]!.variants.find((v) => v.languageTag === "en")!
96
+ .pattern![0]! as Text
97
+ ).value,
98
+ ).toBe("Hello World")
99
+ })
100
+ })
101
+
102
+ describe("update", () => {
103
+ it("should update a message", () => {
104
+ const query = createMessagesQuery(() => [createMessage("first-message", { en: "Hello World" })])
105
+ expect(query.get({ where: { id: "first-message" } })).toBeDefined()
106
+
107
+ const mockMessage = createMessage("first-message", { en: "Hello World 2" })
108
+ const updated = query.update({ where: { id: "first-message" }, data: mockMessage })
109
+
110
+ expect(query.get({ where: { id: "first-message" } })).toEqual(mockMessage)
111
+ expect(updated).toBe(true)
112
+ })
113
+
114
+ it("should return false if message with id does not exist exists", () => {
115
+ const query = createMessagesQuery(() => [])
116
+ expect(query.get({ where: { id: "first-message" } })).toBeUndefined()
117
+
118
+ const mockMessage = createMessage("first-message", { en: "Hello World" })
119
+ const updated = query.update({ where: { id: "first-message" }, data: mockMessage })
120
+ expect(updated).toBe(false)
121
+ })
122
+ })
123
+
124
+ describe("upsert", () => {
125
+ it("should create a message if not present yet", () => {
126
+ const query = createMessagesQuery(() => [])
127
+ expect(query.get({ where: { id: "first-message" } })).toBeUndefined()
128
+
129
+ const mockMessage = createMessage("first-message", { en: "Hello World" })
130
+ const upserted = query.upsert({ where: { id: "first-message" }, data: mockMessage })
131
+
132
+ expect(query.get({ where: { id: "first-message" } })).toEqual(mockMessage)
133
+ expect(upserted).toBe(true)
134
+ })
135
+
136
+ it("should update message if id already exists", () => {
137
+ const query = createMessagesQuery(() => [createMessage("first-message", { en: "Hello World" })])
138
+ expect(query.get({ where: { id: "first-message" } })).toBeDefined()
139
+
140
+ const mockMessage = createMessage("first-message", { en: "Hello World 2" })
141
+ const upserted = query.upsert({ where: { id: "first-message" }, data: mockMessage })
142
+
143
+ expect(query.get({ where: { id: "first-message" } })).toEqual(mockMessage)
144
+ expect(upserted).toBe(true)
145
+ })
146
+ })
147
+
148
+ describe("delete", () => {
149
+ it("should delete a message", () => {
150
+ const query = createMessagesQuery(() => [createMessage("first-message", { en: "Hello World" })])
151
+ expect(query.get({ where: { id: "first-message" } })).toBeDefined()
152
+
153
+ const deleted = query.delete({ where: { id: "first-message" } })
154
+
155
+ expect(query.get({ where: { id: "first-message" } })).toBeUndefined()
156
+ expect(deleted).toBe(true)
157
+ })
158
+
159
+ it("should return false if message with id does not exist", () => {
160
+ const query = createMessagesQuery(() => [])
161
+ expect(query.get({ where: { id: "first-message" } })).toBeUndefined()
162
+
163
+ const deleted = query.delete({ where: { id: "first-message" } })
164
+ expect(deleted).toBe(false)
165
+ })
166
+ })
167
+
168
+ describe("reactivity", () => {
169
+ describe("get", () => {
170
+ it("should react to `create`", async () => {
171
+ await createRoot(async () => {
172
+ const query = createMessagesQuery(() => [])
173
+
174
+ // eslint-disable-next-line unicorn/no-null
175
+ let message: Message | undefined | null = null
176
+ await createChangeListener(() => (message = query.get({ where: { id: "1" } })))
177
+ expect(message).toBeUndefined()
178
+
179
+ query.create({ data: createMessage("1", { en: "before" }) })
180
+ expect(message).toBeDefined()
181
+
182
+ const anotherMessage = query.get({ where: { id: "1" } })
183
+ expect(anotherMessage).toBeDefined()
184
+ expect(message).toStrictEqual(anotherMessage)
185
+ })
186
+ })
187
+
188
+ it("should react to `update`", async () => {
189
+ await createRoot(async () => {
190
+ const query = createMessagesQuery(() => [createMessage("1", { en: "before" })])
191
+
192
+ let message: Message | undefined
193
+ await createChangeListener(() => (message = query.get({ where: { id: "1" } })))
194
+ expect(message).toBeDefined()
195
+ expect(
196
+ (message?.variants.find((variant) => variant.languageTag === "en")?.pattern[0] as Text)
197
+ .value,
198
+ ).toBe("before")
199
+
200
+ query.update({ where: { id: "1" }, data: createMessage("1", { en: "after" }) })
201
+ expect(
202
+ (message?.variants.find((variant) => variant.languageTag === "en")?.pattern[0] as Text)
203
+ .value,
204
+ ).toBe("after")
205
+ })
206
+ })
207
+
208
+ it("should react to `upsert`", async () => {
209
+ await createRoot(async () => {
210
+ const query = createMessagesQuery(() => [])
211
+
212
+ let message: Message | undefined
213
+ await createChangeListener(() => (message = query.get({ where: { id: "1" } })))
214
+ expect(message).toBeUndefined()
215
+
216
+ query.upsert({ where: { id: "1" }, data: createMessage("1", { en: "before" }) })
217
+ expect(message).toBeDefined()
218
+ expect(
219
+ (message?.variants.find((variant) => variant.languageTag === "en")?.pattern[0] as Text)
220
+ .value,
221
+ ).toBe("before")
222
+
223
+ query.upsert({ where: { id: "1" }, data: createMessage("1", { en: "after" }) })
224
+ expect(
225
+ (message?.variants.find((variant) => variant.languageTag === "en")?.pattern[0] as Text)
226
+ .value,
227
+ ).toBe("after")
228
+ })
229
+ })
230
+
231
+ it("should react to `delete`", async () => {
232
+ await createRoot(async () => {
233
+ const query = createMessagesQuery(() => [createMessage("1", { en: "" })])
234
+
235
+ let message: Message | undefined
236
+ await createChangeListener(() => (message = query.get({ where: { id: "1" } })))
237
+ expect(message).toBeDefined()
238
+
239
+ query.delete({ where: { id: "1" } })
240
+ expect(message).toBeUndefined()
241
+ })
242
+ })
243
+
244
+ it("should react to changes to the input `messages`", async () => {
245
+ const [messages, setMessages] = createSignal<Message[]>([])
246
+ const query = createMessagesQuery(messages)
247
+
248
+ // eslint-disable-next-line unicorn/no-null
249
+ let message: Message | undefined | null = null
250
+ await createChangeListener(() => (message = query.get({ where: { id: "1" } })))
251
+ expect(message).toBeUndefined()
252
+
253
+ query.create({ data: createMessage("1", { en: "before" }) })
254
+ expect(message).toBeDefined()
255
+ expect(
256
+ (message!.variants.find((variant) => variant.languageTag === "en")?.pattern[0] as Text)
257
+ .value,
258
+ ).toBe("before")
259
+
260
+ setMessages([createMessage("1", { en: "after" })])
261
+ expect(message).toBeDefined()
262
+ expect(
263
+ (message!.variants.find((variant) => variant.languageTag === "en")?.pattern[0] as Text)
264
+ .value,
265
+ ).toBe("after")
266
+ })
267
+ })
268
+
269
+ describe.todo("subscribe", () => {
270
+ // TODO: add tests for `subscribe`
271
+ })
272
+
273
+ describe("getAll", () => {
274
+ it("should react to `create`", async () => {
275
+ await createRoot(async () => {
276
+ const query = createMessagesQuery(() => [])
277
+
278
+ let messages: Readonly<Message[]> | undefined = undefined
279
+ await createChangeListener(() => (messages = query.getAll()))
280
+ expect(Object.values(messages!)).toHaveLength(0)
281
+
282
+ query.create({ data: createMessage("1", { en: "before" }) })
283
+ expect(Object.values(messages!)).toHaveLength(1)
284
+
285
+ query.create({ data: createMessage("2", { en: "before" }) })
286
+ expect(Object.values(messages!)).toHaveLength(2)
287
+
288
+ query.create({ data: createMessage("3", { en: "before" }) })
289
+ expect(Object.values(messages!)).toHaveLength(3)
290
+ })
291
+ })
292
+
293
+ it("should react to `update`", async () => {
294
+ await createRoot(async () => {
295
+ const query = createMessagesQuery(() => [createMessage("1", { en: "before" })])
296
+
297
+ let messages: Readonly<Message[]> | undefined = undefined
298
+ await createChangeListener(() => (messages = query.getAll()))
299
+ expect(Object.values(messages!)).toHaveLength(1)
300
+ expect(
301
+ (
302
+ Object.values(messages!)![0]!.variants.find((variant) => variant.languageTag === "en")!
303
+ .pattern[0]! as Text
304
+ ).value,
305
+ ).toBe("before")
306
+
307
+ query.update({ where: { id: "1" }, data: createMessage("1", { en: "after" }) })
308
+ expect(Object.values(messages!)).toHaveLength(1)
309
+ expect(
310
+ (
311
+ Object.values(messages!)![0]!.variants.find((variant) => variant.languageTag === "en")!
312
+ .pattern[0]! as Text
313
+ ).value,
314
+ ).toBe("after")
315
+ })
316
+ })
317
+
318
+ it("should react to `upsert`", async () => {
319
+ await createRoot(async () => {
320
+ const query = createMessagesQuery(() => [])
321
+
322
+ let messages: Readonly<Message[]> | undefined = undefined
323
+ await createChangeListener(() => (messages = query.getAll()))
324
+ expect(Object.values(messages!)).toHaveLength(0)
325
+
326
+ query.upsert({ where: { id: "1" }, data: createMessage("1", { en: "before" }) })
327
+ expect(Object.values(messages!)).toHaveLength(1)
328
+ expect(
329
+ (
330
+ Object.values(messages!)![0]!.variants.find((variant) => variant.languageTag === "en")!
331
+ .pattern[0]! as Text
332
+ ).value,
333
+ ).toBe("before")
334
+
335
+ query.upsert({ where: { id: "1" }, data: createMessage("1", { en: "after" }) })
336
+ expect(
337
+ (
338
+ Object.values(messages!)![0]!.variants.find((variant) => variant.languageTag === "en")!
339
+ .pattern[0]! as Text
340
+ ).value,
341
+ ).toBe("after")
342
+ })
343
+ })
344
+
345
+ it("should react to `delete`", async () => {
346
+ await createRoot(async () => {
347
+ const query = createMessagesQuery(() => [
348
+ createMessage("1", { en: "" }),
349
+ createMessage("2", { en: "" }),
350
+ createMessage("3", { en: "" }),
351
+ ])
352
+
353
+ let messages: Readonly<Message[]> | undefined = undefined
354
+ await createChangeListener(() => (messages = query.getAll()))
355
+ expect(Object.values(messages!)).toHaveLength(3)
356
+
357
+ query.delete({ where: { id: "1" } })
358
+ expect(Object.values(messages!)).toHaveLength(2)
359
+
360
+ // deleting the same id again should not have an effect
361
+ query.delete({ where: { id: "1" } })
362
+ expect(Object.values(messages!)).toHaveLength(2)
363
+
364
+ query.delete({ where: { id: "2" } })
365
+ expect(Object.values(messages!)).toHaveLength(1)
366
+
367
+ query.delete({ where: { id: "3" } })
368
+ expect(Object.values(messages!)).toHaveLength(0)
369
+ })
370
+ })
371
+
372
+ it("should react to changes to the input `messages`", async () => {
373
+ const [inputMessages, setMessages] = createSignal<Message[]>([
374
+ createMessage("1", { en: "before" }),
375
+ ])
376
+ const query = createMessagesQuery(inputMessages)
377
+
378
+ let messages: Readonly<Message[]> | undefined = undefined
379
+ await createChangeListener(() => (messages = query.getAll()))
380
+ expect(Object.values(messages!)).toHaveLength(1)
381
+
382
+ query.create({ data: createMessage("2", { en: "" }) })
383
+ expect(Object.values(messages!)).toHaveLength(2)
384
+ expect(
385
+ (
386
+ Object.values(messages!)![0]!.variants.find((variant) => variant.languageTag === "en")!
387
+ .pattern[0]! as Text
388
+ ).value,
389
+ ).toBe("before")
390
+
391
+ setMessages([createMessage("1", { en: "after" })])
392
+ expect(Object.values(messages!)).toHaveLength(1)
393
+ expect(
394
+ (
395
+ Object.values(messages!)![0]!.variants.find((variant) => variant.languageTag === "en")!
396
+ .pattern[0]! as Text
397
+ ).value,
398
+ ).toBe("after")
399
+ })
400
+ })
401
+ })
402
+
403
+ it("instances should not share state", async () => {
404
+ await createRoot(async () => {
405
+ const query1 = createMessagesQuery(() => [createMessage("1", { en: "before" })])
406
+ const query2 = createMessagesQuery(() => [])
407
+
408
+ // eslint-disable-next-line unicorn/no-null
409
+ let message1: Message | undefined | null = null
410
+ await createChangeListener(() => (message1 = query1.get({ where: { id: "1" } })))
411
+ // eslint-disable-next-line unicorn/no-null
412
+ let message2: Message | undefined | null = null
413
+ await createChangeListener(() => (message2 = query2.get({ where: { id: "1" } })))
414
+
415
+ expect(message1).toBeDefined()
416
+ expect(message2).toBeUndefined()
417
+
418
+ query2.create({ data: createMessage("1", { en: "before" }) })
419
+ expect(message2).toBeDefined()
420
+
421
+ query1.update({ where: { id: "1" }, data: createMessage("1", { en: "after" }) })
422
+ expect(
423
+ (message1!.variants.find((variant) => variant.languageTag === "en")!.pattern[0]! as Text)
424
+ .value,
425
+ ).toBe("after")
426
+ expect(
427
+ (message2!.variants.find((variant) => variant.languageTag === "en")!.pattern[0]! as Text)
428
+ .value,
429
+ ).toBe("before")
430
+
431
+ query1.delete({ where: { id: "1" } })
432
+ expect(message1).toBeUndefined()
433
+ expect(message2).toBeDefined()
434
+ })
435
+ })
@@ -0,0 +1,64 @@
1
+ import type { Message } from "@inlang/message"
2
+ import { ReactiveMap } from "./reactivity/map.js"
3
+ import { createEffect } from "./reactivity/solid.js"
4
+ import { createSubscribable } from "./openInlangProject.js"
5
+ import type { InlangProject, MessageQueryApi } from "./api.js"
6
+
7
+ /**
8
+ * Creates a reactive query API for messages.
9
+ */
10
+ export function createMessagesQuery(
11
+ messages: () => Array<Message>,
12
+ ): InlangProject["query"]["messages"] {
13
+ // @ts-expect-error
14
+ const index = new ReactiveMap<string, Message>()
15
+
16
+ createEffect(() => {
17
+ index.clear()
18
+ for (const message of messages()) {
19
+ index.set(message.id, message)
20
+ }
21
+ })
22
+
23
+ const get = (args: Parameters<MessageQueryApi["get"]>[0]) => index.get(args.where.id)
24
+
25
+ return {
26
+ create: ({ data }): boolean => {
27
+ if (index.has(data.id)) return false
28
+ index.set(data.id, data)
29
+ return true
30
+ },
31
+ get: Object.assign(get, {
32
+ subscribe: (
33
+ args: Parameters<MessageQueryApi["get"]["subscribe"]>[0],
34
+ callback: Parameters<MessageQueryApi["get"]["subscribe"]>[1],
35
+ ) => createSubscribable(() => get(args)).subscribe(callback),
36
+ }) as any,
37
+ includedMessageIds: createSubscribable(() => {
38
+ return [...index.keys()]
39
+ }),
40
+ getAll: createSubscribable(() => {
41
+ return [...index.values()]
42
+ }),
43
+ update: ({ where, data }): boolean => {
44
+ const message = index.get(where.id)
45
+ if (message === undefined) return false
46
+ index.set(where.id, { ...message, ...data })
47
+ return true
48
+ },
49
+ upsert: ({ where, data }) => {
50
+ const message = index.get(where.id)
51
+ if (message === undefined) {
52
+ index.set(where.id, data)
53
+ } else {
54
+ index.set(where.id, { ...message, ...data })
55
+ }
56
+ return true
57
+ },
58
+ delete: ({ where }): boolean => {
59
+ if (!index.has(where.id)) return false
60
+ index.delete(where.id)
61
+ return true
62
+ },
63
+ }
64
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,46 @@
1
+ export class InvalidConfigError extends Error {
2
+ constructor(message: string, options: ErrorOptions) {
3
+ super(message, options)
4
+ this.name = "InvalidConfigError"
5
+ }
6
+ }
7
+
8
+ export class ProjectFileJSONSyntaxError extends Error {
9
+ constructor(message: string, options: ErrorOptions) {
10
+ super(message, options)
11
+ this.name = "ProjectFileJSONSyntaxError"
12
+ }
13
+ }
14
+
15
+ export class ProjectFilePathNotFoundError extends Error {
16
+ constructor(message: string, options: ErrorOptions) {
17
+ super(message, options)
18
+ this.name = "ProjectFilePathNotFoundError"
19
+ }
20
+ }
21
+
22
+ export class PluginSaveMessagesError extends Error {
23
+ constructor(message: string, options: ErrorOptions) {
24
+ super(message, options)
25
+ this.name = "PluginSaveMessagesError"
26
+ }
27
+ }
28
+
29
+ export class PluginLoadMessagesError extends Error {
30
+ constructor(message: string, options: ErrorOptions) {
31
+ super(message, options)
32
+ this.name = "PluginLoadMessagesError"
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Error when no package provides the API to handle messages.
38
+ */
39
+ export class NoPluginProvidesLoadOrSaveMessagesError extends Error {
40
+ constructor() {
41
+ super(
42
+ "It seems you did not install any plugin that handles messages. Please add one to make inlang work. See https://inlang.com/documentation/plugins/registry.",
43
+ ) // TODO: check if link is correct
44
+ this.name = "NoPluginProvidesLoadOrSaveMessagesError"
45
+ }
46
+ }
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ *! PUBLIC API FOR THE INLANG SDK.
3
+ *!
4
+ *! EXPORT AS LITTLE AS POSSIBLE TO MINIMIZE THE CHANCE OF BREAKING CHANGES.
5
+ */
6
+
7
+ export type {
8
+ InlangProject,
9
+ InstalledMessageLintRule,
10
+ InstalledPlugin,
11
+ MessageQueryApi,
12
+ Subscribable,
13
+ } from "./api.js"
14
+ export { type ImportFunction, createImport } from "./resolve-modules/index.js"
15
+ export { openInlangProject } from "./openInlangProject.js"
16
+ export { solidAdapter, type InlangProjectWithSolidAdapter } from "./adapter/solidAdapter.js"
17
+ export { createMessagesQuery } from "./createMessagesQuery.js"
18
+ export {
19
+ ProjectFilePathNotFoundError,
20
+ ProjectFileJSONSyntaxError,
21
+ InvalidConfigError,
22
+ NoPluginProvidesLoadOrSaveMessagesError,
23
+ PluginLoadMessagesError,
24
+ PluginSaveMessagesError,
25
+ } from "./errors.js"
26
+
27
+ export * from "./messages/variant.js"
28
+ export * from "./versionedInterfaces.js"
29
+ export { InlangModule } from "@inlang/module"
@@ -0,0 +1,2 @@
1
+ export { lintSingleMessage } from "./message/lintSingleMessage.js"
2
+ export { lintMessages } from "./message/lintMessages.js"
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Error when a lint rule throws during the linting process.
3
+ */
4
+ export class MessagedLintRuleThrowedError extends Error {
5
+ constructor(message: string, options?: ErrorOptions) {
6
+ super(message, options)
7
+ this.name = "MessagedLintRuleThrowedError"
8
+ }
9
+ }