HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
tc39
proposal-record-tuple
HTML2019–2026
tc39 / proposal-record-tuple · Archived

proposal-record-tuple

ECMAScript proposal for the Record and Tuple value types. | Stage 2: it will change!

42Distinct people
47Mentions
0Explicit recommenders
2.5KGitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

ECMAScript proposal for the Record and Tuple value types. | Stage 2: it will change!

GitHub

Where it is used

No documented product use was verified in the last source check.

What people are saying

No recommendation phrase was found in the indexed comments.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

No other repository is repeatedly linked in the same discussions.

This week on Hacker News

Nov 7–Nov 14 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/14/2026, 1:49:46 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year47 dated mentions

Observed mentions: 2019-12-19 16:16:28 UTC to 2026-07-31 07:21:04 UTC.

0369122019-01-01 00:00:00 UTC to 2020-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments20192020-01-01 00:00:00 UTC to 2021-01-01 00:00:00 UTC (exclusive): 9 mentions; 3 submissions, 6 comments2021-01-01 00:00:00 UTC to 2022-01-01 00:00:00 UTC (exclusive): 11 mentions; 0 submissions, 11 comments2022-01-01 00:00:00 UTC to 2023-01-01 00:00:00 UTC (exclusive): 8 mentions; 0 submissions, 8 comments20222023-01-01 00:00:00 UTC to 2024-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments2024-01-01 00:00:00 UTC to 2025-01-01 00:00:00 UTC (exclusive): 10 mentions; 0 submissions, 10 comments2025-01-01 00:00:00 UTC to 2026-01-01 00:00:00 UTC (exclusive): 5 mentions; 2 submissions, 3 comments2026-01-01 00:00:00 UTC to 2027-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments. Incomplete calendar period.2026*

* An incomplete calendar period is marked with an outlined bar. Its count is not directly comparable with a full period.

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20192019-01-01 00:00:00 UTC2020-01-01 00:00:00 UTC101
20202020-01-01 00:00:00 UTC2021-01-01 00:00:00 UTC936
20212021-01-01 00:00:00 UTC2022-01-01 00:00:00 UTC11011
20222022-01-01 00:00:00 UTC2023-01-01 00:00:00 UTC808
20232023-01-01 00:00:00 UTC2024-01-01 00:00:00 UTC202
20242024-01-01 00:00:00 UTC2025-01-01 00:00:00 UTC10010
20252025-01-01 00:00:00 UTC2026-01-01 00:00:00 UTC523
2026 (partial)2026-01-01 00:00:00 UTC2027-01-01 00:00:00 UTC101

From the project README

An excerpt from the repository, fetched Nov 13, 2026.

