@inlang/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +25 -0
  2. package/dist/adapter/solidAdapter.d.ts +32 -0
  3. package/dist/adapter/solidAdapter.d.ts.map +1 -0
  4. package/dist/adapter/solidAdapter.js +39 -0
  5. package/dist/adapter/solidAdapter.test.d.ts +2 -0
  6. package/dist/adapter/solidAdapter.test.d.ts.map +1 -0
  7. package/dist/adapter/solidAdapter.test.js +284 -0
  8. package/dist/api.d.ts +88 -0
  9. package/dist/api.d.ts.map +1 -0
  10. package/dist/api.js +1 -0
  11. package/dist/createMessageLintReportsQuery.d.ts +9 -0
  12. package/dist/createMessageLintReportsQuery.d.ts.map +1 -0
  13. package/dist/createMessageLintReportsQuery.js +48 -0
  14. package/dist/createMessagesQuery.d.ts +7 -0
  15. package/dist/createMessagesQuery.d.ts.map +1 -0
  16. package/dist/createMessagesQuery.js +57 -0
  17. package/dist/createMessagesQuery.test.d.ts +2 -0
  18. package/dist/createMessagesQuery.test.d.ts.map +1 -0
  19. package/dist/createMessagesQuery.test.js +304 -0
  20. package/dist/errors.d.ts +22 -0
  21. package/dist/errors.d.ts.map +1 -0
  22. package/dist/errors.js +39 -0
  23. package/dist/index.d.ts +15 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +13 -0
  26. package/dist/lint/index.d.ts +3 -0
  27. package/dist/lint/index.d.ts.map +1 -0
  28. package/dist/lint/index.js +2 -0
  29. package/dist/lint/message/errors.d.ts +7 -0
  30. package/dist/lint/message/errors.d.ts.map +1 -0
  31. package/dist/lint/message/errors.js +9 -0
  32. package/dist/lint/message/lintMessages.d.ts +17 -0
  33. package/dist/lint/message/lintMessages.d.ts.map +1 -0
  34. package/dist/lint/message/lintMessages.js +12 -0
  35. package/dist/lint/message/lintMessages.test.d.ts +2 -0
  36. package/dist/lint/message/lintMessages.test.d.ts.map +1 -0
  37. package/dist/lint/message/lintMessages.test.js +105 -0
  38. package/dist/lint/message/lintSingleMessage.d.ts +23 -0
  39. package/dist/lint/message/lintSingleMessage.d.ts.map +1 -0
  40. package/dist/lint/message/lintSingleMessage.js +36 -0
  41. package/dist/lint/message/lintSingleMessage.test.d.ts +2 -0
  42. package/dist/lint/message/lintSingleMessage.test.d.ts.map +1 -0
  43. package/dist/lint/message/lintSingleMessage.test.js +155 -0
  44. package/dist/messages/errors.d.ts +13 -0
  45. package/dist/messages/errors.d.ts.map +1 -0
  46. package/dist/messages/errors.js +18 -0
  47. package/dist/messages/index.d.ts +3 -0
  48. package/dist/messages/index.d.ts.map +1 -0
  49. package/dist/messages/index.js +2 -0
  50. package/dist/messages/variant.d.ts +46 -0
  51. package/dist/messages/variant.d.ts.map +1 -0
  52. package/dist/messages/variant.js +177 -0
  53. package/dist/messages/variant.test.d.ts +2 -0
  54. package/dist/messages/variant.test.d.ts.map +1 -0
  55. package/dist/messages/variant.test.js +379 -0
  56. package/dist/openInlangProject.d.ts +18 -0
  57. package/dist/openInlangProject.d.ts.map +1 -0
  58. package/dist/openInlangProject.js +226 -0
  59. package/dist/openInlangProject.test.d.ts +2 -0
  60. package/dist/openInlangProject.test.d.ts.map +1 -0
  61. package/dist/openInlangProject.test.js +627 -0
  62. package/dist/parseConfig.d.ts +8 -0
  63. package/dist/parseConfig.d.ts.map +1 -0
  64. package/dist/parseConfig.js +26 -0
  65. package/dist/reactivity/map.d.ts +66 -0
  66. package/dist/reactivity/map.d.ts.map +1 -0
  67. package/dist/reactivity/map.js +143 -0
  68. package/dist/reactivity/solid.d.ts +12 -0
  69. package/dist/reactivity/solid.d.ts.map +1 -0
  70. package/dist/reactivity/solid.js +13 -0
  71. package/dist/reactivity/trigger.d.ts +11 -0
  72. package/dist/reactivity/trigger.d.ts.map +1 -0
  73. package/dist/reactivity/trigger.js +46 -0
  74. package/dist/resolve-modules/errors.d.ts +34 -0
  75. package/dist/resolve-modules/errors.d.ts.map +1 -0
  76. package/dist/resolve-modules/errors.js +35 -0
  77. package/dist/resolve-modules/import.d.ts +35 -0
  78. package/dist/resolve-modules/import.d.ts.map +1 -0
  79. package/dist/resolve-modules/import.js +40 -0
  80. package/dist/resolve-modules/import.test.d.ts +2 -0
  81. package/dist/resolve-modules/import.test.d.ts.map +1 -0
  82. package/dist/resolve-modules/import.test.js +45 -0
  83. package/dist/resolve-modules/index.d.ts +3 -0
  84. package/dist/resolve-modules/index.d.ts.map +1 -0
  85. package/dist/resolve-modules/index.js +2 -0
  86. package/dist/resolve-modules/message-lint-rules/errors.d.ts +8 -0
  87. package/dist/resolve-modules/message-lint-rules/errors.d.ts.map +1 -0
  88. package/dist/resolve-modules/message-lint-rules/errors.js +8 -0
  89. package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts +9 -0
  90. package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts.map +1 -0
  91. package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.js +21 -0
  92. package/dist/resolve-modules/plugins/errors.d.ts +28 -0
  93. package/dist/resolve-modules/plugins/errors.d.ts.map +1 -0
  94. package/dist/resolve-modules/plugins/errors.js +44 -0
  95. package/dist/resolve-modules/plugins/resolvePlugins.d.ts +3 -0
  96. package/dist/resolve-modules/plugins/resolvePlugins.d.ts.map +1 -0
  97. package/dist/resolve-modules/plugins/resolvePlugins.js +108 -0
  98. package/dist/resolve-modules/plugins/resolvePlugins.test.d.ts +2 -0
  99. package/dist/resolve-modules/plugins/resolvePlugins.test.d.ts.map +1 -0
  100. package/dist/resolve-modules/plugins/resolvePlugins.test.js +289 -0
  101. package/dist/resolve-modules/plugins/types.d.ts +60 -0
  102. package/dist/resolve-modules/plugins/types.d.ts.map +1 -0
  103. package/dist/resolve-modules/plugins/types.js +1 -0
  104. package/dist/resolve-modules/plugins/types.test.d.ts +2 -0
  105. package/dist/resolve-modules/plugins/types.test.d.ts.map +1 -0
  106. package/dist/resolve-modules/plugins/types.test.js +49 -0
  107. package/dist/resolve-modules/resolveModules.d.ts +3 -0
  108. package/dist/resolve-modules/resolveModules.d.ts.map +1 -0
  109. package/dist/resolve-modules/resolveModules.js +70 -0
  110. package/dist/resolve-modules/resolveModules.test.d.ts +2 -0
  111. package/dist/resolve-modules/resolveModules.test.d.ts.map +1 -0
  112. package/dist/resolve-modules/resolveModules.test.js +143 -0
  113. package/dist/resolve-modules/types.d.ts +62 -0
  114. package/dist/resolve-modules/types.d.ts.map +1 -0
  115. package/dist/resolve-modules/types.js +1 -0
  116. package/dist/test-utilities/createMessage.d.ts +17 -0
  117. package/dist/test-utilities/createMessage.d.ts.map +1 -0
  118. package/dist/test-utilities/createMessage.js +16 -0
  119. package/dist/test-utilities/createMessage.test.d.ts +2 -0
  120. package/dist/test-utilities/createMessage.test.d.ts.map +1 -0
  121. package/dist/test-utilities/createMessage.test.js +91 -0
  122. package/dist/test-utilities/index.d.ts +2 -0
  123. package/dist/test-utilities/index.d.ts.map +1 -0
  124. package/dist/test-utilities/index.js +1 -0
  125. package/dist/versionedInterfaces.d.ts +8 -0
  126. package/dist/versionedInterfaces.d.ts.map +1 -0
  127. package/dist/versionedInterfaces.js +8 -0
  128. package/package.json +58 -0
  129. package/src/adapter/solidAdapter.test.ts +363 -0
  130. package/src/adapter/solidAdapter.ts +77 -0
  131. package/src/api.ts +86 -0
  132. package/src/createMessageLintReportsQuery.ts +77 -0
  133. package/src/createMessagesQuery.test.ts +435 -0
  134. package/src/createMessagesQuery.ts +64 -0
  135. package/src/errors.ts +46 -0
  136. package/src/index.ts +29 -0
  137. package/src/lint/index.ts +2 -0
  138. package/src/lint/message/errors.ts +9 -0
  139. package/src/lint/message/lintMessages.test.ts +122 -0
  140. package/src/lint/message/lintMessages.ts +33 -0
  141. package/src/lint/message/lintSingleMessage.test.ts +183 -0
  142. package/src/lint/message/lintSingleMessage.ts +62 -0
  143. package/src/messages/errors.ts +25 -0
  144. package/src/messages/index.ts +2 -0
  145. package/src/messages/variant.test.ts +444 -0
  146. package/src/messages/variant.ts +242 -0
  147. package/src/openInlangProject.test.ts +734 -0
  148. package/src/openInlangProject.ts +337 -0
  149. package/src/parseConfig.ts +33 -0
  150. package/src/reactivity/map.ts +135 -0
  151. package/src/reactivity/solid.ts +36 -0
  152. package/src/reactivity/trigger.ts +46 -0
  153. package/src/resolve-modules/errors.ts +39 -0
  154. package/src/resolve-modules/import.test.ts +58 -0
  155. package/src/resolve-modules/import.ts +69 -0
  156. package/src/resolve-modules/index.ts +2 -0
  157. package/src/resolve-modules/message-lint-rules/errors.ts +9 -0
  158. package/src/resolve-modules/message-lint-rules/resolveMessageLintRules.ts +24 -0
  159. package/src/resolve-modules/plugins/errors.ts +57 -0
  160. package/src/resolve-modules/plugins/resolvePlugins.test.ts +340 -0
  161. package/src/resolve-modules/plugins/resolvePlugins.ts +170 -0
  162. package/src/resolve-modules/plugins/types.test.ts +57 -0
  163. package/src/resolve-modules/plugins/types.ts +77 -0
  164. package/src/resolve-modules/resolveModules.test.ts +176 -0
  165. package/src/resolve-modules/resolveModules.ts +97 -0
  166. package/src/resolve-modules/types.ts +71 -0
  167. package/src/test-utilities/createMessage.test.ts +100 -0
  168. package/src/test-utilities/createMessage.ts +20 -0
  169. package/src/test-utilities/index.ts +1 -0
  170. package/src/versionedInterfaces.ts +9 -0
