@inlang/sdk 0.28.2 → 0.29.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.
@@ -30,7 +30,7 @@ export const solidAdapter = (project, arg) => {
30
30
  },
31
31
  messageLintReports: {
32
32
  get: project.query.messageLintReports.get,
33
- getAll: convert(project.query.messageLintReports.getAll),
33
+ getAll: project.query.messageLintReports.getAll,
34
34
  },
35
35
  },
36
36
  };
@@ -1,9 +1,12 @@
1
1
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
2
  import { describe, it, expect } from "vitest";
3
- import { createEffect, from, createRoot } from "../reactivity/solid.js";
3
+ import { createResource, createSignal, createEffect, from, createRoot, } from "../reactivity/solid.js";
4
4
  import { solidAdapter } from "./solidAdapter.js";
5
5
  import { loadProject } from "../loadProject.js";
6
6
  import { mockRepo } from "@lix-js/client";
7
+ function sleep(ms) {
8
+ return new Promise((resolve) => setTimeout(resolve, ms));
9
+ }
7
10
  // ------------------------------------------------------------------------------------------------
8
11
  const config = {
9
12
  sourceLanguageTag: "en",
@@ -265,50 +268,32 @@ describe("messages", () => {
265
268
  });
266
269
  });
267
270
  describe("lint", () => {
268
- it.todo("should react to changes in config", async () => {
269
- await createRoot(async () => {
270
- const repo = await mockRepo();
271
- const fs = repo.nodeishFs;
272
- await fs.mkdir("./project.inlang", { recursive: true });
273
- await fs.writeFile("./project.inlang/settings.json", JSON.stringify(config));
274
- const project = solidAdapter(await loadProject({
275
- projectPath: "./project.inlang",
276
- repo,
277
- _import: $import,
278
- }), { from });
279
- let counter = 0;
280
- createEffect(() => {
281
- project.query.messageLintReports.getAll();
282
- counter += 1;
283
- });
284
- const newConfig = { ...project.settings(), languageTags: ["en", "de"] };
285
- project.setSettings(newConfig);
286
- expect(counter).toBe(1);
287
- expect(project.query.messageLintReports.getAll()).toEqual([]);
288
- await new Promise((resolve) => setTimeout(resolve, 510));
289
- const newConfig2 = { ...project.settings(), languageTags: ["en", "de", "fr"] };
290
- project.setSettings(newConfig2);
291
- expect(counter).toBe(9);
292
- expect(project.query.messageLintReports.getAll()).toEqual([]);
293
- });
294
- });
295
271
  it.todo("should react to changes to packages");
296
272
  it.todo("should react to changes to modules");
297
- it.todo("should react to changes to messages", async () => {
273
+ it("should react to changes to messages", async () => {
298
274
  await createRoot(async () => {
299
275
  const repo = await mockRepo();
300
276
  const fs = repo.nodeishFs;
301
- await fs.writeFile("./project.config.json", JSON.stringify(config));
277
+ await fs.mkdir("/user/project.inlang", { recursive: true });
278
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(config));
302
279
  const project = solidAdapter(await loadProject({
303
- projectPath: "./project.config.json",
280
+ projectPath: "/user/project.inlang",
304
281
  repo,
305
282
  _import: $import,
306
283
  }), { from });
284
+ const [messages, setMessages] = createSignal();
307
285
  let counter = 0;
308
286
  createEffect(() => {
309
- project.query.messageLintReports.getAll();
287
+ setMessages(project.query.messages.getAll());
288
+ });
289
+ const [lintReports] = createResource(messages, async () => {
290
+ const reports = await project.query.messageLintReports.getAll();
310
291
  counter += 1;
292
+ return reports;
311
293
  });
294
+ await sleep(10);
295
+ expect(counter).toBe(1);
296
+ expect(lintReports()).toStrictEqual([]);
312
297
  project.query.messages.update({
313
298
  where: { id: "a" },
314
299
  data: {
@@ -316,9 +301,9 @@ describe("lint", () => {
316
301
  variants: [{ languageTag: "en", match: [], pattern: [{ type: "Text", value: "new" }] }],
317
302
  },
318
303
  });
319
- expect(counter).toBe(1);
320
- expect(project.query.messageLintReports.getAll()).toEqual([]);
321
- await new Promise((resolve) => setTimeout(resolve, 510));
304
+ await sleep(10);
305
+ expect(counter).toBe(2);
306
+ expect(lintReports()).toStrictEqual([]);
322
307
  project.query.messages.update({
323
308
  where: { id: "a" },
324
309
  data: {
@@ -326,8 +311,9 @@ describe("lint", () => {
326
311
  variants: [{ languageTag: "en", match: [], pattern: [{ type: "Text", value: "new" }] }],
327
312
  },
328
313
  });
329
- expect(counter).toBe(6);
330
- expect(project.query.messageLintReports.getAll()).toEqual([]);
314
+ await sleep(10);
315
+ expect(counter).toBe(3);
316
+ expect(lintReports()).toStrictEqual([]);
331
317
  });
332
318
  });
333
319
  });
package/dist/api.d.ts CHANGED
@@ -85,17 +85,11 @@ export type MessageQueryApi = {
85
85
  }) => boolean;
86
86
  };
87
87
  export type MessageLintReportsQueryApi = {
88
- getAll: Subscribable<MessageLintReport[]>;
89
- get: ((args: {
88
+ getAll: () => Promise<MessageLintReport[]>;
89
+ get: (args: {
90
90
  where: {
91
91
  messageId: MessageLintReport["messageId"];
92
92
  };
93
- }) => Readonly<MessageLintReport[]>) & {
94
- subscribe: (args: {
95
- where: {
96
- messageId: MessageLintReport["messageId"];
97
- };
98
- }, callback: (MessageLintRules: Readonly<MessageLintReport[]>) => void) => void;
99
- };
93
+ }) => Promise<Readonly<MessageLintReport[]>>;
100
94
  };
101
95
  //# sourceMappingURL=api.d.ts.map
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"createMessageLintReportsQuery.d.ts","sourceRoot":"","sources":["../src/createMessageLintReportsQuery.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EACb,wBAAwB,EAExB,eAAe,EACf,MAAM,UAAU,CAAA;AACjB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAMhE;;GAEG;AACH,wBAAgB,6BAA6B,CAC5C,aAAa,EAAE,eAAe,EAC9B,QAAQ,EAAE,MAAM,eAAe,EAC/B,yBAAyB,EAAE,MAAM,KAAK,CAAC,wBAAwB,CAAC,EAChE,eAAe,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,GAAG,SAAS,GAC3E,aAAa,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,CAyF9C"}
1
+ {"version":3,"file":"createMessageLintReportsQuery.d.ts","sourceRoot":"","sources":["../src/createMessageLintReportsQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,aAAa,EACb,wBAAwB,EAExB,eAAe,EACf,MAAM,UAAU,CAAA;AACjB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAahE;;GAEG;AACH,wBAAgB,6BAA6B,CAC5C,aAAa,EAAE,eAAe,EAC9B,QAAQ,EAAE,MAAM,eAAe,EAC/B,yBAAyB,EAAE,MAAM,KAAK,CAAC,wBAAwB,CAAC,EAChE,eAAe,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,GAAG,SAAS,GAC3E,aAAa,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,CA+F9C"}
@@ -1,13 +1,16 @@
1
- import { createSubscribable } from "./loadProject.js";
2
1
  import { lintSingleMessage } from "./lint/index.js";
3
- import { ReactiveMap } from "./reactivity/map.js";
4
2
  import { createRoot, createEffect } from "./reactivity/solid.js";
3
+ import { throttle } from "throttle-debounce";
4
+ import _debug from "debug";
5
+ const debug = _debug("sdk:lintReports");
6
+ function sleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
5
9
  /**
6
10
  * Creates a reactive query API for messages.
7
11
  */
8
12
  export function createMessageLintReportsQuery(messagesQuery, settings, installedMessageLintRules, resolvedModules) {
9
- // @ts-expect-error
10
- const index = new ReactiveMap();
13
+ const index = new Map();
11
14
  const modules = resolvedModules();
12
15
  const rulesArray = modules?.messageLintRules;
13
16
  const messageLintRuleLevels = Object.fromEntries(installedMessageLintRules().map((rule) => [rule.id, rule.level]));
@@ -19,6 +22,12 @@ export function createMessageLintReportsQuery(messagesQuery, settings, installed
19
22
  };
20
23
  const messages = messagesQuery.getAll();
21
24
  const trackedMessages = new Map();
25
+ debug(`createMessageLintReportsQuery ${rulesArray?.length} rules, ${messages.length} messages`);
26
+ // TODO: don't throttle when no debug
27
+ let lintMessageCount = 0;
28
+ const throttledLogLintMessage = throttle(2000, (messageId) => {
29
+ debug(`lintSingleMessage: ${lintMessageCount} id: ${messageId}`);
30
+ });
22
31
  createEffect(() => {
23
32
  const currentMessageIds = new Set(messagesQuery.includedMessageIds());
24
33
  const deletedTrackedMessages = [...trackedMessages].filter((tracked) => !currentMessageIds.has(tracked[0]));
@@ -41,7 +50,10 @@ export function createMessageLintReportsQuery(messagesQuery, settings, installed
41
50
  messages: messages,
42
51
  message: message,
43
52
  }).then((report) => {
53
+ lintMessageCount++;
54
+ throttledLogLintMessage(messageId);
44
55
  if (report.errors.length === 0 && index.get(messageId) !== report.data) {
56
+ // console.log("lintSingleMessage", messageId, report.data.length)
45
57
  index.set(messageId, report.data);
46
58
  }
47
59
  });
@@ -58,19 +70,19 @@ export function createMessageLintReportsQuery(messagesQuery, settings, installed
58
70
  trackedMessages.delete(deletedMessageId);
59
71
  // remove lint report result
60
72
  index.delete(deletedMessageId);
73
+ debug(`delete lint message id: ${deletedMessageId}`);
61
74
  }
62
75
  }
63
76
  }
64
77
  });
65
- const get = (args) => {
66
- return structuredClone(index.get(args.where.messageId));
67
- };
68
78
  return {
69
- getAll: createSubscribable(() => {
79
+ getAll: async () => {
80
+ await sleep(0); // evaluate on next tick to allow for out-of-order effects
70
81
  return structuredClone([...index.values()].flat().length === 0 ? [] : [...index.values()].flat());
71
- }),
72
- get: Object.assign(get, {
73
- subscribe: (args, callback) => createSubscribable(() => get(args)).subscribe(callback),
74
- }),
82
+ },
83
+ get: async (args) => {
84
+ await sleep(0); // evaluate on next tick to allow for out-of-order effects
85
+ return structuredClone(index.get(args.where.messageId) ?? []);
86
+ },
75
87
  };
76
88
  }
@@ -628,7 +628,7 @@ async function saveMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuer
628
628
  });
