@inlang/sdk 0.28.3 → 0.30.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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"loadProject.d.ts","sourceRoot":"","sources":["../src/loadProject.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EAGb,YAAY,EACZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,cAAc,EAAkB,MAAM,4BAA4B,CAAA;AAyBhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAgChD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACvC,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,cAAc,CAAA;CACxB,GAAG,OAAO,CAAC,aAAa,CAAC,CAuYzB;AAsHD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
1
+ {"version":3,"file":"loadProject.d.ts","sourceRoot":"","sources":["../src/loadProject.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EAGb,YAAY,EACZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,cAAc,EAAkB,MAAM,4BAA4B,CAAA;AAyBhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAgChD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACvC,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,cAAc,CAAA;CACxB,GAAG,OAAO,CAAC,aAAa,CAAC,CAqYzB;AAsHD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
@@ -49,20 +49,19 @@ export async function loadProject(args) {
49
49
  else if (/[^\\/]+\.inlang$/.test(projectPath) === false) {
50
50
  throw new LoadProjectInvalidArgument(`Expected a path ending in "{name}.inlang" but received "${projectPath}".\n\nValid examples: \n- "/path/to/micky-mouse.inlang"\n- "/path/to/green-elephant.inlang\n`, { argument: "projectPath" });
51
51
  }
52
- const fs = args.repo.nodeishFs;
53
52
  const nodeishFs = createNodeishFsWithAbsolutePaths({
54
53
  projectPath,
55
- nodeishFs: fs,
54
+ nodeishFs: args.repo.nodeishFs,
56
55
  });
57
56
  // -- migratations ------------------------------------------------
58
- await maybeMigrateToDirectory({ nodeishFs: fs, projectPath });
57
+ await maybeMigrateToDirectory({ nodeishFs, projectPath });
59
58
  await maybeCreateFirstProjectId({ projectPath, repo: args.repo });
60
59
  // -- load project ------------------------------------------------------
61
60
  return await createRoot(async () => {
62
61
  // TODO remove tryCatch after https://github.com/opral/monorepo/issues/2013
63
62
  // - a repo will always be present
64
63
  // - if a repo is present, the project id will always be present
65
- const { data: projectId } = await tryCatch(() => fs.readFile(args.projectPath + "/project_id", { encoding: "utf-8" }));
64
+ const { data: projectId } = await tryCatch(() => nodeishFs.readFile(args.projectPath + "/project_id", { encoding: "utf-8" }));
66
65
  const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable();
67
66
  // -- settings ------------------------------------------------------------
68
67
  const [settings, _setSettings] = createSignal();
@@ -219,7 +218,7 @@ export async function loadProject(args) {
219
218
  // don't trigger saves or set dirty flags during initial setup
220
219
  if (!initialSetup) {
221
220
  messageStates.messageDirtyFlags[message.id] = true;
222
- saveMessagesViaPlugin(fs, messageLockDirPath, messageStates, messagesQuery, settings(), saveMessagesPlugin, loadMessagesPlugin)
221
+ saveMessagesViaPlugin(nodeishFs, messageLockDirPath, messageStates, messagesQuery, settings(), saveMessagesPlugin, loadMessagesPlugin)
223
222
  .catch((e) => setSaveMessagesViaPluginError(new PluginSaveMessagesError({ cause: e })))
224
223
  .then(() => {
225
224
  if (saveMessagesViaPluginError() !== undefined) {
@@ -628,7 +627,7 @@ async function saveMessagesViaPlugin(fs, lockDirPath, messageState, messagesQuer
628
627
  });
629
628
  }
630
629
  }
631
- const maxRetries = 5;
630
+ const maxRetries = 10;
632
631
  const nProbes = 50;
633
632
  const probeInterval = 100;
634
633
  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, };
@@ -1,9 +1,29 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { createSignal, createEffect, createMemo } from "./solid.js";
2
+ import { createRoot, createSignal, createEffect, createMemo, createResource } from "./solid.js";
3
3
  function sleep(ms) {
4
4
  return new Promise((resolve) => setTimeout(resolve, ms));
5
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
+ });
6
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
+ });
7
27
  it("sets and gets", () => {
8
28
  const [get, set] = createSignal(0);
9
29
  expect(get()).toBe(0);
@@ -84,6 +104,32 @@ describe("solid", () => {
84
104
  set(1);
85
105
  expect(count).toBe(2);
86
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
+ });
87
133
  it("memoizes values and updates them when signals change", () => {
88
134
  const [get, set] = createSignal(0);
89
135
  let count = 0;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inlang/sdk",
3
3
  "type": "module",
4
- "version": "0.28.3",
4
+ "version": "0.30.0",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -33,14 +33,14 @@
33
33
  "solid-js": "1.6.12",
34
34
  "throttle-debounce": "^5.0.0",
35
35
  "@inlang/json-types": "1.1.0",
36
+ "@inlang/language-tag": "1.5.1",
36
37
  "@inlang/message": "2.1.0",
37
38
  "@inlang/message-lint-rule": "1.4.5",
38
- "@inlang/language-tag": "1.5.1",
39
39
  "@inlang/module": "1.2.9",
40
- "@inlang/plugin": "2.4.9",
41
40
  "@inlang/project-settings": "2.4.0",
42
- "@inlang/result": "1.1.0",
41
+ "@inlang/plugin": "2.4.9",
43
42
  "@inlang/translatable": "1.3.1",
43
+ "@inlang/result": "1.1.0",
44
44
  "@lix-js/client": "1.2.0",
45
45
  "@lix-js/fs": "1.0.0"
46
46
  },
@@ -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
 
@@ -105,16 +105,14 @@ export async function loadProject(args: {
105
105
  )
106
106
  }
107
107
 
108
- const fs = args.repo.nodeishFs
109
-
110
108
  const nodeishFs = createNodeishFsWithAbsolutePaths({
111
109
  projectPath,
112
- nodeishFs: fs,
110
+ nodeishFs: args.repo.nodeishFs,
113
111
  })
114
112
 
115
113
  // -- migratations ------------------------------------------------
116
114
 
117
- await maybeMigrateToDirectory({ nodeishFs: fs, projectPath })
115
+ await maybeMigrateToDirectory({ nodeishFs, projectPath })
118
116
  await maybeCreateFirstProjectId({ projectPath, repo: args.repo })
119
117
 
120
118
  // -- load project ------------------------------------------------------
@@ -124,7 +122,7 @@ export async function loadProject(args: {
124
122
  // - a repo will always be present
125
123
  // - if a repo is present, the project id will always be present
126
124
  const { data: projectId } = await tryCatch(() =>
127
- fs.readFile(args.projectPath + "/project_id", { encoding: "utf-8" })
125
+ nodeishFs.readFile(args.projectPath + "/project_id", { encoding: "utf-8" })
128
126
  )
129
127
 
130
128
  const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable()
@@ -348,7 +346,7 @@ export async function loadProject(args: {
348
346
  if (!initialSetup) {
349
347
  messageStates.messageDirtyFlags[message.id] = true
350
348
  saveMessagesViaPlugin(
351
- fs,
349
+ nodeishFs,
352
350
  messageLockDirPath,
353
351
  messageStates,
354
352
  messagesQuery,
@@ -901,7 +899,7 @@ async function saveMessagesViaPlugin(
901
899
  }
902
900
  }
903
901
 
904
- const maxRetries = 5
902
+ const maxRetries = 10
905
903
  const nProbes = 50
906
904
  const probeInterval = 100
907
905
  async function acquireFileLock(
@@ -1,11 +1,34 @@
1
1
  import { describe, it, expect } from "vitest"
2
- import { createSignal, createEffect, createMemo } from "./solid.js"
2
+ import { createRoot, createSignal, createEffect, createMemo, createResource } from "./solid.js"
3
3
 
4
4
  function sleep(ms: number) {
5
5
  return new Promise((resolve) => setTimeout(resolve, ms))
6
6
  }
7
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
+
8
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
+
9
32
  it("sets and gets", () => {
10
33
  const [get, set] = createSignal(0)
11
34
 
@@ -93,6 +116,39 @@ describe("solid", () => {
93
116
  expect(count).toBe(2)
94
117
  })
95
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
+
96
152
  it("memoizes values and updates them when signals change", () => {
97
153
  const [get, set] = createSignal(0)
98
154
  let count = 0
@@ -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,