@inlang/sdk 0.35.5 → 0.35.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/api.d.ts +2 -13
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/createNewProject.test.js +1 -3
  4. package/dist/loadProject.d.ts.map +1 -1
  5. package/dist/loadProject.js +67 -32
  6. package/dist/loadProject.test.js +6 -2
  7. package/dist/persistence/batchedIO.d.ts +11 -0
  8. package/dist/persistence/batchedIO.d.ts.map +1 -0
  9. package/dist/persistence/batchedIO.js +49 -0
  10. package/dist/persistence/batchedIO.test.d.ts +2 -0
  11. package/dist/persistence/batchedIO.test.d.ts.map +1 -0
  12. package/dist/persistence/batchedIO.test.js +56 -0
  13. package/dist/persistence/filelock/acquireFileLock.d.ts.map +1 -1
  14. package/dist/persistence/filelock/acquireFileLock.js +3 -1
  15. package/dist/persistence/filelock/releaseLock.d.ts.map +1 -1
  16. package/dist/persistence/filelock/releaseLock.js +2 -1
  17. package/dist/persistence/store.d.ts +107 -0
  18. package/dist/persistence/store.d.ts.map +1 -0
  19. package/dist/persistence/store.js +99 -0
  20. package/dist/persistence/store.test.d.ts +2 -0
  21. package/dist/persistence/store.test.d.ts.map +1 -0
  22. package/dist/persistence/store.test.js +79 -0
  23. package/dist/persistence/storeApi.d.ts +22 -0
  24. package/dist/persistence/storeApi.d.ts.map +1 -0
  25. package/dist/persistence/storeApi.js +1 -0
  26. package/dist/reactivity/solid.test.js +1 -6
  27. package/dist/resolve-modules/plugins/resolvePlugins.d.ts.map +1 -1
  28. package/dist/resolve-modules/plugins/resolvePlugins.js +3 -10
  29. package/dist/test-utilities/sleep.d.ts +4 -0
  30. package/dist/test-utilities/sleep.d.ts.map +1 -0
  31. package/dist/test-utilities/sleep.js +9 -0
  32. package/dist/v2/helper.d.ts +131 -0
  33. package/dist/v2/helper.d.ts.map +1 -0
  34. package/dist/v2/helper.js +75 -0
  35. package/dist/v2/helper.test.d.ts +2 -0
  36. package/dist/v2/helper.test.d.ts.map +1 -0
  37. package/dist/v2/{createMessageBundle.test.js → helper.test.js} +1 -1
  38. package/dist/v2/index.d.ts +2 -0
  39. package/dist/v2/index.d.ts.map +1 -1
  40. package/dist/v2/index.js +2 -1
  41. package/dist/v2/mocks/index.d.ts +3 -0
  42. package/dist/v2/mocks/index.d.ts.map +1 -0
  43. package/dist/v2/mocks/index.js +2 -0
  44. package/dist/v2/mocks/multipleMatcher/bundle.d.ts +3 -0
  45. package/dist/v2/mocks/multipleMatcher/bundle.d.ts.map +1 -0
  46. package/dist/v2/mocks/multipleMatcher/bundle.js +194 -0
  47. package/dist/v2/mocks/multipleMatcher/bundle.test.d.ts +2 -0
  48. package/dist/v2/mocks/multipleMatcher/bundle.test.d.ts.map +1 -0
  49. package/dist/v2/mocks/multipleMatcher/bundle.test.js +10 -0
  50. package/dist/v2/mocks/plural/bundle.d.ts +1 -1
  51. package/dist/v2/mocks/plural/bundle.d.ts.map +1 -1
  52. package/dist/v2/mocks/plural/bundle.js +1 -1
  53. package/dist/v2/mocks/plural/bundle.test.js +6 -6
  54. package/dist/v2/shim.d.ts +12 -0
  55. package/dist/v2/shim.d.ts.map +1 -0
  56. package/dist/v2/shim.js +151 -0
  57. package/dist/v2/shim.test.d.ts +2 -0
  58. package/dist/v2/shim.test.d.ts.map +1 -0
  59. package/dist/v2/shim.test.js +49 -0
  60. package/dist/v2/stubQueryApi.d.ts +9 -0
  61. package/dist/v2/stubQueryApi.d.ts.map +1 -0
  62. package/dist/v2/stubQueryApi.js +38 -0
  63. package/dist/v2/types.d.ts +110 -0
  64. package/dist/v2/types.d.ts.map +1 -1
  65. package/dist/v2/types.js +9 -0
  66. package/package.json +8 -7
  67. package/src/api.ts +2 -13
  68. package/src/createNewProject.test.ts +1 -4
  69. package/src/loadProject.test.ts +6 -2
  70. package/src/loadProject.ts +86 -45
  71. package/src/persistence/batchedIO.test.ts +63 -0
  72. package/src/persistence/batchedIO.ts +64 -0
  73. package/src/persistence/filelock/acquireFileLock.ts +5 -2
  74. package/src/persistence/filelock/releaseLock.ts +2 -1
  75. package/src/persistence/store.test.ts +102 -0
  76. package/src/persistence/store.ts +119 -0
  77. package/src/persistence/storeApi.ts +19 -0
  78. package/src/reactivity/solid.test.ts +1 -8
  79. package/src/resolve-modules/plugins/resolvePlugins.ts +4 -13
  80. package/src/test-utilities/sleep.ts +11 -0
  81. package/src/v2/{createMessageBundle.test.ts → helper.test.ts} +1 -1
  82. package/src/v2/helper.ts +98 -0
  83. package/src/v2/index.ts +2 -0
  84. package/src/v2/mocks/index.ts +2 -0
  85. package/src/v2/mocks/multipleMatcher/bundle.test.ts +11 -0
  86. package/src/v2/mocks/multipleMatcher/bundle.ts +196 -0
  87. package/src/v2/mocks/plural/bundle.test.ts +6 -6
  88. package/src/v2/mocks/plural/bundle.ts +1 -1
  89. package/src/v2/shim.test.ts +56 -0
  90. package/src/v2/shim.ts +173 -0
  91. package/src/v2/stubQueryApi.ts +43 -0
  92. package/src/v2/types.ts +17 -0
  93. package/dist/persistence/plugin.d.ts +0 -31
  94. package/dist/persistence/plugin.d.ts.map +0 -1
  95. package/dist/persistence/plugin.js +0 -42
  96. package/dist/persistence/plugin.test.d.ts +0 -2
  97. package/dist/persistence/plugin.test.d.ts.map +0 -1
  98. package/dist/persistence/plugin.test.js +0 -49
  99. package/dist/v2/createMessageBundle.d.ts +0 -25
  100. package/dist/v2/createMessageBundle.d.ts.map +0 -1
  101. package/dist/v2/createMessageBundle.js +0 -36
  102. package/dist/v2/createMessageBundle.test.d.ts +0 -2
  103. package/dist/v2/createMessageBundle.test.d.ts.map +0 -1
  104. package/src/persistence/plugin.test.ts +0 -60
  105. package/src/persistence/plugin.ts +0 -56
  106. package/src/v2/createMessageBundle.ts +0 -43