629
629
  }
630
630
  }
631
- const maxRetries = 5;
631
+ const maxRetries = 10;
632
632
  const nProbes = 50;
633
633
  const probeInterval = 100;
634
634
  async function acquireFileLock(fs, lockDirPath, lockOrigin, tryCount = 0) {
@@ -515,8 +515,8 @@ describe("functionality", () => {
515
515
  _import,
516
516
  });
517
517
  await new Promise((resolve) => setTimeout(resolve, 510));
518
- expect(project.query.messageLintReports.getAll()).toHaveLength(1);
519
- expect(project.query.messageLintReports.getAll()?.[0]?.ruleId).toBe(_mockLintRule.id);
518
+ expect(await project.query.messageLintReports.getAll()).toHaveLength(1);
519
+ expect((await project.query.messageLintReports.getAll())?.[0]?.ruleId).toBe(_mockLintRule.id);
520
520
  expect(project.installed.messageLintRules()).toHaveLength(1);
521
521
  });
522
522
  it("should return lint reports for a single message", async () => {
@@ -558,7 +558,7 @@ describe("functionality", () => {
558
558
  _import,
559
559
  });
560
560
  await new Promise((resolve) => setTimeout(resolve, 510));
561
- expect(project.query.messageLintReports.get({ where: { messageId: "some-message" } })).toHaveLength(1);
561
+ expect(await project.query.messageLintReports.get({ where: { messageId: "some-message" } })).toHaveLength(1);
562
562
  });
