@superblocksteam/sdk 2.0.0-next.8 → 2.0.0-next.80

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 (43) hide show
  1. package/dist/application-build.d.mts +12 -0
  2. package/dist/application-build.d.mts.map +1 -0
  3. package/dist/application-build.mjs +113 -0
  4. package/dist/application-build.mjs.map +1 -0
  5. package/dist/cli-replacement/dev.d.mts.map +1 -1
  6. package/dist/cli-replacement/dev.mjs +1 -0
  7. package/dist/cli-replacement/dev.mjs.map +1 -1
  8. package/dist/client.d.ts +2 -1
  9. package/dist/client.d.ts.map +1 -1
  10. package/dist/client.js +7 -5
  11. package/dist/client.js.map +1 -1
  12. package/dist/dev-utils/dev-tracer.d.ts.map +1 -1
  13. package/dist/dev-utils/dev-tracer.js +19 -0
  14. package/dist/dev-utils/dev-tracer.js.map +1 -1
  15. package/dist/dev-utils/vite-plugin-react-transform.d.mts.map +1 -1
  16. package/dist/dev-utils/vite-plugin-react-transform.mjs +1 -0
  17. package/dist/dev-utils/vite-plugin-react-transform.mjs.map +1 -1
  18. package/dist/dev-utils/vite-plugin-sb-cdn.d.mts.map +1 -1
  19. package/dist/dev-utils/vite-plugin-sb-cdn.mjs +28 -7
  20. package/dist/dev-utils/vite-plugin-sb-cdn.mjs.map +1 -1
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/socket/index.d.ts +1 -11
  26. package/dist/socket/index.d.ts.map +1 -1
  27. package/dist/socket/index.js +3 -42
  28. package/dist/socket/index.js.map +1 -1
  29. package/dist/vite-plugin-inject-sb-ids-transform.d.mts +15 -0
  30. package/dist/vite-plugin-inject-sb-ids-transform.d.mts.map +1 -0
  31. package/dist/vite-plugin-inject-sb-ids-transform.mjs +86 -0
  32. package/dist/vite-plugin-inject-sb-ids-transform.mjs.map +1 -0
  33. package/package.json +16 -7
  34. package/src/application-build.mts +160 -0
  35. package/src/cli-replacement/dev.mts +1 -0
  36. package/src/client.ts +13 -4
  37. package/src/dev-utils/dev-tracer.ts +18 -0
  38. package/src/dev-utils/vite-plugin-react-transform.mts +1 -0
  39. package/src/dev-utils/vite-plugin-sb-cdn.mts +35 -7
  40. package/src/index.ts +2 -0
  41. package/src/socket/index.ts +4 -96
  42. package/src/vite-plugin-inject-sb-ids-transform.mts +104 -0
  43. package/tsconfig.tsbuildinfo +1 -1
@@ -1,15 +1,29 @@
1
1
  import { trace } from "@opentelemetry/api";
2
2
  import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
3
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
3
4
  import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
4
5
  import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
5
6
  import { Resource } from "@opentelemetry/resources";
6
7
  import { NodeSDK } from "@opentelemetry/sdk-node";
7
8
  import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
9
+ import { getLocalTokenWithUrl } from "@superblocksteam/util";
8
10
  import packageJson from "../../package.json" with { type: "json" };
9
11
  import type { Span } from "@opentelemetry/api";
10
12
 
11
13
  // NOTE: @joeyagreco - this is how the "env" facet is determined in datadog: https://docs.datadoghq.com/opentelemetry/setup/collector_exporter/#3---configure-your-application
12
14
  const ATTR_DEPLOYMENT_ENVIRONMENT = "deployment.environment";
15
+ // NOTE: @joeyagreco - this can be used to determine if we are using mock-csb, staging-csb, prod-csb, etc
16
+ const ATTR_SUPERBLOCKS_BASE_URL = "superblocks.base_url";
17
+ let superblocksTracesUrl = undefined;
18
+ let superblocksHostname = "unknown";
19
+ try {
20
+ const tokenWithUrl = await getLocalTokenWithUrl();
21
+ const superblocksBaseUrl = new URL(tokenWithUrl.superblocksBaseUrl);
22
+ superblocksTracesUrl = superblocksBaseUrl.origin + "/api/v1/traces";
23
+ superblocksHostname = superblocksBaseUrl.hostname;
24
+ } catch (e) {
25
+ console.error("[tracing init] could not determine superblocks base url", e);
26
+ }
13
27
 
