@zapier/zapier-sdk 0.8.3 → 0.10.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 (222) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +17 -40
  3. package/dist/api/client.d.ts.map +1 -1
  4. package/dist/api/client.js +14 -0
  5. package/dist/api/debug.d.ts +1 -0
  6. package/dist/api/debug.d.ts.map +1 -1
  7. package/dist/api/debug.js +42 -1
  8. package/dist/api/debug.test.d.ts +2 -0
  9. package/dist/api/debug.test.d.ts.map +1 -0
  10. package/dist/api/debug.test.js +59 -0
  11. package/dist/api/schemas.d.ts +451 -251
  12. package/dist/api/schemas.d.ts.map +1 -1
  13. package/dist/api/schemas.js +51 -29
  14. package/dist/index.cjs +1149 -751
  15. package/dist/index.d.mts +2359 -2161
  16. package/dist/index.d.ts +3 -5
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2 -4
  19. package/dist/index.mjs +1143 -743
  20. package/dist/plugins/apps/index.d.ts +4 -0
  21. package/dist/plugins/apps/index.d.ts.map +1 -1
  22. package/dist/plugins/findFirstAuthentication/index.d.ts +1 -1
  23. package/dist/plugins/findFirstAuthentication/index.d.ts.map +1 -1
  24. package/dist/plugins/findFirstAuthentication/index.js +9 -1
  25. package/dist/plugins/findFirstAuthentication/index.test.js +3 -4
  26. package/dist/plugins/findFirstAuthentication/schemas.d.ts +5 -3
  27. package/dist/plugins/findFirstAuthentication/schemas.d.ts.map +1 -1
  28. package/dist/plugins/findUniqueAuthentication/index.d.ts.map +1 -1
  29. package/dist/plugins/findUniqueAuthentication/index.js +4 -0
  30. package/dist/plugins/findUniqueAuthentication/schemas.d.ts +5 -3
  31. package/dist/plugins/findUniqueAuthentication/schemas.d.ts.map +1 -1
  32. package/dist/plugins/getAction/index.d.ts.map +1 -1
  33. package/dist/plugins/getAction/index.js +10 -0
  34. package/dist/plugins/getAction/schemas.d.ts +5 -3
  35. package/dist/plugins/getAction/schemas.d.ts.map +1 -1
  36. package/dist/plugins/getApp/index.d.ts +2 -7
  37. package/dist/plugins/getApp/index.d.ts.map +1 -1
  38. package/dist/plugins/getApp/index.js +17 -9
  39. package/dist/plugins/getApp/index.test.js +3 -3
  40. package/dist/plugins/getApp/schemas.d.ts +3 -1
  41. package/dist/plugins/getApp/schemas.d.ts.map +1 -1
  42. package/dist/plugins/getApp/schemas.js +2 -4
  43. package/dist/plugins/getAuthentication/index.d.ts.map +1 -1
  44. package/dist/plugins/getAuthentication/index.js +8 -0
  45. package/dist/plugins/getAuthentication/index.test.js +1 -1
  46. package/dist/plugins/getProfile/index.d.ts.map +1 -1
  47. package/dist/plugins/getProfile/index.js +4 -0
  48. package/dist/plugins/getProfile/schemas.d.ts.map +1 -1
  49. package/dist/plugins/getProfile/schemas.js +4 -3
  50. package/dist/plugins/listActions/index.d.ts +2 -4
  51. package/dist/plugins/listActions/index.d.ts.map +1 -1
  52. package/dist/plugins/listActions/index.js +10 -1
  53. package/dist/plugins/listActions/index.test.js +4 -4
  54. package/dist/plugins/listActions/schemas.d.ts +5 -3
  55. package/dist/plugins/listActions/schemas.d.ts.map +1 -1
  56. package/dist/plugins/listActions/schemas.js +2 -4
  57. package/dist/plugins/listApps/index.d.ts +4 -7
  58. package/dist/plugins/listApps/index.d.ts.map +1 -1
  59. package/dist/plugins/listApps/index.js +37 -17
  60. package/dist/plugins/listApps/index.test.js +23 -3
  61. package/dist/plugins/listApps/schemas.d.ts.map +1 -1
  62. package/dist/plugins/listApps/schemas.js +3 -9
  63. package/dist/plugins/listAuthentications/index.d.ts +2 -4
  64. package/dist/plugins/listAuthentications/index.d.ts.map +1 -1
  65. package/dist/plugins/listAuthentications/index.js +12 -0
  66. package/dist/plugins/listAuthentications/index.test.js +39 -13
  67. package/dist/plugins/listAuthentications/schemas.d.ts +8 -3
  68. package/dist/plugins/listAuthentications/schemas.d.ts.map +1 -1
  69. package/dist/plugins/listAuthentications/schemas.js +4 -0
  70. package/dist/plugins/listInputFieldChoices/index.d.ts.map +1 -1
  71. package/dist/plugins/listInputFieldChoices/index.js +14 -2
  72. package/dist/plugins/listInputFieldChoices/schemas.d.ts +5 -3
  73. package/dist/plugins/listInputFieldChoices/schemas.d.ts.map +1 -1
  74. package/dist/plugins/listInputFieldChoices/schemas.js +10 -19
  75. package/dist/plugins/listInputFields/index.d.ts.map +1 -1
  76. package/dist/plugins/listInputFields/index.js +14 -2
  77. package/dist/plugins/listInputFields/index.test.js +5 -9
  78. package/dist/plugins/listInputFields/schemas.d.ts +5 -3
  79. package/dist/plugins/listInputFields/schemas.d.ts.map +1 -1
  80. package/dist/plugins/manifest/index.d.ts +25 -9
  81. package/dist/plugins/manifest/index.d.ts.map +1 -1
  82. package/dist/plugins/manifest/index.js +239 -67
  83. package/dist/plugins/manifest/index.test.js +426 -171
  84. package/dist/plugins/manifest/schemas.d.ts +5 -1
  85. package/dist/plugins/manifest/schemas.d.ts.map +1 -1
  86. package/dist/plugins/manifest/schemas.js +1 -0
  87. package/dist/plugins/registry/index.d.ts.map +1 -1
  88. package/dist/plugins/registry/index.js +8 -2
  89. package/dist/plugins/request/index.d.ts.map +1 -1
  90. package/dist/plugins/request/index.js +1 -0
  91. package/dist/plugins/runAction/index.d.ts.map +1 -1
  92. package/dist/plugins/runAction/index.js +12 -0
  93. package/dist/plugins/runAction/schemas.d.ts +5 -3
  94. package/dist/plugins/runAction/schemas.d.ts.map +1 -1
  95. package/dist/resolvers/actionKey.d.ts +13 -7
  96. package/dist/resolvers/actionKey.d.ts.map +1 -1
  97. package/dist/resolvers/actionType.d.ts +8 -7
  98. package/dist/resolvers/actionType.d.ts.map +1 -1
  99. package/dist/resolvers/appKey.d.ts +2 -6
  100. package/dist/resolvers/appKey.d.ts.map +1 -1
  101. package/dist/resolvers/authenticationId.d.ts +7 -7
  102. package/dist/resolvers/authenticationId.d.ts.map +1 -1
  103. package/dist/resolvers/authenticationId.js +16 -7
  104. package/dist/resolvers/index.d.ts +3 -35
  105. package/dist/resolvers/index.d.ts.map +1 -1
  106. package/dist/resolvers/index.js +4 -87
  107. package/dist/resolvers/inputFieldKey.d.ts +11 -0
  108. package/dist/resolvers/inputFieldKey.d.ts.map +1 -0
  109. package/dist/resolvers/inputFieldKey.js +23 -0
  110. package/dist/resolvers/inputs.d.ts +11 -6
  111. package/dist/resolvers/inputs.d.ts.map +1 -1
  112. package/dist/resolvers/inputs.js +17 -0
  113. package/dist/schemas/Action.d.ts +8 -8
  114. package/dist/schemas/Action.d.ts.map +1 -1
  115. package/dist/schemas/Action.js +8 -3
  116. package/dist/schemas/App.d.ts +183 -11
  117. package/dist/schemas/App.d.ts.map +1 -1
  118. package/dist/schemas/App.js +7 -9
  119. package/dist/schemas/Auth.d.ts +12 -12
  120. package/dist/schemas/Auth.js +1 -1
  121. package/dist/schemas/Field.d.ts +5 -98
  122. package/dist/schemas/Field.d.ts.map +1 -1
  123. package/dist/schemas/Field.js +24 -52
  124. package/dist/schemas/Run.d.ts +3 -0
  125. package/dist/schemas/Run.d.ts.map +1 -0
  126. package/dist/schemas/Run.js +31 -0
  127. package/dist/schemas/UserProfile.d.ts +11 -11
  128. package/dist/schemas/UserProfile.d.ts.map +1 -1
  129. package/dist/schemas/UserProfile.js +21 -7
  130. package/dist/sdk.d.ts +15 -14
  131. package/dist/sdk.d.ts.map +1 -1
  132. package/dist/sdk.js +1 -4
  133. package/dist/types/plugin.d.ts +6 -0
  134. package/dist/types/plugin.d.ts.map +1 -1
  135. package/dist/types/properties.d.ts +3 -1
  136. package/dist/types/properties.d.ts.map +1 -1
  137. package/dist/types/sdk.d.ts +11 -3
  138. package/dist/types/sdk.d.ts.map +1 -1
  139. package/dist/utils/domain-utils.d.ts +17 -16
  140. package/dist/utils/domain-utils.d.ts.map +1 -1
  141. package/dist/utils/domain-utils.js +53 -78
  142. package/dist/utils/domain-utils.test.js +157 -3
  143. package/dist/utils/file-utils.d.ts +4 -0
  144. package/dist/utils/file-utils.d.ts.map +1 -0
  145. package/dist/utils/file-utils.js +74 -0
  146. package/dist/utils/file-utils.test.d.ts +2 -0
  147. package/dist/utils/file-utils.test.d.ts.map +1 -0
  148. package/dist/utils/file-utils.test.js +51 -0
  149. package/dist/utils/schema-utils.d.ts +44 -21
  150. package/dist/utils/schema-utils.d.ts.map +1 -1
  151. package/dist/utils/schema-utils.js +17 -11
  152. package/package.json +1 -1
  153. package/src/api/client.ts +12 -0
  154. package/src/api/debug.test.ts +76 -0
  155. package/src/api/debug.ts +46 -2
  156. package/src/api/schemas.ts +51 -29
  157. package/src/index.ts +5 -6
  158. package/src/plugins/apps/index.ts +9 -2
  159. package/src/plugins/findFirstAuthentication/index.test.ts +8 -5
  160. package/src/plugins/findFirstAuthentication/index.ts +14 -2
  161. package/src/plugins/findUniqueAuthentication/index.ts +4 -0
  162. package/src/plugins/getAction/index.ts +14 -0
  163. package/src/plugins/getApp/index.test.ts +3 -3
  164. package/src/plugins/getApp/index.ts +20 -14
  165. package/src/plugins/getApp/schemas.ts +7 -12
  166. package/src/plugins/getAuthentication/index.test.ts +1 -1
  167. package/src/plugins/getAuthentication/index.ts +8 -0
  168. package/src/plugins/getProfile/index.ts +4 -0
  169. package/src/plugins/getProfile/schemas.ts +4 -6
  170. package/src/plugins/listActions/index.test.ts +8 -7
  171. package/src/plugins/listActions/index.ts +12 -3
  172. package/src/plugins/listActions/schemas.ts +20 -25
  173. package/src/plugins/listApps/index.test.ts +24 -3
  174. package/src/plugins/listApps/index.ts +50 -25
  175. package/src/plugins/listApps/schemas.ts +17 -26
  176. package/src/plugins/listAuthentications/index.test.ts +52 -15
  177. package/src/plugins/listAuthentications/index.ts +15 -2
  178. package/src/plugins/listAuthentications/schemas.ts +4 -0
  179. package/src/plugins/listInputFieldChoices/index.ts +21 -1
  180. package/src/plugins/listInputFieldChoices/schemas.ts +61 -76
  181. package/src/plugins/listInputFields/index.test.ts +5 -9
  182. package/src/plugins/listInputFields/index.ts +20 -2
  183. package/src/plugins/manifest/index.test.ts +503 -197
  184. package/src/plugins/manifest/index.ts +338 -82
  185. package/src/plugins/manifest/schemas.ts +9 -2
  186. package/src/plugins/registry/index.ts +8 -2
  187. package/src/plugins/request/index.ts +1 -0
  188. package/src/plugins/runAction/index.ts +18 -0
  189. package/src/resolvers/actionKey.ts +15 -13
  190. package/src/resolvers/actionType.ts +10 -12
  191. package/src/resolvers/appKey.ts +2 -6
  192. package/src/resolvers/authenticationId.ts +25 -19
  193. package/src/resolvers/index.ts +7 -113
  194. package/src/resolvers/inputFieldKey.ts +38 -0
  195. package/src/resolvers/inputs.ts +28 -10
  196. package/src/schemas/Action.ts +8 -3
  197. package/src/schemas/App.ts +7 -9
  198. package/src/schemas/Auth.ts +1 -1
  199. package/src/schemas/Field.ts +24 -57
  200. package/src/schemas/Run.ts +40 -0
  201. package/src/schemas/UserProfile.ts +24 -7
  202. package/src/sdk.ts +18 -12
  203. package/src/types/plugin.ts +8 -0
  204. package/src/types/sdk.ts +31 -21
  205. package/src/utils/domain-utils.test.ts +196 -2
  206. package/src/utils/domain-utils.ts +77 -102
  207. package/src/utils/file-utils.test.ts +73 -0
  208. package/src/utils/file-utils.ts +94 -0
  209. package/src/utils/schema-utils.ts +96 -44
  210. package/tsconfig.tsbuildinfo +1 -1
  211. package/dist/plugins/lockVersion/index.d.ts +0 -24
  212. package/dist/plugins/lockVersion/index.d.ts.map +0 -1
  213. package/dist/plugins/lockVersion/index.js +0 -72
  214. package/dist/plugins/lockVersion/index.test.d.ts +0 -2
  215. package/dist/plugins/lockVersion/index.test.d.ts.map +0 -1
  216. package/dist/plugins/lockVersion/index.test.js +0 -129
  217. package/dist/plugins/lockVersion/schemas.d.ts +0 -10
  218. package/dist/plugins/lockVersion/schemas.d.ts.map +0 -1
  219. package/dist/plugins/lockVersion/schemas.js +0 -6
  220. package/src/plugins/lockVersion/index.test.ts +0 -176
  221. package/src/plugins/lockVersion/index.ts +0 -112
  222. package/src/plugins/lockVersion/schemas.ts +0 -9