563
563
  });
564
564
  describe("errors", () => {
@@ -852,7 +852,8 @@ describe("functionality", () => {
852
852
  });
853
853
  // TODO: test with real lint rules
854
854
  try {
855
- project.query.messageLintReports.getAll.subscribe((r) => expect(r).toEqual(undefined));
855
+ const r = await project.query.messageLintReports.getAll();
856
+ expect(r).toEqual(undefined);
856
857
  throw new Error("Should not reach this");
857
858
  }
858
859
  catch (e) {
@@ -877,7 +878,8 @@ describe("functionality", () => {
877
878
  }),
878
879
  });
879
880
  // TODO: test with real lint rules
880
- project.query.messageLintReports.getAll.subscribe((r) => expect(r).toEqual([]));
881
+ const r = await project.query.messageLintReports.getAll();
882
+ expect(r).toEqual([]);
881
883
  });
882
884
  });
883
885
  describe("watcher", () => {
@@ -3,10 +3,11 @@ declare const createSignal: typeof import("solid-js").createSignal;
3
3
  declare const createMemo: typeof import("solid-js").createMemo;
4
4
  declare const createRoot: typeof import("solid-js").createRoot;
5
5
  declare const createEffect: typeof import("solid-js").createEffect;
6
+ declare const createResource: typeof import("solid-js").createResource;
6
7
  declare const observable: typeof import("solid-js").observable;
7
8
  declare const from: typeof import("solid-js").from;
8
9
  declare const batch: typeof import("solid-js").batch;
9
10
  declare const getListener: typeof import("solid-js").getListener;
10
11
  declare const onCleanup: typeof import("solid-js").onCleanup;
11
- export { createSignal, createMemo, createRoot, createEffect, observable, from, batch, getListener, onCleanup, DEV, };
12
+ export { createSignal, createMemo, createRoot, createEffect, createResource, observable, from, batch, getListener, onCleanup, DEV, };
12
13
  //# sourceMappingURL=solid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"solid.d.ts","sourceRoot":"","sources":["../../src/reactivity/solid.ts"],"names":[],"mappings":"AAAA,OAAO,EAUN,GAAG,EAEH,MAAM,wBAAwB,CAAA;AAE/B,QAAA,MAAM,YAAY,wCAA6D,CAAA;AAC/E,QAAA,MAAM,UAAU,sCAAyD,CAAA;AACzE,QAAA,MAAM,UAAU,sCAAyD,CAAA;AACzE,QAAA,MAAM,YAAY,wCAA6D,CAAA;AAC/E,QAAA,MAAM,UAAU,sCAAyD,CAAA;AACzE,QAAA,MAAM,IAAI,gCAA6C,CAAA;AACvD,QAAA,MAAM,KAAK,iCAA+C,CAAA;AAC1D,QAAA,MAAM,WAAW,uCAA2D,CAAA;AAC5E,QAAA,MAAM,SAAS,qCAAuD,CAAA;AAEtE,OAAO,EACN,YAAY,EACZ,UAAU,EACV,UAAU,EACV,YAAY,EACZ,UAAU,EACV,IAAI,EACJ,KAAK,EACL,WAAW,EACX,SAAS,EACT,GAAG,GACH,CAAA"}
1
+ {"version":3,"file":"solid.d.ts","sourceRoot":"","sources":["../../src/reactivity/solid.ts"],"names":[],"mappings":"AAAA,OAAO,EAWN,GAAG,EAEH,MAAM,wBAAwB,CAAA;AAE/B,QAAA,MAAM,YAAY,wCAA6D,CAAA;AAC/E,QAAA,MAAM,UAAU,sCAAyD,CAAA;AACzE,QAAA,MAAM,UAAU,sCAAyD,CAAA;AACzE,QAAA,MAAM,YAAY,wCAA6D,CAAA;AAC/E,QAAA,MAAM,cAAc,0CAAiE,CAAA;AACrF,QAAA,MAAM,UAAU,sCAAyD,CAAA;AACzE,QAAA,MAAM,IAAI,gCAA6C,CAAA;AACvD,QAAA,MAAM,KAAK,iCAA+C,CAAA;AAC1D,QAAA,MAAM,WAAW,uCAA2D,CAAA;AAC5E,QAAA,MAAM,SAAS,qCAAuD,CAAA;AAEtE,OAAO,EACN,YAAY,EACZ,UAAU,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,UAAU,EACV,IAAI,EACJ,KAAK,EACL,WAAW,EACX,SAAS,EACT,GAAG,GACH,CAAA"}
@@ -1,13 +1,14 @@
1
- import { createSignal as _createSignal, createMemo as _createMemo, createRoot as _createRoot, createEffect as _createEffect, observable as _observable, batch as _batch, from as _from, getListener as _getListener, onCleanup as _onCleanup, DEV,
1
+ import { createSignal as _createSignal, createMemo as _createMemo, createRoot as _createRoot, createEffect as _createEffect, createResource as _createResource, observable as _observable, batch as _batch, from as _from, getListener as _getListener, onCleanup as _onCleanup, DEV,
2
2
  // @ts-ignore
3
3
  } from "solid-js/dist/solid.js";
4
4
  const createSignal = _createSignal;
5
5
  const createMemo = _createMemo;
6
6
  const createRoot = _createRoot;
7
7
  const createEffect = _createEffect;
8
+ const createResource = _createResource;
8
9
  const observable = _observable;
9
10
  const from = _from;
10
11
  const batch = _batch;
11
12
  const getListener = _getListener;
12
13
  const onCleanup = _onCleanup;
13
- export { createSignal, createMemo, createRoot, createEffect, observable, from, batch, getListener, onCleanup, DEV, };
14
+ export { createSignal, createMemo, createRoot, createEffect, createResource, observable, from, batch, getListener, onCleanup, DEV, };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=solid.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solid.test.d.ts","sourceRoot":"","sources":["../../src/reactivity/solid.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,157 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { createRoot, createSignal, createEffect, createMemo, createResource } from "./solid.js";
3
+ function sleep(ms) {
4
+ return new Promise((resolve) => setTimeout(resolve, ms));
5
+ }
6
+ function delay(v, ms) {
7
+ return new Promise((resolve) => setTimeout(() => resolve(v), ms));
8
+ }
9
+ describe("vitest", () => {
10
+ it("waits for the result of an async function", async () => {
11
+ const rval = await delay(42, 1);
12
+ expect(rval).toBe(42);
13
+ });
14
+ });
15
+ describe("solid", () => {
16
+ it("await createRoot returns the result the async function", async () => {
17
+ let count = 0;
18
+ await sleep(0);
19
+ const rval = await createRoot(async () => {
20
+ await sleep(0);
21
+ count++;
22
+ return await delay(42, 1);
23
+ });
24
+ expect(rval).toBe(42);
25
+ expect(count).toBe(1);
26
+ });
27
+ it("sets and gets", () => {
28
+ const [get, set] = createSignal(0);
29
+ expect(get()).toBe(0);
30
+ set(1);
31
+ expect(get()).toBe(1);
32
+ });
33
+ it("runs an effect whenn the signal value change", () => {
34
+ const [get, set] = createSignal(0);
35
+ let count = 0;
36
+ createEffect(() => {
37
+ count++;
38
+ get();
39
+ });
40
+ expect(count).toBe(1);
41
+ set(1);
42
+ expect(count).toBe(2);
43
+ set(1);
44
+ expect(count).toBe(2);
45
+ set(0);
46
+ expect(count).toBe(3);
47
+ });
48
+ it("runs an effect on a derived signal, when the underlying signal changes", () => {
49
+ const [get, set] = createSignal(0);
50
+ const derived = () => get() * 2;
51
+ let count = 0;
52
+ createEffect(() => {
53
+ count++;
54
+ derived();
55
+ });
56
+ expect(count).toBe(1);
57
+ set(1);
58
+ expect(count).toBe(2);
59
+ set(1);
60
+ expect(count).toBe(2);
61
+ set(0);
62
+ expect(count).toBe(3);
63
+ });
64
+ it("signal values are not deep diffed for equality", () => {
65
+ const [get, set] = createSignal({ a: 0, b: [1, 2, 3], c: { d: 4 } });
66
+ let count = 0;
67
+ createEffect(() => {
68
+ count++;
69
+ get();
70
+ });
71
+ expect(count).toBe(1);
72
+ set({ a: 0, b: [1, 2, 3], c: { d: 5 } });
73
+ expect(count).toBe(2);
74
+ set({ a: 0, b: [1, 2, 3], c: { d: 5 } });
75
+ expect(count).toBe(3);
76
+ set({ a: 0, b: [1, 2, 3], c: { d: 4 } });
77
+ expect(count).toBe(4);
78
+ });
79
+ it("await in effect prevents signal from being tracked", async () => {
80
+ const [get, set] = createSignal(0);
81
+ let count = 0;
82
+ // effect bumps count - depends on get() - never gets called again
83
+ createEffect(async () => {
84
+ count++;
85
+ await sleep(0);
86
+ get();
87
+ });
88
+ expect(count).toBe(1);
89
+ set(1);
90
+ expect(count).toBe(1);
91
+ await sleep(0);
92
+ set(2);
93
+ expect(count).toBe(1);
94
+ });
95
+ it("signal behind function call is tracked - same as derived", async () => {
96
+ const [get, set] = createSignal(0);
97
+ let count = 0;
98
+ const fn = () => get();
99
+ createEffect(async () => {
100
+ count++;
101
+ fn();
102
+ });
103
+ expect(count).toBe(1);
104
+ set(1);
105
+ expect(count).toBe(2);
106
+ });
107
+ it("Resource async fetch is triggered by signals, and works in effects", async () => {
108
+ const [get, set] = createSignal(0);
109
+ const [resource] = createResource(get, async (v) => {
110
+ return (await delay(v, 10));
111
+ });
112
+ let count = 0;
113
+ createEffect(() => {
114
+ count++;
115
+ resource();
116
+ });
117
+ expect(resource.loading).toBe(true);
118
+ expect(resource()).toBe(undefined);
119
+ expect(count).toBe(1);
120
+ await sleep(20);
121
+ expect(resource.loading).toBe(false);
122
+ expect(resource()).toBe(0);
123
+ expect(count).toBe(2);
124
+ set(42);
125
+ expect(resource.loading).toBe(true);
126
+ expect(resource()).toBe(0);
127
+ expect(count).toBe(2);
128
+ await sleep(20);
129
+ expect(resource.loading).toBe(false);
130
+ expect(resource()).toBe(42);
131
+ expect(count).toBe(3);
132
+ });
133
+ it("memoizes values and updates them when signals change", () => {
134
+ const [get, set] = createSignal(0);
135
+ let count = 0;
136
+ const memo = createMemo(() => {
137
+ count++;
138
+ return `memo = ${2 * get()}`;
139
+ });
140
+ expect(count).toBe(1);
141
+ expect(memo()).toBe("memo = 0");
142
+ expect(count).toBe(1);
143
+ expect(memo()).toBe("memo = 0");
144
+ expect(count).toBe(1);
145
+ set(1);
146
+ expect(count).toBe(2);
147
+ expect(memo()).toBe("memo = 2");
148
+ set(1);
149
+ expect(count).toBe(2);
150
+ expect(memo()).toBe("memo = 2");
151
+ expect(count).toBe(2);
152
+ expect(memo()).toBe("memo = 2");
153
+ set(1000);
154
+ expect(count).toBe(3);
155
+ expect(memo()).toBe("memo = 2000");
156
+ });
157
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inlang/sdk",
3
3
  "type": "module",
4
- "version": "0.28.2",
4
+ "version": "0.29.0",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -33,16 +33,16 @@
33
33
  "solid-js": "1.6.12",
34
34
  "throttle-debounce": "^5.0.0",
35
35
  "@inlang/json-types": "1.1.0",
36
- "@inlang/message": "2.1.0",
37
36
  "@inlang/language-tag": "1.5.1",
37
+ "@inlang/message": "2.1.0",
38
38
  "@inlang/message-lint-rule": "1.4.5",
39
- "@inlang/plugin": "2.4.9",
40
- "@inlang/project-settings": "2.4.0",
41
39
  "@inlang/module": "1.2.9",
42
- "@inlang/translatable": "1.3.1",
40
+ "@inlang/project-settings": "2.4.0",
41
+ "@inlang/plugin": "2.4.9",
43
42
  "@inlang/result": "1.1.0",
44
- "@lix-js/client": "1.1.0",
45
- "@lix-js/fs": "1.0.0"
43
+ "@lix-js/fs": "1.0.0",
44
+ "@lix-js/client": "1.2.0",
45
+ "@inlang/translatable": "1.3.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/debug": "^4.1.12",
@@ -1,7 +1,13 @@
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 { createEffect, from, createRoot } from "../reactivity/solid.js"
4
+ import {
5
+ createResource,
6
+ createSignal,
7
+ createEffect,
8
+ from,
9
+ createRoot,
10
+ } from "../reactivity/solid.js"
5
11
  import { solidAdapter } from "./solidAdapter.js"
6
12
  import { loadProject } from "../loadProject.js"
7
13
  import { mockRepo } from "@lix-js/client"
@@ -13,6 +19,10 @@ import type {
13
19
  Text,
14
20
  } from "../versionedInterfaces.js"
15
21
 
22
+ function sleep(ms: number) {
23
+ return new Promise((resolve) => setTimeout(resolve, ms))
24
+ }
25
+
16
26
  // ------------------------------------------------------------------------------------------------
17
27
 
18
28
  const config: ProjectSettings = {
@@ -353,66 +363,42 @@ describe("messages", () => {
353
363
  })
354
364
 
355
365
  describe("lint", () => {
356
- it.todo("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("./project.inlang", { recursive: true })
361
- await fs.writeFile("./project.inlang/settings.json", JSON.stringify(config))
362
- const project = solidAdapter(
363
- await loadProject({
364
- projectPath: "./project.inlang",
365
- repo,
366
- _import: $import,
367
- }),
368
- { from }
369
- )
370
-
371
- let counter = 0
372
- createEffect(() => {
373
- project.query.messageLintReports.getAll()
374
- counter += 1
375
- })
376
-
377
- const newConfig = { ...project.settings()!, languageTags: ["en", "de"] }
378
- project.setSettings(newConfig)
379
-
380
- expect(counter).toBe(1)
381
- expect(project.query.messageLintReports.getAll()).toEqual([])
382
-
383
- await new Promise((resolve) => setTimeout(resolve, 510))
384
-
385
- const newConfig2 = { ...project.settings()!, languageTags: ["en", "de", "fr"] }
386
- project.setSettings(newConfig2)
387
-
388
- expect(counter).toBe(9)
389
- expect(project.query.messageLintReports.getAll()).toEqual([])
390
- })
391
- })
392
-
393
366
  it.todo("should react to changes to packages")
394
367
  it.todo("should react to changes to modules")
395
368
 
396
- it.todo("should react to changes to messages", async () => {
369
+ it("should react to changes to messages", async () => {
397
370
  await createRoot(async () => {
398
371
  const repo = await mockRepo()
399
372
  const fs = repo.nodeishFs
400
- await fs.writeFile("./project.config.json", JSON.stringify(config))
373
+ await fs.mkdir("/user/project.inlang", { recursive: true })
374
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify(config))
401
375
  const project = solidAdapter(
402
376
  await loadProject({
403
- projectPath: "./project.config.json",
377
+ projectPath: "/user/project.inlang",
404
378
  repo,
405
379
  _import: $import,
406
380
  }),
407
381
  { from }
408
382
  )
409
383
 
384
+ const [messages, setMessages] = createSignal<readonly Message[]>()
385
+
410
386
  let counter = 0
387
+
411
388
  createEffect(() => {
412
- project.query.messageLintReports.getAll()
389
+ setMessages(project.query.messages.getAll())
390
+ })
391
+
392
+ const [lintReports] = createResource(messages, async () => {
393
+ const reports = await project.query.messageLintReports.getAll()
413
394
  counter += 1
395
+ return reports
414
396
  })
415
397
 
398
+ await sleep(10)
399
+ expect(counter).toBe(1)
400
+ expect(lintReports()).toStrictEqual([])
401
+
416
402
  project.query.messages.update({
417
403
  where: { id: "a" },
418
404
  data: {
@@ -421,10 +407,9 @@ describe("lint", () => {
421
407
  },
422
408
  })
423
409
 
424
- expect(counter).toBe(1)
425
- expect(project.query.messageLintReports.getAll()).toEqual([])
426
-
427
- await new Promise((resolve) => setTimeout(resolve, 510))
410
+ await sleep(10)
411
+ expect(counter).toBe(2)
412
+ expect(lintReports()).toStrictEqual([])
428
413
 
429
414
  project.query.messages.update({
430
415
  where: { id: "a" },
@@ -434,8 +419,9 @@ describe("lint", () => {
434
419
  },
435
420
  })
436
421
 
437
- expect(counter).toBe(6)
438
- expect(project.query.messageLintReports.getAll()).toEqual([])
422
+ await sleep(10)
423
+ expect(counter).toBe(3)
424
+ expect(lintReports()).toStrictEqual([])
439
425
  })
440
426
  })
441
427
  })
@@ -39,7 +39,7 @@ export const solidAdapter = (
39
39
  },
40
40
  messageLintReports: {
41
41
  get: project.query.messageLintReports.get,
42
- getAll: convert(project.query.messageLintReports.getAll),
42
+ getAll: project.query.messageLintReports.getAll,
43
43
  },
44
44
  },
45
45
  } satisfies InlangProjectWithSolidAdapter
package/src/api.ts CHANGED
@@ -89,13 +89,8 @@ export type MessageQueryApi = {
89
89
  }
90
90
 
91
91
  export type MessageLintReportsQueryApi = {
92
- getAll: Subscribable<MessageLintReport[]>
93
- get: ((args: {
92
+ getAll: () => Promise<MessageLintReport[]>
93
+ get: (args: {
94
94
  where: { messageId: MessageLintReport["messageId"] }
95
- }) => Readonly<MessageLintReport[]>) & {
96
- subscribe: (
97
- args: { where: { messageId: MessageLintReport["messageId"] } },
98
- callback: (MessageLintRules: Readonly<MessageLintReport[]>) => void
99
- ) => void
100
- }
95
+ }) => Promise<Readonly<MessageLintReport[]>>
101
96
  }
@@ -1,4 +1,3 @@
1
- import { createSubscribable } from "./loadProject.js"
2
1
  import type {
3
2
  InlangProject,
4
3
  InstalledMessageLintRule,
@@ -9,9 +8,16 @@ import type { ProjectSettings } from "@inlang/project-settings"
9
8
  import type { resolveModules } from "./resolve-modules/index.js"
10
9
  import type { MessageLintReport, Message } from "./versionedInterfaces.js"
11
10
  import { lintSingleMessage } from "./lint/index.js"
12
- import { ReactiveMap } from "./reactivity/map.js"
13
11
  import { createRoot, createEffect } from "./reactivity/solid.js"
14
12
 
13
+ import { throttle } from "throttle-debounce"
14
+ import _debug from "debug"
15
+ const debug = _debug("sdk:lintReports")
16
+
17
+ function sleep(ms: number) {
18
+ return new Promise((resolve) => setTimeout(resolve, ms))
19
+ }
20
+
15
21
  /**
16
22
  * Creates a reactive query API for messages.
17
23
  */
@@ -21,8 +27,7 @@ export function createMessageLintReportsQuery(
21
27
  installedMessageLintRules: () => Array<InstalledMessageLintRule>,
22
28
  resolvedModules: () => Awaited<ReturnType<typeof resolveModules>> | undefined
23
29
  ): InlangProject["query"]["messageLintReports"] {
24
- // @ts-expect-error
25
- const index = new ReactiveMap<MessageLintReport["messageId"], MessageLintReport[]>()
30
+ const index = new Map<MessageLintReport["messageId"], MessageLintReport[]>()
26
31
 
27
32
  const modules = resolvedModules()
28
33
 
@@ -41,6 +46,14 @@ export function createMessageLintReportsQuery(
41
46
 
42
47
  const trackedMessages: Map<string, () => void> = new Map()
43
48
 
49
+ debug(`createMessageLintReportsQuery ${rulesArray?.length} rules, ${messages.length} messages`)
50
+
51
+ // TODO: don't throttle when no debug
52
+ let lintMessageCount = 0
53
+ const throttledLogLintMessage = throttle(2000, (messageId) => {
54
+ debug(`lintSingleMessage: ${lintMessageCount} id: ${messageId}`)
55
+ })
56
+
44
57
  createEffect(() => {
45
58
  const currentMessageIds = new Set(messagesQuery.includedMessageIds())
46
59
 
@@ -68,7 +81,10 @@ export function createMessageLintReportsQuery(
68
81
  messages: messages,
69
82
  message: message,
70
83
  }).then((report) => {
84
+ lintMessageCount++
85
+ throttledLogLintMessage(messageId)
71
86
  if (report.errors.length === 0 && index.get(messageId) !== report.data) {
87
+ // console.log("lintSingleMessage", messageId, report.data.length)
72
88
  index.set(messageId, report.data)
73
89
  }
74
90
  })
@@ -87,26 +103,22 @@ export function createMessageLintReportsQuery(
87
103
  trackedMessages.delete(deletedMessageId)
88
104
  // remove lint report result
89
105
  index.delete(deletedMessageId)
106
+ debug(`delete lint message id: ${deletedMessageId}`)
90
107
  }
91
108
  }
92
109
  }
93
110
  })
94
111
 
95
- const get = (args: Parameters<MessageLintReportsQueryApi["get"]>[0]) => {
96
- return structuredClone(index.get(args.where.messageId))
97
- }
98
-
99
112
  return {
100
- getAll: createSubscribable(() => {
113
+ getAll: async () => {
114
+ await sleep(0) // evaluate on next tick to allow for out-of-order effects
101
115
  return structuredClone(
102
116
  [...index.values()].flat().length === 0 ? [] : [...index.values()].flat()
103
117
  )
104
- }),
105
- get: Object.assign(get, {
106
- subscribe: (
107
- args: Parameters<MessageLintReportsQueryApi["get"]["subscribe"]>[0],
108
- callback: Parameters<MessageLintReportsQueryApi["get"]["subscribe"]>[1]
109
- ) => createSubscribable(() => get(args)).subscribe(callback),
110
- }) as any,
118
+ },
119
+ get: async (args: Parameters<MessageLintReportsQueryApi["get"]>[0]) => {
120
+ await sleep(0) // evaluate on next tick to allow for out-of-order effects
121
+ return structuredClone(index.get(args.where.messageId) ?? [])
122
+ },
111
123
  }
112
124
  }
@@ -634,8 +634,8 @@ describe("functionality", () => {
634
634
 
635
635
  await new Promise((resolve) => setTimeout(resolve, 510))
636
636
 
637
- expect(project.query.messageLintReports.getAll()).toHaveLength(1)
638
- expect(project.query.messageLintReports.getAll()?.[0]?.ruleId).toBe(_mockLintRule.id)
637
+ expect(await project.query.messageLintReports.getAll()).toHaveLength(1)
638
+ expect((await project.query.messageLintReports.getAll())?.[0]?.ruleId).toBe(_mockLintRule.id)
639
639
  expect(project.installed.messageLintRules()).toHaveLength(1)
640
640
  })
641
641
 
@@ -687,7 +687,7 @@ describe("functionality", () => {
687
687
  await new Promise((resolve) => setTimeout(resolve, 510))
688
688
 
689
689
  expect(
690
- project.query.messageLintReports.get({ where: { messageId: "some-message" } })
690
+ await project.query.messageLintReports.get({ where: { messageId: "some-message" } })
691
691
  ).toHaveLength(1)
692
692
  })
693
693
  })
@@ -1025,7 +1025,8 @@ describe("functionality", () => {
1025
1025
  })
1026
1026
  // TODO: test with real lint rules
1027
1027
  try {
1028
- project.query.messageLintReports.getAll.subscribe((r) => expect(r).toEqual(undefined))
1028
+ const r = await project.query.messageLintReports.getAll()
1029
+ expect(r).toEqual(undefined)
1029
1030
  throw new Error("Should not reach this")
1030
1031
  } catch (e) {
1031
1032
  expect((e as Error).message).toBe("lint not initialized yet")
@@ -1049,7 +1050,8 @@ describe("functionality", () => {
1049
1050
  }),
1050
1051
  })
1051
1052
  // TODO: test with real lint rules
1052
- project.query.messageLintReports.getAll.subscribe((r) => expect(r).toEqual([]))
1053
+ const r = await project.query.messageLintReports.getAll()
1054
+ expect(r).toEqual([])
1053
1055
  })
