@inlang/sdk 2.4.10 → 2.6.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 (107) hide show
  1. package/README.md +6 -6
  2. package/dist/database/initDbAndSchema.test.js +42 -0
  3. package/dist/database/initDbAndSchema.test.js.map +1 -1
  4. package/dist/database/jsonbPlugin.d.ts.map +1 -1
  5. package/dist/database/jsonbPlugin.js +73 -8
  6. package/dist/database/jsonbPlugin.js.map +1 -1
  7. package/dist/database/jsonbPlugin.test.js +48 -1
  8. package/dist/database/jsonbPlugin.test.js.map +1 -1
  9. package/dist/import-export/upsertBundleNestedMatchByProperties.d.ts.map +1 -1
  10. package/dist/migrations/v2/createMessageV1.d.ts.map +1 -1
  11. package/dist/plugin/importPlugins.test.js +3 -3
  12. package/dist/plugin/importPlugins.test.js.map +1 -1
  13. package/dist/project/README_CONTENT.d.ts +8 -0
  14. package/dist/project/README_CONTENT.d.ts.map +1 -0
  15. package/dist/project/README_CONTENT.js +111 -0
  16. package/dist/project/README_CONTENT.js.map +1 -0
  17. package/dist/project/api.d.ts +4 -0
  18. package/dist/project/api.d.ts.map +1 -1
  19. package/dist/project/api.js.map +1 -1
  20. package/dist/project/loadProjectFromDirectory.d.ts +18 -25
  21. package/dist/project/loadProjectFromDirectory.d.ts.map +1 -1
  22. package/dist/project/loadProjectFromDirectory.js +64 -60
  23. package/dist/project/loadProjectFromDirectory.js.map +1 -1
  24. package/dist/project/loadProjectFromDirectory.test.js +91 -3
  25. package/dist/project/loadProjectFromDirectory.test.js.map +1 -1
  26. package/dist/project/meta.d.ts +11 -0
  27. package/dist/project/meta.d.ts.map +1 -0
  28. package/dist/project/meta.js +129 -0
  29. package/dist/project/meta.js.map +1 -0
  30. package/dist/project/meta.test.d.ts +2 -0
  31. package/dist/project/meta.test.d.ts.map +1 -0
  32. package/dist/project/meta.test.js +21 -0
  33. package/dist/project/meta.test.js.map +1 -0
  34. package/dist/project/path-helpers.d.ts +27 -0
  35. package/dist/project/path-helpers.d.ts.map +1 -0
  36. package/dist/project/path-helpers.js +59 -0
  37. package/dist/project/path-helpers.js.map +1 -0
  38. package/dist/project/path-helpers.test.d.ts +2 -0
  39. package/dist/project/path-helpers.test.d.ts.map +1 -0
  40. package/dist/project/path-helpers.test.js +27 -0
  41. package/dist/project/path-helpers.test.js.map +1 -0
  42. package/dist/project/saveProjectToDirectory.d.ts +29 -0
  43. package/dist/project/saveProjectToDirectory.d.ts.map +1 -1
  44. package/dist/project/saveProjectToDirectory.js +57 -8
  45. package/dist/project/saveProjectToDirectory.js.map +1 -1
  46. package/dist/project/saveProjectToDirectory.test.js +171 -1
  47. package/dist/project/saveProjectToDirectory.test.js.map +1 -1
  48. package/dist/query-utilities/insertBundleNested.d.ts.map +1 -1
  49. package/dist/query-utilities/selectBundleNested.d.ts.map +1 -1
  50. package/dist/query-utilities/updateBundleNested.d.ts.map +1 -1
  51. package/dist/query-utilities/upsertBundleNested.d.ts.map +1 -1
  52. package/dist/services/env-variables/index.js +1 -1
  53. package/dist/services/env-variables/index.js.map +1 -1
  54. package/dist/services/telemetry/capture.d.ts.map +1 -1
  55. package/dist/utilities/detectJsonFormatting.d.ts.map +1 -1
  56. package/package.json +11 -11
  57. package/src/database/initDbAndSchema.test.ts +48 -0
  58. package/src/database/jsonbPlugin.test.ts +60 -1
  59. package/src/database/jsonbPlugin.ts +75 -10
  60. package/src/plugin/importPlugins.test.ts +3 -3
  61. package/src/project/README_CONTENT.ts +110 -0
  62. package/src/project/api.ts +4 -0
  63. package/src/project/loadProjectFromDirectory.test.ts +139 -3
  64. package/src/project/loadProjectFromDirectory.ts +93 -86
  65. package/src/project/meta.test.ts +26 -0
  66. package/src/project/meta.ts +147 -0
  67. package/src/project/path-helpers.test.ts +45 -0
  68. package/src/project/path-helpers.ts +71 -0
  69. package/src/project/saveProjectToDirectory.test.ts +245 -1
  70. package/src/project/saveProjectToDirectory.ts +89 -11
  71. package/src/services/env-variables/createIndexFile.js +0 -4
  72. package/dist/database/jsonPlugin.d.ts +0 -11
  73. package/dist/database/jsonPlugin.d.ts.map +0 -1
  74. package/dist/database/jsonPlugin.js +0 -197
  75. package/dist/database/jsonPlugin.js.map +0 -1
  76. package/dist/project/fs-sync.playground.test.d.ts +0 -2
  77. package/dist/project/fs-sync.playground.test.d.ts.map +0 -1
  78. package/dist/project/fs-sync.playground.test.js +0 -535
  79. package/dist/project/fs-sync.playground.test.js.map +0 -1
  80. package/dist/schema-definitions/bundle.d.ts +0 -22
  81. package/dist/schema-definitions/bundle.d.ts.map +0 -1
  82. package/dist/schema-definitions/bundle.js +0 -18
  83. package/dist/schema-definitions/bundle.js.map +0 -1
  84. package/dist/schema-definitions/bundle.test.d.ts +0 -2
  85. package/dist/schema-definitions/bundle.test.d.ts.map +0 -1
  86. package/dist/schema-definitions/bundle.test.js +0 -23
  87. package/dist/schema-definitions/bundle.test.js.map +0 -1
  88. package/dist/schema-definitions/index.d.ts +0 -4
  89. package/dist/schema-definitions/index.d.ts.map +0 -1
  90. package/dist/schema-definitions/index.js +0 -4
  91. package/dist/schema-definitions/index.js.map +0 -1
  92. package/dist/schema-definitions/message.d.ts +0 -35
  93. package/dist/schema-definitions/message.d.ts.map +0 -1
  94. package/dist/schema-definitions/message.js +0 -26
  95. package/dist/schema-definitions/message.js.map +0 -1
  96. package/dist/schema-definitions/message.test.d.ts +0 -2
  97. package/dist/schema-definitions/message.test.d.ts.map +0 -1
  98. package/dist/schema-definitions/message.test.js +0 -45
  99. package/dist/schema-definitions/message.test.js.map +0 -1
  100. package/dist/schema-definitions/variant.d.ts +0 -35
  101. package/dist/schema-definitions/variant.d.ts.map +0 -1
  102. package/dist/schema-definitions/variant.js +0 -26
  103. package/dist/schema-definitions/variant.js.map +0 -1
  104. package/dist/schema-definitions/variant.test.d.ts +0 -2
  105. package/dist/schema-definitions/variant.test.d.ts.map +0 -1
  106. package/dist/schema-definitions/variant.test.js +0 -67
  107. package/dist/schema-definitions/variant.test.js.map +0 -1
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Inlang file format SDK
2
2
 
