惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
T
Tor Project blog
博客园_首页
F
Fortinet All Blogs
V
V2EX
雷峰网
雷峰网
Microsoft Azure Blog
Microsoft Azure Blog
Y
Y Combinator Blog
博客园 - 【当耐特】
Jina AI
Jina AI
Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
B
Blog RSS Feed
Engineering at Meta
Engineering at Meta
Spread Privacy
Spread Privacy
C
Cyber Attacks, Cyber Crime and Cyber Security
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Apple Machine Learning Research
Apple Machine Learning Research
V2EX - 技术
V2EX - 技术
Latest news
Latest news
L
LINUX DO - 最新话题
IT之家
IT之家
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
博客园 - 叶小钗
博客园 - Franky
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
量子位
博客园 - 聂微东
MyScale Blog
MyScale Blog
S
Security @ Cisco Blogs
Hacker News - Newest:
Hacker News - Newest: "LLM"
小众软件
小众软件
S
Secure Thoughts
D
Darknet – Hacking Tools, Hacker News & Cyber Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
N
News | PayPal Newsroom
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog
Google DeepMind News
Google DeepMind News
J
Java Code Geeks
有赞技术团队
有赞技术团队
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
Vulnerabilities – Threatpost
T
Tailwind CSS Blog
L
Lohrmann on Cybersecurity
T
Troy Hunt's Blog
美团技术团队

oida.dev | TypeScript, Rust

TypeScript's `erasableSyntaxOnly` Flag Unsafe for work Tokio: Macros Tokio: Channels Tokio: Getting Started Network Applications on the Tokio Stack Remake, Remodel, Reduce. The `never` type and error handling in TypeScript 5 Inconvenient Truths about TypeScript Refactoring in Rust: Introducing Traits Refactoring in Rust: Abstraction with the Newtype Pattern Announcing the TypeScript Cookbook TypeScript: Iterating over objects The road to universal JavaScript 10 years of oida.dev Rust: Tiny little traits The TypeScript converging point How not to learn TypeScript Getting started with Rust Introducing Slides and Coverage TypeScript: The humble function overload TypeScript + React: Children types are broken TypeScript: In defense of any Rust: Enums to wrap multiple errors Dissecting Deno Error handling in Rust TypeScript: Unexpected intersections Upgrading Node.js dependencies after a yarn audit TypeScript: Array.includes on narrow types TypeScript + React: Typing Generic forwardRefs shared, util, core: Schroedinger's module names Learning Rust and Go TypeScript: Narrow types in catch clauses TypeScript: Low maintenance types Tidy TypeScript: Name your generics Tidy TypeScript: Avoid traditional OOP patterns Tidy TypeScript: Prefer type aliases over interfaces Tidy TypeScript: Prefer union types over enums My new book: TypeScript in 50 Lessons Go Preact! ❤️ this in JavaScript and TypeScript TypeScript and ECMAScript Modules TypeScript + React: Why I don't use React.FC TypeScript + React: Component patterns TypeScript: Augmenting global and lib.dom.d.ts Vite with Preact and TypeScript TypeScript: Union to intersection type 11ty: Generate Twitter cards automatically Are large node module dependencies an issue? TypeScript: Variadic Tuple Types Preview TypeScript: Improving Object.keys Remake, Remodel. Part 4. TypeScript + React: Typing custom hooks with tuple types TypeScript: Check for object properties and narrow down type Boolean in JavaScript and TypeScript void in JavaScript and TypeScript Symbols in JavaScript and TypeScript Why I use TypeScript TypeScript + React: Extending JSX Elements TypeScript: Validate mapped types and const context TypeScript: Match the exact object shape TypeScript: The constructor interface pattern Streaming your Meetup - Part 4: Directing and Streaming with OBS Streaming your Meetup - Part 3: Speaker audio Streaming your Meetup - Part 2: Speaker video Streaming your Meetup - Part 1: Basics and Projector TypeScript and React Guide: Added a new styles chapter TypeScript and React Guide: Added a new render props chapter TypeScript and React: Styles and CSS TypeScript and React TypeScript and React Guide: Added a new prop types chapter TypeScript without TypeScript -- JSDoc superpowers TypeScript: Mapped types for type maps JAMStack vs serverless web apps The Unsung Benefits of JAMStack Sites TypeScript: Ambient modules for Webpack loaders My most favourite talks in 2018 TypeScript and React Guide: Added a new context chapter TypeScript: Built-in generic types TypeScript: Type predicates JSX is syntactic sugar TypeScript and React Guide: Added a new hooks chapter Getting your CfP application right FAQ on our Angular Connect Talk: Automating UI development TypeScript and Substitutability Debugging Node.js apps in TypeScript with Visual Studio Code From Medium: Deconfusing Pre- and Post-processing From Medium: PostCSS misconceptions Saving and scraping a website with Puppeteer Cutting the mustard - 2018 edition Wordpress as CMS for your JAMStack sites My most favourite podcast episodes in 2017 My most favourite talks in 2017 My most favourite books in 2017 The Best Request Is No Request, Revisited Not so hidden figures - Organizing ScriptConf My podcast journey to ScriptCast Grid layout, grid layout everywhere! #scriptconf and #devone Object streams in Node.js
TypeScript: Assertion signatures and Object.defineProperty
2020-02-06 · via oida.dev | TypeScript, Rust