package/dist/api.d.ts CHANGED
@@ -3,7 +3,7 @@ import type * as RuntimeError from "./errors.js";
3
3
  import type * as ModuleResolutionError from "./resolve-modules/errors.js";
4
4
  import type { MessageLintLevel, MessageLintRule, Message, Plugin, ProjectSettings, MessageLintReport } from "./versionedInterfaces.js";
5
5
  import type { ResolvedPluginApi } from "./resolve-modules/plugins/types.js";
6
- import type * as V2 from "./v2/types.js";
6
+ import type { StoreApi } from "./persistence/storeApi.js";
7
7
  export type InstalledPlugin = {
8
8
  id: Plugin["id"];
9
9
  displayName: Plugin["displayName"];
@@ -42,18 +42,8 @@ export type InlangProject = {
42
42
  messages: MessageQueryApi;
43
43
  messageLintReports: MessageLintReportsQueryApi;
44
44
  };
45
- messageBundles?: Query<V2.MessageBundle>;
46
- messages?: Query<V2.Message>;
47
- variants?: Query<V2.Variant>;
45
+ store?: StoreApi;
48
46
  };
49
- /**
50
- * WIP template for async V2 crud interfaces
51
- * E.g. `await project.messageBundles.get({ id: "..." })`
52
- **/
53
- interface Query<T> {
54
- get: (args: unknown) => Promise<T>;
55
- getAll: () => Promise<T[]>;
56
- }
57
47
  export type Subscribable<Value> = {
58
48
  (): Value;
59
49
  subscribe: (callback: (value: Value) => void) => void;
@@ -120,5 +110,4 @@ export type MessageLintReportsQueryApi = {
120
110
  }, callback: (MessageLintRules: Readonly<MessageLintReport[]>) => void) => void;
121
111
  };
122
112
  };