Read the project’s introduction
JavaScript Records & Tuples Proposal ImportantThis proposal has been withdrawn #394 Authors: Robin Ricard (Bloomberg) Rick Button (Bloomberg) Nicolò Ribaudo (Babel) Champions: Robin Ricard (Bloomberg) Rick Button (Bloomberg) Advisors Philipp Dunkel (Bloomberg) Dan Ehrenberg (Bloomberg) Maxwell Heiber Stage: Withdrawn Try out Record and Tuple in the playground! Spec Text Tutorial Cookbook Content Overview Examples Syntax Equality The object model Record and Tuple standard library support Rationale Overview This proposal introduces two new deeply immutable data structures to JavaScript: Record, a deeply immutable Object-like structure #{ x: 1, y: 2 } Tuple, a deeply immutable Array-like structure #[1, 2, 3, 4] Records and Tuples can only contain primitives and other Records and Tuples. You could think of Records and Tuples as "compound primitives". By being thoroughly based on primitives, not objects, Records and Tuples are deeply immutable. Records and Tuples support comfortable idioms for construction, manipulation and use, similar to working with objects and Arrays. They are compared deeply by their contents, rather than by their identity. JavaScript engines may perform certain optimizations on construction, manipulation and comparison of Records and Tuples, analogous to the way Strings are often implemented in JS engines. (It should be understood that these optimizations are not guaranteed.) Records and Tuples aim to be usable and understood with external typesystem supersets such as TypeScript or Flow. Prior work on immutable data structures in JavaScript Today, userland libraries implement similar concepts, such as Immutable.js. Also a previous proposal has been attempted but abandoned because of the complexity of the proposal and lack of sufficient use cases. This new proposal is still inspired by this previous proposal but introduces some significant changes: Record and Tuples are now deeply immutable. This property is fundamentally based on the observation that, in large projects, the risk of mixing immutable and mutable data structures grows as the amount of data being stored and passed around grows as well so you'll be more likely to handle large record & tuple structures. This can introduce hard-to-find bugs. As a built-in, deeply immutable data structure, this proposal also offers a few usability advantages compared to userland libraries: Records and Tuples are easily introspectable in a debugger, while library provided immutable types are often hard to inspect as you have to inspect through data structure details. Because they are accessed through typical object and array idioms, no additional branching is needed in order to write a generic library that consumes both immutable and JS objects; with user libraries, method calls may be needed just in the immutable case. We avoid cases where developers may expensively convert between regular JS objects and immutable structures, by making it easier to just always use the immutable ones. Immer is a notable approach to immutable data structures, and prescribes a pattern for manipulation through producers and reducers. However, it is configurable if it generates frozen objects or not. This same pattern can be adapted to the structures defined in this proposal in addition to frozen objects. Deep equality as defined in user libraries can vary significantly, in part due to possible references to mutable objects. By drawing a hard line about only deeply containing primitives, Records and Tuples, and recursing through the entire structure, this proposal defines simple, unified semantics for comparisons. Examples Record const proposal = #{ id: 1234, title: "Record & Tuple proposal", contents: `...`, // tuples are primitive types so you can put them in records: keywords: #["ecma", "tc39", "proposal", "record", "tuple"], }; // Accessing keys like you would with objects! console.log(proposal.title); // Record & Tuple proposal console.log(proposal.keywords[1]); // tc39 // Spread like objects! const proposal2 = #{ ...proposal, title: "Stage 2: Record & Tuple", }; console.log(proposal2.title); // Stage 2: Record & Tuple console.log(proposal2.keywords[1]); // tc39 // Object functions work on Records: console.log(Object.keys(proposal)); // ["contents", "id", "keywords", "title"] Open in playground Functions can handle Records and Objects in generally the same way: const ship1 = #{ x: 1, y: 2 }; // ship2 is an ordinary object: const ship2 = { x: -1, y: 3 }; function move(start, deltaX, deltaY) { // we always return a record after moving return #{ x: start.x + deltaX, y: start.y + deltaY, }; } const ship1Moved = move(ship1, 1, 0); // passing an ordinary object to move() still works: const ship2Moved = move(ship2, 3, -1); console.log(ship1Moved === ship2Moved); // true // ship1 and ship2 have the same coordinates after moving Open in playground See more examples here. Tuple const measures = #[42, 12, 67, "measure error: foo happened"]; // Accessing indices like you would with arrays! console.log(measures[0]); // 42 console.log(measures[3]); // measure error: foo happened // Slice and spread like arrays! const correctedMeasures = #[ ...measures.slice(0, measures.length - 1), -1 ]; console.log(correctedMeasures[0]); // 42 console.log(correctedMeasures[3]); // -1 // or use the .with() shorthand for the same result: const correctedMeasures2 = measures.with(3, -1); console.log(correctedMeasures2[0]); // 42 console.log(correctedMeasures2[3]); // -1 // Tuples support methods similar to Arrays console.log(correctedMeasures2.map(x => x + 1)); // #[43, 13, 68, 0] Open in playground Similarly than with records, we can treat tuples as array-like: const ship1 = #[1, 2]; // ship2 is an array: const ship2 = [-1, 3]; function move(start, deltaX, deltaY) { // we always return a tuple after moving return #[ start[0] + deltaX, start[1] + deltaY, ]; } const ship1Moved = move(ship1, 1, 0); // p
Read the full README on GitHub

What Hacker News says

