@superblocksteam/sdk 2.0.99 → 2.0.100-next.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 (71) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/cli-replacement/dev.d.mts.map +1 -1
  3. package/dist/cli-replacement/dev.mjs +64 -20
  4. package/dist/cli-replacement/dev.mjs.map +1 -1
  5. package/dist/cli-replacement/init.d.ts.map +1 -1
  6. package/dist/cli-replacement/init.js +1 -0
  7. package/dist/cli-replacement/init.js.map +1 -1
  8. package/dist/client.d.ts +8 -21
  9. package/dist/client.d.ts.map +1 -1
  10. package/dist/client.js +36 -5
  11. package/dist/client.js.map +1 -1
  12. package/dist/collect-sdk-apis.d.mts +10 -6
  13. package/dist/collect-sdk-apis.d.mts.map +1 -1
  14. package/dist/collect-sdk-apis.mjs +20 -5
  15. package/dist/collect-sdk-apis.mjs.map +1 -1
  16. package/dist/collect-sdk-apis.test.mjs +53 -4
  17. package/dist/collect-sdk-apis.test.mjs.map +1 -1
  18. package/dist/dbfs/client.d.ts +2 -0
  19. package/dist/dbfs/client.d.ts.map +1 -1
  20. package/dist/dbfs/client.js +43 -17
  21. package/dist/dbfs/client.js.map +1 -1
  22. package/dist/dbfs/client.test.d.ts +2 -0
  23. package/dist/dbfs/client.test.d.ts.map +1 -0
  24. package/dist/dbfs/client.test.js +81 -0
  25. package/dist/dbfs/client.test.js.map +1 -0
  26. package/dist/extract-api-integrations.d.mts +17 -0
  27. package/dist/extract-api-integrations.d.mts.map +1 -0
  28. package/dist/extract-api-integrations.mjs +233 -0
  29. package/dist/extract-api-integrations.mjs.map +1 -0
  30. package/dist/extract-api-integrations.test.d.mts +2 -0
  31. package/dist/extract-api-integrations.test.d.mts.map +1 -0
  32. package/dist/extract-api-integrations.test.mjs +97 -0
  33. package/dist/extract-api-integrations.test.mjs.map +1 -0
  34. package/dist/sdk.d.ts +13 -6
  35. package/dist/sdk.d.ts.map +1 -1
  36. package/dist/sdk.js +13 -6
  37. package/dist/sdk.js.map +1 -1
  38. package/dist/sdk.test.d.ts +2 -0
  39. package/dist/sdk.test.d.ts.map +1 -0
  40. package/dist/sdk.test.js +71 -0
  41. package/dist/sdk.test.js.map +1 -0
  42. package/dist/telemetry/index.d.ts.map +1 -1
  43. package/dist/telemetry/index.js +9 -2
  44. package/dist/telemetry/index.js.map +1 -1
  45. package/dist/telemetry/logging.d.ts.map +1 -1
  46. package/dist/telemetry/logging.js +1 -0
  47. package/dist/telemetry/logging.js.map +1 -1
  48. package/dist/version-control.d.mts +5 -3
  49. package/dist/version-control.d.mts.map +1 -1
  50. package/dist/version-control.mjs +18 -10
  51. package/dist/version-control.mjs.map +1 -1
  52. package/dist/vite-plugin-generate-api-build-manifest.d.mts.map +1 -1
  53. package/dist/vite-plugin-generate-api-build-manifest.mjs.map +1 -1
  54. package/package.json +6 -6
  55. package/src/cli-replacement/dev.mts +73 -24
  56. package/src/cli-replacement/init.ts +1 -0
  57. package/src/client.ts +53 -22
  58. package/src/collect-sdk-apis.mts +30 -6
  59. package/src/collect-sdk-apis.test.mts +58 -4
  60. package/src/dbfs/client.test.ts +116 -0
  61. package/src/dbfs/client.ts +60 -21
  62. package/src/extract-api-integrations.mts +345 -0
  63. package/src/extract-api-integrations.test.mts +114 -0
  64. package/src/sdk.test.ts +106 -0
  65. package/src/sdk.ts +40 -3
  66. package/src/telemetry/index.ts +13 -2
  67. package/src/telemetry/logging.ts +1 -0
  68. package/src/version-control.mts +23 -8
  69. package/src/vite-plugin-generate-api-build-manifest.mts +7 -1
  70. package/test/version-control.test.mts +81 -1
  71. package/tsconfig.tsbuildinfo +1 -1