3
- [![NPM Downloads](https://img.shields.io/npm/dw/%40inlang%2Fsdk?logo=npm&logoColor=red&label=npm%20downloads)](https://www.npmjs.com/package/@inlang/sdk) [![Discord](https://img.shields.io/discord/897438559458430986?style=flat&logo=discord&labelColor=white)](https://discord.gg/ecsc6bFtZw)
3
+ [![NPM Downloads](https://img.shields.io/npm/dw/%40inlang%2Fsdk?logo=npm&logoColor=red&label=npm%20downloads)](https://www.npmjs.com/package/@inlang/sdk) [![Discord](https://img.shields.io/discord/897438559458430986?style=flat&logo=discord&labelColor=white)](https://discord.gg/gdMPPWy57R)
4
4
 
5
5
 
6
6
  <p align="center">
7
- <img src="https://cdn.jsdelivr.net/gh/opral/monorepo/inlang/packages/sdk/assets/open-file.svg" alt="Inlang SDK opens .inlang files">
7
+ <img src="https://cdn.jsdelivr.net/gh/opral/inlang/packages/sdk/assets/open-file.svg" alt="Inlang SDK opens .inlang files">
8
8
  </p>
9
9
 
10
10
  ## Outline
@@ -27,7 +27,7 @@ The inlang SDK is the official specification and parser for `.inlang` files.
27
27
  - 🖊️ **CRUD API**: Query messages with SQL.
28
28
  - 🧩 **Plugin System**: Extend the capabilities with plugins.
29
29
  - 📦 **Import/Export**: Import and export messages in different file formats.
30
- - [<img src="https://raw.githubusercontent.com/opral/monorepo/refs/heads/main/lix/assets/lix-icon.svg" width="20" height="12" alt="Lix Icon">**Change control**](https://lix.opral.com/): Collaboration, change proposals, reviews, and automation.
30
+ - [<img src="https://raw.githubusercontent.com/opral/inlang/refs/heads/main/lix/assets/lix-icon.svg" width="20" height="12" alt="Lix Icon">**Change control**](https://lix.opral.com/): Collaboration, change proposals, reviews, and automation.
31
31
 
32
32
 
33
33
 
@@ -76,7 +76,7 @@ Find available plugins on https://inlang.com/c/plugins.
76
76
 
77
77
  Implement the `InlangPlugin` type.
78
78
 
79
- Examples can be found [here](https://github.com/opral/monorepo/tree/main/inlang/packages/plugins). Particulary the [message format plugin](https://github.com/opral/monorepo/tree/main/inlang/packages/plugins/inlang-message-format) is a good starting point.
79
+ Examples can be found [here](https://github.com/opral/inlang/tree/main/packages/plugins). Particulary the [message format plugin](https://github.com/opral/inlang/tree/main/packages/plugins/inlang-message-format) is a good starting point.
80
80
 
81
81
  ```typescript
82
82
  const myPlugin: InlangPlugin = {
@@ -237,6 +237,6 @@ await saveProjectToDirectory({
237
237
 
238
238
  ## Listing on inlang.com
239
239
 
240
- To list your app/plugin on inlang.com, please open a pull request to the [registry.json file](https://github.com/opral/monorepo/blob/main/inlang/packages/marketplace-registry/registry.json).
240
+ To list your app/plugin on inlang.com, please open a pull request to the [registry.json file](https://github.com/opral/inlang/blob/main/packages/marketplace-registry/registry.json).
241
241
 
242
- Make sure that the link you are contributing points to a `marketplace-manifest.json` file. An example of can be found [here](https://github.com/opral/monorepo/blob/main/inlang/packages/fink/marketplace-manifest.json)
242
+ Make sure that the link you are contributing points to a `marketplace-manifest.json` file. An example of can be found [here](https://github.com/opral/inlang/blob/main/packages/fink/marketplace-manifest.json)
@@ -90,6 +90,48 @@ test("it should handle json serialization and parsing for bundles", async () =>
90
90
  },
91
91
  ]);
92
92
  });
93
+ // https://github.com/opral/paraglide-js/issues/571
94
+ test("it should preserve json-like text in variant patterns", async () => {
95
+ const sqlite = await createInMemoryDatabase({
96
+ readOnly: false,
97
+ });
98
+ const db = initDb({ sqlite });
99
+ const bundle = await db
100
+ .insertInto("bundle")
101
+ .values({ id: "json_array" })
102
+ .returningAll()
103
+ .executeTakeFirstOrThrow();
104
+ const message = await db
105
+ .insertInto("message")
106
+ .values({
107
+ bundleId: bundle.id,
108
+ locale: "en",
109
+ })
110
+ .returningAll()
111
+ .executeTakeFirstOrThrow();
112
+ await db
113
+ .insertInto("variant")
114
+ .values({
115
+ messageId: message.id,
116
+ pattern: [
117
+ {
118
+ type: "text",
119
+ value: '["a", "b", "c"]',
120
+ },
121
+ ],
122
+ })
123
+ .execute();
124
+ const variant = await db
125
+ .selectFrom("variant")
126
+ .selectAll()
127
+ .executeTakeFirstOrThrow();
128
+ expect(variant.pattern).toStrictEqual([
129
+ {
130
+ type: "text",
131
+ value: '["a", "b", "c"]',
132
+ },
133
+ ]);
134
+ });
93
135
  // https://github.com/opral/inlang-sdk/issues/209
94
136
  test.todo("it should enable foreign key constraints", async () => {
95
137
  const sqlite = await createInMemoryDatabase({
@@ -1 +1 @@
1
- {"version":3,"file":"initDbAndSchema.test.js","sourceRoot":"/","sources":["database/initDbAndSchema.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { createInMemoryDatabase } from \"sqlite-wasm-kysely\";\nimport { test, expect } from \"vitest\";\nimport { initDb } from \"./initDb.js\";\nimport { isHumanId } from \"../human-id/human-id.js\";\nimport { validate as isUuid } from \"uuid\";\n\ntest(\"bundle default values\", async () => {\n\tconst sqlite = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\tconst db = initDb({ sqlite });\n\n\tconst bundle = await db\n\t\t.insertInto(\"bundle\")\n\t\t.defaultValues()\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(isHumanId(bundle.id)).toBe(true);\n\texpect(bundle.declarations).toStrictEqual([]);\n});\n\ntest(\"message default values\", async () => {\n\tconst sqlite = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\tconst db = initDb({ sqlite });\n\n\tconst bundle = await db\n\t\t.insertInto(\"bundle\")\n\t\t.defaultValues()\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\tconst message = await db\n\t\t.insertInto(\"message\")\n\t\t.values({\n\t\t\tbundleId: bundle.id,\n\t\t\tlocale: \"en\",\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(isUuid(message.id)).toBe(true);\n\texpect(message.selectors).toStrictEqual([]);\n});\n\ntest(\"variant default values\", async () => {\n\tconst sqlite = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\tconst db = initDb({ sqlite });\n\n\tconst bundle = await db\n\t\t.insertInto(\"bundle\")\n\t\t.defaultValues()\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\tconst message = await db\n\t\t.insertInto(\"message\")\n\t\t.values({\n\t\t\tbundleId: bundle.id,\n\t\t\tlocale: \"en\",\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\tconst variant = await db\n\t\t.insertInto(\"variant\")\n\t\t.values({\n\t\t\tmessageId: message.id,\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(isUuid(variant.id)).toBe(true);\n\texpect(variant.matches).toStrictEqual([]);\n\texpect(variant.pattern).toStrictEqual([]);\n});\n\ntest(\"it should handle json serialization and parsing for bundles\", async () => {\n\tconst sqlite = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\tconst db = initDb({ sqlite });\n\n\tconst bundle = await db\n\t\t.insertInto(\"bundle\")\n\t\t.values({\n\t\t\tdeclarations: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"input-variable\",\n\t\t\t\t\tname: \"mock\",\n\t\t\t\t},\n\t\t\t],\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(bundle.declarations).toStrictEqual([\n\t\t{\n\t\t\ttype: \"input-variable\",\n\t\t\tname: \"mock\",\n\t\t},\n\t]);\n});\n\n// https://github.com/opral/inlang-sdk/issues/209\ntest.todo(\"it should enable foreign key constraints\", async () => {\n\tconst sqlite = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\tconst db = initDb({ sqlite });\n\n\texpect(() =>\n\t\tdb\n\t\t\t.insertInto(\"message\")\n\t\t\t.values({\n\t\t\t\tbundleId: \"non-existent\",\n\t\t\t\tlocale: \"en\",\n\t\t\t})\n\t\t\t.execute()\n\t).rejects.toThrow();\n});\n"]}
1
+ {"version":3,"file":"initDbAndSchema.test.js","sourceRoot":"/","sources":["database/initDbAndSchema.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { createInMemoryDatabase } from \"sqlite-wasm-kysely\";\nimport { test, expect } from \"vitest\";\nimport { initDb } from \"./initDb.js\";\nimport { isHumanId } from \"../human-id/human-id.js\";\nimport { validate as isUuid } from \"uuid\";\n\ntest(\"bundle default values\", async () => {\n\tconst sqlite = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\tconst db = initDb({ sqlite });\n\n\tconst bundle = await db\n\t\t.insertInto(\"bundle\")\n\t\t.defaultValues()\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(isHumanId(bundle.id)).toBe(true);\n\texpect(bundle.declarations).toStrictEqual([]);\n});\n\ntest(\"message default values\", async () => {\n\tconst sqlite = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\tconst db = initDb({ sqlite });\n\n\tconst bundle = await db\n\t\t.insertInto(\"bundle\")\n\t\t.defaultValues()\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\tconst message = await db\n\t\t.insertInto(\"message\")\n\t\t.values({\n\t\t\tbundleId: bundle.id,\n\t\t\tlocale: \"en\",\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(isUuid(message.id)).toBe(true);\n\texpect(message.selectors).toStrictEqual([]);\n});\n\ntest(\"variant default values\", async () => {\n\tconst sqlite = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\tconst db = initDb({ sqlite });\n\n\tconst bundle = await db\n\t\t.insertInto(\"bundle\")\n\t\t.defaultValues()\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\tconst message = await db\n\t\t.insertInto(\"message\")\n\t\t.values({\n\t\t\tbundleId: bundle.id,\n\t\t\tlocale: \"en\",\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\tconst variant = await db\n\t\t.insertInto(\"variant\")\n\t\t.values({\n\t\t\tmessageId: message.id,\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(isUuid(variant.id)).toBe(true);\n\texpect(variant.matches).toStrictEqual([]);\n\texpect(variant.pattern).toStrictEqual([]);\n});\n\ntest(\"it should handle json serialization and parsing for bundles\", async () => {\n\tconst sqlite = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\tconst db = initDb({ sqlite });\n\n\tconst bundle = await db\n\t\t.insertInto(\"bundle\")\n\t\t.values({\n\t\t\tdeclarations: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"input-variable\",\n\t\t\t\t\tname: \"mock\",\n\t\t\t\t},\n\t\t\t],\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(bundle.declarations).toStrictEqual([\n\t\t{\n\t\t\ttype: \"input-variable\",\n\t\t\tname: \"mock\",\n\t\t},\n\t]);\n});\n\n// https://github.com/opral/paraglide-js/issues/571\ntest(\"it should preserve json-like text in variant patterns\", async () => {\n\tconst sqlite = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\tconst db = initDb({ sqlite });\n\n\tconst bundle = await db\n\t\t.insertInto(\"bundle\")\n\t\t.values({ id: \"json_array\" })\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\tconst message = await db\n\t\t.insertInto(\"message\")\n\t\t.values({\n\t\t\tbundleId: bundle.id,\n\t\t\tlocale: \"en\",\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\tawait db\n\t\t.insertInto(\"variant\")\n\t\t.values({\n\t\t\tmessageId: message.id,\n\t\t\tpattern: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tvalue: '[\"a\", \"b\", \"c\"]',\n\t\t\t\t},\n\t\t\t],\n\t\t})\n\t\t.execute();\n\n\tconst variant = await db\n\t\t.selectFrom(\"variant\")\n\t\t.selectAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(variant.pattern).toStrictEqual([\n\t\t{\n\t\t\ttype: \"text\",\n\t\t\tvalue: '[\"a\", \"b\", \"c\"]',\n\t\t},\n\t]);\n});\n\n// https://github.com/opral/inlang-sdk/issues/209\ntest.todo(\"it should enable foreign key constraints\", async () => {\n\tconst sqlite = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\tconst db = initDb({ sqlite });\n\n\texpect(() =>\n\t\tdb\n\t\t\t.insertInto(\"message\")\n\t\t\t.values({\n\t\t\t\tbundleId: \"non-existent\",\n\t\t\t\tlocale: \"en\",\n\t\t\t})\n\t\t\t.execute()\n\t).rejects.toThrow();\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"jsonbPlugin.d.ts","sourceRoot":"/","sources":["database/jsonbPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAON,KAAK,YAAY,EACjB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,UAAU,EAEf,MAAM,QAAQ,CAAC;AAChB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,qBAAa,WAAY,YAAW,YAAY;;gBAKnC,IAAI,EAAE;QAAE,QAAQ,EAAE,kBAAkB,CAAA;KAAE;IAIlD;;;;OAIG;IACH,cAAc,CAAC,IAAI,EAAE,wBAAwB,GAAG,iBAAiB;IAWjE;;;OAGG;IACG,eAAe,CACpB,IAAI,EAAE,yBAAyB,GAC7B,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;CAwBnC"}
1
+ {"version":3,"file":"jsonbPlugin.d.ts","sourceRoot":"/","sources":["database/jsonbPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAMN,KAAK,YAAY,EACjB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,UAAU,EAEf,MAAM,QAAQ,CAAC;AAChB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,qBAAa,WAAY,YAAW,YAAY;;gBAInC,IAAI,EAAE;QAAE,QAAQ,EAAE,kBAAkB,CAAA;KAAE;IAIlD;;;;OAIG;IACH,cAAc,CAAC,IAAI,EAAE,wBAAwB,GAAG,iBAAiB;IAWjE;;;OAGG;IACG,eAAe,CACpB,IAAI,EAAE,yBAAyB,GAC7B,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;CAsCnC"}
@@ -1,7 +1,6 @@
1
- import { OperationNodeTransformer, sql, ValueListNode, ValueNode, ValuesNode, ParseJSONResultsPlugin, OnConflictNode, } from "kysely";
1
+ import { OperationNodeTransformer, sql, ValueListNode, ValueNode, ValuesNode, OnConflictNode, } from "kysely";
2
2
  export class JsonbPlugin {
3
3
  #serializeJsonTransformer = new SerializeJsonbTransformer();
4
- #parseJsonPlugin = new ParseJSONResultsPlugin();
5
4
  #database;
6
5
  constructor(args) {
7
6
  this.#database = args.database;
@@ -26,26 +25,92 @@ export class JsonbPlugin {
26
25
  async transformResult(args) {
27
26
  for (const row of args.result.rows) {
28
27
  for (const key in row) {
29
- if (row[key] instanceof ArrayBuffer ||
30
- // uint8array, etc
31
- ArrayBuffer.isView(row[key])) {
28
+ const value = row[key];
29
+ // Always try to decode JSONB binary payloads.
30
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
32
31
  try {
33
32
  const res = this.#database.exec(`SELECT json(?)`, {
34
33
  returnValue: "resultRows",
35
- bind: [row[key]],
34
+ bind: [value],
36
35
  });
37
36
  row[key] = JSON.parse(res[0]);
38
37
  }
39
38
  catch {
40
39
  // it's not a json binary
41
40
  }
41
+ continue;
42
+ }
43
+ // Only parse JSON text for known JSON columns to avoid
44
+ // coercing nested string values that look like JSON.
45
+ if (JSON_COLUMNS.has(key) && typeof value === "string") {
46
+ try {
47
+ row[key] = JSON.parse(value);
48
+ }
49
+ catch {
50
+ // leave as-is if parsing fails
51
+ }
52
+ }
53
+ if (key === "messages" && Array.isArray(row[key])) {
54
+ row[key] = normalizeMessages(row[key]);
55
+ }
56
+ else if (key === "variants" && Array.isArray(row[key])) {
57
+ row[key] = normalizeVariants(row[key]);
42
58
  }
43
59
  }
44
60
  }
45
- // in case it's a regular (text) json, run it through kyseley's json parser
46
- return this.#parseJsonPlugin.transformResult(args);
61
+ return args.result;
47
62
  }
48
63
  }
64
+ const JSON_COLUMNS = new Set([
65
+ "declarations",
66
+ "selectors",
67
+ "matches",
68
+ "pattern",
69
+ // jsonArrayFrom results in nested bundle queries
70
+ "messages",
71
+ "variants",
72
+ ]);
73
+ function parseJsonIfString(value) {
74
+ if (typeof value !== "string") {
75
+ return value;
76
+ }
77
+ try {
78
+ return JSON.parse(value);
79
+ }
80
+ catch {
81
+ return value;
82
+ }
83
+ }
84
+ function normalizeVariants(variants) {
85
+ return variants.map((variant) => {
86
+ if (variant && typeof variant === "object") {
87
+ return {
88
+ ...variant,
89
+ matches: parseJsonIfString(variant.matches),
90
+ pattern: parseJsonIfString(variant.pattern),
91
+ };
92
+ }
93
+ return variant;
94
+ });
95
+ }
96
+ function normalizeMessages(messages) {
97
+ return messages.map((message) => {
98
+ if (message && typeof message === "object") {
99
+ const rawVariants = Array.isArray(message.variants)
100
+ ? message.variants
101
+ : parseJsonIfString(message.variants);
102
+ const normalizedVariants = Array.isArray(rawVariants)
103
+ ? normalizeVariants(rawVariants)
104
+ : rawVariants;
105
+ return {
106
+ ...message,
107
+ selectors: parseJsonIfString(message.selectors),
108
+ variants: normalizedVariants,
109
+ };
110
+ }
111
+ return message;
112
+ });
113
+ }
49
114
  class SerializeJsonbTransformer extends OperationNodeTransformer {
50
115
  transformOnConflict(node) {
51
116
  return super.transformOnConflict({
@@ -1 +1 @@
1
- {"version":3,"file":"jsonbPlugin.js","sourceRoot":"/","sources":["database/jsonbPlugin.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tOperationNodeTransformer,\n\tsql,\n\tValueListNode,\n\tValueNode,\n\tValuesNode,\n\tParseJSONResultsPlugin,\n\ttype KyselyPlugin,\n\ttype PluginTransformQueryArgs,\n\ttype PluginTransformResultArgs,\n\ttype QueryResult,\n\ttype RootOperationNode,\n\ttype UnknownRow,\n\tOnConflictNode,\n} from \"kysely\";\nimport type { SqliteWasmDatabase } from \"sqlite-wasm-kysely\";\n\nexport class JsonbPlugin implements KyselyPlugin {\n\t#serializeJsonTransformer = new SerializeJsonbTransformer();\n\t#parseJsonPlugin = new ParseJSONResultsPlugin();\n\t#database: SqliteWasmDatabase;\n\n\tconstructor(args: { database: SqliteWasmDatabase }) {\n\t\tthis.#database = args.database;\n\t}\n\n\t/**\n\t * For an outgoing query like insert or update, the JSON\n\t * values are transformed into `jsonb` function calls when\n\t * executed against the database.\n\t */\n\ttransformQuery(args: PluginTransformQueryArgs): RootOperationNode {\n\t\tif (\n\t\t\targs.node.kind === \"InsertQueryNode\" ||\n\t\t\targs.node.kind === \"UpdateQueryNode\"\n\t\t) {\n\t\t\tconst result = this.#serializeJsonTransformer.transformNode(args.node);\n\t\t\treturn result;\n\t\t}\n\t\treturn args.node;\n\t}\n\n\t/**\n\t * For incoming query results, the JSON binaries are parsed\n\t * into JSON objects.\n\t */\n\tasync transformResult(\n\t\targs: PluginTransformResultArgs\n\t): Promise<QueryResult<UnknownRow>> {\n\t\tfor (const row of args.result.rows) {\n\t\t\tfor (const key in row) {\n\t\t\t\tif (\n\t\t\t\t\trow[key] instanceof ArrayBuffer ||\n\t\t\t\t\t// uint8array, etc\n\t\t\t\t\tArrayBuffer.isView(row[key])\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst res = this.#database.exec(`SELECT json(?)`, {\n\t\t\t\t\t\t\treturnValue: \"resultRows\",\n\t\t\t\t\t\t\tbind: [row[key] as any],\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\trow[key] = JSON.parse(res[0] as any);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// it's not a json binary\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// in case it's a regular (text) json, run it through kyseley's json parser\n\t\treturn this.#parseJsonPlugin.transformResult(args);\n\t}\n}\n\nclass SerializeJsonbTransformer extends OperationNodeTransformer {\n\tprotected override transformOnConflict(node: OnConflictNode): OnConflictNode {\n\t\treturn super.transformOnConflict({\n\t\t\t...node,\n\t\t\tupdates: node.updates?.map((updateItem) => {\n\t\t\t\tif (updateItem.kind !== \"ColumnUpdateNode\") {\n\t\t\t\t\treturn updateItem;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"ColumnUpdateNode\",\n\t\t\t\t\tcolumn: updateItem.column,\n\t\t\t\t\t// @ts-expect-error - we know that the value is a ValueNode\n\t\t\t\t\tvalue: this.transformValue(updateItem.value),\n\t\t\t\t};\n\t\t\t}),\n\t\t});\n\t}\n\n\tprotected override transformValue(node: ValueNode): ValueNode {\n\t\tconst { value } = node;\n\t\tconst serializedValue = maybeSerializeJson(value);\n\t\tif (value === serializedValue) {\n\t\t\treturn node;\n\t\t}\n\t\t// @ts-expect-error - we know that the node is a ValueNode\n\t\treturn sql`jsonb(${serializedValue})`.toOperationNode();\n\t}\n\t/**\n\t * Transforms the value list node by replacing all JSON objects with `jsonb` function calls.\n\t */\n\tprotected override transformValueList(node: ValueListNode): ValueListNode {\n\t\treturn super.transformValueList({\n\t\t\t...node,\n\t\t\tvalues: node.values.map((listNodeItem) => {\n\t\t\t\tif (listNodeItem.kind !== \"ValueNode\") {\n\t\t\t\t\treturn listNodeItem;\n\t\t\t\t}\n\t\t\t\t// @ts-expect-error - we know that the node is a ValueNode\n\t\t\t\tconst { value } = listNodeItem;\n\t\t\t\tconst serializedValue = maybeSerializeJson(value);\n\n\t\t\t\tif (value === serializedValue) {\n\t\t\t\t\treturn listNodeItem;\n\t\t\t\t}\n\t\t\t\treturn sql`jsonb(${serializedValue})`.toOperationNode();\n\t\t\t}),\n\t\t});\n\t}\n\n\t/**\n\t * Why this function is needed or why this works remains a mystery.\n\t */\n\toverride transformValues(node: ValuesNode): ValuesNode {\n\t\treturn super.transformValues({\n\t\t\t...node,\n\t\t\tvalues: node.values.map((valueItemNode) => {\n\t\t\t\tif (valueItemNode.kind !== \"PrimitiveValueListNode\") {\n\t\t\t\t\treturn valueItemNode;\n\t\t\t\t}\n\n\t\t\t\t// change valueItem to ValueListNode\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"ValueListNode\",\n\t\t\t\t\tvalues: valueItemNode.values.map(\n\t\t\t\t\t\t(value) =>\n\t\t\t\t\t\t\t({\n\t\t\t\t\t\t\t\tkind: \"ValueNode\",\n\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t}) as ValueNode\n\t\t\t\t\t),\n\t\t\t\t} as ValueListNode;\n\t\t\t}),\n\t\t});\n\t}\n}\n\nfunction maybeSerializeJson(value: any): any {\n\tif (\n\t\t// binary data\n\t\tvalue instanceof ArrayBuffer ||\n\t\t// uint8array, etc\n\t\tArrayBuffer.isView(value) ||\n\t\tvalue === null ||\n\t\tvalue === undefined\n\t) {\n\t\treturn value;\n\t} else if (typeof value === \"object\" || Array.isArray(value)) {\n\t\treturn JSON.stringify(value);\n\t}\n\treturn value;\n}\n\n// The code here didn't work https://github.com/opral/inlang-sdk/issues/132#issuecomment-2339321910\n// but would be the \"right\" solution to avoid heuristics which column might or might not be a json column\n// // modifies the query in place for readability and performance\n// function mapQuery(\n// \tnode: InsertQueryNode,\n// \tjsonColumns: TableSchema\n// ): InsertQueryNode {\n// \t// if the query is not an insert query, we don't need to do anything\n// \tif (node.into === undefined) {\n// \t\treturn node;\n// \t}\n// \t// if the table is not in the schema that has json columns, we don't need to do anything\n// \tconst columnsWithJson = jsonColumns[node.into.table.identifier.name];\n// \tif (columnsWithJson === undefined) {\n// \t\treturn node;\n// \t}\n// \t// find the indexes of the values that need to be transformed\n// \t// SQL query: INSERT INTO table (col1, col2) VALUES (val1, val2)\n// \tconst indexesThatNeedToBeTransformed: [number, string][] = [];\n// \tfor (const [i, col] of node.columns?.entries() ?? []) {\n// \t\tconst jsonType = columnsWithJson[col.column.name];\n// \t\tif (jsonType !== undefined) {\n// \t\t\tindexesThatNeedToBeTransformed.push([i, jsonType]);\n// \t\t}\n// \t}\n// \tconst values = structuredClone(node.values);\n// \tfor (const [i, jsonType] of indexesThatNeedToBeTransformed) {\n// \t\tif (\n// \t\t\t// top level values node that should contain a list of values\n// \t\t\tnode.values?.kind !== \"ValuesNode\" &&\n// \t\t\t// the node we are interested in must be a value node\n// \t\t\t// @ts-expect-error - we know that the node is a ValuesNode with values\n// \t\t\t(node.values as ValuesNode).values?.[i].kind !== \"ValueNode\"\n// \t\t) {\n// \t\t\tthrow new Error(\"Unexpected node structure\");\n// \t\t}\n// \t\tconst serializedJson = JSON.stringify(node.values.values[0].values[i]);\n// \t\t// @ts-expect-error - we know that the node is a ValuesNode with values\n// \t\tvalues.values[0].values[i] =\n// \t\t\tjsonType === \"jsonb\"\n// \t\t\t\t? sql`jsonb(${serializedJson})`.toOperationNode()\n// \t\t\t\t: sql`json(${serializedJson})`.toOperationNode();\n// \t}\n\n// \treturn {\n// \t\t...node,\n// \t\tvalues,\n// \t};\n// }\n"]}
1
+ {"version":3,"file":"jsonbPlugin.js","sourceRoot":"/","sources":["database/jsonbPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,wBAAwB,EACxB,GAAG,EACH,aAAa,EACb,SAAS,EACT,UAAU,EAOV,cAAc,GACd,MAAM,QAAQ,CAAC;AAGhB,MAAM,OAAO,WAAW;IACvB,yBAAyB,GAAG,IAAI,yBAAyB,EAAE,CAAC;IAC5D,SAAS,CAAqB;IAE9B,YAAY,IAAsC;QACjD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC;IAChC,CAAC;IAED;;;;OAIG;IACH,cAAc,CAAC,IAA8B;QAC5C,IACC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB;YACpC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB,EACnC,CAAC;YACF,MAAM,MAAM,GAAG,IAAI,CAAC,yBAAyB,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvE,OAAO,MAAM,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC;IAClB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAe,CACpB,IAA+B;QAE/B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACpC,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;gBACvB,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;gBACvB,8CAA8C;gBAC9C,IAAI,KAAK,YAAY,WAAW,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC/D,IAAI,CAAC;wBACJ,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,gBAAgB,EAAE;4BACjD,WAAW,EAAE,YAAY;4BACzB,IAAI,EAAE,CAAC,KAAY,CAAC;yBACpB,CAAC,CAAC;wBAEH,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAQ,CAAC,CAAC;oBACtC,CAAC;oBAAC,MAAM,CAAC;wBACR,yBAAyB;oBAC1B,CAAC;oBACD,SAAS;gBACV,CAAC;gBAED,uDAAuD;gBACvD,qDAAqD;gBACrD,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;oBACxD,IAAI,CAAC;wBACJ,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;oBAC9B,CAAC;oBAAC,MAAM,CAAC;wBACR,+BAA+B;oBAChC,CAAC;gBACF,CAAC;gBAED,IAAI,GAAG,KAAK,UAAU,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;oBACnD,GAAG,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;gBACxC,CAAC;qBAAM,IAAI,GAAG,KAAK,UAAU,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;oBAC1D,GAAG,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;gBACxC,CAAC;YACF,CAAC;QACF,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;CACD;AAED,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC;IAC5B,cAAc;IACd,WAAW;IACX,SAAS;IACT,SAAS;IACT,iDAAiD;IACjD,UAAU;IACV,UAAU;CACV,CAAC,CAAC;AAEH,SAAS,iBAAiB,CAAI,KAAQ;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAe;IACzC,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC/B,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC5C,OAAO;gBACN,GAAG,OAAO;gBACV,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC;gBAC3C,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC;aAC3C,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IAChB,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAe;IACzC,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC/B,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC5C,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAClD,CAAC,CAAC,OAAO,CAAC,QAAQ;gBAClB,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,kBAAkB,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC;gBACpD,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC;gBAChC,CAAC,CAAC,WAAW,CAAC;YACf,OAAO;gBACN,GAAG,OAAO;gBACV,SAAS,EAAE,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC;gBAC/C,QAAQ,EAAE,kBAAkB;aAC5B,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IAChB,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,yBAA0B,SAAQ,wBAAwB;IAC5C,mBAAmB,CAAC,IAAoB;QAC1D,OAAO,KAAK,CAAC,mBAAmB,CAAC;YAChC,GAAG,IAAI;YACP,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE;gBACzC,IAAI,UAAU,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;oBAC5C,OAAO,UAAU,CAAC;gBACnB,CAAC;gBACD,OAAO;oBACN,IAAI,EAAE,kBAAkB;oBACxB,MAAM,EAAE,UAAU,CAAC,MAAM;oBACzB,2DAA2D;oBAC3D,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,KAAK,CAAC;iBAC5C,CAAC;YACH,CAAC,CAAC;SACF,CAAC,CAAC;IACJ,CAAC;IAEkB,cAAc,CAAC,IAAe;QAChD,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;QACvB,MAAM,eAAe,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAClD,IAAI,KAAK,KAAK,eAAe,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC;QACb,CAAC;QACD,0DAA0D;QAC1D,OAAO,GAAG,CAAA,SAAS,eAAe,GAAG,CAAC,eAAe,EAAE,CAAC;IACzD,CAAC;IACD;;OAEG;IACgB,kBAAkB,CAAC,IAAmB;QACxD,OAAO,KAAK,CAAC,kBAAkB,CAAC;YAC/B,GAAG,IAAI;YACP,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE;gBACxC,IAAI,YAAY,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBACvC,OAAO,YAAY,CAAC;gBACrB,CAAC;gBACD,0DAA0D;gBAC1D,MAAM,EAAE,KAAK,EAAE,GAAG,YAAY,CAAC;gBAC/B,MAAM,eAAe,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;gBAElD,IAAI,KAAK,KAAK,eAAe,EAAE,CAAC;oBAC/B,OAAO,YAAY,CAAC;gBACrB,CAAC;gBACD,OAAO,GAAG,CAAA,SAAS,eAAe,GAAG,CAAC,eAAe,EAAE,CAAC;YACzD,CAAC,CAAC;SACF,CAAC,CAAC;IACJ,CAAC;IAED;;OAEG;IACM,eAAe,CAAC,IAAgB;QACxC,OAAO,KAAK,CAAC,eAAe,CAAC;YAC5B,GAAG,IAAI;YACP,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,aAAa,EAAE,EAAE;gBACzC,IAAI,aAAa,CAAC,IAAI,KAAK,wBAAwB,EAAE,CAAC;oBACrD,OAAO,aAAa,CAAC;gBACtB,CAAC;gBAED,oCAAoC;gBACpC,OAAO;oBACN,IAAI,EAAE,eAAe;oBACrB,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,CAC/B,CAAC,KAAK,EAAE,EAAE,CACT,CAAC;wBACA,IAAI,EAAE,WAAW;wBACjB,KAAK;qBACL,CAAc,CAChB;iBACgB,CAAC;YACpB,CAAC,CAAC;SACF,CAAC,CAAC;IACJ,CAAC;CACD;AAED,SAAS,kBAAkB,CAAC,KAAU;IACrC;IACC,cAAc;IACd,KAAK,YAAY,WAAW;QAC5B,kBAAkB;QAClB,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC;QACzB,KAAK,KAAK,IAAI;QACd,KAAK,KAAK,SAAS,EAClB,CAAC;QACF,OAAO,KAAK,CAAC;IACd,CAAC;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,mGAAmG;AACnG,yGAAyG;AACzG,iEAAiE;AACjE,qBAAqB;AACrB,0BAA0B;AAC1B,4BAA4B;AAC5B,uBAAuB;AACvB,wEAAwE;AACxE,kCAAkC;AAClC,iBAAiB;AACjB,KAAK;AACL,4FAA4F;AAC5F,yEAAyE;AACzE,wCAAwC;AACxC,iBAAiB;AACjB,KAAK;AACL,iEAAiE;AACjE,oEAAoE;AACpE,kEAAkE;AAClE,2DAA2D;AAC3D,uDAAuD;AACvD,kCAAkC;AAClC,yDAAyD;AACzD,MAAM;AACN,KAAK;AACL,gDAAgD;AAChD,iEAAiE;AACjE,SAAS;AACT,mEAAmE;AACnE,2CAA2C;AAC3C,2DAA2D;AAC3D,6EAA6E;AAC7E,kEAAkE;AAClE,QAAQ;AACR,mDAAmD;AACnD,MAAM;AACN,4EAA4E;AAC5E,4EAA4E;AAC5E,iCAAiC;AACjC,0BAA0B;AAC1B,wDAAwD;AACxD,wDAAwD;AACxD,KAAK;AAEL,YAAY;AACZ,aAAa;AACb,YAAY;AACZ,MAAM;AACN,IAAI","sourcesContent":["import {\n\tOperationNodeTransformer,\n\tsql,\n\tValueListNode,\n\tValueNode,\n\tValuesNode,\n\ttype KyselyPlugin,\n\ttype PluginTransformQueryArgs,\n\ttype PluginTransformResultArgs,\n\ttype QueryResult,\n\ttype RootOperationNode,\n\ttype UnknownRow,\n\tOnConflictNode,\n} from \"kysely\";\nimport type { SqliteWasmDatabase } from \"sqlite-wasm-kysely\";\n\nexport class JsonbPlugin implements KyselyPlugin {\n\t#serializeJsonTransformer = new SerializeJsonbTransformer();\n\t#database: SqliteWasmDatabase;\n\n\tconstructor(args: { database: SqliteWasmDatabase }) {\n\t\tthis.#database = args.database;\n\t}\n\n\t/**\n\t * For an outgoing query like insert or update, the JSON\n\t * values are transformed into `jsonb` function calls when\n\t * executed against the database.\n\t */\n\ttransformQuery(args: PluginTransformQueryArgs): RootOperationNode {\n\t\tif (\n\t\t\targs.node.kind === \"InsertQueryNode\" ||\n\t\t\targs.node.kind === \"UpdateQueryNode\"\n\t\t) {\n\t\t\tconst result = this.#serializeJsonTransformer.transformNode(args.node);\n\t\t\treturn result;\n\t\t}\n\t\treturn args.node;\n\t}\n\n\t/**\n\t * For incoming query results, the JSON binaries are parsed\n\t * into JSON objects.\n\t */\n\tasync transformResult(\n\t\targs: PluginTransformResultArgs\n\t): Promise<QueryResult<UnknownRow>> {\n\t\tfor (const row of args.result.rows) {\n\t\t\tfor (const key in row) {\n\t\t\t\tconst value = row[key];\n\t\t\t\t// Always try to decode JSONB binary payloads.\n\t\t\t\tif (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst res = this.#database.exec(`SELECT json(?)`, {\n\t\t\t\t\t\t\treturnValue: \"resultRows\",\n\t\t\t\t\t\t\tbind: [value as any],\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\trow[key] = JSON.parse(res[0] as any);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// it's not a json binary\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Only parse JSON text for known JSON columns to avoid\n\t\t\t\t// coercing nested string values that look like JSON.\n\t\t\t\tif (JSON_COLUMNS.has(key) && typeof value === \"string\") {\n\t\t\t\t\ttry {\n\t\t\t\t\t\trow[key] = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// leave as-is if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (key === \"messages\" && Array.isArray(row[key])) {\n\t\t\t\t\trow[key] = normalizeMessages(row[key]);\n\t\t\t\t} else if (key === \"variants\" && Array.isArray(row[key])) {\n\t\t\t\t\trow[key] = normalizeVariants(row[key]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn args.result;\n\t}\n}\n\nconst JSON_COLUMNS = new Set([\n\t\"declarations\",\n\t\"selectors\",\n\t\"matches\",\n\t\"pattern\",\n\t// jsonArrayFrom results in nested bundle queries\n\t\"messages\",\n\t\"variants\",\n]);\n\nfunction parseJsonIfString<T>(value: T): T | unknown {\n\tif (typeof value !== \"string\") {\n\t\treturn value;\n\t}\n\ttry {\n\t\treturn JSON.parse(value);\n\t} catch {\n\t\treturn value;\n\t}\n}\n\nfunction normalizeVariants(variants: any[]) {\n\treturn variants.map((variant) => {\n\t\tif (variant && typeof variant === \"object\") {\n\t\t\treturn {\n\t\t\t\t...variant,\n\t\t\t\tmatches: parseJsonIfString(variant.matches),\n\t\t\t\tpattern: parseJsonIfString(variant.pattern),\n\t\t\t};\n\t\t}\n\t\treturn variant;\n\t});\n}\n\nfunction normalizeMessages(messages: any[]) {\n\treturn messages.map((message) => {\n\t\tif (message && typeof message === \"object\") {\n\t\t\tconst rawVariants = Array.isArray(message.variants)\n\t\t\t\t? message.variants\n\t\t\t\t: parseJsonIfString(message.variants);\n\t\t\tconst normalizedVariants = Array.isArray(rawVariants)\n\t\t\t\t? normalizeVariants(rawVariants)\n\t\t\t\t: rawVariants;\n\t\t\treturn {\n\t\t\t\t...message,\n\t\t\t\tselectors: parseJsonIfString(message.selectors),\n\t\t\t\tvariants: normalizedVariants,\n\t\t\t};\n\t\t}\n\t\treturn message;\n\t});\n}\n\nclass SerializeJsonbTransformer extends OperationNodeTransformer {\n\tprotected override transformOnConflict(node: OnConflictNode): OnConflictNode {\n\t\treturn super.transformOnConflict({\n\t\t\t...node,\n\t\t\tupdates: node.updates?.map((updateItem) => {\n\t\t\t\tif (updateItem.kind !== \"ColumnUpdateNode\") {\n\t\t\t\t\treturn updateItem;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"ColumnUpdateNode\",\n\t\t\t\t\tcolumn: updateItem.column,\n\t\t\t\t\t// @ts-expect-error - we know that the value is a ValueNode\n\t\t\t\t\tvalue: this.transformValue(updateItem.value),\n\t\t\t\t};\n\t\t\t}),\n\t\t});\n\t}\n\n\tprotected override transformValue(node: ValueNode): ValueNode {\n\t\tconst { value } = node;\n\t\tconst serializedValue = maybeSerializeJson(value);\n\t\tif (value === serializedValue) {\n\t\t\treturn node;\n\t\t}\n\t\t// @ts-expect-error - we know that the node is a ValueNode\n\t\treturn sql`jsonb(${serializedValue})`.toOperationNode();\n\t}\n\t/**\n\t * Transforms the value list node by replacing all JSON objects with `jsonb` function calls.\n\t */\n\tprotected override transformValueList(node: ValueListNode): ValueListNode {\n\t\treturn super.transformValueList({\n\t\t\t...node,\n\t\t\tvalues: node.values.map((listNodeItem) => {\n\t\t\t\tif (listNodeItem.kind !== \"ValueNode\") {\n\t\t\t\t\treturn listNodeItem;\n\t\t\t\t}\n\t\t\t\t// @ts-expect-error - we know that the node is a ValueNode\n\t\t\t\tconst { value } = listNodeItem;\n\t\t\t\tconst serializedValue = maybeSerializeJson(value);\n\n\t\t\t\tif (value === serializedValue) {\n\t\t\t\t\treturn listNodeItem;\n\t\t\t\t}\n\t\t\t\treturn sql`jsonb(${serializedValue})`.toOperationNode();\n\t\t\t}),\n\t\t});\n\t}\n\n\t/**\n\t * Why this function is needed or why this works remains a mystery.\n\t */\n\toverride transformValues(node: ValuesNode): ValuesNode {\n\t\treturn super.transformValues({\n\t\t\t...node,\n\t\t\tvalues: node.values.map((valueItemNode) => {\n\t\t\t\tif (valueItemNode.kind !== \"PrimitiveValueListNode\") {\n\t\t\t\t\treturn valueItemNode;\n\t\t\t\t}\n\n\t\t\t\t// change valueItem to ValueListNode\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"ValueListNode\",\n\t\t\t\t\tvalues: valueItemNode.values.map(\n\t\t\t\t\t\t(value) =>\n\t\t\t\t\t\t\t({\n\t\t\t\t\t\t\t\tkind: \"ValueNode\",\n\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t}) as ValueNode\n\t\t\t\t\t),\n\t\t\t\t} as ValueListNode;\n\t\t\t}),\n\t\t});\n\t}\n}\n\nfunction maybeSerializeJson(value: any): any {\n\tif (\n\t\t// binary data\n\t\tvalue instanceof ArrayBuffer ||\n\t\t// uint8array, etc\n\t\tArrayBuffer.isView(value) ||\n\t\tvalue === null ||\n\t\tvalue === undefined\n\t) {\n\t\treturn value;\n\t} else if (typeof value === \"object\" || Array.isArray(value)) {\n\t\treturn JSON.stringify(value);\n\t}\n\treturn value;\n}\n\n// The code here didn't work https://github.com/opral/inlang-sdk/issues/132#issuecomment-2339321910\n// but would be the \"right\" solution to avoid heuristics which column might or might not be a json column\n// // modifies the query in place for readability and performance\n// function mapQuery(\n// \tnode: InsertQueryNode,\n// \tjsonColumns: TableSchema\n// ): InsertQueryNode {\n// \t// if the query is not an insert query, we don't need to do anything\n// \tif (node.into === undefined) {\n// \t\treturn node;\n// \t}\n// \t// if the table is not in the schema that has json columns, we don't need to do anything\n// \tconst columnsWithJson = jsonColumns[node.into.table.identifier.name];\n// \tif (columnsWithJson === undefined) {\n// \t\treturn node;\n// \t}\n// \t// find the indexes of the values that need to be transformed\n// \t// SQL query: INSERT INTO table (col1, col2) VALUES (val1, val2)\n// \tconst indexesThatNeedToBeTransformed: [number, string][] = [];\n// \tfor (const [i, col] of node.columns?.entries() ?? []) {\n// \t\tconst jsonType = columnsWithJson[col.column.name];\n// \t\tif (jsonType !== undefined) {\n// \t\t\tindexesThatNeedToBeTransformed.push([i, jsonType]);\n// \t\t}\n// \t}\n// \tconst values = structuredClone(node.values);\n// \tfor (const [i, jsonType] of indexesThatNeedToBeTransformed) {\n// \t\tif (\n// \t\t\t// top level values node that should contain a list of values\n// \t\t\tnode.values?.kind !== \"ValuesNode\" &&\n// \t\t\t// the node we are interested in must be a value node\n// \t\t\t// @ts-expect-error - we know that the node is a ValuesNode with values\n// \t\t\t(node.values as ValuesNode).values?.[i].kind !== \"ValueNode\"\n// \t\t) {\n// \t\t\tthrow new Error(\"Unexpected node structure\");\n// \t\t}\n// \t\tconst serializedJson = JSON.stringify(node.values.values[0].values[i]);\n// \t\t// @ts-expect-error - we know that the node is a ValuesNode with values\n// \t\tvalues.values[0].values[i] =\n// \t\t\tjsonType === \"jsonb\"\n// \t\t\t\t? sql`jsonb(${serializedJson})`.toOperationNode()\n// \t\t\t\t: sql`json(${serializedJson})`.toOperationNode();\n// \t}\n\n// \treturn {\n// \t\t...node,\n// \t\tvalues,\n// \t};\n// }\n"]}
@@ -102,7 +102,7 @@ test("storing json as text is supposed to fail to avoid heuristics if the json s
102
102
  }),
103
103
  plugins: [new JsonbPlugin({ database })],
104
104
  });
105
- expect(() => db
105
+ await expect(() => db
106
106
  .insertInto("foo")
107
107
  .values({
108
108
  id: "mock",
@@ -113,4 +113,51 @@ test("storing json as text is supposed to fail to avoid heuristics if the json s
113
113
  .returningAll()
114
114
  .executeTakeFirstOrThrow()).rejects.toThrowErrorMatchingInlineSnapshot(`[SQLite3Error: SQLITE_CONSTRAINT_DATATYPE: sqlite3 result code 3091: cannot store BLOB value in TEXT column foo.data]`);
115
115
  });
116
+ test("normalizes variants when messages is a JSON string", async () => {
117
+ const database = await createInMemoryDatabase({
118
+ readOnly: false,
119
+ });
120
+ database.exec(`
121
+ CREATE TABLE foo (
122
+ id TEXT PRIMARY KEY,
123
+ messages TEXT NOT NULL
124
+ ) strict;
125
+ `);
126
+ const db = new Kysely({
127
+ dialect: createDialect({
128
+ database,
129
+ }),
130
+ plugins: [new JsonbPlugin({ database })],
131
+ });
132
+ const rawMessages = JSON.stringify([
133
+ {
134
+ selectors: "[]",
135
+ variants: JSON.stringify([
136
+ {
137
+ matches: "[]",
138
+ pattern: '[{"type":"text","value":"x"}]',
139
+ },
140
+ ]),
141
+ },
142
+ ]);
143
+ const foo = await db
144
+ .insertInto("foo")
145
+ .values({
146
+ id: "mock",
147
+ messages: rawMessages,
148
+ })
149
+ .returningAll()
150
+ .executeTakeFirstOrThrow();
151
+ expect(foo.messages).toEqual([
152
+ {
153
+ selectors: [],
154
+ variants: [
155
+ {
156
+ matches: [],
157
+ pattern: [{ type: "text", value: "x" }],
158
+ },
159
+ ],
160
+ },
161
+ ]);
162
+ });
116
163
  //# sourceMappingURL=jsonbPlugin.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"jsonbPlugin.test.js","sourceRoot":"/","sources":["database/jsonbPlugin.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { Kysely } from \"kysely\";\nimport { createDialect, createInMemoryDatabase } from \"sqlite-wasm-kysely\";\nimport { test, expect } from \"vitest\";\nimport { JsonbPlugin } from \"./jsonbPlugin.js\";\n\ntest(\"parsing and serializing of jsonb should work\", async () => {\n\ttype MockSchema = {\n\t\tfoo: {\n\t\t\tid: string;\n\t\t\tdata: Record<string, any>;\n\t\t};\n\t};\n\tconst database = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\n\tdatabase.exec(`\n CREATE TABLE foo (\n id TEXT PRIMARY KEY,\n data BLOB NOT NULL\n ) strict; \n `);\n\n\tconst db = new Kysely<MockSchema>({\n\t\tdialect: createDialect({\n\t\t\tdatabase,\n\t\t}),\n\t\tplugins: [new JsonbPlugin({ database })],\n\t});\n\n\tconst foo = await db\n\t\t.insertInto(\"foo\")\n\t\t.values({\n\t\t\tid: \"mock\",\n\t\t\tdata: {\n\t\t\t\tdata: \"baz\",\n\t\t\t},\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(foo).toEqual({\n\t\tid: \"mock\",\n\t\tdata: {\n\t\t\tdata: \"baz\",\n\t\t},\n\t});\n});\n\ntest(\"upserts should be handled\", async () => {\n\ttype MockSchema = {\n\t\tfoo: {\n\t\t\tid: string;\n\t\t\tdata: Record<string, any>;\n\t\t};\n\t};\n\tconst database = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\n\tdatabase.exec(`\n CREATE TABLE foo (\n id TEXT PRIMARY KEY,\n data BLOB NOT NULL\n ) strict; \n `);\n\n\tconst db = new Kysely<MockSchema>({\n\t\tdialect: createDialect({\n\t\t\tdatabase,\n\t\t}),\n\t\tplugins: [\n\t\t\tnew JsonbPlugin({\n\t\t\t\tdatabase,\n\t\t\t}),\n\t\t],\n\t});\n\n\tconst foo = await db\n\t\t.insertInto(\"foo\")\n\t\t.values({\n\t\t\tid: \"mock\",\n\t\t\tdata: {\n\t\t\t\tbar: \"baz\",\n\t\t\t},\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(foo).toEqual({\n\t\tid: \"mock\",\n\t\tdata: {\n\t\t\tbar: \"baz\",\n\t\t},\n\t});\n\n\tconst updatedFoo = {\n\t\tid: \"mock\",\n\t\tdata: {\n\t\t\tbar: \"baz\",\n\t\t\tbaz: \"qux\",\n\t\t},\n\t};\n\n\tconst updatedFooResult = await db\n\t\t.insertInto(\"foo\")\n\t\t.values(updatedFoo)\n\t\t.returningAll()\n\t\t.onConflict((oc) => oc.column(\"id\").doUpdateSet(updatedFoo))\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(updatedFooResult).toEqual(updatedFoo);\n});\n\ntest(\"storing json as text is supposed to fail to avoid heuristics if the json should be stored as blob or text\", async () => {\n\ttype MockSchema = {\n\t\tfoo: {\n\t\t\tid: string;\n\t\t\tdata: Record<string, any>;\n\t\t};\n\t};\n\tconst database = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\n\tdatabase.exec(`\n CREATE TABLE foo (\n id TEXT PRIMARY KEY,\n data TEXT NOT NULL\n ) strict; \n `);\n\n\tconst db = new Kysely<MockSchema>({\n\t\tdialect: createDialect({\n\t\t\tdatabase,\n\t\t}),\n\t\tplugins: [new JsonbPlugin({ database })],\n\t});\n\n\texpect(() =>\n\t\tdb\n\t\t\t.insertInto(\"foo\")\n\t\t\t.values({\n\t\t\t\tid: \"mock\",\n\t\t\t\tdata: {\n\t\t\t\t\tbar: \"baz\",\n\t\t\t\t},\n\t\t\t})\n\t\t\t.returningAll()\n\t\t\t.executeTakeFirstOrThrow()\n\t).rejects.toThrowErrorMatchingInlineSnapshot(\n\t\t`[SQLite3Error: SQLITE_CONSTRAINT_DATATYPE: sqlite3 result code 3091: cannot store BLOB value in TEXT column foo.data]`\n\t);\n});\n"]}
1
+ {"version":3,"file":"jsonbPlugin.test.js","sourceRoot":"/","sources":["database/jsonbPlugin.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { Kysely } from \"kysely\";\nimport { createDialect, createInMemoryDatabase } from \"sqlite-wasm-kysely\";\nimport { test, expect } from \"vitest\";\nimport { JsonbPlugin } from \"./jsonbPlugin.js\";\n\ntest(\"parsing and serializing of jsonb should work\", async () => {\n\ttype MockSchema = {\n\t\tfoo: {\n\t\t\tid: string;\n\t\t\tdata: Record<string, any>;\n\t\t};\n\t};\n\tconst database = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\n\tdatabase.exec(`\n CREATE TABLE foo (\n id TEXT PRIMARY KEY,\n data BLOB NOT NULL\n ) strict; \n `);\n\n\tconst db = new Kysely<MockSchema>({\n\t\tdialect: createDialect({\n\t\t\tdatabase,\n\t\t}),\n\t\tplugins: [new JsonbPlugin({ database })],\n\t});\n\n\tconst foo = await db\n\t\t.insertInto(\"foo\")\n\t\t.values({\n\t\t\tid: \"mock\",\n\t\t\tdata: {\n\t\t\t\tdata: \"baz\",\n\t\t\t},\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(foo).toEqual({\n\t\tid: \"mock\",\n\t\tdata: {\n\t\t\tdata: \"baz\",\n\t\t},\n\t});\n});\n\ntest(\"upserts should be handled\", async () => {\n\ttype MockSchema = {\n\t\tfoo: {\n\t\t\tid: string;\n\t\t\tdata: Record<string, any>;\n\t\t};\n\t};\n\tconst database = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\n\tdatabase.exec(`\n CREATE TABLE foo (\n id TEXT PRIMARY KEY,\n data BLOB NOT NULL\n ) strict; \n `);\n\n\tconst db = new Kysely<MockSchema>({\n\t\tdialect: createDialect({\n\t\t\tdatabase,\n\t\t}),\n\t\tplugins: [\n\t\t\tnew JsonbPlugin({\n\t\t\t\tdatabase,\n\t\t\t}),\n\t\t],\n\t});\n\n\tconst foo = await db\n\t\t.insertInto(\"foo\")\n\t\t.values({\n\t\t\tid: \"mock\",\n\t\t\tdata: {\n\t\t\t\tbar: \"baz\",\n\t\t\t},\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(foo).toEqual({\n\t\tid: \"mock\",\n\t\tdata: {\n\t\t\tbar: \"baz\",\n\t\t},\n\t});\n\n\tconst updatedFoo = {\n\t\tid: \"mock\",\n\t\tdata: {\n\t\t\tbar: \"baz\",\n\t\t\tbaz: \"qux\",\n\t\t},\n\t};\n\n\tconst updatedFooResult = await db\n\t\t.insertInto(\"foo\")\n\t\t.values(updatedFoo)\n\t\t.returningAll()\n\t\t.onConflict((oc) => oc.column(\"id\").doUpdateSet(updatedFoo))\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(updatedFooResult).toEqual(updatedFoo);\n});\n\ntest(\"storing json as text is supposed to fail to avoid heuristics if the json should be stored as blob or text\", async () => {\n\ttype MockSchema = {\n\t\tfoo: {\n\t\t\tid: string;\n\t\t\tdata: Record<string, any>;\n\t\t};\n\t};\n\tconst database = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\n\tdatabase.exec(`\n CREATE TABLE foo (\n id TEXT PRIMARY KEY,\n data TEXT NOT NULL\n ) strict; \n `);\n\n\tconst db = new Kysely<MockSchema>({\n\t\tdialect: createDialect({\n\t\t\tdatabase,\n\t\t}),\n\t\tplugins: [new JsonbPlugin({ database })],\n\t});\n\n\tawait expect(() =>\n\t\tdb\n\t\t\t.insertInto(\"foo\")\n\t\t\t.values({\n\t\t\t\tid: \"mock\",\n\t\t\t\tdata: {\n\t\t\t\t\tbar: \"baz\",\n\t\t\t\t},\n\t\t\t})\n\t\t\t.returningAll()\n\t\t\t.executeTakeFirstOrThrow()\n\t).rejects.toThrowErrorMatchingInlineSnapshot(\n\t\t`[SQLite3Error: SQLITE_CONSTRAINT_DATATYPE: sqlite3 result code 3091: cannot store BLOB value in TEXT column foo.data]`\n\t);\n});\n\ntest(\"normalizes variants when messages is a JSON string\", async () => {\n\ttype MockSchema = {\n\t\tfoo: {\n\t\t\tid: string;\n\t\t\tmessages: string | any[];\n\t\t};\n\t};\n\tconst database = await createInMemoryDatabase({\n\t\treadOnly: false,\n\t});\n\n\tdatabase.exec(`\n CREATE TABLE foo (\n id TEXT PRIMARY KEY,\n messages TEXT NOT NULL\n ) strict;\n `);\n\n\tconst db = new Kysely<MockSchema>({\n\t\tdialect: createDialect({\n\t\t\tdatabase,\n\t\t}),\n\t\tplugins: [new JsonbPlugin({ database })],\n\t});\n\n\tconst rawMessages = JSON.stringify([\n\t\t{\n\t\t\tselectors: \"[]\",\n\t\t\tvariants: JSON.stringify([\n\t\t\t\t{\n\t\t\t\t\tmatches: \"[]\",\n\t\t\t\t\tpattern: '[{\"type\":\"text\",\"value\":\"x\"}]',\n\t\t\t\t},\n\t\t\t]),\n\t\t},\n\t]);\n\n\tconst foo = await db\n\t\t.insertInto(\"foo\")\n\t\t.values({\n\t\t\tid: \"mock\",\n\t\t\tmessages: rawMessages,\n\t\t})\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n\n\texpect(foo.messages).toEqual([\n\t\t{\n\t\t\tselectors: [],\n\t\t\tvariants: [\n\t\t\t\t{\n\t\t\t\t\tmatches: [],\n\t\t\t\t\tpattern: [{ type: \"text\", value: \"x\" }],\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t]);\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"upsertBundleNestedMatchByProperties.d.ts","sourceRoot":"/","sources":["import-export/upsertBundleNestedMatchByProperties.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EACX,oBAAoB,EACpB,eAAe,EACf,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,mCAAmC,GAC/C,IAAI,MAAM,CAAC,oBAAoB,CAAC,EAChC,QAAQ,eAAe,KACrB,OAAO,CAAC,IAAI,CAgEd,CAAC"}
1
+ {"version":3,"file":"upsertBundleNestedMatchByProperties.d.ts","sourceRoot":"/","sources":["import-export/upsertBundleNestedMatchByProperties.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EACX,oBAAoB,EACpB,eAAe,EACf,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,mCAAmC,OAC3C,MAAM,CAAC,oBAAoB,CAAC,UACxB,eAAe,KACrB,OAAO,CAAC,IAAI,CAgEd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"createMessageV1.d.ts","sourceRoot":"/","sources":["migrations/v2/createMessageV1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,SAAS,EACT,MAAM,8CAA8C,CAAC;AAEtD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAC3B,IAAI,MAAM,EACV,UAAU,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC;;;;;;;;;;;;;;;CAmBtB,CAAC"}
1
+ {"version":3,"file":"createMessageV1.d.ts","sourceRoot":"/","sources":["migrations/v2/createMessageV1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,SAAS,EACT,MAAM,8CAA8C,CAAC;AAEtD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,OACvB,MAAM,YACA,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC;;;;;;;;;;;;;;;CAmBtB,CAAC"}
@@ -37,14 +37,14 @@ test("if a fetch fails, a plugin import error is expected", async () => {
37
37
  modules: ["https://mock.com/module.js"],
38
38
  },
39
39
  });
40
- expect(global.fetch).toHaveBeenCalledTimes(1);
40
+ expect(global.fetch).toHaveBeenCalled();
41
41
  expect(result.plugins.length).toBe(0);
42
42
  expect(result.errors.length).toBe(1);
43
43
  expect(result.errors[0]).toBeInstanceOf(PluginImportError);
44
44
  });
45
45
  test("if a network error occurs during fetch, a plugin import error is expected", async () => {
46
- global.fetch = vi.fn().mockRejectedValue(new Error("Network error"));
47
46
  const lix = await openLixInMemory({ blob: await newLixFile() });
47
+ global.fetch = vi.fn().mockRejectedValue(new Error("Network error"));
48
48
  const result = await importPlugins({
49
49
  lix,
50
50
  settings: {
@@ -53,7 +53,7 @@ test("if a network error occurs during fetch, a plugin import error is expected"
53
53
  modules: ["https://example.com/non-existent-paraglide-plugin.js"],
54
54
  },
55
55
  });
56
- expect(global.fetch).toHaveBeenCalledTimes(1);
56
+ expect(global.fetch).toHaveBeenCalled();
57
57
  expect(result.plugins.length).toBe(0);
58
58
  expect(result.errors.length).toBe(1);
59
59
  expect(result.errors[0]).toBeInstanceOf(PluginImportError);
@@ -1 +1 @@
1
- {"version":3,"file":"importPlugins.test.js","sourceRoot":"/","sources":["plugin/importPlugins.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { test, expect, vi } from \"vitest\";\nimport { importPlugins } from \"./importPlugins.js\";\nimport { PluginImportError } from \"./errors.js\";\nimport { newLixFile, openLixInMemory } from \"@lix-js/sdk\";\n\ntest(\"it should preprocess a plugin\", async () => {\n\tglobal.fetch = vi.fn().mockResolvedValue({\n\t\tok: true,\n\t\ttext: vi.fn().mockResolvedValue(\"export default { key: 'mock' }\"),\n\t});\n\n\tconst lix = await openLixInMemory({ blob: await newLixFile() });\n\tconst result = await importPlugins({\n\t\tlix,\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t\tmodules: [\"https://mock.com/module.js\"],\n\t\t},\n\t\tpreprocessPluginBeforeImport: async (moduleText) => {\n\t\t\treturn moduleText.replace(\"mock\", \"preprocessed\");\n\t\t},\n\t});\n\n\texpect(result.plugins.length).toBe(1);\n\texpect(result.errors.length).toBe(0);\n\texpect(result.plugins[0]?.key).toBe(\"preprocessed\");\n});\n\ntest(\"if a fetch fails, a plugin import error is expected\", async () => {\n\tglobal.fetch = vi.fn().mockResolvedValue({\n\t\tok: false,\n\t\tstatusText: \"HTTP 404\",\n\t});\n\tconst lix = await openLixInMemory({ blob: await newLixFile() });\n\n\tconst result = await importPlugins({\n\t\tlix,\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t\tmodules: [\"https://mock.com/module.js\"],\n\t\t},\n\t});\n\n\texpect(global.fetch).toHaveBeenCalledTimes(1);\n\texpect(result.plugins.length).toBe(0);\n\texpect(result.errors.length).toBe(1);\n\texpect(result.errors[0]).toBeInstanceOf(PluginImportError);\n});\n\ntest(\"if a network error occurs during fetch, a plugin import error is expected\", async () => {\n\tglobal.fetch = vi.fn().mockRejectedValue(new Error(\"Network error\"));\n\tconst lix = await openLixInMemory({ blob: await newLixFile() });\n\n\tconst result = await importPlugins({\n\t\tlix,\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t\tmodules: [\"https://example.com/non-existent-paraglide-plugin.js\"],\n\t\t},\n\t});\n\n\texpect(global.fetch).toHaveBeenCalledTimes(1);\n\texpect(result.plugins.length).toBe(0);\n\texpect(result.errors.length).toBe(1);\n\texpect(result.errors[0]).toBeInstanceOf(PluginImportError);\n\texpect(result.errors[0]?.message).toContain(\n\t\t\"https://example.com/non-existent-paraglide-plugin.js\"\n\t);\n\texpect(result.errors[0]?.message).toContain(\"Network error\");\n});\n\ntest(\"it should filter message lint rules for legacy reasons\", async () => {\n\tglobal.fetch = vi.fn().mockResolvedValue({\n\t\tok: true,\n\t\ttext: vi\n\t\t\t.fn()\n\t\t\t.mockResolvedValue(\"export default { id: 'messageLintRule.something' }\"),\n\t});\n\n\tconst lix = await openLixInMemory({ blob: await newLixFile() });\n\n\tconst result = await importPlugins({\n\t\tlix,\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t\tmodules: [\"https://mock.com/module.js\"],\n\t\t},\n\t});\n\n\texpect(result.plugins.length).toBe(0);\n\texpect(result.errors.length).toBe(0);\n});\n\n// more tests are found in cache.test.ts\ntest(\"cache should work\", async () => {\n\tglobal.fetch = vi.fn().mockResolvedValue({\n\t\tok: false,\n\t});\n\n\tconst mockModulePath = \"https://mock.com/module.js\";\n\tconst mockModuleCachePath = \"/cache/plugins/31i1etp0l413h\";\n\n\tconst lix = await openLixInMemory({ blob: await newLixFile() });\n\n\tawait lix.db\n\t\t.insertInto(\"file\")\n\t\t.values({\n\t\t\tpath: mockModuleCachePath,\n\t\t\tdata: new TextEncoder().encode(\"export default { key: 'mock' }\"),\n\t\t})\n\t\t.execute();\n\n\tconst result = await importPlugins({\n\t\tlix,\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t\tmodules: [mockModulePath],\n\t\t},\n\t});\n\n\texpect(result.errors).lengthOf(0);\n\texpect(result.plugins).lengthOf(1);\n\texpect(result.plugins[0]?.key).toBe(\"mock\");\n});\n"]}
1
+ {"version":3,"file":"importPlugins.test.js","sourceRoot":"/","sources":["plugin/importPlugins.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { test, expect, vi } from \"vitest\";\nimport { importPlugins } from \"./importPlugins.js\";\nimport { PluginImportError } from \"./errors.js\";\nimport { newLixFile, openLixInMemory } from \"@lix-js/sdk\";\n\ntest(\"it should preprocess a plugin\", async () => {\n\tglobal.fetch = vi.fn().mockResolvedValue({\n\t\tok: true,\n\t\ttext: vi.fn().mockResolvedValue(\"export default { key: 'mock' }\"),\n\t});\n\n\tconst lix = await openLixInMemory({ blob: await newLixFile() });\n\tconst result = await importPlugins({\n\t\tlix,\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t\tmodules: [\"https://mock.com/module.js\"],\n\t\t},\n\t\tpreprocessPluginBeforeImport: async (moduleText) => {\n\t\t\treturn moduleText.replace(\"mock\", \"preprocessed\");\n\t\t},\n\t});\n\n\texpect(result.plugins.length).toBe(1);\n\texpect(result.errors.length).toBe(0);\n\texpect(result.plugins[0]?.key).toBe(\"preprocessed\");\n});\n\ntest(\"if a fetch fails, a plugin import error is expected\", async () => {\n\tglobal.fetch = vi.fn().mockResolvedValue({\n\t\tok: false,\n\t\tstatusText: \"HTTP 404\",\n\t});\n\tconst lix = await openLixInMemory({ blob: await newLixFile() });\n\n\tconst result = await importPlugins({\n\t\tlix,\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t\tmodules: [\"https://mock.com/module.js\"],\n\t\t},\n\t});\n\n\texpect(global.fetch).toHaveBeenCalled();\n\texpect(result.plugins.length).toBe(0);\n\texpect(result.errors.length).toBe(1);\n\texpect(result.errors[0]).toBeInstanceOf(PluginImportError);\n});\n\ntest(\"if a network error occurs during fetch, a plugin import error is expected\", async () => {\n\tconst lix = await openLixInMemory({ blob: await newLixFile() });\n\tglobal.fetch = vi.fn().mockRejectedValue(new Error(\"Network error\"));\n\n\tconst result = await importPlugins({\n\t\tlix,\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t\tmodules: [\"https://example.com/non-existent-paraglide-plugin.js\"],\n\t\t},\n\t});\n\n\texpect(global.fetch).toHaveBeenCalled();\n\texpect(result.plugins.length).toBe(0);\n\texpect(result.errors.length).toBe(1);\n\texpect(result.errors[0]).toBeInstanceOf(PluginImportError);\n\texpect(result.errors[0]?.message).toContain(\n\t\t\"https://example.com/non-existent-paraglide-plugin.js\"\n\t);\n\texpect(result.errors[0]?.message).toContain(\"Network error\");\n});\n\ntest(\"it should filter message lint rules for legacy reasons\", async () => {\n\tglobal.fetch = vi.fn().mockResolvedValue({\n\t\tok: true,\n\t\ttext: vi\n\t\t\t.fn()\n\t\t\t.mockResolvedValue(\"export default { id: 'messageLintRule.something' }\"),\n\t});\n\n\tconst lix = await openLixInMemory({ blob: await newLixFile() });\n\n\tconst result = await importPlugins({\n\t\tlix,\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t\tmodules: [\"https://mock.com/module.js\"],\n\t\t},\n\t});\n\n\texpect(result.plugins.length).toBe(0);\n\texpect(result.errors.length).toBe(0);\n});\n\n// more tests are found in cache.test.ts\ntest(\"cache should work\", async () => {\n\tglobal.fetch = vi.fn().mockResolvedValue({\n\t\tok: false,\n\t});\n\n\tconst mockModulePath = \"https://mock.com/module.js\";\n\tconst mockModuleCachePath = \"/cache/plugins/31i1etp0l413h\";\n\n\tconst lix = await openLixInMemory({ blob: await newLixFile() });\n\n\tawait lix.db\n\t\t.insertInto(\"file\")\n\t\t.values({\n\t\t\tpath: mockModuleCachePath,\n\t\t\tdata: new TextEncoder().encode(\"export default { key: 'mock' }\"),\n\t\t})\n\t\t.execute();\n\n\tconst result = await importPlugins({\n\t\tlix,\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\"],\n\t\t\tmodules: [mockModulePath],\n\t\t},\n\t});\n\n\texpect(result.errors).lengthOf(0);\n\texpect(result.plugins).lengthOf(1);\n\texpect(result.plugins[0]?.key).toBe(\"mock\");\n});\n"]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * README content that gets written to every .inlang project folder.
3
+ *
4
+ * The goal is to help coding agents understand what this folder is
5
+ * and how to use the inlang SDK to build tooling.
6
+ */
7
+ export declare const README_CONTENT = "\n## What is this folder?\n\nThis is an [unpacked (git-friendly)](https://inlang.com/docs/unpacked-project) inlang project.\n\n## At a glance\n\nPurpose:\n- This folder stores inlang project configuration and plugin cache data.\n- Translation files live outside this folder and are referenced from `settings.json`.\n\nSafe to edit:\n- `settings.json`\n\nDo not edit:\n- `cache/`\n- `.gitignore`\n\nKey files:\n- `settings.json` \u2014 locales, plugins, file patterns (source of truth)\n- `cache/` \u2014 plugin caches (safe to delete)\n- `.gitignore` \u2014 generated\n\n```\n*.inlang/\n\u251C\u2500\u2500 settings.json # Locales, plugins, and file patterns (source of truth)\n\u251C\u2500\u2500 cache/ # Plugin caches (gitignored)\n\u2514\u2500\u2500 .gitignore # Ignores everything except settings.json\n```\n\nTranslation files (like `messages/en.json`) live **outside** this folder and are referenced via plugins in `settings.json`.\n\n## What is inlang?\n\n[Inlang](https://inlang.com) is an open file format for building custom localization (i18n) tooling. It provides:\n\n- **CRUD API** \u2014 Read and write translations programmatically via SQL\n- **Plugin system** \u2014 Import/export any format (JSON, XLIFF, etc.)\n- **Version control** \u2014 Built-in version control via [lix](https://lix.dev)\n\n```\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 i18n lib \u2502 \u2502Translation\u2502 \u2502 CI/CD \u2502\n\u2502 \u2502 \u2502 Tool \u2502 \u2502 Automation \u2502\n\u2514\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u25BC \u25BC \u25BC\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 *.inlang file \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n## Quick start\n\n```bash\nnpm install @inlang/sdk\n```\n\n```ts\nimport { loadProjectFromDirectory, saveProjectToDirectory } from \"@inlang/sdk\";\n\nconst project = await loadProjectFromDirectory({ path: \"./project.inlang\" });\n// Query messages with SQLite + [Kysely](https://kysely.dev/) under the hood.\nconst messages = await project.db.selectFrom(\"message\").selectAll().execute();\n\n// Use project.db to update messages.\nawait saveProjectToDirectory({ path: \"./project.inlang\", project });\n```\n\n## Ideas for custom tooling\n\n- Translation health dashboard (missing/empty/stale messages)\n- Locale coverage report in CI\n- Auto-PR for new keys with placeholders\n- Migration tool between file formats via plugins\n- Glossary/term consistency checker\n\n## Data model ([docs](https://inlang.com/docs/data-model))\n\n```\nbundle (a concept, e.g., \"welcome_header\")\n \u2514\u2500\u2500 message (per locale, e.g., \"en\", \"de\")\n \u2514\u2500\u2500 variant (plural forms, gender, etc.)\n```\n\n- **bundle**: Groups messages by ID (e.g., `welcome_header`)\n- **message**: A translation for a specific locale\n- **variant**: Handles pluralization/selectors (most messages have one variant)\n\n## Common tasks\n\n- List bundles: `project.db.selectFrom(\"bundle\").selectAll().execute()`\n- List messages for locale: `project.db.selectFrom(\"message\").where(\"locale\", \"=\", \"en\").selectAll().execute()`\n- Find missing translations: compare message counts across locales\n- Update a message: `project.db.updateTable(\"message\").set({ ... }).where(\"id\", \"=\", \"...\").execute()`\n\n## Links\n\n- [SDK documentation](https://inlang.com/docs)\n- [inlang.com](https://inlang.com)\n- [List of plugins](https://inlang.com/c/plugins)\n- [List of tools](https://inlang.com/c/tools)\n";
8
+ //# sourceMappingURL=README_CONTENT.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"README_CONTENT.d.ts","sourceRoot":"/","sources":["project/README_CONTENT.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,cAAc,wiJAuG1B,CAAC"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * README content that gets written to every .inlang project folder.
3
+ *
4
+ * The goal is to help coding agents understand what this folder is
5
+ * and how to use the inlang SDK to build tooling.
6
+ */
7
+ export const README_CONTENT = `
8
+ ## What is this folder?
9
+
10
+ This is an [unpacked (git-friendly)](https://inlang.com/docs/unpacked-project) inlang project.
11
+
12
+ ## At a glance
13
+
14
+ Purpose:
15
+ - This folder stores inlang project configuration and plugin cache data.
16
+ - Translation files live outside this folder and are referenced from \`settings.json\`.
17
+
18
+ Safe to edit:
19
+ - \`settings.json\`
20
+
21
+ Do not edit:
22
+ - \`cache/\`
23
+ - \`.gitignore\`
24
+
25
+ Key files:
26
+ - \`settings.json\` — locales, plugins, file patterns (source of truth)
27
+ - \`cache/\` — plugin caches (safe to delete)
28
+ - \`.gitignore\` — generated
29
+
30
+ \`\`\`
31
+ *.inlang/
32
+ ├── settings.json # Locales, plugins, and file patterns (source of truth)
33
+ ├── cache/ # Plugin caches (gitignored)
34
+ └── .gitignore # Ignores everything except settings.json
35
+ \`\`\`
36
+
37
+ Translation files (like \`messages/en.json\`) live **outside** this folder and are referenced via plugins in \`settings.json\`.
38
+
39
+ ## What is inlang?
40
+
41
+ [Inlang](https://inlang.com) is an open file format for building custom localization (i18n) tooling. It provides:
42
+
43
+ - **CRUD API** — Read and write translations programmatically via SQL
44
+ - **Plugin system** — Import/export any format (JSON, XLIFF, etc.)
45
+ - **Version control** — Built-in version control via [lix](https://lix.dev)
46
+
47
+ \`\`\`
48
+ ┌──────────┐ ┌───────────┐ ┌────────────┐
49
+ │ i18n lib │ │Translation│ │ CI/CD │
50
+ │ │ │ Tool │ │ Automation │
51
+ └────┬─────┘ └─────┬─────┘ └─────┬──────┘
52
+ │ │ │
53
+ └─────────┐ │ ┌──────────┘
54
+ ▼ ▼ ▼
55
+ ┌──────────────────────────────────┐
56
+ │ *.inlang file │
57
+ └──────────────────────────────────┘
58
+ \`\`\`
59
+
60
+ ## Quick start
61
+
62
+ \`\`\`bash
63
+ npm install @inlang/sdk
64
+ \`\`\`
65
+
66
+ \`\`\`ts
67
+ import { loadProjectFromDirectory, saveProjectToDirectory } from "@inlang/sdk";
68
+
69
+ const project = await loadProjectFromDirectory({ path: "./project.inlang" });
70
+ // Query messages with SQLite + [Kysely](https://kysely.dev/) under the hood.
71
+ const messages = await project.db.selectFrom("message").selectAll().execute();
72
+
73
+ // Use project.db to update messages.
74
+ await saveProjectToDirectory({ path: "./project.inlang", project });
75
+ \`\`\`
76
+
77
+ ## Ideas for custom tooling
78
+
79
+ - Translation health dashboard (missing/empty/stale messages)
80
+ - Locale coverage report in CI
81
+ - Auto-PR for new keys with placeholders
82
+ - Migration tool between file formats via plugins
83
+ - Glossary/term consistency checker
84
+
85
+ ## Data model ([docs](https://inlang.com/docs/data-model))
86
+
87
+ \`\`\`
88
+ bundle (a concept, e.g., "welcome_header")
89
+ └── message (per locale, e.g., "en", "de")
90
+ └── variant (plural forms, gender, etc.)
91
+ \`\`\`
92
+
93
+ - **bundle**: Groups messages by ID (e.g., \`welcome_header\`)
94
+ - **message**: A translation for a specific locale
95
+ - **variant**: Handles pluralization/selectors (most messages have one variant)
96
+
97
+ ## Common tasks
98
+
99
+ - List bundles: \`project.db.selectFrom("bundle").selectAll().execute()\`
100
+ - List messages for locale: \`project.db.selectFrom("message").where("locale", "=", "en").selectAll().execute()\`
101
+ - Find missing translations: compare message counts across locales
102
+ - Update a message: \`project.db.updateTable("message").set({ ... }).where("id", "=", "...").execute()\`
103
+
104
+ ## Links
105
+
106
+ - [SDK documentation](https://inlang.com/docs)
107
+ - [inlang.com](https://inlang.com)
108
+ - [List of plugins](https://inlang.com/c/plugins)
109
+ - [List of tools](https://inlang.com/c/tools)
110
+ `;
111
+ //# sourceMappingURL=README_CONTENT.js.map