@@ -22,7 +22,12 @@ export interface PluginProvides extends Record<string, any> {
22
22
 
23
23
  export interface PluginMeta {
24
24
  categories: string[];
25
+ type?: "list" | "item";
26
+ itemType?: string;
27
+ returnType?: string;
25
28
  inputSchema: z.ZodSchema;
29
+ outputSchema?: z.ZodSchema;
30
+ resolvers?: Record<string, any>;
26
31
  [key: string]: any;
27
32
  }
28
33
 
@@ -41,6 +46,9 @@ export type GetSdkType<TPluginProvides extends PluginProvides> =
41
46
  getContext(): NonNullable<ExtractContextProperties<TPluginProvides>>;
42
47
  };
43
48
 
49
+ export type GetContextType<TPluginProvides extends PluginProvides> =
50
+ NonNullable<ExtractContextProperties<TPluginProvides>>;
51
+
44
52
  // Helper type for standard plugin options with SDK dependency and meta context
45
53
  export type PluginOptions<TSdk = {}, TContext = {}> = {
46
54
  sdk: TSdk;
package/src/types/sdk.ts CHANGED
@@ -29,12 +29,12 @@ import type { GetAuthenticationSdkFunction } from "../plugins/getAuthentication/
29
29
  import type { FindFirstAuthenticationSdkFunction } from "../plugins/findFirstAuthentication/schemas";
30
30
  import type { FindUniqueAuthenticationSdkFunction } from "../plugins/findUniqueAuthentication/schemas";
31
31
  import type { RelayRequestSdkFunction } from "../plugins/request/schemas";
32
- import type { LockVersionPluginProvides } from "../plugins/lockVersion";
33
32
 
34
33
  import type { z } from "zod";
35
34
  import type { RegistryPluginProvides } from "../plugins/registry";
36
35
  import type { GetProfilePluginProvides } from "../plugins/getProfile";
37
- import type { AppsPluginProvides } from "../plugins/apps";
36
+ import type { AppsPluginProvides, ZapierSdkApps } from "../plugins/apps";
37
+ import type { ActionProxy } from "../plugins/apps/types";
38
38
  import type { FetchPluginProvides } from "../plugins/fetch";
39
39
  import type { ListAppsPluginProvides } from "../plugins/listApps";
40
40
  import type { GetAppPluginProvides } from "../plugins/getApp";
@@ -46,6 +46,7 @@ import type { GetAuthenticationPluginProvides } from "../plugins/getAuthenticati
46
46
  import type { FindFirstAuthenticationPluginProvides } from "../plugins/findFirstAuthentication";
47
47
  import type { FindUniqueAuthenticationPluginProvides } from "../plugins/findUniqueAuthentication";
48
48
  import type { ListInputFieldsPluginProvides } from "../plugins/listInputFields";
49
+ import type { ListInputFieldChoicesPluginProvides } from "../plugins/listInputFieldChoices";
49
50
  import type { RequestPluginProvides } from "../plugins/request";
50
51
  import type { GetSdkType } from "./plugin";
51
52
  import type { ManifestPluginProvides } from "../plugins/manifest";
@@ -56,8 +57,13 @@ import type { ManifestPluginProvides } from "../plugins/manifest";
56
57
  // Registry entry interface with proper types
57
58
  export interface FunctionRegistryEntry {
58
59
  name: string;
60
+ type?: "list" | "item";
61
+ itemType?: string;
62
+ returnType?: string;
59
63
  inputSchema: z.ZodSchema;
64
+ outputSchema?: z.ZodSchema;
60
65
  categories: string[];
66
+ resolvers?: Record<string, any>;
61
67
  }
62
68
 
63
69
  // Compose SDK functions from individual function interfaces
@@ -78,22 +84,26 @@ export interface ZapierSdkFunctions
78
84
  // Note: ZapierSdk type removed - use ReturnType<typeof createZapierSdk> instead
79
85
  // This ensures the type matches exactly what the builder produces
80
86
 
81
- export type ZapierSdk = GetSdkType<
82
- RegistryPluginProvides &
83
- FetchPluginProvides &
84
- AppsPluginProvides &
85
- ListAppsPluginProvides &
86
- ManifestPluginProvides &
87
- GetAppPluginProvides &
88
- ListActionsPluginProvides &
89
- GetActionPluginProvides &
90
- RunActionPluginProvides &
91
- LockVersionPluginProvides &
92
- ListAuthenticationsPluginProvides &
93
- GetAuthenticationPluginProvides &
94
- FindFirstAuthenticationPluginProvides &
95
- FindUniqueAuthenticationPluginProvides &
96
- ListInputFieldsPluginProvides &
97
- RequestPluginProvides &
98
- GetProfilePluginProvides
99
- >;
87
+ export interface ZapierSdk
88
+ extends GetSdkType<
89
+ RegistryPluginProvides &
90
+ FetchPluginProvides &
91
+ AppsPluginProvides &
92
+ ListAppsPluginProvides &
93
+ ManifestPluginProvides &
94
+ GetAppPluginProvides &
95
+ ListActionsPluginProvides &
96
+ GetActionPluginProvides &
97
+ RunActionPluginProvides &
98
+ ListAuthenticationsPluginProvides &
99
+ GetAuthenticationPluginProvides &
100
+ FindFirstAuthenticationPluginProvides &
101
+ FindUniqueAuthenticationPluginProvides &
102
+ ListInputFieldsPluginProvides &
103
+ ListInputFieldChoicesPluginProvides &
104
+ RequestPluginProvides &
105
+ GetProfilePluginProvides
106
+ > {
107
+ // Override apps property to intersect ActionProxy with our typed apps
108
+ apps: ActionProxy & ZapierSdkApps;
109
+ }
@@ -2,6 +2,7 @@ import { describe, it, expect } from "vitest";
2
2
  import {
3
3
  groupVersionedAppKeysByType,
4
4
  groupAppKeysByType,
5
+ toAppLocator,
5
6
  } from "./domain-utils";
6
7
 
7
8
  describe("domain-utils", () => {
@@ -134,11 +135,13 @@ describe("domain-utils", () => {
134
135
  "FormatterCLIAPI@1.0.0",
135
136
  "slack@2.1.0", // exact duplicate
136
137
  "FormatterCLIAPI@1.0.0", // exact duplicate
138
+ "slack@2.1.1",
139
+ "FormatterCLIAPI@1.0.1",
137
140
  ]);
138
141
 
139
142
  expect(result).toEqual({
140
- selectedApi: ["FormatterCLIAPI@1.0.0"],
141
- slug: ["slack@2.1.0"],
143
+ selectedApi: ["FormatterCLIAPI@1.0.0", "FormatterCLIAPI@1.0.1"],
144
+ slug: ["slack@2.1.0", "slack@2.1.1"],
142
145
  });
143
146
  });
144
147
  });