1054
1056
  })
1055
1057
 
@@ -37,7 +37,7 @@ import { maybeCreateFirstProjectId } from "./migrations/maybeCreateFirstProjectI
37
37
 
38
38
  import { capture } from "./telemetry/capture.js"
39
39
  import { identifyProject } from "./telemetry/groupIdentify.js"
40
- import type { NodeishStats } from "../../../../lix/source-code/fs/dist/NodeishFilesystemApi.js"
40
+ import type { NodeishStats } from "@lix-js/fs"
41
41
 
42
42
  import _debug from "debug"
43
43
  const debug = _debug("loadProject")
@@ -901,7 +901,7 @@ async function saveMessagesViaPlugin(
901
901
  }
902
902
  }
903
903
 
904
- const maxRetries = 5
904
+ const maxRetries = 10
905
905
  const nProbes = 50
906
906
  const probeInterval = 100
907
907
  async function acquireFileLock(
@@ -0,0 +1,176 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { createRoot, createSignal, createEffect, createMemo, createResource } from "./solid.js"
3
+
4
+ function sleep(ms: number) {
5
+ return new Promise((resolve) => setTimeout(resolve, ms))
6
+ }
7
+
8
+ function delay(v: unknown, ms: number) {
9
+ return new Promise((resolve) => setTimeout(() => resolve(v), ms))
10
+ }
11
+
12
+ describe("vitest", () => {
13
+ it("waits for the result of an async function", async () => {
14
+ const rval = await delay(42, 1)
15
+ expect(rval).toBe(42)
16
+ })
17
+ })
18
+
19
+ describe("solid", () => {
20
+ it("await createRoot returns the result the async function", async () => {
21
+ let count = 0
22
+ await sleep(0)
23
+ const rval = await createRoot(async () => {
24
+ await sleep(0)
25
+ count++
26
+ return await delay(42, 1)
27
+ })
28
+ expect(rval).toBe(42)
29
+ expect(count).toBe(1)
30
+ })
31
+
32
+ it("sets and gets", () => {
33
+ const [get, set] = createSignal(0)
34
+
35
+ expect(get()).toBe(0)
36
+ set(1)
37
+ expect(get()).toBe(1)
38
+ })
39
+
40
+ it("runs an effect whenn the signal value change", () => {
41
+ const [get, set] = createSignal(0)
42
+ let count = 0
43
+ createEffect(() => {
44
+ count++
45
+ get()
46
+ })
47
+ expect(count).toBe(1)
48
+ set(1)
49
+ expect(count).toBe(2)
50
+ set(1)
51
+ expect(count).toBe(2)
52
+ set(0)
53
+ expect(count).toBe(3)
54
+ })
55
+
56
+ it("runs an effect on a derived signal, when the underlying signal changes", () => {
57
+ const [get, set] = createSignal(0)
58
+ const derived = () => get() * 2
59
+ let count = 0
60
+ createEffect(() => {
61
+ count++
62
+ derived()
63
+ })
64
+ expect(count).toBe(1)
65
+ set(1)
66
+ expect(count).toBe(2)
67
+ set(1)
68
+ expect(count).toBe(2)
69
+ set(0)
70
+ expect(count).toBe(3)
71
+ })
72
+
73
+ it("signal values are not deep diffed for equality", () => {
74
+ const [get, set] = createSignal({ a: 0, b: [1, 2, 3], c: { d: 4 } })
75
+ let count = 0
76
+ createEffect(() => {
77
+ count++
78
+ get()
79
+ })
80
+ expect(count).toBe(1)
81
+ set({ a: 0, b: [1, 2, 3], c: { d: 5 } })
82
+ expect(count).toBe(2)
83
+ set({ a: 0, b: [1, 2, 3], c: { d: 5 } })
84
+ expect(count).toBe(3)
85
+ set({ a: 0, b: [1, 2, 3], c: { d: 4 } })
86
+ expect(count).toBe(4)
87
+ })
88
+
89
+ it("await in effect prevents signal from being tracked", async () => {
90
+ const [get, set] = createSignal(0)
91
+ let count = 0
92
+ // effect bumps count - depends on get() - never gets called again
93
+ createEffect(async () => {
94
+ count++
95
+ await sleep(0)
96
+ get()
97
+ })
98
+ expect(count).toBe(1)
99
+ set(1)
100
+ expect(count).toBe(1)
101
+ await sleep(0)
102
+ set(2)
103
+ expect(count).toBe(1)
104
+ })
105
+
106
+ it("signal behind function call is tracked - same as derived", async () => {
107
+ const [get, set] = createSignal(0)
108
+ let count = 0
109
+ const fn = () => get()
110
+ createEffect(async () => {
111
+ count++
112
+ fn()
113
+ })
114
+ expect(count).toBe(1)
115
+ set(1)
116
+ expect(count).toBe(2)
117
+ })
118
+
119
+ it("Resource async fetch is triggered by signals, and works in effects", async () => {
120
+ const [get, set] = createSignal<number>(0)
121
+
122
+ const [resource] = createResource(get, async (v) => {
123
+ return (await delay(v, 10)) as number
124
+ })
125
+
126
+ let count = 0
127
+ createEffect(() => {
128
+ count++
129
+ resource()
130
+ })
131
+
132
+ expect(resource.loading).toBe(true)
133
+ expect(resource()).toBe(undefined)
134
+ expect(count).toBe(1)
135
+
136
+ await sleep(20)
137
+ expect(resource.loading).toBe(false)
138
+ expect(resource()).toBe(0)
139
+ expect(count).toBe(2)
140
+
141
+ set(42)
142
+ expect(resource.loading).toBe(true)
143
+ expect(resource()).toBe(0)
144
+ expect(count).toBe(2)
145
+
146
+ await sleep(20)
147
+ expect(resource.loading).toBe(false)
148
+ expect(resource()).toBe(42)
149
+ expect(count).toBe(3)
150
+ })
151
+
152
+ it("memoizes values and updates them when signals change", () => {
153
+ const [get, set] = createSignal(0)
154
+ let count = 0
155
+ const memo = createMemo(() => {
156
+ count++
157
+ return `memo = ${2 * get()}`
158
+ })
159
+ expect(count).toBe(1)
160
+ expect(memo()).toBe("memo = 0")
161
+ expect(count).toBe(1)
162
+ expect(memo()).toBe("memo = 0")
163
+ expect(count).toBe(1)
164
+ set(1)
165
+ expect(count).toBe(2)
166
+ expect(memo()).toBe("memo = 2")
167
+ set(1)
168
+ expect(count).toBe(2)
169
+ expect(memo()).toBe("memo = 2")
170
+ expect(count).toBe(2)
171
+ expect(memo()).toBe("memo = 2")
172
+ set(1000)
173
+ expect(count).toBe(3)
174
+ expect(memo()).toBe("memo = 2000")
175
+ })
176
+ })
@@ -3,6 +3,7 @@ import {
3
3
  createMemo as _createMemo,
4
4
  createRoot as _createRoot,
5
5
  createEffect as _createEffect,
6
+ createResource as _createResource,
6
7
  observable as _observable,
7
8
  batch as _batch,
8
9
  from as _from,
@@ -16,6 +17,7 @@ const createSignal = _createSignal as typeof import("solid-js")["createSignal"]
16
17
  const createMemo = _createMemo as typeof import("solid-js")["createMemo"]
17
18
  const createRoot = _createRoot as typeof import("solid-js")["createRoot"]
18
19
  const createEffect = _createEffect as typeof import("solid-js")["createEffect"]
20
+ const createResource = _createResource as typeof import("solid-js")["createResource"]
19
21
  const observable = _observable as typeof import("solid-js")["observable"]
20
22
  const from = _from as typeof import("solid-js")["from"]
21
23
  const batch = _batch as typeof import("solid-js")["batch"]
@@ -27,6 +29,7 @@ export {
27
29
  createMemo,
28
30
  createRoot,
29
31
  createEffect,
32
+ createResource,
30
33
  observable,
31
34
  from,
32
35
  batch,