In JavaScript, you can define object properties on the fly with Object.defineProperty. This is useful if you want your properties to be read-only or similar. Think of a storage object that has a maximum value that shouldn’t be overwritten:

const storage = {
currentValue: 0
}

Object.defineProperty(storage, 'maxValue', {
value: 9001,
writable: false
})

console.log(storage.maxValue) // 9001

storage.maxValue = 2

console.log(storage.maxValue) // still 9001

defineProperty and property descriptors are very complex. They allow you to do everything with properties that usually is reserved for built-in objects. So they’re common in larger codebases. TypeScript – at the time of this writing – has a little problem with defineProperty:

const storage = {
currentValue: 0
}

Object.defineProperty(storage, 'maxValue', {
value: 9001,
writable: false
})

// 💥 Property 'maxValue' does not exist on type...
console.log(storage.maxValue)

If we don’t explicitly typecast, we don’t get maxValue attached to the type of storage. However, for simple use cases, we can help!

assertion signatures #

With TypeScript 3.7, the team introduced assertion signatures. Think of an assertIsNumber function where you can make sure some value is of type number. Otherwise, it throws an error. This is similar to the assert function in Node.js:

function assertIsNumber(val: any) {
if (typeof val !== "number") {
throw new AssertionError("Not a number!");
}
}

function multiply(x, y) {
assertIsNumber(x);
assertIsNumber(y);
// at this point I'm sure x and y are numbers
// if one assert condition is not true, this position
// is never reached
return x * y;
}

To comply with behavior like this, we can add an assertion signature that tells TypeScript that we know more about the type after this function:

- function assertIsNumber(val: any) {
+ function assertIsNumber(val: any) : asserts val is number
if (typeof val !== "number") {
throw new AssertionError("Not a number!");
}
}

This works a lot like type predicates, but without the control flow of a condition-based structure like if or switch.

function multiply(x, y) {
assertIsNumber(x);
assertIsNumber(y);
// Now also TypeScript knows that both x and y are numbers
return x * y;
}

If you look at it closely, you can see those assertion signatures can change the type of a parameter or variable on the fly. This is just what Object.defineProperty does as well.

custom defineProperty #

Disclaimer: The following helper does not aim to be 100% accurate or complete. It might have errors, it might not tackle every edge case of the defineProperty specification. It might, however, handle a lot of use cases well enough. So use it at your own risk!

Just as with hasOwnProperty, we create a helper function that mimics the original function signature:

function defineProperty<
Obj extends object,
Key extends PropertyKey,
PDesc extends PropertyDescriptor>

(obj: Obj, prop: Key, val: PDesc) {
Object.defineProperty(obj, prop, val);
}

We work with 3 generics:

  1. The object we want to modify, of type Obj, which is a subtype of object
  2. Type Key, which is a subtype of PropertyKey (built-in), so string | number | symbol.
  3. PDesc, a subtype of PropertyDescriptor (built-in). This allows us to define the property with all its features (writability, enumerability, reconfigurability).

