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

推荐订阅源

Martin Fowler
Martin Fowler
L
LINUX DO - 最新话题
P
Proofpoint News Feed
Cyberwarzone
Cyberwarzone
Know Your Adversary
Know Your Adversary
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
L
Lohrmann on Cybersecurity
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Security Latest
Security Latest
T
The Exploit Database - CXSecurity.com
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy & Cybersecurity Law Blog
K
Kaspersky official blog
The Last Watchdog
The Last Watchdog
Webroot Blog
Webroot Blog
Scott Helme
Scott Helme
T
Threat Research - Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
WordPress大学
WordPress大学
L
LINUX DO - 热门话题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - Franky
V
Visual Studio Blog
O
OpenAI News
AI
AI
Hacker News: Ask HN
Hacker News: Ask HN
V2EX - 技术
V2EX - 技术
GbyAI
GbyAI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Simon Willison's Weblog
Simon Willison's Weblog
S
Schneier on Security
Spread Privacy
Spread Privacy
Y
Y Combinator Blog
I
InfoQ
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
F
Fortinet All Blogs
C
CERT Recently Published Vulnerability Notes
T
The Blog of Author Tim Ferriss
C
Check Point Blog
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队
人人都是产品经理
人人都是产品经理
N
News and Events Feed by Topic
Project Zero
Project Zero
小众软件
小众软件
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
B
Blog
G
Google Developers 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