Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/release-sdks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,19 @@ jobs:
- name: Build binary (${{ matrix.target }})
run: make -C sdk/java build-ffi-lib PROFILE=release

# The JavaScript package bundles this alongside libconnector_service_ffi —
# its gRPC client loads it from generated/<platform>-<arch>/ at runtime, so
# `make -C sdk/javascript dist` needs it staged for every target here.
- name: Build gRPC FFI binary (${{ matrix.target }})
run: make -C sdk/java build-grpc-ffi-lib PROFILE=release

- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.target }}
path: |
target/${{ matrix.target }}/release/libconnector_service_ffi.*
target/${{ matrix.target }}/release/libhyperswitch_grpc_ffi.*

- name: Verify FFI library has UniFFI scaffolding
if: matrix.target == 'aarch64-apple-darwin'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import koffi from "koffi";
import path from "path";
// @ts-ignore - generated CommonJS module
import { types } from "./generated/proto.js";
import { resolveNativeLib } from "./native_lib";

// Standard Node.js __dirname
declare const __dirname: string;
Expand Down Expand Up @@ -43,8 +44,8 @@ interface GrpcFfi {

function loadGrpcFfi(libPath?: string): GrpcFfi {
if (!libPath) {
const ext = process.platform === "darwin" ? "dylib" : "so";
libPath = path.join(_dirname, "generated", `libhyperswitch_grpc_ffi.${ext}`);
// Bundled per platform under generated/<platform>-<arch>/; see native_lib.ts.
libPath = resolveNativeLib(path.join(_dirname, "generated"), "libhyperswitch_grpc_ffi");
}

const lib = koffi.load(libPath);
Expand Down
33 changes: 21 additions & 12 deletions sdk/javascript/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,17 @@ generate-proto: install-deps
# ---------------------------------------------------------------------------
# generate-grpc-bindings
# Copies the pre-built gRPC FFI native library into the generated output
# directory so it is bundled with the npm package.
# directory so it is bundled with the npm package. Staged under
# generated/<platform>-<arch>/ to match how the loader resolves it.
# Requires: build-grpc-ffi-lib to have been run first.
# ---------------------------------------------------------------------------
generate-grpc-bindings:
@[ -f "$(GRPC_FFI_LIBRARY)" ] || \
(echo "Error: gRPC FFI library not found at $(GRPC_FFI_LIBRARY). Run 'make build-grpc-ffi-lib' first." && exit 1)
@echo "Copying gRPC FFI library to $(GENERATED_OUT)/..."
@mkdir -p $(GENERATED_OUT)
@cp -f $(GRPC_FFI_LIBRARY) $(GENERATED_OUT)/
@echo "gRPC library copied to $(GENERATED_OUT)/"
@echo "Copying gRPC FFI library to $(GENERATED_OUT)/$(NODE_TARGET)/..."
@mkdir -p $(GENERATED_OUT)/$(NODE_TARGET)
@cp -f $(GRPC_FFI_LIBRARY) $(GENERATED_OUT)/$(NODE_TARGET)/
@echo "gRPC library copied to $(GENERATED_OUT)/$(NODE_TARGET)/"

# ---------------------------------------------------------------------------
# generate-all
Expand Down Expand Up @@ -163,6 +164,12 @@ JS_PLATFORMS := \
aarch64-unknown-linux-gnu:linux-arm64 \
aarch64-apple-darwin:darwin-arm64

# Native libraries bundled with the package. Both are resolved at runtime by
# resolveNativeLib() in src/payments/native_lib.ts — libconnector_service_ffi by
# the UniFFI client, libhyperswitch_grpc_ffi by the gRPC client — so both have to
# be staged for every target, or the tarball loads one and throws on the other.
JS_NATIVE_LIBS := libconnector_service_ffi libhyperswitch_grpc_ffi

# node platform-arch dir for the CURRENT build platform (used by generate-bindings).
NODE_TARGET := $(strip \
$(if $(filter x86_64-unknown-linux-gnu,$(PLATFORM)),linux-x64,\
Expand All @@ -174,7 +181,7 @@ NODE_TARGET := $(strip \
dist:
@echo "Building JavaScript SDK distribution package..."
@mkdir -p $(GENERATED_OUT)
@# The published tarball bundles each platform's native lib under
@# The published tarball bundles each platform's native libs under
@# generated/<platform>-<arch>/ so a single package works on every platform
@# and the loader selects by process.arch. Fail closed if a required binary
@# is missing unless ALLOW_PARTIAL_DIST=1 (deliberate single-platform build;
Expand All @@ -183,12 +190,14 @@ dist:
for entry in $(JS_PLATFORMS); do \
triple=$${entry%%:*}; target=$${entry##*:}; \
ext=so; case "$$triple" in *apple-darwin) ext=dylib;; esac; \
lib="$(REPO_ROOT)/target/$$triple/release/libconnector_service_ffi.$$ext"; \
if [ ! -f "$$lib" ]; then echo " missing $$triple: $$lib"; missing=1; continue; fi; \
mkdir -p "$(GENERATED_OUT)/$$target"; \
cp -f "$$lib" "$(GENERATED_OUT)/$$target/"; \
echo " staged $$target/libconnector_service_ffi.$$ext"; \
built=$$((built+1)); \
for name in $(JS_NATIVE_LIBS); do \
lib="$(REPO_ROOT)/target/$$triple/release/$$name.$$ext"; \
if [ ! -f "$$lib" ]; then echo " missing $$triple: $$lib"; missing=1; continue; fi; \
mkdir -p "$(GENERATED_OUT)/$$target"; \
cp -f "$$lib" "$(GENERATED_OUT)/$$target/"; \
echo " staged $$target/$$name.$$ext"; \
built=$$((built+1)); \
done; \
done; \
if [ "$$missing" = "1" ]; then \
if [ "$(ALLOW_PARTIAL_DIST)" = "1" ]; then \
Expand Down
5 changes: 3 additions & 2 deletions sdk/javascript/src/payments/_generated_grpc_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import koffi from "koffi";
import path from "path";
// @ts-ignore - generated CommonJS module
import { types } from "./generated/proto.js";
import { resolveNativeLib } from "./native_lib";

// Standard Node.js __dirname
declare const __dirname: string;
Expand Down Expand Up @@ -43,8 +44,8 @@ interface GrpcFfi {

function loadGrpcFfi(libPath?: string): GrpcFfi {
if (!libPath) {
const ext = process.platform === "darwin" ? "dylib" : "so";
libPath = path.join(_dirname, "generated", `libhyperswitch_grpc_ffi.${ext}`);
// Bundled per platform under generated/<platform>-<arch>/; see native_lib.ts.
libPath = resolveNativeLib(path.join(_dirname, "generated"), "libhyperswitch_grpc_ffi");
}

const lib = koffi.load(libPath);
Expand Down
49 changes: 49 additions & 0 deletions sdk/javascript/src/payments/native_lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Locates the native libraries bundled with this package.
*
* Natives are staged under generated/<platform>-<arch>/ (matching Node's
* process.platform + process.arch — e.g. linux-x64, linux-arm64, darwin-arm64)
* so a single package serves every supported runtime. Selecting on platform
* alone picks the x86-64 binary on aarch64 hosts, so both parts are needed.
*/

import fs from "fs";
import path from "path";

/** Shared library extension per platform; anything else follows ELF naming. */
const LIB_EXTENSION: Record<string, string | undefined> = {
darwin: "dylib",
win32: "dll",
};

/**
* Absolute path to `libName` for the platform and architecture we are running on.
*
* When nothing matches, reports the target we looked for and the ones this
* package does carry — otherwise the failure surfaces from dlopen as a "wrong
* ELF class" message that never names the architecture it expected.
*/
export function resolveNativeLib(generatedDir: string, libName: string): string {
const target = `${process.platform}-${process.arch}`;
const ext = LIB_EXTENSION[process.platform] ?? "so";
const libPath = path.join(generatedDir, target, `${libName}.${ext}`);
if (fs.existsSync(libPath)) return libPath;

let bundled: string[] = [];
try {
bundled = fs
.readdirSync(generatedDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort();
} catch {
// generated/ absent entirely; reported as "bundles no native libraries" below.
}

throw new Error(
`hyperswitch-prism: ${libName} is not bundled for ${target} (expected ${libPath}). ` +
(bundled.length > 0
? `This package bundles: ${bundled.join(", ")}.`
: "This package bundles no native libraries.")
);
}
9 changes: 3 additions & 6 deletions sdk/javascript/src/payments/uniffi_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { FLOWS, SINGLE_FLOWS } from "./_generated_flows.js";
// @ts-ignore - generated protobuf types
import { types } from "./generated/proto.js";
import { IntegrationError, ConnectorError } from "./errors";
import { resolveNativeLib } from "./native_lib";

// Standard Node.js __dirname
declare const __dirname: string;
Expand Down Expand Up @@ -60,12 +61,8 @@ interface FfiFunctions {

function loadLib(libPath?: string): FfiFunctions {
if (!libPath) {
const ext = process.platform === "darwin" ? "dylib" : "so";
// Native libs are bundled per platform under generated/<platform>-<arch>/
// (e.g. linux-x64, linux-arm64, darwin-arm64) so one package serves every
// architecture; select the one matching this runtime.
const target = `${process.platform}-${process.arch}`;
libPath = path.join(_dirname, "generated", target, `libconnector_service_ffi.${ext}`);
// Bundled per platform under generated/<platform>-<arch>/; see native_lib.ts.
libPath = resolveNativeLib(path.join(_dirname, "generated"), "libconnector_service_ffi");
}

const lib = koffi.load(libPath);
Expand Down
Loading