Braid

Declaring joins

Every field .join() accepts, and how joins compose independently on the builder.

.join() adds a detail collection, attached to every output row under name. Joins are independent of each other and chainable: declaring a fourth join doesn’t change how the first three behave.

new Braid()
	.main({ source: products, key: (p) => p.sku })
	.join({
		name: "bigcommerce",
		source: bcProducts,
		on: (bc) => bc.sku,
		type: "single",
	})
	.join({
		name: "variations",
		source: variations,
		on: (variation) => variation.productId,
		key: (product) => product.id,
		type: "many",
	});

Fields

fieldtyperequireddefaultdescription
namestringyesProperty name the result is attached under on each output row.
sourceBraidSource<D>yesDetail collection: an array, a promise, another braid, or a function returning either.
on(item: D) => BraidKeyyesKey extractor for the detail rows.
key(item: T) => BraidKeynothe main collection’s keyOverrides which key on the main row this join matches against.
type"single" | "many"yesOne matching row, or an array of them.
defaultD | D[] | nullnonull for single, [] for manyValue used when nothing matches.
requiredbooleannofalseThrow if any main row finds no match.

type has no default and is never inferred. See Single vs many for why. default and required are covered in full in Defaults and required joins. source also accepts a promise or another Braid instance, not only an array or a fetcher. See Async sources for the mechanics and Composing braids for joining against another braid’s result.

Indexing, not scanning

Each join’s source is indexed exactly once into a Map, before any main row is processed. Adding a fourth join costs one more pass over one more collection, not another scan per main row. See Performance and indexing for the mechanics and measured numbers.

Overriding the key

By default, a join matches against the main collection’s own key. Pass key on the join config to match against a different field instead, useful when different detail sources reference the main row differently:

new Braid()
	.main({ source: products, key: (p) => p.sku })
	.join({
		name: "variations",
		source: variations,
		on: (variation) => variation.productId,
		// The channel manager keys off the internal id, not the SKU.
		key: (product) => product.id,
		type: "many",
	});

key must produce the same primitive type as on. See Type safety for what happens when it doesn’t.

Validation

Like .main(), .join() validates its config at the call that made it, not at .run():

withMain().join({ source: bcProducts, on: (bc) => bc.sku, type: "single" });
// BraidError: .join() requires a non-empty `name` string.

withMain().join({ name: "bigcommerce", on: (bc) => bc.sku, type: "single" });
// BraidError: Join "bigcommerce" requires a `source` array, promise, braid,
// or a function returning one.

withMain().join({ name: "bigcommerce", source: bcProducts, type: "single" });
// BraidError: Join "bigcommerce" requires an `on` function, e.g.
// { on: (row) => row.productId }.

withMain().join({ name: "bigcommerce", source: bcProducts, on: (bc) => bc.sku });
// BraidError: Join "bigcommerce" requires `type` to be "single" or "many",
// got undefined.

withMain().join({
	name: "bigcommerce",
	source: bcProducts,
	on: (bc) => bc.sku,
	key: "sku",
	type: "single",
});
// BraidError: Join "bigcommerce" was given a `key` that is not a function.

A reused join name throws too, because join names become properties on the output row and two joins sharing a name would silently overwrite each other:

withMain()
	.join({ name: "bigcommerce", source: bcProducts, on: (bc) => bc.sku, type: "single" })
	.join({ name: "bigcommerce", source: bcProducts, on: (bc) => bc.sku, type: "single" });
// BraidError: A join named "bigcommerce" is already defined. Join names
// become properties on the output row, so they must be unique.

This one is also a compile error. See Type safety.

The same restriction applies to the property the main row is nested under, if .main() was given an as: a join can’t take that name either, for the same reason. See Composing braids.