14
28
  // Initialize the OpenTelemetry SDK
15
29
  const sdk = new NodeSDK({
@@ -17,8 +31,12 @@ const sdk = new NodeSDK({
17
31
  new Resource({
18
32
  [ATTR_SERVICE_NAME]: "sdk-dev-server",
19
33
  [ATTR_DEPLOYMENT_ENVIRONMENT]: process.env.SUPERBLOCKS_CLI_ENV,
34
+ [ATTR_SUPERBLOCKS_BASE_URL]: superblocksHostname,
20
35
  }),
21
36
  ),
37
+ traceExporter: new OTLPTraceExporter({
38
+ url: superblocksTracesUrl, // OTLPTraceExporter defaults to sending traffic to http://localhost:4318/v1/traces
39
+ }),
22
40
  contextManager: new AsyncLocalStorageContextManager(),
23
41
  instrumentations: [
24
42
  // Configure HTTP instrumentation with custom attributes
@@ -11,6 +11,7 @@ export function reactTransformPlugin(): Plugin {
11
11
  return {
12
12
  name: "vite-plugin-react-transform",
13
13
  enforce: "pre", // Run before other plugins
14
+ apply: "serve",
14
15
  async transform(code, id) {
15
16
  // Check for React modules
16
17
  if (id.includes("node_modules/.vite/deps/react.js")) {
@@ -469,6 +469,16 @@ async function extractNeededExports(
469
469
  }
470
470
  }
471
471
 
472
+ const wellKnownPackages = new Map<string, string>([
473
+ ["react", "https://esm.sh/react@18.2.0"],
474
+ ["react-dom", "https://esm.sh/react-dom@18.2.0"],
475
+ ["react/jsx-runtime", "https://esm.sh/react@18.2.0/jsx-runtime"],
476
+ ["react/jsx-dev-runtime", "https://esm.sh/react@18.2.0/jsx-dev-runtime"],
477
+ ]);
478
+
479
+ const react18CdnUrl = "https://esm.sh/react@18.2.0";
480
+ const reactDom18CdnUrl = "https://esm.sh/react-dom@18.2.0";
481
+
472
482
  /**
473
483
  * Creates a Vite plugin that injects an import map into the HTML.
474
484
  * It analyzes remote modules to find their imports and maps them to local Vite modules.
@@ -558,7 +568,14 @@ export async function superblocksCdnPlugin(
558
568
  const modulePreloadData = modulesToPreload.get(importUrl);
559
569
  if (modulePreloadData) {
560
570
  modulePreloadData.content = content;
561
- modulePreloadData.integrity = await calculateIntegrity(binary);
571
+
572
+ // Don't calculate integrity for react 18 CDN modules
573
+ if (
574
+ !importUrl.startsWith(react18CdnUrl) &&
575
+ !importUrl.startsWith(reactDom18CdnUrl)
576
+ ) {
577
+ modulePreloadData.integrity = await calculateIntegrity(binary);
578
+ }
562
579
  }
563
580
 
564
581
  // Process all discovered modules and add them to the preload list
@@ -570,8 +587,15 @@ export async function superblocksCdnPlugin(
570
587
  if (moduleUrl.startsWith(baseUrl)) {
571
588
  // Add the module to preload if not already added
572
589
  if (!modulesToPreload.has(moduleUrl)) {
573
- // Calculate the integrity hash
574
- const integrity = await calculateIntegrity(moduleData.binary);
590
+ // Calculate the integrity hash for non-react 18 CDN modules
591
+ let integrity: string | undefined;
592
+ if (
593
+ !moduleUrl.startsWith(react18CdnUrl) &&
594
+ !moduleUrl.startsWith(reactDom18CdnUrl)
595
+ ) {
596
+ integrity = await calculateIntegrity(moduleData.binary);
597
+ }
598
+
575
599
  modulesToPreload.set(moduleUrl, {
576
600
  url: moduleUrl,
577
601
  content: moduleData.content,
@@ -802,10 +826,12 @@ export async function superblocksCdnPlugin(
802
826
  const importMap = {
803
827
  imports: {
804
828
  ...Object.fromEntries(
805
- Object.entries(initialImportMap).map(([module, url]) => [
806
- `${isDevMode ? "/@id/" : ""}cdn:${module}`,
807
- url,
808
- ]),
829
+ Object.entries(initialImportMap).map(([module, url]) => {
830
+ if (wellKnownPackages.has(module)) {
831
+ return [module, wellKnownPackages.get(module)!];
832
+ }
833
+ return [`${isDevMode ? "/@id/" : ""}cdn:${module}`, url];
834
+ }),
809
835
  ),
810
836
  },
811
837
  // Scopes apply to specific URL prefixes for more granular control
@@ -871,6 +897,8 @@ export async function superblocksCdnPlugin(
871
897
  const chunkPath = importToChunkMap.get(moduleName);
872
898
  if (chunkPath) {
873
899
  dependencyChunkUrl = `${base}${chunkPath}`;
900
+ } else if (wellKnownPackages.has(moduleName)) {
901
+ dependencyChunkUrl = wellKnownPackages.get(moduleName)!;
874
902
  }
875
903
  }
876
904
 
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { buildApplication } from "./application-build.mjs";
2
+
1
3
  export {
2
4
  createSocketConnectionIfNeeded,
3
5
  fetchApi,
@@ -1,13 +1,7 @@
1
- import { createISocketClient, ISocket } from "@superblocksteam/shared";
2
- import WebSocket from "ws";
1
+ import { connectISocket } from "@superblocksteam/shared";
2
+ import tracer from "../dev-utils/dev-tracer.js";
3
3
  import { createRequestHandlers } from "./handlers.js";
4
- import type {
5
- GenericMiddleware,
6
- ISocketClient,
7
- MethodHandlers,
8
- RequestContextBase,
9
- SocketTimeouts,
10
- } from "../types/index.js";
4
+ import type { ISocketClient } from "../types/index.js";
11
5
  import type { ClientMethods, ServerMethods } from "@superblocksteam/shared";
12
6
 
13
7
  export type StdISocketRPCClient = ISocketClient<ServerMethods>;
@@ -44,96 +38,10 @@ export async function connectToISocketRPCServer({
44
38
  authorization,
45
39
  requestHandlers,
46
40
  [],
41
+ tracer,
47
42
  {
48
43
  connectionTimeoutInSeconds: 6 * 60, // 6 minutes
49
44
  noResponseTimeoutInSeconds: 5 * 60, // 5 minutes
50
45
  },
51
46
  );
52
47
  }
53
-
54
- // a subclass of ISocket that sends an auth token on the first request
55
- // this is useful for client-side sockets that need to authenticate
56
- // TODO(george): if we start using this for long-lived connections, we should add a way to refresh the token
57
- export class ISocketWithClientAuth<
58
- ImplementedMethods,
59
- CallableMethods,
60
- RequestContext extends RequestContextBase,
61
- > extends ISocket<ImplementedMethods, CallableMethods, RequestContext> {
62
- private readonly authorization?: string;
63
- private hasSentAuth = false;
64
-
65
- constructor(
66
- ws: WebSocket,
67
- authorization: string | undefined,
68
- requestHandlers: MethodHandlers<
69
- ImplementedMethods,
70
- CallableMethods,
71
- RequestContext
72
- >,
73
- globalMiddlewares: GenericMiddleware<CallableMethods, RequestContext>[],
74
- timeouts?: SocketTimeouts,
75
- ) {
76
- super(ws, requestHandlers, globalMiddlewares, timeouts);
77
- this.authorization = authorization;
78
- }
79
-
80
- // override `request` from the base class to send `authorization` when appropriate
81
- async request<Params, Result>(
82
- method: string,
83
- params: Params,
84
- ): Promise<Result> {
85
- // only send `authorization` on the first request
86
- const authorization = this.hasSentAuth ? undefined : this.authorization;
87
- const result = await super.request<Params, Result>(
88
- method,
89
- params,
90
- authorization,
91
- );
92
- this.hasSentAuth = true;
93
- return result;
94
- }
95
- }
96
-
97
- export async function connectISocket<
98
- CallableMethods,
99
- ImplementedMethods,
100
- RequestContext extends RequestContextBase = RequestContextBase,
101
- >(
102
- wsUrl: string,
103
- authorization: string | undefined,
104
- requestHandlers: MethodHandlers<
105
- ImplementedMethods,
106
- CallableMethods,
107
- RequestContext
108
- >,
109
- globalMiddlewares: GenericMiddleware<CallableMethods, RequestContext>[],
110
- timeouts?: SocketTimeouts,
111
- ): Promise<ISocketClient<CallableMethods>> {
112
- const ws = await connectWebSocket(wsUrl);
113
- const isocket = new ISocketWithClientAuth(
114
- ws,
115
- authorization,
116
- requestHandlers,
117
- globalMiddlewares,
118
- timeouts,
119
- );
120
- return createISocketClient(isocket);
121
- }
122
-
123
- export function connectWebSocket(wsUrl: string): Promise<WebSocket> {
124
- return new Promise((resolve, reject) => {
125
- const ws = new WebSocket(wsUrl);
126
-
127
- ws.addEventListener("open", () => {
128
- // Resolve the promise with the WebSocket instance when the connection is open
129
- resolve(ws);
130
- });
131
-
132
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
133
- // @ts-ignore
134
- ws.addEventListener("error", (error: Error) => {
135
- // Reject the promise if there's an error
136
- reject(error);
137
- });
138
- });
139
- }
@@ -0,0 +1,104 @@
1
+ import path from "node:path";
2
+ import babelGenerate from "@babel/generator";
3
+ import { parse } from "@babel/parser";
4
+ import {
5
+ supplementElementIds,
6
+ generateRootSource,
7
+ } from "@superblocksteam/vite-plugin-file-sync";
8
+ import { yellow, red } from "colorette";
9
+ import fs from "fs-extra";
10
+ import { createLogger } from "vite";
11
+ import { getLogger } from "./dev-utils/dev-logger.mjs";
12
+ import type { HydratedRoute } from "@superblocksteam/vite-plugin-file-sync";
13
+ import type { Logger, Plugin } from "vite";
14
+
15
+ const routesFileBaseName = "routes.json";
16
+
17
+ /**
18
+ * Creates a Vite plugin that injects Superblocks IDs into the application.
19
+ * This will primarily be used during builds, as the dev server leverages the
20
+ * file sync manager to inject and keep the IDs up to date.
21
+ *
22
+ * Features:
23
+ * - Injects the root component with the routes data
24
+ * - Injects Superblocks IDs into all components
25
+ *
26
+ * @param root - The root directory of the application
27
+ * @returns A Vite plugin that injects Superblocks IDs into the application's components
28
+ */
29
+ export async function injectSuperblocksIdsPlugin(root: string) {
30
+ const viteLogger = createLogger();
31
+ const logger = getLogger();
32
+ viteLogger.info = logger.info;
33
+ viteLogger.warn = (msg: string) => {
34
+ logger.warn(yellow(msg));
35
+ };
36
+ viteLogger.warnOnce = (msg: string) => {
37
+ logger.warn(yellow(msg));
38
+ };
39
+ viteLogger.error = (msg: string) => {
40
+ logger.error(red(msg));
41
+ };
42
+
43
+ viteLogger.clearScreen = () => {};
44
+
45
+ const routes: HydratedRoute[] = await getRoutes(root, viteLogger);
46
+
47
+ return {
48
+ name: "sb-inject-superblocks-ids",
49
+ enforce: "pre",
50
+
51
+ transform(code, id) {
52
+ const relativePath = path.relative(root, id);
53
+
54
+ if (relativePath === "root.tsx") {
55
+ const source = generateRootSource(code, routes);
56
+ return {
57
+ code: source,
58
+ map: null,
59
+ };
60
+ } else if (id.endsWith(".tsx")) {
61
+ const ast = parse(code, {
62
+ sourceType: "module",
63
+ sourceFilename: id,
64
+ plugins: ["jsx"],
65
+ });
66
+
67
+ supplementElementIds({
68
+ fileName: id,
69
+ ast,
70
+ shouldModifyAst: true,
71
+ });
72
+
73
+ const result = babelGenerate.default(ast);
74
+ return result.code;
75
+ }
76
+
77
+ return code;
78
+ },
79
+ } as Plugin;
80
+ }
81
+
82
+ async function getRoutes(root: string, logger: Logger) {
83
+ const routesFile = path.join(root, routesFileBaseName);
84
+ if (!(await fs.pathExists(routesFile))) {
85
+ logger.warn(`routes file not found at expected location: ${routesFile}`);
86
+ return [];
87
+ }
88
+
89
+ try {
90
+ const routesData = await fs.readFile(routesFile, "utf-8");
91
+ const routes = JSON.parse(routesData) as Record<string, { file: string }>;
92
+
93
+ return Object.entries(routes).map(([path, { file }]) => ({
94
+ path,
95
+ component: file,
96
+ }));
97
+ } catch (err) {
98
+ logger.error(
99
+ `error reading routes file: ${routesFile}. error=[${JSON.stringify(err)}]`,
100
+ );
101
+ }
102
+
103
+ return [];
104
+ }