Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c23b00d
feat(wasm): add WebAssembly build with JS/TS interface (Phase 0)
zeroshade Jun 18, 2026
551ebee
feat(wasm): browser/node build seam, resolve API, credential-resolver…
zeroshade Jun 18, 2026
dc223b0
test(wasm): exercise resolve in the node spike harness
zeroshade Jun 18, 2026
7534fe7
fix(wasm): address roborev 6502 — partial search results and list env…
zeroshade Jun 18, 2026
c5abfe9
fix(wasm): preserve registry error in resolve not-found path (roborev…
zeroshade Jun 18, 2026
54d2cb3
feat(wasm): add @columnar-tech/dbc-wasm npm package (Phase 3)
zeroshade Jun 18, 2026
e96f1e0
fix(wasm): per-call client config so loadDbc instances are isolated (…
zeroshade Jun 19, 2026
b40e4bc
feat(wasm): stream HTTP response bodies instead of buffering (Phase 4)
zeroshade Jun 19, 2026
82aab72
fix(wasm): per-instance client handle to retain refreshed tokens (rob…
zeroshade Jun 19, 2026
28893ec
feat(wasm): Windows-host path groundwork via splitConfigList seam (Ph…
zeroshade Jun 19, 2026
a1c8a00
ci(wasm): Windows-host smoke gate + normalizeLocation unit test (Phas…
zeroshade Jun 19, 2026
03ae190
fix(wasm): only normalize locations on Windows (roborev 6527)
zeroshade Jun 19, 2026
c23b35e
ci(wasm): trigger smoke on testdata fixture changes (roborev 6528)
zeroshade Jun 19, 2026
2f8b4b7
feat(wasm): opt-in Node Worker Thread runtime via loadDbc({worker:tru…
zeroshade Jun 19, 2026
1ede706
fix(wasm): reject worker RPCs after close or exit instead of hanging …
zeroshade Jun 19, 2026
2aef7ee
fix(wasm): terminate worker when loadDbc({worker:true}) init fails (r…
zeroshade Jun 19, 2026
5a3eaf5
refactor(wasm): unify dbc-wasm backends and fix API inconsistencies (…
zeroshade Jun 19, 2026
75658b1
refactor(wasm): extract shared Go/wasm boot sequence into boot.cjs
zeroshade Jun 19, 2026
f32cb2b
refactor(wasm): fold worker close/reject lifecycle into markClosed he…
zeroshade Jun 19, 2026
ace0f46
refactor(wasm): fold worker init-gate rejection into markClosed
zeroshade Jun 19, 2026
86f0636
fix(wasm): prefix in-process dbcNewClient init errors with dbc-wasm: …
zeroshade Jun 19, 2026
35b127b
test(wasm): cover in-process loadDbc init-error dbc-wasm: prefix (rob…
zeroshade Jun 19, 2026
d6379ea
docs(wasm): resolve design-review findings (roborev 6565)
zeroshade Jun 19, 2026
da96a32
fix(wasm): curate go.env to avoid wasm_exec argv/env size limit on Wi…
zeroshade Jun 19, 2026
772858a
fix(wasm): map TMPDIR from TEMP/TMP for Go js/wasm os.TempDir on Wind…
zeroshade Jun 19, 2026
1fb85ac
test(wasm): dump listInstalled result + dir contents on smoke mismatch
zeroshade Jun 19, 2026
49820e5
fix(config): discover driver manifests via os.ReadDir for GOOS=js Win…
zeroshade Jun 19, 2026
6fc700e
ci(wasm): drop experimental windows-latest from matrix (#396)
zeroshade Jun 19, 2026
e627427
docs(wasm): note baseURL defaults to the Columnar driver CDN
zeroshade Jun 19, 2026
83b4832
chore(wasm): remove Phase 0 de-risk spike (superseded by shipped pack…
zeroshade Jun 19, 2026
4920054
docs(wasm): document runtime support (Node + Deno; Bun blocked by ups…
zeroshade Jun 19, 2026
45e3f9b
ci(wasm): run the npm-wasm test suite under Deno alongside Node
zeroshade Jun 19, 2026
16ec53a
docs(wasm): link upstream Bun issue (oven-sh/bun#32505) in runtime-su…
zeroshade Jun 19, 2026
c033e68
Update packages/npm-wasm/scripts/build.js
zeroshade Jun 24, 2026
3d27969
fix(wasm): terminate dbc-wasm package description string
zeroshade Jun 24, 2026
044ae69
ci(wasm): address review feedback on the workflow
zeroshade Jun 24, 2026
6c2ca74
ci(wasm): build with Go 1.26 to match go.mod
zeroshade Jun 24, 2026
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
93 changes: 93 additions & 0 deletions .github/workflows/wasm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Copyright 2026 Columnar Technologies Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Wasm
on:
push:
branches:
- 'main'
paths:
- '**/*.go'
- 'auth/**'
- 'config/**'
- 'internal/**'
- 'wasm/**'
- 'packages/npm-wasm/**'
- 'cmd/dbc/testdata/**'
- 'go.mod'
- 'go.sum'
- '.github/workflows/wasm.yml'
pull_request:
paths:
- '**/*.go'
- 'auth/**'
- 'config/**'
- 'internal/**'
- 'wasm/**'
- 'packages/npm-wasm/**'
- 'cmd/dbc/testdata/**'
- 'go.mod'
- 'go.sum'
- '.github/workflows/wasm.yml'

concurrency:
group: ${{ github.repository }}-${{ github.head_ref || github.sha }}-${{ github.workflow }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
build-and-smoke:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# windows-latest is intentionally excluded: the WASM Windows-host path is
# experimental and not yet passing (driver discovery under GOOS=js — see
# https://github.com/columnar-tech/dbc/issues/396). Re-add when fixed.
os: [ 'ubuntu-latest', 'macos-latest' ]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: 1.26
cache: true
cache-dependency-path: go.sum
- name: Node version
run: node --version
- name: Build @columnar-tech/dbc-wasm
run: node packages/npm-wasm/scripts/build.js
- name: Node loader unit tests
run: node packages/npm-wasm/test/normalize.test.cjs
- name: Node smoke test
run: node packages/npm-wasm/test/smoke.cjs
- name: Node worker smoke test
run: node packages/npm-wasm/test/worker.test.cjs
- name: Setup Deno
uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
with:
deno-version: v2.x
- name: Deno version
run: deno --version
- name: Deno loader unit tests
run: deno run -A packages/npm-wasm/test/normalize.test.cjs
- name: Deno smoke test
run: deno run -A packages/npm-wasm/test/smoke.cjs
- name: Deno worker smoke test
run: deno run -A packages/npm-wasm/test/worker.test.cjs
10 changes: 8 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"github.com/columnar-tech/dbc/auth"
"github.com/columnar-tech/dbc/internal"
"github.com/google/uuid"
machineid "github.com/zeroshade/machine-id"
)

type clientConfig struct {
Expand Down Expand Up @@ -129,7 +128,7 @@ func NewClient(opts ...Option) (*Client, error) {

func (c *Client) setup() {
c.setupOnce.Do(func() {
c.mid, _ = machineid.ProtectedID()
c.mid, _ = telemetryMachineID()

userdir, err := internal.GetUserConfigPath()
if err != nil {
Expand Down Expand Up @@ -167,6 +166,13 @@ func WithHTTPClient(hc *http.Client) Option {
return func(cfg *clientConfig) { cfg.httpClient = hc }
}

// WithCredentialResolver sets the resolver used to look up credentials for a
// registry URL, overriding the default on-disk resolver. Useful where there is
// no credential file (e.g. WASM hosts injecting a token).
func WithCredentialResolver(resolver func(*url.URL) (*auth.Credential, error)) Option {
return func(cfg *clientConfig) { cfg.credentialResolver = resolver }
}

// WithRegistries sets the driver registries to use. When passed, this takes
// precedence over any WithGlobalConfig / WithProjectRegistries options: the
// caller's explicit list is used as-is, not merged with configuration files.
Expand Down
26 changes: 25 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (c *ConfigLevel) UnmarshalText(b []byte) error {
func EnsureLocation(cfg Config) (string, error) {
loc := cfg.Location
if cfg.Level == ConfigEnv {
list := filepath.SplitList(loc)
list := splitConfigList(loc)
if len(list) == 0 {
return "", errors.New("ADBC_DRIVER_PATH is empty, must be set to valid path to use")
}
Expand Down Expand Up @@ -204,6 +204,30 @@ func loadConfig(lvl ConfigLevel) Config {
return cfg
}

// FindDriverConfigsIn lists installed drivers from an explicit location without
// consulting environment variables, so it is safe for request-scoped concurrent
// callers. A location containing the OS list separator is treated as a path list
// (matching the env config level).
func FindDriverConfigsIn(location string) []DriverInfo {
if location == "" {
return nil
}
paths := splitConfigList(location)
slices.Reverse(paths)
merged := make(map[string]DriverInfo)
for _, p := range paths {
if p == "" {
continue
}
drivers, err := loadDir(p)
if err != nil {
continue
}
maps.Copy(merged, drivers)
}
return slices.Collect(maps.Values(merged))
}

func getEnvConfigDir() string {
envConfigLoc := filepath.SplitList(os.Getenv(adbcEnvVar))
if venv := os.Getenv("VIRTUAL_ENV"); venv != "" {
Expand Down
2 changes: 1 addition & 1 deletion config/dirs_unixlike.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func FindDriverConfigs(lvl ConfigLevel) []DriverInfo {

func GetDriver(cfg Config, driverName string) (DriverInfo, error) {
if cfg.Level == ConfigEnv {
for _, prefix := range filepath.SplitList(cfg.Location) {
for _, prefix := range splitConfigList(cfg.Location) {
if di, err := loadDriverFromManifest(prefix, driverName); err == nil {
return di, nil
}
Expand Down
66 changes: 66 additions & 0 deletions config/findin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2026 Columnar Technologies Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config_test

import (
"os"
"path/filepath"
"testing"

"github.com/columnar-tech/dbc/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const findInManifestTOML = `
name = 'Test Driver'
publisher = 'Test Publisher'
license = 'MIT'
version = '1.2.3'
source = 'dbc'

[ADBC]
version = '1.1.0'

[Driver]
entrypoint = 'AdbcDriverInit'

[Driver.shared]
linux_amd64 = '/path/to/driver.so'
`

func TestFindDriverConfigsIn(t *testing.T) {
t.Run("lists drivers from an explicit location without env", func(t *testing.T) {
t.Setenv("ADBC_DRIVER_PATH", "/should/not/be/read")
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "drv1.toml"), []byte(findInManifestTOML), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "drv2.toml"), []byte(findInManifestTOML), 0o644))

got := config.FindDriverConfigsIn(dir)
ids := make([]string, len(got))
for i, d := range got {
ids[i] = d.ID
}
assert.ElementsMatch(t, []string{"drv1", "drv2"}, ids)
})

t.Run("empty location returns nil", func(t *testing.T) {
assert.Empty(t, config.FindDriverConfigsIn(""))
})

t.Run("nonexistent location returns empty", func(t *testing.T) {
assert.Empty(t, config.FindDriverConfigsIn(filepath.Join(t.TempDir(), "nope")))
})
}
25 changes: 25 additions & 0 deletions config/platform_override.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2026 Columnar Technologies Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build js

package config

// SetPlatformTupleOverride sets the host platform tuple (e.g. "linux_amd64").
// Required under GOARCH=wasm, where the detected tuple is "unknown_wasm64". It
// sets the package var directly so every reader observes it. Call once at
// startup before any install/uninstall.
func SetPlatformTupleOverride(tuple string) {
platformTuple = tuple
}
28 changes: 28 additions & 0 deletions config/splitlist_js.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2026 Columnar Technologies Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build js

package config

// splitConfigList does not split under GOOS=js: the wasm API passes a single

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really needed? Since SplitList only ever runs on a single platform, mixing isn't an issue? Not sure if a change is needed here but just commenting.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, but not because of platform mixing — it's what GOOS=js does to filepath. Under js, filepath.ListSeparator is : (js is treated as a Unix target regardless of the actual host OS), so on a Windows host a drive-lettered location like C:/drivers would split into ["C", "/drivers"]. The wasm API always passes a single explicit directory, so the correct behavior under js is "don't split," which is what this override does; native builds keep the normal filepath.SplitList in splitlist_other.go.

// explicit directory as the location, and ':' (the Unix list separator that
// filepath.SplitList uses under js) would corrupt a Windows drive-lettered
// path such as "C:/drivers".
func splitConfigList(s string) []string {
if s == "" {
return nil
}
return []string{s}
}
21 changes: 21 additions & 0 deletions config/splitlist_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2026 Columnar Technologies Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !js

package config

import "path/filepath"

func splitConfigList(s string) []string { return filepath.SplitList(s) }
55 changes: 55 additions & 0 deletions credresolver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2026 Columnar Technologies Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dbc

import (
"net/url"
"testing"

"github.com/columnar-tech/dbc/auth"
)

func TestWithCredentialResolver(t *testing.T) {
want := &auth.Credential{}
var gotHost string
c, err := NewClient(WithCredentialResolver(func(u *url.URL) (*auth.Credential, error) {
gotHost = u.Host
return want, nil
}))
if err != nil {
t.Fatalf("NewClient: %v", err)
}

got, err := c.credentialResolver(&url.URL{Host: "registry.example.com"})
if err != nil {
t.Fatalf("resolver returned error: %v", err)
}
if gotHost != "registry.example.com" {
t.Fatalf("resolver host = %q, want registry.example.com", gotHost)
}
if got != want {
t.Fatal("resolver returned a different credential than provided")
}
}

func TestWithCredentialResolverDefault(t *testing.T) {
c, err := NewClient()
if err != nil {
t.Fatalf("NewClient: %v", err)
}
if c.credentialResolver == nil {
t.Fatal("default credentialResolver should be non-nil")
}
}
3 changes: 1 addition & 2 deletions drivers.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import (
"github.com/columnar-tech/dbc/auth"
"github.com/columnar-tech/dbc/internal"
"github.com/google/uuid"
machineid "github.com/zeroshade/machine-id"
)

var (
Expand Down Expand Up @@ -113,7 +112,7 @@ func ensureSetup() {
},
}

mid, _ = machineid.ProtectedID()
mid, _ = telemetryMachineID()

userdir, err := internal.GetUserConfigPath()
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions packages/npm-wasm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules/

# Build artifacts generated by scripts/build.js — not committed
dbc.wasm
wasm_exec.js
package.json
LICENSE
*.tgz
Loading
Loading