@@ -236,4 +239,195 @@ describe("domain-utils", () => {
236
239
  });
237
240
  });
238
241
  });
242
+
243
+ describe("toAppLocator", () => {
244
+ it("should reject UUID app keys", () => {
245
+ expect(() => {
246
+ toAppLocator("61e47557-af91-4b0c-a3e0-c28606357664");
247
+ }).toThrow(
248
+ "UUID app keys are not supported. Use app slug or implementation ID instead of: 61e47557-af91-4b0c-a3e0-c28606357664",
249
+ );
250
+ });
251
+
252
+ it("should reject UUID app keys with versions", () => {
253
+ expect(() => {
254
+ toAppLocator("61e47557-af91-4b0c-a3e0-c28606357664@1.0.0");
255
+ }).toThrow(
256
+ "UUID app keys are not supported. Use app slug or implementation ID instead of: 61e47557-af91-4b0c-a3e0-c28606357664@1.0.0",
257
+ );
258
+ });
259
+
260
+ it("should reject uppercase UUID app keys", () => {
261
+ expect(() => {
262
+ toAppLocator("61E47557-AF91-4B0C-A3E0-C28606357664");
263
+ }).toThrow(
264
+ "UUID app keys are not supported. Use app slug or implementation ID instead of: 61E47557-AF91-4B0C-A3E0-C28606357664",
265
+ );
266
+ });
267
+
268
+ it("should handle simple slug without version", () => {
269
+ const result = toAppLocator("slack");
270
+
271
+ expect(result).toEqual({
272
+ lookupAppKey: "slack",
273
+ slug: "slack",
274
+ implementationName: undefined,
275
+ version: undefined,
276
+ });
277
+ });
278
+
279
+ it("should handle slug with version", () => {
280
+ const result = toAppLocator("slack@1.0.0");
281
+
282
+ expect(result).toEqual({
283
+ lookupAppKey: "slack",
284
+ slug: "slack",
285
+ implementationName: undefined,
286
+ version: "1.0.0",
287
+ });
288
+ });
289
+
290
+ it("should handle dashified slug", () => {
291
+ const result = toAppLocator("google-sheets");
292
+
293
+ expect(result).toEqual({
294
+ lookupAppKey: "google-sheets",
295
+ slug: "google-sheets",
296
+ implementationName: undefined,
297
+ version: undefined,
298
+ });
299
+ });
300
+
301
+ it("should handle dashified slug with version", () => {
302
+ const result = toAppLocator("google-sheets@2.1.3");
303
+
304
+ expect(result).toEqual({
305
+ lookupAppKey: "google-sheets",
306
+ slug: "google-sheets",
307
+ implementationName: undefined,
308
+ version: "2.1.3",
309
+ });
310
+ });
311
+
312
+ it("should handle snake_cased slug and convert to dash", () => {
313
+ const result = toAppLocator("google_sheets");
314
+
315
+ expect(result).toEqual({
316
+ lookupAppKey: "google_sheets",
317
+ slug: "google-sheets",
318
+ implementationName: undefined,
319
+ version: undefined,
320
+ });
321
+ });
322
+
323
+ it("should handle snake_cased slug with version and convert to dash", () => {
324
+ const result = toAppLocator("google_sheets@2.1.3");
325
+
326
+ expect(result).toEqual({
327
+ lookupAppKey: "google_sheets",
328
+ slug: "google-sheets",
329
+ implementationName: undefined,
330
+ version: "2.1.3",
331
+ });
332
+ });
333
+
334
+ it("should handle leading underscore snake_cased slug", () => {
335
+ const result = toAppLocator("_100hires_ats");
336
+
337
+ expect(result).toEqual({
338
+ lookupAppKey: "_100hires_ats",
339
+ slug: "100hires-ats",
340
+ implementationName: undefined,
341
+ version: undefined,
342
+ });
343
+ });
344
+
345
+ it("should handle leading underscore snake_cased slug with version", () => {
346
+ const result = toAppLocator("_100hires_ats@3.0.0");
347
+
348
+ expect(result).toEqual({
349
+ lookupAppKey: "_100hires_ats",
350
+ slug: "100hires-ats",
351
+ implementationName: undefined,
352
+ version: "3.0.0",
353
+ });
354
+ });
355
+
356
+ it("should handle implementation name without version", () => {
357
+ const result = toAppLocator("SlackCLIAPI");
358
+
359
+ expect(result).toEqual({
360
+ lookupAppKey: "SlackCLIAPI",
361
+ slug: undefined,
362
+ implementationName: "SlackCLIAPI",
363
+ version: undefined,
364
+ });
365
+ });
366
+
367
+ it("should handle implementation name with version", () => {
368
+ const result = toAppLocator("SlackCLIAPI@1.29.0");
369
+
370
+ expect(result).toEqual({
371
+ lookupAppKey: "SlackCLIAPI",
372
+ slug: undefined,
373
+ implementationName: "SlackCLIAPI",
374
+ version: "1.29.0",
375
+ });
376
+ });
377
+
378
+ it("should handle edge cases with complex version numbers", () => {
379
+ const result = toAppLocator("slack@1.2.3-beta.4+build.5");
380
+
381
+ expect(result).toEqual({
382
+ lookupAppKey: "slack",
383
+ slug: "slack",
384
+ implementationName: undefined,
385
+ version: "1.2.3-beta.4+build.5",
386
+ });
387
+ });
388
+
389
+ it("should handle single character app keys", () => {
390
+ const result = toAppLocator("a");
391
+
392
+ expect(result).toEqual({
393
+ lookupAppKey: "a",
394
+ slug: "a",
395
+ implementationName: undefined,
396
+ version: undefined,
397
+ });
398
+ });
399
+
400
+ it("should handle app keys with numbers", () => {
401
+ const result = toAppLocator("app123");
402
+
403
+ expect(result).toEqual({
404
+ lookupAppKey: "app123",
405
+ slug: "app123",
406
+ implementationName: undefined,
407
+ version: undefined,
408
+ });
409
+ });
410
+
411
+ it("should handle mixed case app keys that aren't implementation names", () => {
412
+ const result = toAppLocator("SlackBot");
413
+
414
+ expect(result).toEqual({
415
+ lookupAppKey: "SlackBot",
416
+ slug: undefined,
417
+ implementationName: "SlackBot",
418
+ version: undefined,
419
+ });
420
+ });
421
+
422
+ it("should handle empty version (app key ending with @)", () => {
423
+ const result = toAppLocator("slack@");
424
+
425
+ expect(result).toEqual({
426
+ lookupAppKey: "slack",
427
+ slug: "slack",
428
+ implementationName: undefined,
429
+ version: "",
430
+ });
431
+ });
432
+ });
239
433
  });
