Skip to content

Commit fe9dca4

Browse files
committed
feat(cbjs): override the binary platform & architecture
1 parent ac1b855 commit fe9dca4

5 files changed

Lines changed: 110 additions & 12 deletions

File tree

docs/.vitepress/config.mts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ export default defineConfig({
158158
},
159159
]
160160
},
161+
{
162+
text: 'AWS Lambda',
163+
link: '/guide/lambda',
164+
},
161165
{
162166
text: 'FAQ',
163167
link: '/guide/faq',

docs/src/guide/lambda.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
title: AWS Lambda | Guide
3+
outline: [2, 3]
4+
---
5+
6+
# AWS Lambda
7+
8+
During installation, Cbjs downloads a native binary that matches the machine running the install, based on its platform and architecture. This is fine when you install and run on the same platform.
9+
10+
It becomes a problem when you build on one platform and run on another. The usual case is bundling on CI and deploying to AWS Lambda, which runs on Linux/x64: the binary that ends up in the bundle is the one for your CI, not the one Lambda can load. If they are not both the same platform and architecture, you'll end up with an error.
11+
12+
## Selecting the binary
13+
14+
Set `COUCHBASE_BINARY_PLATFORM` to `linux` and `COUCHBASE_BINARY_ARCH` to the Lambda architecture when you install. Use `x64` for `x86_64` functions and `arm64` for Graviton functions. Both variables override the auto-detected value and have no effect when left unset.
15+
16+
::: code-group
17+
18+
```bash [x86_64]
19+
COUCHBASE_BINARY_PLATFORM=linux COUCHBASE_BINARY_ARCH=x64 npm install
20+
```
21+
22+
```bash [arm64]
23+
COUCHBASE_BINARY_PLATFORM=linux COUCHBASE_BINARY_ARCH=arm64 npm install
24+
```
25+
26+
:::
27+
28+
::: tip
29+
Set the variables in your deploy script so the right binary is selected on every deploy.
30+
:::
31+
32+
## Checking the binary
33+
34+
Run `file` on the binary that ends up in the bundle to confirm it targets the Lambda architecture:
35+
36+
```bash
37+
file node_modules/@cbjsdev/cbjs/dist/src/couchbase-native.node
38+
```
39+
40+
You want `ELF 64-bit ... x86-64` for an `x86_64` function, or `ELF 64-bit ... ARM aarch64` for an `arm64` one. If you see `Mach-O`, the macOS binary leaked into the bundle.
41+
42+
::: warning
43+
When no override is set, the install is skipped if a binary is already present. Setting `COUCHBASE_BINARY_PLATFORM` or `COUCHBASE_BINARY_ARCH` forces a fresh download, so a binary from a previous local install won't end up in a cross-platform bundle.
44+
:::
45+
46+
## Pinning the version
47+
48+
`COUCHBASE_BINARY_VERSION` pins the version of the underlying Couchbase binary that is downloaded, regardless of platform and architecture.
49+
50+
```bash
51+
COUCHBASE_BINARY_VERSION=4.6.1 npm install
52+
```

packages/cbjs/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ Built on top of the official library, Cbjs is a drop-in replacement for the `cou
3030
The package that is specific to your platform is downloaded during the install process.
3131
Cbjs is also full **ESM native**.
3232

33+
### Cross-platform install
34+
35+
By default the native binary is selected from `process.platform` / `process.arch` of the
36+
machine running the install. When you bundle for a different target than your build machine
37+
— for example building a Linux/x64 AWS Lambda artifact from a darwin/arm64 Mac — set these
38+
environment variables so the matching binary is downloaded instead:
39+
40+
```bash
41+
COUCHBASE_BINARY_PLATFORM=linux COUCHBASE_BINARY_ARCH=x64 npm install
42+
```
43+
3344
Cbjs is your new [Couchbase SDK for Node.js with TypeScript](https://cbjs.dev/guide/features.html#compatible-with-the-official-client).
3445

3546
## Exclusive Features

packages/cbjs/scripts/install.mjs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import fs from 'node:fs';
55
import path from 'node:path';
66

77
import { downloadBinary } from './utils/downloadBinary.mjs';
8+
import { resolveBinaryTarget } from './utils/resolveBinaryTarget.mjs';
89

910
const packageAbsolutePath = process.cwd();
1011
const packageRelative = 'packages/cbjs';
@@ -13,14 +14,12 @@ const isProjectDev =
1314
packageAbsolutePath === process.env.INIT_CWD ||
1415
packageAbsolutePath.substring(process.env.INIT_CWD.length) === `/${packageRelative}`;
1516

16-
const arch = process.arch;
17-
const platform = process.platform;
18-
const sslType = 'boringssl';
17+
const target = resolveBinaryTarget({ version: process.argv[2] });
1918

20-
const binaryPackageName = `couchbase-${platform}-${arch}-napi`;
21-
22-
const binaryPackageVersion = process.env.COUCHBASE_BINARY_VERSION || process.argv[2];
23-
const binarySourcePath = `package/couchbase-v${binaryPackageVersion}-napi-v6-${platform}-${arch}-${sslType}.node`;
19+
console.info(
20+
`Resolving Couchbase binary for ${target.platform}/${target.arch}` +
21+
(target.isOverridden ? ' (override)' : '')
22+
);
2423

2524
const buildOutputDirectory = path.resolve(packageAbsolutePath, 'dist/src');
2625

@@ -38,15 +37,17 @@ if (isProjectDev) {
3837
);
3938
}
4039

41-
if (binaryDestinationPaths.every((path) => fs.existsSync(path))) {
40+
// Skip the download only when no override is set: a forced cross-platform install
41+
// must re-download, otherwise a stale binary from the build machine would be kept.
42+
if (!target.isOverridden && binaryDestinationPaths.every((path) => fs.existsSync(path))) {
4243
console.info(`Couchbase binary is already installed.`);
4344
process.exit(0);
4445
}
4546

4647
downloadBinary(
47-
binaryPackageName,
48-
binaryPackageVersion,
49-
binarySourcePath,
48+
target.binaryPackageName,
49+
target.version,
50+
target.binarySourcePath,
5051
binaryDestinationPaths
5152
)
5253
.then(() => console.info(`Couchbase binary has been installed.`))

packages/cbjs/scripts/utils/downloadBinary.mjs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,28 @@ export async function downloadBinary(
2525
const download = new Promise((resolve, reject) => {
2626
https
2727
.get(url, (response) => {
28+
if (response.statusCode !== 200) {
29+
response.resume();
30+
reject(
31+
new Error(
32+
`Failed to download Couchbase binary ${packageName}@${packageVersion} ` +
33+
`(HTTP ${response.statusCode}). This platform/arch combination may not ` +
34+
`be published on npm. URL: ${url}`
35+
)
36+
);
37+
return;
38+
}
39+
40+
let writeComplete;
41+
2842
const extractor = new tar.Parse({
2943
onentry: (entry) => {
3044
if (entry.path === binarySourcePath) {
3145
const output = fs.createWriteStream(binaryDestinationPath);
46+
writeComplete = new Promise((resolveWrite, rejectWrite) => {
47+
output.on('finish', resolveWrite);
48+
output.on('error', rejectWrite);
49+
});
3250
entry.pipe(output);
3351
} else {
3452
entry.resume();
@@ -37,7 +55,19 @@ export async function downloadBinary(
3755
});
3856

3957
extractor.on('error', reject);
40-
extractor.on('end', resolve);
58+
extractor.on('end', () => {
59+
if (!writeComplete) {
60+
reject(
61+
new Error(
62+
`Couchbase binary ${binarySourcePath} was not found in ` +
63+
`${packageName}@${packageVersion}.`
64+
)
65+
);
66+
return;
67+
}
68+
69+
writeComplete.then(resolve, reject);
70+
});
4171

4272
response.pipe(extractor);
4373
})

0 commit comments

Comments
 (0)