We use generics because TypeScript can narrow them down to a very specific unit type. PropertyKey for example is all numbers, strings, and symbols. But if I use Key extends PropertyKey, I can pinpoint prop to be of e.g. type "maxValue". This is helpful if we want to change the original type by adding more properties.

The Object.defineProperty function either changes the object or throws an error should something go wrong. Exactly what an assertion function does. Our custom helper defineProperty thus does the same.

Let’s add an assertion signature. Once defineProperty successfully executes, our object has another property. We are creating some helper types for that. The signature first:

function defineProperty<
Obj extends object,
Key extends PropertyKey,
PDesc extends PropertyDescriptor>
- (obj: Obj, prop: Key, val: PDesc) {
+ (obj: Obj, prop: Key, val: PDesc):
+ asserts obj is Obj & DefineProperty<Key, PDesc> {
Object.defineProperty(obj, prop, val);
}

obj then is of type Obj (narrowed down through a generic), and our newly defined property.

This is the DefineProperty helper type:

type DefineProperty<
Prop extends PropertyKey,
Desc extends PropertyDescriptor>
=
Desc extends { writable: any, set(val: any): any } ? never :
Desc extends { writable: any, get(): any } ? never :
Desc extends { writable: false } ? Readonly<InferValue<Prop, Desc>> :
Desc extends { writable: true } ? InferValue<Prop, Desc> :
Readonly<InferValue<Prop, Desc>>

First, we deal with the writeable property of a PropertyDescriptor. It’s a set of conditions to define some edge cases and conditions of how the original property descriptors work:

  1. If we set writable and any property accessor (get, set), we fail. never tells us that an error was thrown.
  2. If we set writable to false, the property is read-only. We defer to the InferValue helper type.
  3. If we set writable to true, the property is not read-only. We defer as well
  4. The last, default case is the same as writeable: false, so Readonly<InferValue<Prop, Desc>>. (Readonly<T> is built-in)

This is the InferValue helper type, dealing with the set value property.

type InferValue<Prop extends PropertyKey, Desc> =
Desc extends { get(): any, value: any } ? never :
Desc extends { value: infer T } ? Record<Prop, T> :
Desc extends { get(): infer T } ? Record<Prop, T> : never;

Again a set of conditions:

  1. Do we have a getter and a value set, Object.defineProperty throws an error, so never.
  2. If we have set a value, let’s infer the type of this value and create an object with our defined property key, and the value type
  3. Or we infer the type from the return type of a getter.
  4. Anything else: We forgot. TypeScript won’t let us work with the object as it’s becoming never

In action! #

Lots of helper types, but roughly 20 lines of code to get it right:

type InferValue<Prop extends PropertyKey, Desc> =
Desc extends { get(): any, value: any } ? never :
Desc extends { value: infer T } ? Record<Prop, T> :
Desc extends { get(): infer T } ? Record<Prop, T> : never;

type DefineProperty<
Prop extends PropertyKey,
Desc extends PropertyDescriptor>
=
Desc extends { writable: any, set(val: any): any } ? never :
Desc extends { writable: any, get(): any } ? never :
Desc extends { writable: false } ? Readonly<InferValue<Prop, Desc>> :
Desc extends { writable: true } ? InferValue<Prop, Desc> :
Readonly<InferValue<Prop, Desc>>

function defineProperty<
Obj extends object,
Key extends PropertyKey,
PDesc extends PropertyDescriptor>

(obj: Obj, prop: Key, val: PDesc):
asserts obj is Obj & DefineProperty<Key, PDesc> {
Object.defineProperty(obj, prop, val)
}

Let’s see what TypeScript does:


const storage = {
currentValue: 0
}

defineProperty(storage, 'maxValue', {
writable: false, value: 9001
})

storage.maxValue // it's a number
storage.maxValue = 2 // Error! It's read-only

const storageName = 'My Storage'
defineProperty(storage, 'name', {
get() {
return storageName
}
})

storage.name // it's a string!

// it's not possible to assing a value and a getter
defineProperty(storage, 'broken', {
get() {
return storageName
},
value: 4000
})

// storage is never because we have a malicious
// property descriptor
storage

As said, this most likely won’t deal with all edge cases, but it’s a good start. And if you know what you’re dealing with, you can get very far.

As always, there’s a playground for you to fiddle around.

Related Articles