123
- export {};
124
113
  //# 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":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,KAAK,YAAY,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,KAAK,qBAAqB,MAAM,6BAA6B,CAAA;AACzE,OAAO,KAAK,EACX,gBAAgB,EAChB,eAAe,EACf,OAAO,EACP,MAAM,EACN,eAAe,EACf,iBAAiB,EACjB,MAAM,0BAA0B,CAAA;AACjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAA;AAC3E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAEzD,MAAM,MAAM,eAAe,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;IAChB,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC,CAAA;IAClC,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC,CAAA;IAClC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAA;CAExC,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACtC,EAAE,EAAE,eAAe,CAAC,IAAI,CAAC,CAAA;IACzB,WAAW,EAAE,eAAe,CAAC,aAAa,CAAC,CAAA;IAC3C,WAAW,EAAE,eAAe,CAAC,aAAa,CAAC,CAAA;IAC3C;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,gBAAgB,CAAA;IACvB,cAAc,EAAE,eAAe,CAAC,gBAAgB,CAAC,CAAA;CACjD,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC3B;;OAEG;IAEH,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,EAAE;QACV,OAAO,EAAE,YAAY,CAAC,eAAe,EAAE,CAAC,CAAA;QACxC,gBAAgB,EAAE,YAAY,CAAC,wBAAwB,EAAE,CAAC,CAAA;KAC1D,CAAA;IACD,MAAM,EAAE,YAAY,CACnB,CAAC,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,OAAO,qBAAqB,CAAC,GAAG,KAAK,CAAC,EAAE,CAC9E,CAAA;IACD,SAAS,EAAE,YAAY,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAA;IACvD,QAAQ,EAAE,YAAY,CAAC,eAAe,CAAC,CAAA;IACvC,WAAW,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,2BAA2B,CAAC,CAAA;IAChG,KAAK,EAAE;QACN,QAAQ,EAAE,eAAe,CAAA;QACzB,kBAAkB,EAAE,0BAA0B,CAAA;KAC9C,CAAA;IAGD,KAAK,CAAC,EAAE,QAAQ,CAAA;CAChB,CAAA;AAMD,MAAM,MAAM,YAAY,CAAC,KAAK,IAAI;IACjC,IAAI,KAAK,CAAA;IACT,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAAK,IAAI,CAAA;CACrD,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IAClC,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IACnF,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IACnF,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IACjE,QAAQ,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IACvC,SAAS,EAAE,MAAM,IAAI,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC7B,MAAM,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAA;IAC5C,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE,CAAA;KAAE,KAAK,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG;QACtE,SAAS,EAAE,CACV,IAAI,EAAE;YAAE,KAAK,EAAE;gBAAE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;aAAE,CAAA;SAAE,EACtC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAChC,IAAI,CAAA;KACT,CAAA;IAED,iBAAiB,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG;QAChF,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,CAAA;KAC7F,CAAA;IACD,kBAAkB,EAAE,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAIjD,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;IACzC,MAAM,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,KAAK,OAAO,CAAA;IACnF,MAAM,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;IACvE,MAAM,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE,CAAA;KAAE,KAAK,OAAO,CAAA;IAC3D,WAAW,EAAE,CAAC,QAAQ,EAAE,oBAAoB,GAAG,SAAS,EAAE,UAAU,EAAE,OAAO,KAAK,IAAI,CAAA;CACtF,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACxC,MAAM,EAAE,YAAY,CAAC,iBAAiB,EAAE,CAAC,GAAG;QAC3C,OAAO,EAAE,MAAM,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAA;KAC3C,CAAA;IACD,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE;QACZ,KAAK,EAAE;YAAE,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAA;SAAE,CAAA;KACpD,KAAK,QAAQ,CAAC,iBAAiB,EAAE,CAAC,CAAC,GAAG;QACtC,SAAS,EAAE,CACV,IAAI,EAAE;YAAE,KAAK,EAAE;gBAAE,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAA;aAAE,CAAA;SAAE,EAC9D,QAAQ,EAAE,CAAC,gBAAgB,EAAE,QAAQ,CAAC,iBAAiB,EAAE,CAAC,KAAK,IAAI,KAC/D,IAAI,CAAA;KACT,CAAA;CACD,CAAA"}
@@ -4,9 +4,7 @@ import { mockRepo } from "@lix-js/client";
4
4
  import { defaultProjectSettings } from "./defaultProjectSettings.js";
5
5
  import { loadProject } from "./loadProject.js";
6
6
  import { createMessage } from "./test-utilities/createMessage.js";