@@ -0,0 +1,36 @@
1
+ import { MessagedLintRuleThrowedError } from "./errors.js";
2
+ /**
3
+ * Lint a single message.
4
+ *
5
+ * - the lint rule levels defaults to `warning`.
6
+ */
7
+ export const lintSingleMessage = async (args) => {
8
+ const reports = [];
9
+ const errors = [];
10
+ const promises = args.rules.map(async (rule) => {
11
+ const ruleId = rule.meta.id;
12
+ const settings = args.ruleSettings?.[ruleId] ?? {};
13
+ const level = args.ruleLevels?.[ruleId];
14
+ if (level === undefined) {
15
+ throw Error("No lint level provided for lint rule: " + ruleId);
16
+ }
17
+ try {
18
+ await rule.message({
19
+ ...args,
20
+ settings,
21
+ report: (reportArgs) => {
22
+ reports.push({
23
+ ruleId,
24
+ level,
25
+ ...reportArgs,
26
+ });
27
+ },
28
+ });
29
+ }
30
+ catch (error) {
31
+ errors.push(new MessagedLintRuleThrowedError(`Lint rule '${ruleId}' throwed while linting message "${args.message.id}".`, { cause: error }));
32
+ }
33
+ });
34
+ await Promise.all(promises);
35
+ return { data: reports, errors };
36
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lintSingleMessage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lintSingleMessage.test.d.ts","sourceRoot":"","sources":["../../../src/lint/message/lintSingleMessage.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,155 @@
1
+ import { beforeEach, describe, expect, test, vi } from "vitest";
2
+ import { lintSingleMessage } from "./lintSingleMessage.js";
3
+ import { tryCatch } from "@inlang/result";
4
+ const lintRule1 = {
5
+ meta: {
6
+ id: "messageLintRule.r.1",
7
+ displayName: { en: "" },
8
+ description: { en: "" },
9
+ },
10
+ message: vi.fn(),
11
+ };
12
+ const lintRule2 = {
13
+ meta: {
14
+ id: "messageLintRule.r.2",
15
+ displayName: { en: "" },
16
+ description: { en: "" },
17
+ },
18
+ message: vi.fn(),
19
+ };
20
+ const message1 = {};
21
+ const messages = [message1];
22
+ describe("lintSingleMessage", async () => {
23
+ beforeEach(() => {
24
+ vi.resetAllMocks();
25
+ });
26
+ describe("resolve rules and settings", async () => {
27
+ // the lint function is un-opinionated and does not set a default level.
28
+ // opinionated users like the inlang instance can very well set a default level (separation of concerns)
29
+ test("it should throw if a lint level is not provided for a given lint rule", async () => {
30
+ lintRule1.message.mockImplementation(({ report }) => report({}));
31
+ const result = await tryCatch(() => lintSingleMessage({
32
+ ruleLevels: {},
33
+ ruleSettings: {},
34
+ sourceLanguageTag: "en",
35
+ languageTags: ["en"],
36
+ messages,
37
+ message: message1,
38
+ rules: [lintRule1],
39
+ }));
40
+ expect(result.error).toBeDefined();
41
+ expect(result.data).toBeUndefined();
42
+ });
43
+ test("it should override the default lint level", async () => {
44
+ lintRule1.message.mockImplementation(({ report }) => report({}));
45
+ const reports = await lintSingleMessage({
46
+ ruleLevels: {
47
+ [lintRule1.meta.id]: "error",
48
+ },
49
+ ruleSettings: {},
50
+ sourceLanguageTag: "en",
51
+ languageTags: ["en"],
52
+ messages,
53
+ message: message1,
54
+ rules: [lintRule1],
55
+ });
56
+ expect(reports.data[0]?.level).toBe("error");
57
+ });
58
+ test("it should pass the correct settings", async () => {
59
+ const settings = {};
60
+ const fn = vi.fn();
61
+ lintRule1.message.mockImplementation(({ settings }) => fn(settings));
62
+ await lintSingleMessage({
63
+ ruleLevels: {
64
+ [lintRule1.meta.id]: "warning",
65
+ },
66
+ ruleSettings: {
67
+ [lintRule1.meta.id]: settings,
68
+ },
69
+ sourceLanguageTag: "en",
70
+ languageTags: ["en"],
71
+ messages,
72
+ message: message1,
73
+ rules: [lintRule1],
74
+ });
75
+ expect(fn).toHaveBeenCalledWith(settings);
76
+ });
77
+ });
78
+ test("it should await all rules", async () => {
79
+ let m1Called = false;
80
+ let m2Called = false;
81
+ lintRule1.message.mockImplementation(() => {
82
+ m1Called = true;
83
+ });
84
+ lintRule2.message.mockImplementation(async () => {
85
+ await new Promise((resolve) => setTimeout(resolve, 0));
86
+ m2Called = true;
87
+ });
88
+ await lintSingleMessage({
89
+ ruleLevels: {
90
+ [lintRule1.meta.id]: "warning",
91
+ [lintRule2.meta.id]: "warning",
92
+ },
93
+ ruleSettings: {},
94
+ sourceLanguageTag: "en",
95
+ languageTags: ["en"],
96
+ messages,
97
+ message: message1,
98
+ rules: [lintRule1, lintRule2],
99
+ });
100
+ expect(m1Called).toBe(true);
101
+ expect(m2Called).toBe(true);
102
+ });
103
+ test("it should process all rules in parallel", async () => {
104
+ const fn = vi.fn();
105
+ lintRule1.message.mockImplementation(async () => {
106
+ fn(lintRule1.meta.id, "before");
107
+ await new Promise((resolve) => setTimeout(resolve, 0));
108
+ fn(lintRule1.meta.id, "after");
109
+ });
110
+ lintRule2.message.mockImplementation(async () => {
111
+ fn(lintRule2.meta.id, "before");
112
+ await new Promise((resolve) => setTimeout(resolve, 0));
113
+ fn(lintRule2.meta.id, "after");
114
+ });
115
+ await lintSingleMessage({
116
+ ruleLevels: {
117
+ [lintRule1.meta.id]: "warning",
118
+ [lintRule2.meta.id]: "warning",
119
+ },
120
+ ruleSettings: {},
121
+ sourceLanguageTag: "en",
122
+ languageTags: ["en"],
123
+ messages,
124
+ message: message1,
125
+ rules: [lintRule1, lintRule2],
126
+ });
127
+ expect(fn).toHaveBeenCalledTimes(4);
128
+ expect(fn).toHaveBeenNthCalledWith(1, lintRule1.meta.id, "before");
129
+ expect(fn).toHaveBeenNthCalledWith(2, lintRule2.meta.id, "before");
130
+ expect(fn).toHaveBeenNthCalledWith(3, lintRule1.meta.id, "after");
131
+ expect(fn).toHaveBeenNthCalledWith(4, lintRule2.meta.id, "after");
132
+ });
133
+ test("it should not abort the linting process when errors occur", async () => {
134
+ lintRule1.message.mockImplementation(() => {
135
+ throw new Error("error");
136
+ });
137
+ lintRule2.message.mockImplementation(({ report }) => {
138
+ report({});
139
+ });
140
+ const result = await lintSingleMessage({
141
+ ruleLevels: {
142
+ [lintRule1.meta.id]: "warning",
143
+ [lintRule2.meta.id]: "warning",
144
+ },
145
+ ruleSettings: {},
146
+ sourceLanguageTag: "en",
147
+ languageTags: ["en"],
148
+ messages,
149
+ message: message1,
150
+ rules: [lintRule1, lintRule2],
151
+ });
152
+ expect(result.data).length(1);
153
+ expect(result.errors).length(1);
154
+ });
155
+ });
@@ -0,0 +1,13 @@
1
+ export declare class MessageVariantDoesNotExistError extends Error {
2
+ #private;
3
+ constructor(messageId: string, languageTag: string);
4
+ }
5
+ export declare class MessageVariantAlreadyExistsError extends Error {
6
+ #private;
7
+ constructor(messageId: string, languageTag: string);
8
+ }
9
+ export declare class MessagePatternsForLanguageTagDoNotExistError extends Error {
10
+ #private;
11
+ constructor(messageId: string, languageTag: string);
12
+ }
13
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/messages/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,+BAAgC,SAAQ,KAAK;;gBAG7C,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAKlD;AACD,qBAAa,gCAAiC,SAAQ,KAAK;;gBAG9C,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAKlD;AACD,qBAAa,4CAA6C,SAAQ,KAAK;;gBAG1D,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAGlD"}
@@ -0,0 +1,18 @@
1
+ export class MessageVariantDoesNotExistError extends Error {
2
+ #id = "MessageVariantDoesNotExistError";
3
+ constructor(messageId, languageTag) {
4
+ super(`For message '${messageId}' and '${languageTag}', there doesn't exist a variant for this specific matchers.`);
5
+ }
6
+ }
7
+ export class MessageVariantAlreadyExistsError extends Error {
8
+ #id = "MessageVariantAlreadyExistsError";
9
+ constructor(messageId, languageTag) {
10
+ super(`For message '${messageId}' and '${languageTag}', there already exists a variant for this specific matchers.`);
11
+ }
12
+ }
13
+ export class MessagePatternsForLanguageTagDoNotExistError extends Error {
14
+ #id = "MessagePatternsForLanguageTagDoNotExistError";
15
+ constructor(messageId, languageTag) {
16
+ super(`For message '${messageId}' there are no patterns with the languageTag '${languageTag}'.`);
17
+ }
18
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./variant.js";
2
+ export * from "./errors.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/messages/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from "./variant.js";
2
+ export * from "./errors.js";
@@ -0,0 +1,46 @@
1
+ import type { LanguageTag, Message, Variant } from "../versionedInterfaces.js";
2
+ import type { Result } from "@inlang/result";
3
+ import { MessagePatternsForLanguageTagDoNotExistError, MessageVariantAlreadyExistsError, MessageVariantDoesNotExistError } from "./errors.js";
4
+ /**
5
+ * Tries to match the most specific variant of a message.
6
+ *
7
+ * The selectors determine the specificity of a variant. If no selectors are provided,
8
+ * or if the selectors do not match any variant, the catch all variant is returned
9
+ * (if it exists).
10
+ *
11
+ * @example
12
+ * const variant = getVariant(message, { where: { languageTag: "en", selectors: { gender: "male" }}});
13
+ */
14
+ export declare function getVariant(message: Message, args: {
15
+ where: {
16
+ languageTag: LanguageTag;
17
+ selectors?: Variant["match"];
18
+ };
19
+ }): Variant | undefined;
20
+ /**
21
+ * Create a variant for a message
22
+ *
23
+ * All actions are immutable.
24
+ *
25
+ * @example
26
+ * const message = createVariant(message, { languageTag: "en", data: variant })
27
+ */
28
+ export declare function createVariant(message: Message, args: {
29
+ data: Variant;
30
+ }): Result<Message, MessageVariantAlreadyExistsError>;
31
+ /**
32
+ * Update a variant of a message
33
+ *
34
+ * All actions are immutable.
35
+ *
36
+ * @example
37
+ * const message = updateVariant(message, { languageTag: "en", selectors: { gender: "male" }, pattern: []})
38
+ */
39
+ export declare function updateVariantPattern(message: Message, args: {
40
+ where: {
41
+ languageTag: LanguageTag;
42
+ selectors: Record<string, string>;
43
+ };
44
+ data: Variant["pattern"];
45
+ }): Result<Message, MessageVariantDoesNotExistError | MessagePatternsForLanguageTagDoNotExistError>;
46
+ //# sourceMappingURL=variant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variant.d.ts","sourceRoot":"","sources":["../../src/messages/variant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EACN,4CAA4C,EAC5C,gCAAgC,EAChC,+BAA+B,EAC/B,MAAM,aAAa,CAAA;AAEpB;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CACzB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE;IACL,KAAK,EAAE;QACN,WAAW,EAAE,WAAW,CAAA;QACxB,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAC5B,CAAA;CACD,GACC,OAAO,GAAG,SAAS,CASrB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC5B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE;IACL,IAAI,EAAE,OAAO,CAAA;CACb,GACC,MAAM,CAAC,OAAO,EAAE,gCAAgC,CAAC,CAcnD;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE;IACL,KAAK,EAAE;QACN,WAAW,EAAE,WAAW,CAAA;QACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACjC,CAAA;IACD,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;CACxB,GACC,MAAM,CAAC,OAAO,EAAE,+BAA+B,GAAG,4CAA4C,CAAC,CAqBjG"}
@@ -0,0 +1,177 @@
1
+ import { MessagePatternsForLanguageTagDoNotExistError, MessageVariantAlreadyExistsError, MessageVariantDoesNotExistError, } from "./errors.js";
2
+ /**
3
+ * Tries to match the most specific variant of a message.
4
+ *
5
+ * The selectors determine the specificity of a variant. If no selectors are provided,
6
+ * or if the selectors do not match any variant, the catch all variant is returned
7
+ * (if it exists).
8
+ *
9
+ * @example
10
+ * const variant = getVariant(message, { where: { languageTag: "en", selectors: { gender: "male" }}});
11
+ */
12
+ export function getVariant(message, args) {
13
+ const variant = matchMostSpecificVariant(message, args.where.languageTag, args.where.selectors);
14
+ if (variant) {
15
+ //! do not return a reference to the message in a resource
16
+ //! modifications to the returned message will leak into the
17
+ //! resource which is considered to be immutable.
18
+ return structuredClone(variant);
19
+ }
20
+ return undefined;
21
+ }
22
+ /**
23
+ * Create a variant for a message
24
+ *
25
+ * All actions are immutable.
26
+ *
27
+ * @example
28
+ * const message = createVariant(message, { languageTag: "en", data: variant })
29
+ */
30
+ export function createVariant(message, args) {
31
+ const copy = structuredClone(message);
32
+ // check if variant already exists
33
+ if (matchVariant(copy, args.data.languageTag, args.data.match)) {
34
+ return { error: new MessageVariantAlreadyExistsError(message.id, args.data.languageTag) };
35
+ }
36
+ // need to resolve selectors to match length and order of message selectors
37
+ copy.variants.push({
38
+ ...args.data,
39
+ match: resolveSelector(copy.selectors, args.data.match),
40
+ });
41
+ return { data: copy };
42
+ }
43
+ /**
44
+ * Update a variant of a message
45
+ *
46
+ * All actions are immutable.
47
+ *
48
+ * @example
49
+ * const message = updateVariant(message, { languageTag: "en", selectors: { gender: "male" }, pattern: []})
50
+ */
51
+ export function updateVariantPattern(message, args) {
52
+ const copy = structuredClone(message);
53
+ const containsLanguageTag = message.variants.some((variant) => variant.languageTag === args.where.languageTag);
54
+ if (!containsLanguageTag) {
55
+ return {
56
+ error: new MessagePatternsForLanguageTagDoNotExistError(message.id, args.where.languageTag),
57
+ };
58
+ }
59
+ const variant = matchVariant(copy, args.where.languageTag, args.where.selectors);
60
+ if (variant === undefined) {
61
+ return { error: new MessageVariantDoesNotExistError(message.id, args.where.languageTag) };
62
+ }
63
+ if (variant) {
64
+ variant.pattern = args.data;
65
+ return { data: copy };
66
+ }
67
+ return { error: new MessageVariantDoesNotExistError(message.id, args.where.languageTag) };
68
+ }
69
+ /**
70
+ * Returns the specific variant defined by selectors or undefined
71
+ *
72
+ * @example
73
+ * const variant = matchVariant(message, languageTag: "en", selectors: { gender: "male" })
74
+ */
75
+ const matchVariant = (message, languageTag, selectors) => {
76
+ // resolve preferenceSelectors to match length and order of message selectors
77
+ const resolvedSelectors = resolveSelector(message.selectors, selectors);
78
+ const languageVariants = message.variants.filter((variant) => variant.languageTag === languageTag);
79
+ if (languageVariants.length === 0)
80
+ return undefined;
81
+ for (const variant of languageVariants) {
82
+ let isMatch = true;
83
+ //check if vaiant is a match
84
+ for (const [key, value] of Object.entries(variant.match)) {
85
+ if (resolvedSelectors[key] !== value) {
86
+ isMatch = false;
87
+ }
88
+ }
89
+ if (isMatch) {
90
+ return variant;
91
+ }
92
+ }
93
+ return undefined;
94
+ };
95
+ /**
96
+ * Returns the most specific variant of a message.
97
+ *
98
+ * @example
99
+ * const variant = matchMostSpecificVariant(message, languageTag: "en", selectors: { gender: "male" })
100
+ */
101
+ const matchMostSpecificVariant = (message, languageTag, selectors) => {
102
+ // make selector undefined if empty object
103
+ selectors = JSON.stringify(selectors) === "{}" ? undefined : selectors;
104
+ // resolve preferenceSelectors to match length and order of message selectors
105
+ const resolvedSelectors = resolveSelector(message.selectors, selectors);
106
+ const index = {};
107
+ const languageVariants = message.variants.filter((variant) => variant.languageTag === languageTag);
108
+ if (languageVariants.length === 0)
109
+ return undefined;
110
+ for (const variant of languageVariants) {
111
+ let isMatch = true;
112
+ //check if variant is a match
113
+ for (const [key, value] of Object.entries(variant.match)) {
114
+ if (resolvedSelectors[key] !== value && value !== "*") {
115
+ isMatch = false;
116
+ }
117
+ }
118
+ if (isMatch && selectors) {
119
+ // add variant to nested index
120
+ // eslint-disable-next-line no-inner-declarations
121
+ function recursiveAddToIndex(currentIndex, currentKeys, variant) {
122
+ if (currentKeys[0]?.name) {
123
+ const key = variant.match[currentKeys[0].name];
124
+ if (key) {
125
+ if (currentKeys.length === 1) {
126
+ currentIndex[key] = variant;
127
+ }
128
+ else {
129
+ if (!currentIndex[key]) {
130
+ currentIndex[key] = {};
131
+ }
132
+ recursiveAddToIndex(currentIndex[key], currentKeys.slice(1), variant);
133
+ }
134
+ }
135
+ }
136
+ }
137
+ recursiveAddToIndex(index, message.selectors, variant);
138
+ }
139
+ else if (isMatch && !selectors) {
140
+ return variant;
141
+ }
142
+ }
143
+ //find the most specific variant
144
+ const findOptimalMatch = (index, selectors) => {
145
+ const keys = Object.keys(index);
146
+ for (const key of keys) {
147
+ if (key === selectors[0] || key === "*") {
148
+ const nextOptimal = selectors.slice(1);
149
+ if (nextOptimal.length === 0) {
150
+ return index[key] || undefined;
151
+ }
152
+ const match = findOptimalMatch(index[key], nextOptimal);
153
+ if (match !== undefined) {
154
+ return match;
155
+ }
156
+ }
157
+ }
158
+ return undefined;
159
+ };
160
+ return findOptimalMatch(index, Object.values(resolvedSelectors));
161
+ };
162
+ /**
163
+ * Returns resolved selector.
164
+ * -> Adds all possible selectors, if not defined it adds '*'. Order is determined by messageSelectors
165
+ *
166
+ * @example
167
+ * const variant = resolveSelector(["gender","count"], selector: {count: "2"})
168
+ */
169
+ const resolveSelector = (messageSelectors, selectors) => {
170
+ const resolvedSelectors = {};
171
+ if (!selectors)
172
+ return {};
173
+ for (const messageSelector of messageSelectors) {
174
+ resolvedSelectors[messageSelector.name] = selectors[messageSelector.name] ?? "*";
175
+ }
176
+ return resolvedSelectors;
177
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=variant.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variant.test.d.ts","sourceRoot":"","sources":["../../src/messages/variant.test.ts"],"names":[],"mappings":""}