@@ -2,14 +2,7 @@
2
2
  * Domain utility functions for working with app identifiers and data normalization
3
3
  */
4
4
 
5
- import type {
6
- Action,
7
- App,
8
- Authentication,
9
- Implementation,
10
- ImplementationMeta,
11
- Service,
12
- } from "../api/types";
5
+ import type { Action, Authentication, ImplementationMeta } from "../api/types";
13
6
  import type { ActionItem, AppItem, AuthenticationItem } from "../types/domain";
14
7
 
15
8
  /**
@@ -34,48 +27,6 @@ export function splitVersionedKey(
34
27
  return [versionedKey, undefined];
35
28
  }
36
29
 
37
- /**
38
- * Converts an API App to an AppItem with normalized field names
39
- *
40
- * @param app - Raw App from API
41
- * @returns Normalized AppItem
42
- */
43
- export function normalizeAppItem(app: App): AppItem {
44
- // Extract API name and version from current_implementation_id
45
- const [apiName, appVersion] = app.current_implementation_id
46
- ? splitVersionedKey(app.current_implementation_id)
47
- : [app.current_implementation_id || "", undefined];
48
-
49
- return {
50
- title: app.name,
51
- key: app.slug || apiName,
52
- current_implementation_id: app.current_implementation_id || "",
53
- version: appVersion, // Extract version separately
54
- };
55
- }
56
-
57
- /**
58
- * Converts an Implementation to an AppItem with normalized field names
59
- *
60
- * @param implementation - Raw Implementation from API
61
- * @returns Normalized AppItem
62
- */
63
- export function normalizeImplementationToAppItem(
64
- implementation: Implementation,
65
- ): AppItem {
66
- // Extract API name and version from selected_api
67
- const [selectedApi, appVersion] = implementation.selected_api
68
- ? splitVersionedKey(implementation.selected_api)
69
- : [implementation.selected_api || "", undefined];
70
-
71
- return {
72
- title: implementation.name || selectedApi,
73
- key: selectedApi,
74
- current_implementation_id: implementation.selected_api || "",
75
- version: appVersion, // Extract version separately
76
- };
77
- }
78
-
79
30
  /**
80
31
  * Converts a lightweight ImplementationMeta to a slimmed AppItem
81
32
  *
@@ -88,26 +39,17 @@ export function normalizeImplementationMetaToAppItem(
88
39
  // Extract API name and version from the implementation ID
89
40
  const [selectedApi, appVersion] = splitVersionedKey(implementationMeta.id);
90
41
 
91
- return {
92
- title: implementationMeta.name,
93
- key: selectedApi,
94
- current_implementation_id: implementationMeta.id, // Keep the full versioned ID
95
- version: appVersion, // Extract version separately
96
- slug: implementationMeta.slug,
97
- };
98
- }
99
-
100
- export function normalizeServiceToAppItem(service: Service): AppItem {
101
- const [selectedApi, appVersion] = service.current_implementation_id
102
- ? splitVersionedKey(service.current_implementation_id)
103
- : ["", undefined];
42
+ // Destructure to exclude id and name before spreading
43
+ const { id, name, ...restOfImplementationMeta } = implementationMeta;
104
44
 
105
45
  return {
106
- title: service.name,
46
+ // Pass through all ImplementationMeta fields except id and name
47
+ ...restOfImplementationMeta,
48
+ // Transform key fields
49
+ title: name,
107
50
  key: selectedApi,
108
- current_implementation_id: service.current_implementation_id,
109
- version: appVersion,
110
- description: service.description,
51
+ implementation_id: id, // Keep the full versioned ID
52
+ version: appVersion, // Extract version separately
111
53
  };
112
54
  }
113
55
 
@@ -204,40 +146,23 @@ export function groupVersionedAppKeysByType(appKeys: string[]): {
204
146
  const seenSlugs = new Set<string>();
205
147
 
206
148
  for (const key of appKeys) {
207
- // Split by @ to get base name for classification (but preserve full key in results)
208
- const [keyWithoutVersion, version] = splitVersionedKey(key);
209
-
210
- // Check if it's a UUID (canonical ID) - not supported
211
- const uuidRegex =
212
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
213
- if (uuidRegex.test(keyWithoutVersion)) {
214
- throw new Error(
215
- `UUID app keys are not supported. Use app slug or implementation ID instead of: ${key}`,
216
- );
217
- }
218
-
219
- // Check if it's a snake_case string
220
- if (isSnakeCasedSlug(keyWithoutVersion)) {
221
- const dashified = dashifySnakeCasedSlug(keyWithoutVersion);
222
- const slugWithVersion = version ? `${dashified}@${version}` : dashified;
223
- if (!seenSlugs.has(slugWithVersion)) {
224
- seenSlugs.add(slugWithVersion);
225
- result.slug.push(slugWithVersion); // Preserve full key including version
149
+ const appLocator = toAppLocator(key);
150
+
151
+ if (appLocator.slug) {
152
+ // For slugs, we need to reconstruct the versioned slug
153
+ const versionedSlug = appLocator.version
154
+ ? `${appLocator.slug}@${appLocator.version}`
155
+ : appLocator.slug;
156
+ if (!seenSlugs.has(versionedSlug)) {
157
+ seenSlugs.add(versionedSlug);
158
+ result.slug.push(versionedSlug);
159
+ }
160
+ } else {
161
+ // For implementation names (selectedApi)
162
+ if (!seenSelectedApi.has(key)) {
163
+ seenSelectedApi.add(key);
164
+ result.selectedApi.push(key);
226
165
  }
227
- continue;
228
- }
229
-
230
- // Check if it's a slug (lowercase and dashes)
231
- if (keyWithoutVersion.match(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)) {
232
- seenSlugs.add(key);
233
- result.slug.push(key);
234
- continue;
235
- }
236
-
237
- // Everything else is a selected_api
238
- if (!seenSelectedApi.has(key)) {
239
- seenSelectedApi.add(key);
240
- result.selectedApi.push(key); // Preserve full key including version
241
166
  }
242
167
  }
243
168
 
@@ -259,7 +184,11 @@ export function groupAppKeysByType(appKeys: string[]): {
259
184
  };
260
185
  }
261
186
 
262
- function isSnakeCasedSlug(slug: string): boolean {
187
+ export function isSlug(slug: string): boolean {
188
+ return !!slug.match(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
189
+ }
190
+
191
+ export function isSnakeCasedSlug(slug: string): boolean {
263
192
  // Allow leading underscore for slugs starting with a number.
264
193
  if (slug.match(/^_[0-9]/)) {
265
194
  slug = slug.slice(1);
@@ -268,7 +197,7 @@ function isSnakeCasedSlug(slug: string): boolean {
268
197
  return !!slug.match(/^[a-z0-9]+(?:_[a-z0-9]+)*$/);
269
198
  }
270
199
 
271
- function dashifySnakeCasedSlug(slug: string): string {
200
+ export function dashifySnakeCasedSlug(slug: string): string {
272
201
  // Only dashify if it's a valid snake_cased slug.
273
202
  if (!isSnakeCasedSlug(slug)) {
274
203
  return slug;
@@ -281,3 +210,49 @@ function dashifySnakeCasedSlug(slug: string): string {
281
210
 
282
211
  return slug.replace(/_/g, "-");
283
212
  }
213
+
214
+ export interface AppLocator {
215
+ lookupAppKey: string;
216
+ slug?: string;
217
+ implementationName?: string;
218
+ version?: string;
219
+ }
220
+ export interface ResolvedAppLocator extends AppLocator {
221
+ implementationName: string;
222
+ }
223
+
224
+ export function isUuid(appKey: string): boolean {
225
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
226
+ appKey,
227
+ );
228
+ }
229
+
230
+ export function toAppLocator(appKey: string): AppLocator {
231
+ const [appKeyWithoutVersion, version] = splitVersionedKey(appKey);
232
+ if (isUuid(appKeyWithoutVersion)) {
233
+ throw new Error(
234
+ `UUID app keys are not supported. Use app slug or implementation ID instead of: ${appKey}`,
235
+ );
236
+ }
237
+ const slug = isSlug(appKeyWithoutVersion)
238
+ ? appKeyWithoutVersion
239
+ : isSnakeCasedSlug(appKeyWithoutVersion)
240
+ ? dashifySnakeCasedSlug(appKeyWithoutVersion)
241
+ : undefined;
242
+ return {
243
+ lookupAppKey: appKeyWithoutVersion,
244
+ slug,
245
+ implementationName: slug ? undefined : appKeyWithoutVersion,
246
+ version,
247
+ };
248
+ }
249
+
250
+ export function isResolvedAppLocator(
251
+ appLocator: AppLocator,
252
+ ): appLocator is ResolvedAppLocator {
253
+ return !!appLocator.implementationName;
254
+ }
255
+
256
+ export function toImplementationId(appLocator: ResolvedAppLocator): string {
257
+ return `${appLocator.implementationName}@${appLocator.version || "latest"}`;
258
+ }
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { resolve, writeFile, readFile } from "./file-utils";
3
+
4
+ // Mock the dynamic imports to simulate missing modules
5
+ vi.mock("fs/promises", () => {
6
+ throw new Error("Module not found");
7
+ });
8
+
9
+ vi.mock("path", () => {
10
+ throw new Error("Module not found");
11
+ });
12
+
13
+ describe("file-utils", () => {
14
+ describe("resolve", () => {
15
+ it("should handle absolute paths", async () => {
16
+ expect(await resolve("/absolute/path")).toBe("/absolute/path");
17
+ });
18
+
19
+ it("should handle relative paths with ./", async () => {
20
+ expect(await resolve("./relative/path")).toBe("/relative/path");
21
+ expect(await resolve("./relative/path", "/base")).toBe(
22
+ "/base/relative/path",
23
+ );
24
+ expect(await resolve("./relative/path", "/base/")).toBe(
25
+ "/base/relative/path",
26
+ );
27
+ });
28
+
29
+ it("should handle parent directory paths with ../", async () => {
30
+ expect(await resolve("../parent/path")).toBe("/parent/path");
31
+ expect(await resolve("../../nested/parent/path")).toBe(
32
+ "/nested/parent/path",
33
+ );
34
+ expect(await resolve("../parent/path", "/base")).toBe(
35
+ "/base/parent/path",
36
+ );
37
+ });
38
+
39
+ it("should handle paths without prefix", async () => {
40
+ expect(await resolve("simple/path")).toBe("/simple/path");
41
+ expect(await resolve("simple/path", "/base")).toBe("/base/simple/path");
42
+ expect(await resolve("simple/path", "/base/")).toBe("/base/simple/path");
43
+ });
44
+ });
45
+
46
+ describe("writeFile and readFile with in-memory filesystem", () => {
47
+ it("should write and read files using in-memory filesystem when fs is not available", async () => {
48
+ const filePath = "/test/file.txt";
49
+ const content = "Hello, world!";
50
+
51
+ await writeFile(filePath, content);
52
+
53
+ const readContent = await readFile(filePath);
54
+ expect(readContent).toBe(content);
55
+ });
56
+
57
+ it("should throw error when reading non-existent file", async () => {
58
+ await expect(readFile("/non/existent/file.txt")).rejects.toThrow(
59
+ "File not found: /non/existent/file.txt",
60
+ );
61
+ });
62
+
63
+ it("should handle multiple files in in-memory filesystem", async () => {
64
+ await writeFile("/file1.txt", "content1");
65
+ await writeFile("/file2.txt", "content2");
66
+ await writeFile("/dir/file3.txt", "content3");
67
+
68
+ expect(await readFile("/file1.txt")).toBe("content1");
69
+ expect(await readFile("/file2.txt")).toBe("content2");
70
+ expect(await readFile("/dir/file3.txt")).toBe("content3");
71
+ });
72
+ });
73
+ });