7
- function sleep(ms) {
8
- return new Promise((resolve) => setTimeout(resolve, ms));
9
- }
7
+ import { sleep } from "./test-utilities/sleep.js";
10
8
  describe("createNewProject", () => {
11
9
  it("should throw if a path does not end with .inlang", async () => {
12
10
  const repo = await mockRepo();
@@ -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;AAkBhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAYhD;;;;;;;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,CAsOzB;AA+GD,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,EAGZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,cAAc,EAAkB,MAAM,4BAA4B,CAAA;AAkBhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAgBhD;;;;;;;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,CAyQzB;AA+GD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
@@ -14,6 +14,8 @@ import { maybeMigrateToDirectory } from "./migrations/migrateToDirectory.js";
14
14
  import { maybeCreateFirstProjectId } from "./migrations/maybeCreateFirstProjectId.js";
15
15
  import { capture } from "./telemetry/capture.js";
16
16
  import { identifyProject } from "./telemetry/groupIdentify.js";
17
+ import { stubMessagesQuery, stubMessageLintReportsQuery } from "./v2/stubQueryApi.js";
18
+ import { openStore } from "./persistence/store.js";
17
19
  import _debug from "debug";
18
20
  const debug = _debug("sdk:loadProject");
19
21
  const settingsCompiler = TypeCompiler.Compile(ProjectSettings);
@@ -47,8 +49,14 @@ export async function loadProject(args) {
47
49
  // - if a repo is present, the project id will always be present
48
50
  const { data: projectId } = await tryCatch(() => nodeishFs.readFile(args.projectPath + "/project_id", { encoding: "utf-8" }));
49
51
  const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable();
52
+ const [loadedSettings, markSettingsAsLoaded, markSettingsAsFailed] = createAwaitable();
50
53
  // -- settings ------------------------------------------------------------
51
54
  const [settings, _setSettings] = createSignal();
55
+ let v2Persistence = false;
56
+ let locales = [];
57
+ // This effect currently has no signals
58
+ // TODO: replace createEffect with await loadSettings
59
+ // https://github.com/opral/inlang-message-sdk/issues/77
52
60
  createEffect(() => {
53
61
  // TODO:
54
62
  // if (projectId) {
@@ -57,21 +65,23 @@ export async function loadProject(args) {
57
65
  // })
58
66
  // }
59
67
  loadSettings({ settingsFilePath: projectPath + "/settings.json", nodeishFs })
60
- .then((settings) => setSettings(settings))
68
+ .then((settings) => {
69
+ setSettings(settings);
70
+ markSettingsAsLoaded();
71
+ })
61
72
  .catch((err) => {
62
73
  markInitAsFailed(err);
74
+ markSettingsAsFailed(err);
63
75
  });
64
76
  });
65
77
  // TODO: create FS watcher and update settings on change
78
+ // https://github.com/opral/inlang-message-sdk/issues/35
66
79
  const writeSettingsToDisk = skipFirst((settings) => _writeSettingsToDisk({ nodeishFs, settings, projectPath }));
67
80
  const setSettings = (settings) => {
68
81
  try {
69
82
  const validatedSettings = parseSettings(settings);
70
- if (validatedSettings.experimental?.persistence) {
71
- settings["plugin.sdk.persistence"] = {
72
- pathPattern: projectPath + "/messages.json",
73
- };
74
- }
83
+ v2Persistence = !!validatedSettings.experimental?.persistence;
84
+ locales = validatedSettings.languageTags;
75
85
  batch(() => {
76
86
  // reset the resolved modules first - since they are no longer valid at that point
77
87
  setResolvedModules(undefined);
@@ -99,32 +109,10 @@ export async function loadProject(args) {
99
109
  })
100
110
  .catch((err) => markInitAsFailed(err));
101
111
  });
102
- // -- messages ----------------------------------------------------------
103
- let settingsValue;
104
- createEffect(() => (settingsValue = settings())); // workaround to not run effects twice (e.g. settings change + modules change) (I'm sure there exists a solid way of doing this, but I haven't found it yet)
105
- const [loadMessagesViaPluginError, setLoadMessagesViaPluginError] = createSignal();
106
- const [saveMessagesViaPluginError, setSaveMessagesViaPluginError] = createSignal();
107
- const messagesQuery = createMessagesQuery({
108
- projectPath,
109
- nodeishFs,
110
- settings,
111
- resolvedModules,
112
- onInitialMessageLoadResult: (e) => {
113
- if (e) {
114
- markInitAsFailed(e);
115
- }
116
- else {
117
- markInitAsComplete();
118
- }
119
- },
120
- onLoadMessageResult: (e) => {
121
- setLoadMessagesViaPluginError(e);
122
- },
123
- onSaveMessageResult: (e) => {
124
- setSaveMessagesViaPluginError(e);
125
- },
126
- });
127
112
  // -- installed items ----------------------------------------------------
113
+ let settingsValue;
114
+ // workaround to not run effects twice (e.g. settings change + modules change) (I'm sure there exists a solid way of doing this, but I haven't found it yet)
115
+ createEffect(() => (settingsValue = settings()));
128
116
  const installedMessageLintRules = () => {
129
117
  if (!resolvedModules())
130
118
  return [];
@@ -151,9 +139,53 @@ export async function loadProject(args) {
151
139
  settingsSchema: plugin.settingsSchema,
152
140
  }));
153
141
  };
142
+ // -- messages ----------------------------------------------------------
143
+ const [loadMessagesViaPluginError, setLoadMessagesViaPluginError] = createSignal();
144
+ const [saveMessagesViaPluginError, setSaveMessagesViaPluginError] = createSignal();
145
+ let messagesQuery;
146
+ let lintReportsQuery;
147
+ let store;
148
+ // wait for seetings to load v2Persistence flag
149
+ // .catch avoids throwing here if the awaitable is rejected
150
+ // error is recorded via markInitAsFailed so no need to capture it again
151
+ await loadedSettings.catch(() => { });
152
+ if (v2Persistence) {
153
+ messagesQuery = stubMessagesQuery;
154
+ lintReportsQuery = stubMessageLintReportsQuery;
155
+ try {
156
+ store = await openStore({ projectPath, nodeishFs, locales });
157
+ markInitAsComplete();
158
+ }
159
+ catch (e) {
160
+ markInitAsFailed(e);
161
+ }
162
+ }
163
+ else {
164
+ messagesQuery = createMessagesQuery({
165
+ projectPath,
166
+ nodeishFs,
167
+ settings,
168
+ resolvedModules,
169
+ onInitialMessageLoadResult: (e) => {
170
+ if (e) {
171
+ markInitAsFailed(e);
172
+ }
173
+ else {
174
+ markInitAsComplete();
175
+ }
176
+ },
177
+ onLoadMessageResult: (e) => {
178
+ setLoadMessagesViaPluginError(e);
179
+ },
180
+ onSaveMessageResult: (e) => {
181
+ setSaveMessagesViaPluginError(e);
182
+ },
183
+ });
184
+ lintReportsQuery = createMessageLintReportsQuery(messagesQuery, settings, installedMessageLintRules, resolvedModules);
185
+ store = undefined;
186
+ }
154
187
  // -- app ---------------------------------------------------------------
155
188
  const initializeError = await initialized.catch((error) => error);
156
- const lintReportsQuery = createMessageLintReportsQuery(messagesQuery, settings, installedMessageLintRules, resolvedModules);
157
189
  /**
158
190
  * Utility to escape reactive tracking and avoid multiple calls to
159
191
  * the capture event.
@@ -179,6 +211,8 @@ export async function loadProject(args) {
179
211
  settings: settings(),
180
212
  installedPluginIds: installedPlugins().map((p) => p.id),
181
213
  installedMessageLintRuleIds: installedMessageLintRules().map((r) => r.id),
214
+ // TODO: fix for v2Persistence
215
+ // https://github.com/opral/inlang-message-sdk/issues/78
182
216
  numberOfMessages: messagesQuery.includedMessageIds().length,
183
217
  },
184
218
  });
@@ -204,6 +238,7 @@ export async function loadProject(args) {
204
238
  messages: messagesQuery,
205
239
  messageLintReports: lintReportsQuery,
206
240
  },
241
+ store,
207
242
  };
208
243
  });
209
244
  }
@@ -219,11 +219,15 @@ describe("initialization", () => {
219
219
  expect(result.error).toBeUndefined();
220
220
  expect(result.data).toBeDefined();
221
221
  });
222
+ // TODO: fix this test
223
+ // https://github.com/opral/inlang-message-sdk/issues/76
224
+ // it doesn't work because failure to open the settings file doesn't throw
225
+ // errors are returned in project.errors()
222
226
  it("should resolve from a windows path", async () => {
223
227
  const repo = await mockRepo();
224
228
  const fs = repo.nodeishFs;
225
- fs.mkdir("C:\\Users\\user\\project.inlang", { recursive: true });
226
- fs.writeFile("C:\\Users\\user\\project.inlang\\settings.json", JSON.stringify(settings));
229
+ await fs.mkdir("C:\\Users\\user\\project.inlang", { recursive: true });
230
+ await fs.writeFile("C:\\Users\\user\\project.inlang\\settings.json", JSON.stringify(settings));
227
231
  const result = await tryCatch(() => loadProject({
228
232
  projectPath: "C:\\Users\\user\\project.inlang",
229
233
  repo,
@@ -0,0 +1,11 @@
1
+ /**
2
+ * State machine to convert async save() into batched async save()
3
+ * states = idle -> acquiring -> saving -> idle
4
+ * idle = nothing queued, ready to acquire lock.
5
+ * aquiring = waiting to acquire a lock: requests go into the queue.
6
+ * saving = lock is acquired, save has begun: new requests go into the next batch.
7
+ * The next batch should not acquire the lock while current save is in progress.
8
+ * Queued requests are only resolved when the save completes.
9
+ */
10
+ export declare function batchedIO(acquireLock: () => Promise<number>, releaseLock: (lock: number) => Promise<void>, save: () => Promise<void>): (id: string) => Promise<string>;
11
+ //# sourceMappingURL=batchedIO.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batchedIO.d.ts","sourceRoot":"","sources":["../../src/persistence/batchedIO.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CACxB,WAAW,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAClC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EAC5C,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GACvB,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CA+CjC"}
@@ -0,0 +1,49 @@
1
+ import _debug from "debug";
2
+ const debug = _debug("sdk:batchedIO");
3
+ /**
4
+ * State machine to convert async save() into batched async save()
5
+ * states = idle -> acquiring -> saving -> idle
6
+ * idle = nothing queued, ready to acquire lock.
7
+ * aquiring = waiting to acquire a lock: requests go into the queue.
8
+ * saving = lock is acquired, save has begun: new requests go into the next batch.
9
+ * The next batch should not acquire the lock while current save is in progress.
10
+ * Queued requests are only resolved when the save completes.
11
+ */
12
+ export function batchedIO(acquireLock, releaseLock, save) {
13
+ // 3-state machine
14
+ let state = "idle";
15
+ const queue = [];
16
+ // initialize nextBatch lazily, reset after saving
17
+ let nextBatch = undefined;
18
+ // batched save function
19
+ return async (id) => {
20
+ if (state === "idle") {
21
+ state = "acquiring";
22
+ const lock = await acquireLock();
23
+ state = "saving";
24
+ await save();
25
+ await releaseLock(lock);
26
+ resolveQueued();
27
+ nextBatch = undefined;
28
+ state = "idle";
29
+ return id;
30
+ }
31
+ else if (state === "acquiring") {
32
+ return new Promise((resolve, reject) => {
33
+ queue.push({ id, resolve, reject });
34
+ });
35
+ }
36
+ else {
37
+ // state === "saving"
38
+ nextBatch = nextBatch ?? batchedIO(acquireLock, releaseLock, save);
39
+ return await nextBatch(id);
40
+ }
41
+ };
42
+ function resolveQueued() {
43
+ debug("batched", queue.length + 1);
44
+ for (const { id, resolve } of queue) {
45
+ resolve(id);
46
+ }
47
+ queue.length = 0;
48
+ }
49
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=batchedIO.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batchedIO.test.d.ts","sourceRoot":"","sources":["../../src/persistence/batchedIO.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,56 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { sleep } from "../test-utilities/sleep.js";
3
+ import { batchedIO } from "./batchedIO.js";
4
+ let locked = false;
5
+ const instrumentAquireLockStart = vi.fn();
6
+ async function mockAquireLock() {
7
+ instrumentAquireLockStart();
8
+ let pollCount = 0;
9
+ while (locked && pollCount++ < 100) {
10
+ await sleep(10);
11
+ }
12
+ if (locked) {
13
+ throw new Error("Timeout acquiring lock");
14
+ }
15
+ await sleep(10);
16
+ locked = true;
17
+ return 69;
18
+ }
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
+ async function mockReleaseLock(_) {
21
+ sleep(10);
22
+ locked = false;
23
+ }
24
+ const instrumentSaveStart = vi.fn();
25
+ const instrumentSaveEnd = vi.fn();
26
+ async function mockSave() {
27
+ instrumentSaveStart();
28
+ await sleep(50);
29
+ instrumentSaveEnd();
30
+ }
31
+ describe("batchedIO", () => {
32
+ it("queues 2 requests while waiting for lock and pushes 2 more to the next batch", async () => {
33
+ const save = batchedIO(mockAquireLock, mockReleaseLock, mockSave);
34
+ const p1 = save("1");
35
+ const p2 = save("2");
36
+ await sleep(5);
37
+ expect(instrumentAquireLockStart).toHaveBeenCalledTimes(1);
38
+ expect(instrumentSaveStart).not.toHaveBeenCalled();
39
+ await sleep(10);
40
+ expect(instrumentSaveStart).toHaveBeenCalled();
41
+ expect(instrumentSaveEnd).not.toHaveBeenCalled();
42
+ const p3 = save("3");
43
+ const p4 = save("4");
44
+ expect(instrumentAquireLockStart).toHaveBeenCalledTimes(2);
45
+ expect(locked).toBe(true);
46
+ await sleep(50);
47
+ expect(instrumentSaveEnd).toHaveBeenCalled();
48
+ expect(await p1).toBe("1");
49
+ expect(await p2).toBe("2");
50
+ expect(instrumentSaveStart).toHaveBeenCalledTimes(1);
51
+ expect(await p3).toBe("3");
52
+ expect(await p4).toBe("4");
53
+ expect(instrumentAquireLockStart).toHaveBeenCalledTimes(2);
54
+ expect(instrumentSaveStart).toHaveBeenCalledTimes(2);
55
+ });
56
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"acquireFileLock.d.ts","sourceRoot":"","sources":["../../../src/persistence/filelock/acquireFileLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAQnD,wBAAsB,eAAe,CACpC,EAAE,EAAE,iBAAiB,EACrB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,GAAE,MAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAgHjB"}
1
+ {"version":3,"file":"acquireFileLock.d.ts","sourceRoot":"","sources":["../../../src/persistence/filelock/acquireFileLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAQnD,wBAAsB,eAAe,CACpC,EAAE,EAAE,iBAAiB,EACrB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,GAAE,MAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAmHjB"}
@@ -11,16 +11,18 @@ export async function acquireFileLock(fs, lockDirPath, lockOrigin, tryCount = 0)
11
11
  try {
12
12
  debug(lockOrigin + " tries to acquire a lockfile Retry Nr.: " + tryCount);
13
13
  await fs.mkdir(lockDirPath);
14
+ // NOTE: fs.stat does not need to be atomic since mkdir would crash atomically - if we are here its save to consider the lock held by this process
14
15
  const stats = await fs.stat(lockDirPath);
15
16
  debug(lockOrigin + " acquired a lockfile Retry Nr.: " + tryCount);
16
17
  return stats.mtimeMs;
17
18
  }
18
19
  catch (error) {
19
20
  if (error.code !== "EEXIST") {
20
- // we only expect the error that the file exists already (locked by other process)
21
+ // NOTE in case we have an EEXIST - this is an expected error: the folder existed - another process already acquired the lock. Rethrow all other errors
21
22
  throw error;
22
23
  }
23
24
  }
25
+ // land here if the lockDirPath already exists => lock is held by other process
24
26
  let currentLockTime;
25
27
  try {
26
28
  const stats = await fs.stat(lockDirPath);
@@ -1 +1 @@
1
- {"version":3,"file":"releaseLock.d.ts","sourceRoot":"","sources":["../../../src/persistence/filelock/releaseLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAInD,wBAAsB,WAAW,CAChC,EAAE,EAAE,iBAAiB,EACrB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,iBAmBhB"}
1
+ {"version":3,"file":"releaseLock.d.ts","sourceRoot":"","sources":["../../../src/persistence/filelock/releaseLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAInD,wBAAsB,WAAW,CAChC,EAAE,EAAE,iBAAiB,EACrB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,iBAoBhB"}
@@ -5,8 +5,9 @@ export async function releaseLock(fs, lockDirPath, lockOrigin, lockTime) {
5
5
  debug(lockOrigin + " releasing the lock ");
6
6
  try {
7
7
  const stats = await fs.stat(lockDirPath);
8
+ // I believe this check associates the lock with the aquirer
8
9
  if (stats.mtimeMs === lockTime) {
9
- // this can be corrupt as welll since the last getStat and the current a modification could have occured :-/
10
+ // NOTE: since we have to use a timeout for stale detection (uTimes is not exposed via mermoryfs) the check for the locktime is not sufficient and can fail in rare cases when another process accuires a lock that was identifiert as tale between call to fs.state and rmDir
10
11
  await fs.rmdir(lockDirPath);
11
12
  }
12
13
  }
@@ -0,0 +1,107 @@
1
+ import type { MessageBundle } from "../v2/types.js";
2
+ import { type NodeishFilesystem } from "@lix-js/fs";
3
+ import type { StoreApi } from "./storeApi.js";
4
+ export declare function openStore(args: {
5
+ projectPath: string;
6
+ nodeishFs: NodeishFilesystem;
7
+ locales: string[];
8
+ }): Promise<StoreApi>;
9
+ export declare function readJSON(args: {
10
+ filePath: string;
11
+ nodeishFs: NodeishFilesystem;
12
+ }): Promise<{
13
+ id: string;
14
+ alias: Record<string, string>;
15
+ messages: {
16
+ selectors: {
17
+ annotation?: {
18
+ type: "function";
19
+ name: string;
20
+ options: {
21
+ name: string;
22
+ value: {
23
+ type: "literal";
24
+ value: string;
25
+ } | {
26
+ type: "variable";
27
+ name: string;
28
+ };
29
+ }[];
30
+ } | undefined;
31
+ type: "expression";
32
+ arg: {
33
+ type: "literal";
34
+ value: string;
35
+ } | {
36
+ type: "variable";
37
+ name: string;
38
+ };
39
+ }[];
40
+ variants: {
41
+ match: string[];
42
+ pattern: ({
43
+ type: "text";
44
+ value: string;
45
+ } | {
46
+ annotation?: {
47
+ type: "function";
48
+ name: string;
49
+ options: {
50
+ name: string;
51
+ value: {
52
+ type: "literal";
53
+ value: string;
54
+ } | {
55
+ type: "variable";
56
+ name: string;
57
+ };
58
+ }[];
59
+ } | undefined;
60
+ type: "expression";
61
+ arg: {
62
+ type: "literal";
63
+ value: string;
64
+ } | {
65
+ type: "variable";
66
+ name: string;
67
+ };
68
+ })[];
69
+ }[];
70
+ locale: string;
71
+ declarations: {
72
+ type: "input";
73
+ name: string;
74
+ value: {
75
+ annotation?: {
76
+ type: "function";
77
+ name: string;
78
+ options: {
79
+ name: string;
80
+ value: {
81
+ type: "literal";
82
+ value: string;
83
+ } | {
84
+ type: "variable";
85
+ name: string;
86
+ };
87
+ }[];
88
+ } | undefined;
89
+ type: "expression";
90
+ arg: {
91
+ type: "literal";
92
+ value: string;
93
+ } | {
94
+ type: "variable";
95
+ name: string;
96
+ };
97
+ };
98
+ }[];
99
+ }[];
100
+ }[]>;
101
+ export declare function writeJSON(args: {
102
+ filePath: string;
103
+ nodeishFs: NodeishFilesystem;
104
+ messages: MessageBundle[];
105
+ locales: string[];
106
+ }): Promise<void>;
107
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/persistence/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,EAAc,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAI/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAK7C,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACrC,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,iBAAiB,CAAA;IAC5B,OAAO,EAAE,MAAM,EAAE,CAAA;CACjB,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0DpB;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,iBAAiB,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KActF;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,iBAAiB,CAAA;IAC5B,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,OAAO,EAAE,MAAM,EAAE,CAAA;CACjB,iBAYA"}
@@ -0,0 +1,99 @@
1
+ import { addSlots, removeSlots, injectJSONNewlines } from "../v2/helper.js";
2
+ import { getDirname } from "@lix-js/fs";
3
+ import { acquireFileLock } from "./filelock/acquireFileLock.js";
4
+ import { releaseLock } from "./filelock/releaseLock.js";
5
+ import { batchedIO } from "./batchedIO.js";
6
+ import _debug from "debug";
7
+ const debug = _debug("sdk:store");
8
+ export async function openStore(args) {
9
+ const nodeishFs = args.nodeishFs;
10
+ const filePath = args.projectPath + "/messages.json";
11
+ const lockDirPath = args.projectPath + "/messagelock";
12
+ // the index holds the in-memory state
13
+ // TODO: reload when file changes on disk
14
+ // https://github.com/opral/inlang-message-sdk/issues/80
15
+ let index = await load();
16
+ const batchedSave = batchedIO(acquireSaveLock, releaseSaveLock, save);
17
+ return {
18
+ messageBundles: {
19
+ reload: async () => {
20
+ index.clear();
21
+ index = await load();
22
+ },
23
+ get: async (args) => {
24
+ return index.get(args.id);
25
+ },
26
+ set: async (args) => {
27
+ index.set(args.data.id, args.data);
28
+ await batchedSave(args.data.id);
29
+ },
30
+ delete: async (args) => {
31
+ index.delete(args.id);
32
+ await batchedSave(args.id);
33
+ },
34
+ getAll: async () => {
35
+ return [...index.values()];
36
+ },
37
+ },
38
+ };
39
+ // load and save messages from file system atomically
40
+ // using a lock file to prevent partial reads and writes
41
+ async function load() {
42
+ const lockTime = await acquireFileLock(nodeishFs, lockDirPath, "load");
43
+ const messages = await readJSON({ filePath, nodeishFs: nodeishFs });
44
+ const index = new Map(messages.map((message) => [message.id, message]));
45
+ await releaseLock(nodeishFs, lockDirPath, "load", lockTime);
46
+ return index;
47
+ }
48
+ async function acquireSaveLock() {
49
+ return await acquireFileLock(nodeishFs, lockDirPath, "save");
50
+ }
51
+ async function releaseSaveLock(lock) {
52
+ return await releaseLock(nodeishFs, lockDirPath, "save", lock);
53
+ }
54
+ async function save() {
55
+ await writeJSON({
56
+ filePath,
57
+ nodeishFs: nodeishFs,
58
+ messages: [...index.values()],
59
+ locales: args.locales,
60
+ });
61
+ }
62
+ }
63
+ export async function readJSON(args) {
64
+ let result = [];
65
+ debug("loadAll", args.filePath);
66
+ try {
67
+ const file = await args.nodeishFs.readFile(args.filePath, { encoding: "utf-8" });
68
+ result = JSON.parse(file);
69
+ }
70
+ catch (error) {
71
+ if (error?.code !== "ENOENT") {
72
+ debug("loadMessages", error);
73
+ throw error;
74
+ }
75
+ }
76
+ return result.map(removeSlots);
77
+ }
78
+ export async function writeJSON(args) {
79
+ debug("saveall", args.filePath);
80
+ try {
81
+ await createDirectoryIfNotExits(getDirname(args.filePath), args.nodeishFs);
82
+ const output = injectJSONNewlines(JSON.stringify(args.messages.map((bundle) => addSlots(bundle, args.locales))));
83
+ await args.nodeishFs.writeFile(args.filePath, output);
84
+ }
85
+ catch (error) {
86
+ debug("saveMessages", error);
87
+ throw error;
88
+ }
89
+ }
90
+ async function createDirectoryIfNotExits(path, nodeishFs) {
91
+ try {
92
+ await nodeishFs.mkdir(path, { recursive: true });
93
+ }
94
+ catch (error) {
95
+ if (error.code !== "EEXIST") {
96
+ throw error;
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.test.d.ts","sourceRoot":"","sources":["../../src/persistence/store.test.ts"],"names":[],"mappings":""}