package/src/client.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  import * as fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { Bucketeer, FileDescriptor } from "@superblocksteam/bucketeer-sdk";
4
- import { ExportViewMode } from "@superblocksteam/shared";
4
+ import {
5
+ CommitType,
6
+ ExportViewMode,
7
+ type GetApplicationCommitsResponseBody,
8
+ type GetPublicOrganizationSummaryResponseBody,
9
+ } from "@superblocksteam/shared";
5
10
  import {
6
11
  COMPONENT_EVENT_HEADER,
7
12
  ComponentEvent,
@@ -72,6 +77,7 @@ export interface ApplicationWrapper {
72
77
 
73
78
  export interface MultiPageApplicationWrapper {
74
79
  application: Record<string, any>;
80
+ deployedCommitId?: string | null;
75
81
  pages: Page[];
76
82
  apis: Record<string, any>[];
77
83
  }
@@ -116,25 +122,7 @@ type Branch = {
116
122
 
117
123
  type ResponseWithMeta<T> = { responseMeta: unknown; data: T };
118
124
 
119
- interface CommitDto {
120
- commitMessage: string;
121
- committer: {
122
- name?: string;
123
- email: string;
124
- };
125
- commitId: string;
126
- commitDate: number;
127
- branch?: string;
128
- autosave?: boolean;
129
- tag: string;
130
- externalCommitId?: string | null;
131
- externalCommitDate?: number | null;
132
- }
133
-
134
- export interface GetCommitsResponseBody {
135
- autosaves: CommitDto[];
136
- commits: CommitDto[];
137
- }
125
+ export type GetCommitsResponseBody = GetApplicationCommitsResponseBody;
138
126
 
139
127
  enum ResourceType {
140
128
  APPLICATION = "APPLICATION",
@@ -959,6 +947,45 @@ export async function fetchCurrentUser(
959
947
  }
960
948
  }
961
949
 
950
+ export async function fetchOrganizationSummary(
951
+ cliVersion: string,
952
+ token: string,
953
+ superblocksBaseUrl: string,
954
+ organizationId: string,
955
+ ): Promise<GetPublicOrganizationSummaryResponseBody> {
956
+ try {
957
+ const config: AxiosRequestConfig = {
958
+ method: "get",
959
+ url: new URL(
960
+ `${BASE_SERVER_PUBLIC_API_URL_v2}/organizations/${organizationId}/summary`,
961
+ superblocksBaseUrl,
962
+ ).toString(),
963
+ headers: {
964
+ Authorization: "Bearer " + token,
965
+ [CLI_VERSION_HEADER]: cliVersion,
966
+ },
967
+ };
968
+ const response = await axios(config);
969
+ return response.data.data as GetPublicOrganizationSummaryResponseBody;
970
+ } catch (e: any) {
971
+ if (axios.isAxiosError(e) && e.response?.status === 404) {
972
+ throw new NotFoundError(`Organization ${organizationId} was not found`);
973
+ }
974
+
975
+ let message: string;
976
+ if (e instanceof AxiosError) {
977
+ message =
978
+ (e.response?.data?.responseMeta?.message as string) ??
979
+ JSON.stringify(e.response?.data) ??
980
+ e.response?.statusText ??
981
+ e?.message;
982
+ } else {
983
+ message = `${e?.message ? e?.message : e}`;
984
+ }
985
+ throw new Error(`Could not fetch organization summary: ${message}`);
986
+ }
987
+ }
988
+
962
989
  const createSocketConnectionIfNeeded = async (
963
990
  cliVersion: string,
964
991
  token: string,
@@ -1381,6 +1408,7 @@ export async function fetchApplicationCommits({
1381
1408
  cliVersion,
1382
1409
  applicationId,
1383
1410
  branch,
1411
+ commitType = CommitType.COMMIT,
1384
1412
  token,
1385
1413
  superblocksBaseUrl,
1386
1414
  injectedHeaders = {},
@@ -1390,6 +1418,7 @@ export async function fetchApplicationCommits({
1390
1418
  cliVersion: string;
1391
1419
  applicationId: string;
1392
1420
  branch?: string;
1421
+ commitType?: CommitType;
1393
1422
  token: string;
1394
1423
  superblocksBaseUrl: string;
1395
1424
  injectedHeaders: Record<string, string>;
@@ -1409,7 +1438,7 @@ export async function fetchApplicationCommits({
1409
1438
  superblocksBaseUrl,
1410
1439
  );
1411
1440
  serverURL.search = new URLSearchParams({
1412
- commitType: "commit",
1441
+ commitType,
1413
1442
  ...(limit ? { limit: limit.toString() } : {}),
1414
1443
  ...(offset ? { offset: offset.toString() } : {}),
1415
1444
  }).toString();
@@ -1437,6 +1466,7 @@ export async function fetchApiCommits({
1437
1466
  cliVersion,
1438
1467
  applicationId,
1439
1468
  branch,
1469
+ commitType = CommitType.COMMIT,
1440
1470
  token,
1441
1471
  superblocksBaseUrl,
1442
1472
  injectedHeaders = {},
@@ -1446,6 +1476,7 @@ export async function fetchApiCommits({
1446
1476
  cliVersion: string;
1447
1477
  applicationId: string;
1448
1478
  branch?: string;
1479
+ commitType?: CommitType;
1449
1480
  token: string;
1450
1481
  superblocksBaseUrl: string;
1451
1482
  injectedHeaders: Record<string, string>;
@@ -1465,7 +1496,7 @@ export async function fetchApiCommits({
1465
1496
  superblocksBaseUrl,
1466
1497
  );
1467
1498
  serverURL.search = new URLSearchParams({
1468
- commitType: "commit",
1499
+ commitType,
1469
1500
  ...(limit ? { limit: limit.toString() } : {}),
1470
1501
  ...(offset ? { offset: offset.toString() } : {}),
1471
1502
  }).toString();
@@ -1,7 +1,16 @@
1
1
  import path from "node:path";
2
+ import {
3
+ extractIntegrationsFromSource,
4
+ type IntegrationInfo,
5
+ } from "./extract-api-integrations.mjs";
2
6
  import { parseSdkRegistry } from "./parse-sdk-registry.mjs";
3
7
 
4
- export type SdkApisResult = Record<string, { entryPoint: string }>;
8
+ export type SdkApiEntry = {
9
+ entryPoint: string;
10
+ integrations?: IntegrationInfo[];
11
+ };
12
+
13
+ export type SdkApisResult = Record<string, SdkApiEntry>;
5
14
 
6
15
  export type CollectSdkApisFs = {
7
16
  pathExists: (p: string) => Promise<boolean>;
@@ -9,13 +18,14 @@ export type CollectSdkApisFs = {
9
18
  };
10
19
 
11
20
  /**
12
- * Collect SDK API entry points from the app's registry at server/apis/index.ts.
13
- * Keys match Object.keys(registry.default) so useApi("Key") works in both edit
14
- * and deployed modes.
21
+ * Collect SDK API entry points and declared integrations from the app's registry
22
+ * at server/apis/index.ts. Keys match Object.keys(registry.default) so
23
+ * useApi("Key") works in both edit and deployed modes.
15
24
  *
16
25
  * @param root - App root directory
17
26
  * @param fs - File system operations (pathExists, readFile)
18
- * @returns Map of API name → { entryPoint } or empty object if no registry
27
+ * @returns Map of API name → { entryPoint, integrations? } or empty object
28
+ * if no registry exists
19
29
  */
20
30
  export async function collectSdkApisFromRegistry(
21
31
  root: string,
@@ -37,7 +47,21 @@ export async function collectSdkApisFromRegistry(
37
47
 
38
48
  const sdkApis: SdkApisResult = {};
39
49
  for (const [apiName, entryPoint] of keyToPath) {
40
- sdkApis[apiName] = { entryPoint };
50
+ const entry: SdkApiEntry = { entryPoint };
51
+ const absolutePath = path.join(root, entryPoint);
52
+
53
+ try {
54
+ const source = await fs.readFile(absolutePath, "utf-8");
55
+ const integrations = extractIntegrationsFromSource(source);
56
+ if (integrations.length > 0) {
57
+ entry.integrations = integrations;
58
+ }
59
+ } catch {
60
+ // Integration extraction is best-effort. If a source file cannot be read,
61
+ // still emit the entry point so SDK execution continues to work.
62
+ }
63
+
64
+ sdkApis[apiName] = entry;
41
65
  }
42
66
  return sdkApis;
43
67
  }
@@ -44,13 +44,30 @@ import CreateOrder from './CreateOrder/api.js';
44
44
  const apis = { GetUsers, CreateOrder } as const;
45
45
  export default apis;
46
46
  `;
47
+ const readFile = vi.fn(async (filePath: string) => {
48
+ const rel = path.relative(root, filePath).replace(/\\/g, "/");
49
+ if (rel === "server/apis/index.ts") {
50
+ return registryContent;
51
+ }
52
+ if (rel === "server/apis/GetUsers/api.ts") {
53
+ return `export default api({ integrations: { db: postgres("pg-1") } });`;
54
+ }
55
+ if (rel === "server/apis/CreateOrder/api.ts") {
56
+ return `export default api({});`;
57
+ }
58
+ throw new Error(`Unexpected read: ${rel}`);
59
+ });
60
+
47
61
  const result = await collectSdkApisFromRegistry(root, {
48
62
  pathExists,
49
- readFile: vi.fn().mockResolvedValue(registryContent),
63
+ readFile,
50
64
  });
51
65
 
52
66
  expect(result).toEqual({
53
- GetUsers: { entryPoint: "server/apis/GetUsers/api.ts" },
67
+ GetUsers: {
68
+ entryPoint: "server/apis/GetUsers/api.ts",
69
+ integrations: [{ key: "db", pluginId: "postgres", id: "pg-1" }],
70
+ },
54
71
  CreateOrder: { entryPoint: "server/apis/CreateOrder/api.ts" },
55
72
  });
56
73
  });
@@ -61,13 +78,50 @@ import GetUsers from './v2/GetUsers/api.js';
61
78
  const apis = { GetUsers } as const;
62
79
  export default apis;
63
80
  `;
81
+ const readFile = vi.fn(async (filePath: string) => {
82
+ const rel = path.relative(root, filePath).replace(/\\/g, "/");
83
+ if (rel === "server/apis/index.ts") {
84
+ return registryContent;
85
+ }
86
+ if (rel === "server/apis/v2/GetUsers/api.ts") {
87
+ return `const DB = postgres("pg-2"); export default api({ integrations: { db: DB } });`;
88
+ }
89
+ throw new Error(`Unexpected read: ${rel}`);
90
+ });
91
+
92
+ const result = await collectSdkApisFromRegistry(root, {
93
+ pathExists,
94
+ readFile,
95
+ });
96
+
97
+ expect(result).toEqual({
98
+ GetUsers: {
99
+ entryPoint: "server/apis/v2/GetUsers/api.ts",
100
+ integrations: [{ key: "db", pluginId: "postgres", id: "pg-2" }],
101
+ },
102
+ });
103
+ });
104
+
105
+ it("keeps entryPoint when integration extraction fails", async () => {
106
+ const registryContent = `
107
+ import GetUsers from './GetUsers/api.js';
108
+ export default { GetUsers };
109
+ `;
110
+ const readFile = vi.fn(async (filePath: string) => {
111
+ const rel = path.relative(root, filePath).replace(/\\/g, "/");
112
+ if (rel === "server/apis/index.ts") {
113
+ return registryContent;
114
+ }
115
+ throw new Error("source unreadable");
116
+ });
117
+
64
118
  const result = await collectSdkApisFromRegistry(root, {
65
119
  pathExists,
66
- readFile: vi.fn().mockResolvedValue(registryContent),
120
+ readFile,
67
121
  });
68
122
 
69
123
  expect(result).toEqual({
70
- GetUsers: { entryPoint: "server/apis/v2/GetUsers/api.ts" },
124
+ GetUsers: { entryPoint: "server/apis/GetUsers/api.ts" },
71
125
  });
72
126
  });
73
127
 
@@ -0,0 +1,116 @@
1
+ import { ExportViewMode, NotFoundError } from "@superblocksteam/shared";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ const { connectToISocketRPCServer, unwrapResponseDto, warn } = vi.hoisted(
5
+ () => ({
6
+ connectToISocketRPCServer: vi.fn(),
7
+ unwrapResponseDto: vi.fn(),
8
+ warn: vi.fn(),
9
+ }),
10
+ );
11
+
12
+ vi.mock("../socket/index.js", () => ({
13
+ connectToISocketRPCServer,
14
+ }));
15
+
16
+ vi.mock("../telemetry/logging.js", () => ({
17
+ getLogger: () => ({
18
+ warn,
19
+ }),
20
+ }));
21
+
22
+ vi.mock("@superblocksteam/shared", async () => {
23
+ const actual = await vi.importActual("@superblocksteam/shared");
24
+
25
+ return {
26
+ ...actual,
27
+ unwrapResponseDto,
28
+ };
29
+ });
30
+
31
+ import { getApplicationDirectoryHash } from "./client.js";
32
+
33
+ describe("getApplicationDirectoryHash", () => {
34
+ const directoryContentsGet = vi.fn();
35
+ const rpcClient = {
36
+ call: {
37
+ v3: {
38
+ application: {
39
+ directoryContents: {
40
+ get: directoryContentsGet,
41
+ },
42
+ },
43
+ },
44
+ },
45
+ close: vi.fn(),
46
+ };
47
+
48
+ beforeEach(() => {
49
+ connectToISocketRPCServer.mockReset();
50
+ directoryContentsGet.mockReset();
51
+ rpcClient.close.mockReset();
52
+ unwrapResponseDto.mockReset();
53
+ warn.mockReset();
54
+
55
+ connectToISocketRPCServer.mockResolvedValue(rpcClient);
56
+ });
57
+
58
+ it("does not fall back to the live hash for commit lookups", async () => {
59
+ directoryContentsGet.mockResolvedValue("commit-request");
60
+ unwrapResponseDto.mockRejectedValueOnce(
61
+ new NotFoundError("commit hash not found"),
62
+ );
63
+
64
+ await expect(
65
+ getApplicationDirectoryHash(
66
+ "token",
67
+ "https://app.superblocks.com",
68
+ "app-123",
69
+ "main",
70
+ { commitId: "commit-123" },
71
+ ),
72
+ ).rejects.toBeInstanceOf(NotFoundError);
73
+
74
+ expect(directoryContentsGet).toHaveBeenCalledTimes(1);
75
+ expect(directoryContentsGet).toHaveBeenCalledWith({
76
+ applicationId: "app-123",
77
+ commitId: "commit-123",
78
+ viewMode: ExportViewMode.EXPORT_COMMIT,
79
+ });
80
+ expect(warn).not.toHaveBeenCalled();
81
+ expect(rpcClient.close).toHaveBeenCalledOnce();
82
+ });
83
+
84
+ it("falls back from draft to live for non-commit lookups", async () => {
85
+ directoryContentsGet
86
+ .mockResolvedValueOnce("draft-request")
87
+ .mockResolvedValueOnce("live-request");
88
+ unwrapResponseDto
89
+ .mockRejectedValueOnce(new NotFoundError("draft hash not found"))
90
+ .mockResolvedValueOnce({ hash: "live-hash" });
91
+
92
+ await expect(
93
+ getApplicationDirectoryHash(
94
+ "token",
95
+ "https://app.superblocks.com",
96
+ "app-123",
97
+ "main",
98
+ ),
99
+ ).resolves.toBe("live-hash");
100
+
101
+ expect(directoryContentsGet).toHaveBeenNthCalledWith(1, {
102
+ applicationId: "app-123",
103
+ branchName: "main",
104
+ viewMode: ExportViewMode.EXPORT_DRAFT,
105
+ });
106
+ expect(directoryContentsGet).toHaveBeenNthCalledWith(2, {
107
+ applicationId: "app-123",
108
+ branchName: "main",
109
+ viewMode: ExportViewMode.EXPORT_LIVE,
110
+ });
111
+ expect(warn).toHaveBeenCalledWith(
112
+ "Draft state not found, using live edit hash: live-hash",
113
+ );
114
+ expect(rpcClient.close).toHaveBeenCalledOnce();
115
+ });
116
+ });
@@ -15,44 +15,62 @@ export async function getApplicationDirectoryHash(
15
15
  superblocksBaseUrl: string,
16
16
  applicationId: string,
17
17
  branch: string | undefined,
18
- options?: { silent?: boolean },
18
+ options?: { commitId?: string; silent?: boolean },
19
19
  ) {
20
20
  const rpcClient = await connectToISocketRPCServer({
21
21
  token,
22
22
  superblocksBaseUrl,
23
23
  });
24
24
  try {
25
- const response = await unwrapResponseDto(
26
- rpcClient.call.v3.application.directoryContents.get({
27
- applicationId,
28
- branchName: branch,
29
- viewMode: ExportViewMode.EXPORT_DRAFT,
30
- }),
31
- );
32
- const liveEditHash = response.hash;
33
- if (!liveEditHash) {
34
- throw new Error(
35
- `No directory contents hash found for the view mode ${ExportViewMode.EXPORT_DRAFT}`,
25
+ if (options?.commitId) {
26
+ const response = await unwrapResponseDto(
27
+ rpcClient.call.v3.application.directoryContents.get({
28
+ applicationId,
29
+ commitId: options.commitId,
30
+ viewMode: ExportViewMode.EXPORT_COMMIT,
31
+ }),
36
32
  );
33
+ const commitHash = response.hash;
34
+ if (!commitHash) {
35
+ throw new Error(
36
+ `No directory contents hash found for commit ${options.commitId}`,
37
+ );
38
+ }
39
+ return commitHash;
37
40
  }
38
- return liveEditHash;
39
- } catch (e) {
40
- if (e instanceof NotFoundError) {
41
+ try {
41
42
  const response = await unwrapResponseDto(
42
43
  rpcClient.call.v3.application.directoryContents.get({
43
44
  applicationId,
44
45
  branchName: branch,
45
- viewMode: ExportViewMode.EXPORT_LIVE,
46
+ viewMode: ExportViewMode.EXPORT_DRAFT,
46
47
  }),
47
48
  );
48
- if (!options?.silent) {
49
- getLogger().warn(
50
- `Draft state not found, using live edit hash: ${response.hash}`,
49
+ const liveEditHash = response.hash;
50
+ if (!liveEditHash) {
51
+ throw new Error(
52
+ `No directory contents hash found for the view mode ${ExportViewMode.EXPORT_DRAFT}`,
51
53
  );
52
54
  }
53
- return response.hash;
55
+ return liveEditHash;
56
+ } catch (e) {
57
+ if (e instanceof NotFoundError) {
58
+ const response = await unwrapResponseDto(
59
+ rpcClient.call.v3.application.directoryContents.get({
60
+ applicationId,
61
+ branchName: branch,
62
+ viewMode: ExportViewMode.EXPORT_LIVE,
63
+ }),
64
+ );
65
+ if (!options?.silent) {
66
+ getLogger().warn(
67
+ `Draft state not found, using live edit hash: ${response.hash}`,
68
+ );
69
+ }
70
+ return response.hash;
71
+ }
72
+ throw e;
54
73
  }
55
- throw e;
56
74
  } finally {
57
75
  rpcClient.close();
58
76
  }
@@ -86,6 +104,27 @@ export async function downloadApplicationDirectory(
86
104
  }
87
105
  }
88
106
 
107
+ export async function downloadDirectoryByHash(
108
+ token: string,
109
+ superblocksBaseUrl: string,
110
+ directoryHash: string,
111
+ localDirectoryPath: string,
112
+ ): Promise<void> {
113
+ const rpcClient = await connectToISocketRPCServer({
114
+ token,
115
+ superblocksBaseUrl,
116
+ });
117
+ try {
118
+ await doDownloadDirectoryToLocal(
119
+ rpcClient,
120
+ directoryHash,
121
+ localDirectoryPath,
122
+ );
123
+ } finally {
124
+ rpcClient.close();
125
+ }
126
+ }
127
+
89
128
  export async function uploadLocalApplication(
90
129
  token: string,
91
130
  superblocksBaseUrl: string,