@inlang/sdk 0.34.8 → 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.
- package/dist/adapter/solidAdapter.js +1 -1
- package/dist/adapter/solidAdapter.test.js +60 -23
- package/dist/api.d.ts +16 -8
- package/dist/api.d.ts.map +1 -1
- package/dist/createMessageLintReportsQuery.d.ts +5 -1
- package/dist/createMessageLintReportsQuery.d.ts.map +1 -1
- package/dist/createMessageLintReportsQuery.js +165 -62
- package/dist/createMessagesQuery.d.ts.map +1 -1
- package/dist/createMessagesQuery.js +30 -12
- package/dist/createNewProject.d.ts +0 -5
- package/dist/createNewProject.d.ts.map +1 -1
- package/dist/createNewProject.js +0 -5
- package/dist/lint/message/lintSingleMessage.d.ts.map +1 -1
- package/dist/lint/message/lintSingleMessage.js +3 -1
- package/dist/loadProject.d.ts.map +1 -1
- package/dist/loadProject.js +6 -2
- package/dist/loadProject.test.js +38 -25
- package/dist/persistence/filelock/acquireFileLock.d.ts.map +1 -1
- package/dist/persistence/filelock/acquireFileLock.js +2 -2
- package/dist/persistence/filelock/releaseLock.js +1 -1
- package/dist/reactivity/solid.d.ts +2 -1
- package/dist/reactivity/solid.d.ts.map +1 -1
- package/dist/reactivity/solid.js +3 -2
- package/dist/reactivity/solid.test.js +38 -1
- package/dist/v2/createMessageBundle.d.ts +25 -0
- package/dist/v2/createMessageBundle.d.ts.map +1 -0
- package/dist/v2/createMessageBundle.js +36 -0
- package/dist/v2/createMessageBundle.test.d.ts +2 -0
- package/dist/v2/createMessageBundle.test.d.ts.map +1 -0
- package/dist/v2/createMessageBundle.test.js +92 -0
- package/dist/v2/mocks/plural/bundle.d.ts +3 -0
- package/dist/v2/mocks/plural/bundle.d.ts.map +1 -0
- package/dist/v2/mocks/plural/bundle.js +140 -0
- package/dist/v2/mocks/plural/bundle.test.d.ts +2 -0
- package/dist/v2/mocks/plural/bundle.test.d.ts.map +1 -0
- package/dist/v2/mocks/plural/bundle.test.js +15 -0
- package/package.json +3 -3
- package/src/adapter/solidAdapter.test.ts +78 -33
- package/src/adapter/solidAdapter.ts +1 -1
- package/src/api.ts +15 -8
- package/src/createMessageLintReportsQuery.ts +190 -67
- package/src/createMessagesQuery.ts +33 -12
- package/src/createNewProject.ts +0 -5
- package/src/createNodeishFsWithWatcher.ts +1 -1
- package/src/lint/message/lintSingleMessage.ts +4 -1
- package/src/loadProject.test.ts +45 -24
- package/src/loadProject.ts +7 -2
- package/src/persistence/filelock/acquireFileLock.ts +4 -2
- package/src/persistence/filelock/releaseLock.ts +1 -1
- package/src/reactivity/solid.test.ts +54 -1
- package/src/reactivity/solid.ts +3 -0
- package/src/v2/createMessageBundle.test.ts +95 -0
- package/src/v2/createMessageBundle.ts +43 -0
- package/src/v2/mocks/plural/bundle.test.ts +18 -0
- package/src/v2/mocks/plural/bundle.ts +142 -0
|
@@ -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"
|
|
@@ -15,7 +14,7 @@ import type { ProjectSettings } from "@inlang/project-settings"
|
|
|
15
14
|
import { releaseLock } from "./persistence/filelock/releaseLock.js"
|
|
16
15
|
import { PluginLoadMessagesError, PluginSaveMessagesError } from "./errors.js"
|
|
17
16
|
import { humanIdHash } from "./storage/human-id/human-readable-id.js"
|
|
18
|
-
const debug = _debug("sdk:
|
|
17
|
+
const debug = _debug("sdk:messages")
|
|
19
18
|
|
|
20
19
|
function sleep(ms: number) {
|
|
21
20
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
@@ -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?.
|
|
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
|
|
package/src/createNewProject.ts
CHANGED
|
@@ -6,11 +6,6 @@ import { defaultProjectSettings } from "./defaultProjectSettings.js"
|
|
|
6
6
|
/**
|
|
7
7
|
* Creates a new project in the given directory.
|
|
8
8
|
* The directory must be an absolute path, must not exist, and must end with {name}.inlang
|
|
9
|
-
* Uses defaultProjectSettings unless projectSettings are provided.
|
|
10
|
-
*
|
|
11
|
-
* @param projectPath - Absolute path to the [name].inlang directory
|
|
12
|
-
* @param repo - An instance of a lix repo as returned by `openRepository`
|
|
13
|
-
* @param projectSettings - Optional project settings to use for the new project.
|
|
14
9
|
*/
|
|
15
10
|
export async function createNewProject(args: {
|
|
16
11
|
projectPath: string
|
|
@@ -50,5 +50,8 @@ export const lintSingleMessage = async (args: {
|
|
|
50
50
|
|
|
51
51
|
await Promise.all(promises)
|
|
52
52
|
|
|
53
|
-
|
|
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
|
}
|
package/src/loadProject.test.ts
CHANGED
|
@@ -635,8 +635,10 @@ describe("functionality", () => {
|
|
|
635
635
|
|
|
636
636
|
await new Promise((resolve) => setTimeout(resolve, 510))
|
|
637
637
|
|
|
638
|
-
expect(await project.query.messageLintReports.getAll()).toHaveLength(1)
|
|
639
|
-
expect((await project.query.messageLintReports.getAll())?.[0]?.ruleId).toBe(
|
|
638
|
+
expect(await project.query.messageLintReports.getAll.settled()).toHaveLength(1)
|
|
639
|
+
expect((await project.query.messageLintReports.getAll.settled())?.[0]?.ruleId).toBe(
|
|
640
|
+
_mockLintRule.id
|
|
641
|
+
)
|
|
640
642
|
expect(project.installed.messageLintRules()).toHaveLength(1)
|
|
641
643
|
})
|
|
642
644
|
|
|
@@ -1014,25 +1016,6 @@ describe("functionality", () => {
|
|
|
1014
1016
|
})
|
|
1015
1017
|
|
|
1016
1018
|
describe("lint", () => {
|
|
1017
|
-
it.todo("should throw if lint reports are not initialized yet", async () => {
|
|
1018
|
-
const repo = await mockRepo()
|
|
1019
|
-
const fs = repo.nodeishFs
|
|
1020
|
-
await fs.mkdir("/user/project", { recursive: true })
|
|
1021
|
-
await fs.writeFile("/user/project/project.inlang.json", JSON.stringify(settings))
|
|
1022
|
-
const project = await loadProject({
|
|
1023
|
-
projectPath: "/user/project/project.inlang.json",
|
|
1024
|
-
repo,
|
|
1025
|
-
_import,
|
|
1026
|
-
})
|
|
1027
|
-
// TODO: test with real lint rules
|
|
1028
|
-
try {
|
|
1029
|
-
const r = await project.query.messageLintReports.getAll()
|
|
1030
|
-
expect(r).toEqual(undefined)
|
|
1031
|
-
throw new Error("Should not reach this")
|
|
1032
|
-
} catch (e) {
|
|
1033
|
-
expect((e as Error).message).toBe("lint not initialized yet")
|
|
1034
|
-
}
|
|
1035
|
-
})
|
|
1036
1019
|
it("should return the message lint reports", async () => {
|
|
1037
1020
|
const settings: ProjectSettings = {
|
|
1038
1021
|
sourceLanguageTag: "en",
|
|
@@ -1051,7 +1034,7 @@ describe("functionality", () => {
|
|
|
1051
1034
|
}),
|
|
1052
1035
|
})
|
|
1053
1036
|
// TODO: test with real lint rules
|
|
1054
|
-
const r = await project.query.messageLintReports.getAll()
|
|
1037
|
+
const r = await project.query.messageLintReports.getAll.settled()
|
|
1055
1038
|
expect(r).toEqual([])
|
|
1056
1039
|
})
|
|
1057
1040
|
})
|
|
@@ -1083,6 +1066,23 @@ describe("functionality", () => {
|
|
|
1083
1066
|
],
|
|
1084
1067
|
}
|
|
1085
1068
|
|
|
1069
|
+
const newMessage = {
|
|
1070
|
+
id: "test2",
|
|
1071
|
+
selectors: [],
|
|
1072
|
+
variants: [
|
|
1073
|
+
{
|
|
1074
|
+
match: [],
|
|
1075
|
+
languageTag: "en",
|
|
1076
|
+
pattern: [
|
|
1077
|
+
{
|
|
1078
|
+
type: "Text",
|
|
1079
|
+
value: "test",
|
|
1080
|
+
},
|
|
1081
|
+
],
|
|
1082
|
+
},
|
|
1083
|
+
],
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
1086
|
await fs.writeFile("./messages.json", JSON.stringify(messages))
|
|
1087
1087
|
|
|
1088
1088
|
const getMessages = async (customFs: NodeishFilesystemSubset) => {
|
|
@@ -1121,9 +1121,11 @@ describe("functionality", () => {
|
|
|
1121
1121
|
})
|
|
1122
1122
|
|
|
1123
1123
|
let counter = 0
|
|
1124
|
+
let messageCount = 0
|
|
1124
1125
|
|
|
1125
|
-
project.query.messages.getAll.subscribe(() => {
|
|
1126
|
+
project.query.messages.getAll.subscribe((messages) => {
|
|
1126
1127
|
counter = counter + 1
|
|
1128
|
+
messageCount = messages.length
|
|
1127
1129
|
})
|
|
1128
1130
|
|
|
1129
1131
|
// subscribe fires once
|
|
@@ -1135,6 +1137,7 @@ describe("functionality", () => {
|
|
|
1135
1137
|
|
|
1136
1138
|
// we didn't change the message we write into message.json - shouldn't change the messages
|
|
1137
1139
|
expect(counter).toBe(1)
|
|
1140
|
+
expect(messageCount).toBe(1)
|
|
1138
1141
|
|
|
1139
1142
|
// saving the file without changing should trigger a change
|
|
1140
1143
|
messages.data[0]!.variants[0]!.pattern[0]!.value = "changed"
|
|
@@ -1142,14 +1145,32 @@ describe("functionality", () => {
|
|
|
1142
1145
|
await new Promise((resolve) => setTimeout(resolve, 200)) // file event will lock a file and be handled sequentially - give it time to pickup the change
|
|
1143
1146
|
|
|
1144
1147
|
expect(counter).toBe(2)
|
|
1148
|
+
expect(messageCount).toBe(1)
|
|
1145
1149
|
|
|
1146
1150
|
messages.data[0]!.variants[0]!.pattern[0]!.value = "changed3"
|
|
1147
1151
|
|
|
1148
|
-
// change file
|
|
1152
|
+
// change file - update message
|
|
1149
1153
|
await fs.writeFile("./messages.json", JSON.stringify(messages))
|
|
1150
1154
|
await new Promise((resolve) => setTimeout(resolve, 200)) // file event will lock a file and be handled sequentially - give it time to pickup the change
|
|
1151
1155
|
|
|
1152
1156
|
expect(counter).toBe(3)
|
|
1157
|
+
expect(messageCount).toBe(1)
|
|
1158
|
+
|
|
1159
|
+
// change file - add a message
|
|
1160
|
+
messages.data.push(newMessage)
|
|
1161
|
+
await fs.writeFile("./messages.json", JSON.stringify(messages))
|
|
1162
|
+
await new Promise((resolve) => setTimeout(resolve, 200)) // file event will lock a file and be handled sequentially - give it time to pickup the change
|
|
1163
|
+
|
|
1164
|
+
expect(counter).toBe(4)
|
|
1165
|
+
expect(messageCount).toBe(2)
|
|
1166
|
+
|
|
1167
|
+
// change file - remove a message
|
|
1168
|
+
messages.data.pop()
|
|
1169
|
+
await fs.writeFile("./messages.json", JSON.stringify(messages))
|
|
1170
|
+
await new Promise((resolve) => setTimeout(resolve, 200)) // file event will lock a file and be handled sequentially - give it time to pickup the change
|
|
1171
|
+
|
|
1172
|
+
expect(counter).toBe(5)
|
|
1173
|
+
expect(messageCount).toBe(1)
|
|
1153
1174
|
})
|
|
1154
1175
|
})
|
|
1155
1176
|
})
|
package/src/loadProject.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
ProjectSettingsFileNotFoundError,
|
|
13
13
|
ProjectSettingsInvalidError,
|
|
14
14
|
} from "./errors.js"
|
|
15
|
-
import { createRoot, createSignal, createEffect } from "./reactivity/solid.js"
|
|
15
|
+
import { createRoot, createSignal, createEffect, batch } from "./reactivity/solid.js"
|
|
16
16
|
import { createMessagesQuery } from "./createMessagesQuery.js"
|
|
17
17
|
import { createMessageLintReportsQuery } from "./createMessageLintReportsQuery.js"
|
|
18
18
|
import { ProjectSettings, type NodeishFilesystemSubset } from "./versionedInterfaces.js"
|
|
@@ -111,7 +111,12 @@ export async function loadProject(args: {
|
|
|
111
111
|
pathPattern: projectPath + "/messages.json",
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
|
|
114
|
+
|
|
115
|
+
batch(() => {
|
|
116
|
+
// reset the resolved modules first - since they are no longer valid at that point
|
|
117
|
+
setResolvedModules(undefined)
|
|
118
|
+
_setSettings(validatedSettings)
|
|
119
|
+
})
|
|
115
120
|
|
|
116
121
|
writeSettingsToDisk(validatedSettings)
|
|
117
122
|
return { data: undefined }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type NodeishFilesystem } from "@lix-js/fs"
|
|
2
2
|
import type { NodeishStats } from "@lix-js/fs"
|
|
3
3
|
import _debug from "debug"
|
|
4
|
-
const debug = _debug("sdk:
|
|
4
|
+
const debug = _debug("sdk:fileLock")
|
|
5
5
|
|
|
6
6
|
const maxRetries = 10
|
|
7
7
|
const nProbes = 50
|
|
@@ -13,7 +13,9 @@ export async function acquireFileLock(
|
|
|
13
13
|
tryCount: number = 0
|
|
14
14
|
): Promise<number> {
|
|
15
15
|
if (tryCount > maxRetries) {
|
|
16
|
-
throw new Error(
|
|
16
|
+
throw new Error(
|
|
17
|
+
`${lockOrigin} exceeded maximum retries (${maxRetries}) to acquire lockfile ${tryCount}`
|
|
18
|
+
)
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
try {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createRoot,
|
|
4
|
+
createSignal,
|
|
5
|
+
createEffect,
|
|
6
|
+
createMemo,
|
|
7
|
+
createResource,
|
|
8
|
+
untrack,
|
|
9
|
+
} from "./solid.js"
|
|
10
|
+
import { ReactiveMap } from "./map.js"
|
|
3
11
|
|
|
4
12
|
function sleep(ms: number) {
|
|
5
13
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
@@ -174,3 +182,48 @@ describe("solid", () => {
|
|
|
174
182
|
expect(memo()).toBe("memo = 2000")
|
|
175
183
|
})
|
|
176
184
|
})
|
|
185
|
+
|
|
186
|
+
describe("solid", () => {
|
|
187
|
+
it("solid reactive map allows to use untrack", () => {
|
|
188
|
+
// @ts-expect-error -- ReactiveMap seem to have problems type arguments here
|
|
189
|
+
const reactiveMap = new ReactiveMap<string, any>()
|
|
190
|
+
const [plainSignal, setPlainSignal] = createSignal(0)
|
|
191
|
+
|
|
192
|
+
let shouldTriggerOnBoth = -1
|
|
193
|
+
createEffect(() => {
|
|
194
|
+
shouldTriggerOnBoth++
|
|
195
|
+
reactiveMap.values()
|
|
196
|
+
plainSignal()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
let shouldTriggerOnReactiveMapOnly = -1
|
|
200
|
+
createEffect(() => {
|
|
201
|
+
shouldTriggerOnReactiveMapOnly++
|
|
202
|
+
reactiveMap.values()
|
|
203
|
+
untrack(() => plainSignal())
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
let shouldTriggerOnPlainSignalOnly = -1
|
|
207
|
+
createEffect(() => {
|
|
208
|
+
shouldTriggerOnPlainSignalOnly++
|
|
209
|
+
untrack(() => reactiveMap.values())
|
|
210
|
+
plainSignal()
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
expect(shouldTriggerOnBoth).toBe(0)
|
|
214
|
+
expect(shouldTriggerOnReactiveMapOnly).toBe(0)
|
|
215
|
+
expect(shouldTriggerOnPlainSignalOnly).toBe(0)
|
|
216
|
+
|
|
217
|
+
setPlainSignal(1)
|
|
218
|
+
|
|
219
|
+
expect(shouldTriggerOnBoth).toBe(1)
|
|
220
|
+
expect(shouldTriggerOnReactiveMapOnly).toBe(0)
|
|
221
|
+
expect(shouldTriggerOnPlainSignalOnly).toBe(1)
|
|
222
|
+
|
|
223
|
+
reactiveMap.set("a", "a")
|
|
224
|
+
|
|
225
|
+
expect(shouldTriggerOnBoth).toBe(2)
|
|
226
|
+
expect(shouldTriggerOnReactiveMapOnly).toBe(1)
|
|
227
|
+
expect(shouldTriggerOnPlainSignalOnly).toBe(1)
|
|
228
|
+
})
|
|
229
|
+
})
|
package/src/reactivity/solid.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createRoot as _createRoot,
|
|
5
5
|
createEffect as _createEffect,
|
|
6
6
|
createResource as _createResource,
|
|
7
|
+
untrack as _untrack,
|
|
7
8
|
observable as _observable,
|
|
8
9
|
batch as _batch,
|
|
9
10
|
from as _from,
|
|
@@ -23,6 +24,7 @@ const from = _from as typeof import("solid-js")["from"]
|
|
|
23
24
|
const batch = _batch as typeof import("solid-js")["batch"]
|
|
24
25
|
const getListener = _getListener as typeof import("solid-js")["getListener"]
|
|
25
26
|
const onCleanup = _onCleanup as typeof import("solid-js")["onCleanup"]
|
|
27
|
+
const untrack = _untrack as typeof import("solid-js")["untrack"]
|
|
26
28
|
|
|
27
29
|
export {
|
|
28
30
|
createSignal,
|
|
@@ -35,5 +37,6 @@ export {
|
|
|
35
37
|
batch,
|
|
36
38
|
getListener,
|
|
37
39
|
onCleanup,
|
|
40
|
+
untrack,
|
|
38
41
|
DEV,
|
|
39
42
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { createMessageBundle, createMessage } from "./createMessageBundle.js"
|
|
3
|
+
import { MessageBundle } from "./types.js"
|
|
4
|
+
import { Value } from "@sinclair/typebox/value"
|
|
5
|
+
|
|
6
|
+
describe("createMessageBundle", () => {
|
|
7
|
+
it("creates a bundle with no messages", () => {
|
|
8
|
+
const bundle: unknown = createMessageBundle({ id: "no_messages", messages: [] })
|
|
9
|
+
expect(Value.Check(MessageBundle, bundle)).toBe(true)
|
|
10
|
+
expect(bundle).toEqual({
|
|
11
|
+
id: "no_messages",
|
|
12
|
+
alias: {},
|
|
13
|
+
messages: [],
|
|
14
|
+
} satisfies MessageBundle)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it("creates a bundle with a single text-only message", () => {
|
|
18
|
+
const bundle: unknown = createMessageBundle({
|
|
19
|
+
id: "hello_world",
|
|
20
|
+
messages: [createMessage({ locale: "en", text: "Hello World!" })],
|
|
21
|
+
})
|
|
22
|
+
expect(Value.Check(MessageBundle, bundle)).toBe(true)
|
|
23
|
+
expect(bundle).toEqual({
|
|
24
|
+
id: "hello_world",
|
|
25
|
+
alias: {},
|
|
26
|
+
messages: [
|
|
27
|
+
{
|
|
28
|
+
locale: "en",
|
|
29
|
+
declarations: [],
|
|
30
|
+
selectors: [],
|
|
31
|
+
variants: [
|
|
32
|
+
{
|
|
33
|
+
match: [],
|
|
34
|
+
pattern: [
|
|
35
|
+
{
|
|
36
|
+
type: "text",
|
|
37
|
+
value: "Hello World!",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
} satisfies MessageBundle)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("creates a bundle with multiple text-only messages", () => {
|
|
48
|
+
const bundle: unknown = createMessageBundle({
|
|
49
|
+
id: "hello_world_2",
|
|
50
|
+
messages: [
|
|
51
|
+
createMessage({ locale: "en", text: "Hello World!" }),
|
|
52
|
+
createMessage({ locale: "de", text: "Hallo Welt!" }),
|
|
53
|
+
],
|
|
54
|
+
})
|
|
55
|
+
expect(Value.Check(MessageBundle, bundle)).toBe(true)
|
|
56
|
+
expect(bundle).toEqual({
|
|
57
|
+
id: "hello_world_2",
|
|
58
|
+
alias: {},
|
|
59
|
+
messages: [
|
|
60
|
+
{
|
|
61
|
+
locale: "en",
|
|
62
|
+
declarations: [],
|
|
63
|
+
selectors: [],
|
|
64
|
+
variants: [
|
|
65
|
+
{
|
|
66
|
+
match: [],
|
|
67
|
+
pattern: [
|
|
68
|
+
{
|
|
69
|
+
type: "text",
|
|
70
|
+
value: "Hello World!",
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
locale: "de",
|
|
78
|
+
declarations: [],
|
|
79
|
+
selectors: [],
|
|
80
|
+
variants: [
|
|
81
|
+
{
|
|
82
|
+
match: [],
|
|
83
|
+
pattern: [
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
value: "Hallo Welt!",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
} satisfies MessageBundle)
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { LanguageTag, MessageBundle, Message, Text } from "./types.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* create v2 MessageBundle
|
|
5
|
+
* @example createMessageBundle({
|
|
6
|
+
* id: "greeting",
|
|
7
|
+
* messages: [
|
|
8
|
+
* createMessage({locale: "en", text: "Hello world!"})
|
|
9
|
+
* createMessage({locale: "de", text: "Hallo Welt!"})
|
|
10
|
+
* ]
|
|
11
|
+
* })
|
|
12
|
+
*/
|
|
13
|
+
export function createMessageBundle(args: {
|
|
14
|
+
id: string
|
|
15
|
+
messages: Message[]
|
|
16
|
+
alias?: MessageBundle["alias"]
|
|
17
|
+
}): MessageBundle {
|
|
18
|
+
return {
|
|
19
|
+
id: args.id,
|
|
20
|
+
alias: args.alias ?? {},
|
|
21
|
+
messages: args.messages,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* create v2 Messsage AST with text-only pattern
|
|
27
|
+
* @example createMessage({locale: "en", text: "Hello world"})
|
|
28
|
+
*/
|
|
29
|
+
export function createMessage(args: { locale: LanguageTag; text: string }): Message {
|
|
30
|
+
return {
|
|
31
|
+
locale: args.locale,
|
|
32
|
+
declarations: [],
|
|
33
|
+
selectors: [],
|
|
34
|
+
variants: [{ match: [], pattern: [toTextElement(args.text ?? "")] }],
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function toTextElement(text: string): Text {
|
|
39
|
+
return {
|
|
40
|
+
type: "text",
|
|
41
|
+
value: text,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect } from "vitest"
|
|
4
|
+
import { bundle } from "./bundle.js"
|
|
5
|
+
import { MessageBundle } from "../../types.js"
|
|
6
|
+
import { Value } from "@sinclair/typebox/value"
|
|
7
|
+
|
|
8
|
+
describe("mock plural messageBundle", () => {
|
|
9
|
+
it("is valid", () => {
|
|
10
|
+
const messageBundle: unknown = bundle
|
|
11
|
+
expect(Value.Check(MessageBundle, messageBundle)).toBe(true)
|
|
12
|
+
|
|
13
|
+
expect(bundle.messages.length).toBe(2)
|
|
14
|
+
expect(bundle.messages[0]!.declarations.length).toBe(1)
|
|
15
|
+
expect(bundle.messages[0]!.selectors.length).toBe(1)
|
|
16
|
+
expect(bundle.messages[0]!.variants.length).toBe(3)
|
|
17
|
+
})
|
|
18
|
+
})
|