47 sources
It's pretty interesting that while both languages still receive new features, Java seems to stay ahead of JavaScript: - Java has long had a modern replacement for Date, while JavaScript's recently standardized Temporal API still isn't supported in Safari. - Java has switch expressions, while JavaScript, despite its Scheme influence, does not. - And now Java is getting value objects, while JavaScript's equivalent tuples & records proposal has been withdrawn.[0] JavaScript was not developed as a fork of Java, but much of its basic syntax still resembles Java, so I think it's fair to compare them. [0]: https://github.com/tc39/proposal-record-tuple
In JEP 401: Value Objects (Preview) merged to OpenJDK master
Just want to add that even though ivi is using tagged templates, I am strongly against using tagged templates to describe UIs as a Web Standard. One of the most useful features that could make a lot of incremental computation problems easier is "value types"[1], but unfortunately it seems that isn't going to happen anytime soon. The biggest constraint when developing an efficient UI framework with good DX is JavaScript. Also, it would be nice to have `Node.prototype.insertAfter()` :) 1. https://github.com/tc39/proposal-record-tuple
In The time is right for a DOM templating API
Records and tuples are unlikely to ever ship, or at least are extremely unlikely to ship with the semantics around equality people want. My understanding is that browser vendors already have pretty strong opposition to introducing new primitives at this point. Reading this thread[1] will give some explanation as to the hiccups with how equality would behave. It sucks, because while I wouldn't have expected BigInt to have gotten much adoption in the first place, I constantly run into cases where I'd want collections with structural equality. Additionally, my understanding is that the opposition to adding new primitives also affects pattern matching, though I'm not sure why. I'm much less up to date on that proposal. [1] https://github.com/tc39/proposal-record-tuple/issues/387
In JavaScript Temporal is coming
> Out of curiosity, do you think that the Shadow Realms proposal they refer to will ever go anywhere? I haven't really been following the Shadow Realm proposal (I'm not part of TC39, so only familiar with certain proposals), but I don't think it should conflict with R/T. If R/T values are allowed to be passed between realms, they should effectively be "transformed" such that eg, `f(#[v])` is equivalent to `f(#[f(v)])` (where `f` is the transformation that allows values to be passed between realms). For "deeply immutable" values (no object references), `f(v)` will simply return `v` (eg, `#[42]`, f(#[42])` and `f(#[f(42)])` are all the same) and a membrane should be able to trivially optimise this case. From this comment[0] it sounds like `f({})` in the current Shadow Realm proposal will throw an error, so I'd expect that `f(#[{}])` would a
Read the rest
lso throw an error. As you were pointing out, I think the only real contention between R/T and realms is in existing JS implementations of membranes, particularly because they might use the following condition to detect if something is "deeply immutable": v === null || typeof v !== "object" && typeof v !== "function" If `typeof #[{}] === "tuple"`, then their `f` function will pass that value through without handling the contained object value by throwing or by creating/finding a proxy. If `typeof #[{}] === "object"`, it should be fine because `f(#[{}])` will either throw or create/find a proxy for the tuple. There might be some unexpected behaviour around equality of R/T values passed through the membrane, but this is pretty obscure and it should be fixed once the membrane library is updated to handle R/T values. Personally, I'm still not 100% convinced that the assumptions made from the above condition are important enough to cause such a change to the proposal, but I don't see the value of `typeof #[]` as being a usability issue. Code that needs to check the types of things is a bit smelly to me, but in cases where you do need to check the type, `typeof v === "tuple"` and `Tuple.isTuple(v)` both seem usable to me, so just making `typeof #[] === "object"` should be fine and it solves this hypothetical issue. This is similar to array objects, which are also fundamentally special (`Object.create(Array.prototype)` is not an array object) and are detected using `Array.isArray(v)`. > Otherwise, there's the argument that "x.y" syntax shan't be used to access a mutable object from an immutable record, but that just feels like the all-too-common motive of "we must ensure that users write morally-correct code (given our weird idiosyncratic idea of moral correctness), or otherwise make them pay the price for their sins". Agreed, and I've pointed out[1] that even the current proposal doesn't address this, since unless you've done some defensive check on `x`, there's nothing stopping someone passing a mutable object for `x` instead of a record. If you do want to perform a dynamic[2] defensive check, perhaps you should be asking "is it deeply immutable?" or even checking its shape rather than "is it a record?". [0] https://github.com/tc39/proposal-record-tuple/issues/390#iss... [1] https://github.com/tc39/proposal-record-tuple/issues/292#iss... [2] If you're using a type system like TypeScript, this check should happen statically, because you'll use a type that specifies that it's both a record and the types of the properties within it, so your type will encode whether or not it contains mutable objects
In Proposal: JavaScript Structs
> If you allow arbitrary values, what's the difference between a record and a frozen object? The behaviour of equality. Frozen objects are already considered to have unique identities, in that `Object.freeze({}) !== Object.freeze({})` even though both objects are otherwise indistinguishable. This behaviour can't be changed and it relates to the fact that `Object.freeze(a) === a`. > I thought that the whole point is to have guaranteed deep immutability Not really. The whole point apparently according to most people[0] is to have composite values that don't have unique identities, so they fit in with all the existing comparison operations (eg, `===`, `Map`, `indexOf`, `includes`) just as you can do with strings. Immutability is a prerequisite for this, since if `a` and `b` are mutable, mutating `a` might be different to mutating `b`. Thinki
Read the rest
ng again about strings, equality works because strings are immutable: const foo = "foo", bar = "bar"; const a = foo + bar; const b = foo + bar; a === b; // true Implementations will typically use different underlying memory allocations for these strings[1], but at a language level they are considered to be the same value. If it were possible to modify one of the strings (but not the other) using `a[0] = "x";` it would mean `a` and `b` are not equivalent so should not be considered equal. As explained here[2], deep immutability is not necessary for this behaviour. In my opinion guaranteed "deep immutability" is not generally useful/meaningful (if you have a particular use case, feel free to share it). In theory it's not possible to enforce "deep immutability" because someone can always refer to something mutable, whether that's an object reference or a number indexing a mutable array. If you really do want something that guarantees a certain notion of "deep immutability", this concept seems somewhat orthogonal to records/tuples, since there are existing values (eg, strings and numbers) that should be considered deeply immutable, so you'd expect to have a separate predicate[3][4] for detecting this, which would be able to effectively search a given value for object references. In case you're interested I tried to summarise the logic behind the rejection of this behaviour[5] (which I disagree with), but it's very much a TLDR so further reading of linked issues would be required to understand the points made. Interestingly, this post is on an issue raised by the odd person that actually tried to use the feature and naturally ran into this restriction. Sorry for this massive wall of text, but I think it's hard to capture the various trains of thought concisely. [0] https://github.com/tc39/proposal-record-tuple/issues/387#iss... [1] https://github.com/tc39/proposal-record-tuple/issues/292#iss... [2] https://github.com/tc39/proposal-record-tuple/issues/292#iss... [3] https://github.com/tc39/proposal-record-tuple/issues/292#iss... [4] https://github.com/tc39/proposal-record-tuple/issues/206 (I believe sjrd (GP) earlier independently came up with the same function name and behaviour somewhere in this thread, but GitHub seems to be failing to load it) [5] https://github.com/tc39/proposal-record-tuple/issues/390#iss...
In Proposal: JavaScript Structs
I think in this specific case it's JavaScript's requirement for backwards compatibility that bloats it... but there's a lot you can ignore. Like, you can declare a variable with var, let or const but there's absolutely no reason to use var any more. I feel similarly about the proposals to introduce records and tuples: https://github.com/tc39/proposal-record-tuple... in most scenarios you'll probably be better off using records rather than objects, and maybe that's what folks will end up doing. But boy does it all get confusing. > Now, Typescript is on version 5.6 and there is so much stuff you can do with it that it's overwhelming. And nobody uses most of it! I'm not so sure about that. I think we end up consuming a lot of these features in the TS types that get published alongside libraries. We just don't know it, we just get surprisingl
Read the rest
y intuitive type interfaces.
In Proposal: JavaScript Structs
The next big step is dependent on JS language/runtime features. Specifically the Record-Tuple proposal (https://github.com/tc39/proposal-record-tuple) TL;DR: It's a way to designate data as being immutable. JS doesn't currently have a good way to do this, so everything built for it tries to assume that data hasn't changed since it was last set. This leads to headaches for developers and wasted cycles for runtimes. When JS has a proper way to make data immutable, it makes it easier for libraries/frameworks to make guarantees about the underlying data and makes it possible for runtimes to better optimize code.
In Ask HN: What's the next big step in the evolution of web applications?
relevant issue, which is at the crux of this problem: https://github.com/tc39/proposal-record-tuple/issues/387 and which shows that a symbol based protocol was the kind of approach that could've worked from the start. See `Symbol.keyEquality` - although that still shows what I feel is a misunderstanding about the direction at which the protocol should work. I don't want to be creating new types of maps and sets, I want existing ones to have controllable key equality. If it was for new types of maps and sets, I'd just implement a new Set class independent of JS builtins and be done with it. (Its not like the built in Set offers any rich features that I'd have a hard time replicating anyway) Protocols (traits) should really be the cornerstone of TC39 work, IMO. They'll help with JavaScript's serious ecosystem compatibility issues.
In Union, intersection, difference, and more are coming to JavaScript Sets
It works fine on objects. const set = new Set(); const x = { id: 'some object' }; foo.add(x); foo.has(x); > true I'm guessing you're taking issue with the fact that it uses reference equality instead of structural equality, which can definitely be a pain point. There's a proposal to improve the situation with "Records and Tuples" [0], but it's been stuck in committee hell for years. [0] https://github.com/tc39/proposal-record-tuple
In Union, intersection, difference, and more are coming to JavaScript Sets

Showing the 15 most recent of 47 sources. Enable JavaScript to search and browse every indexed mention.

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.