any objs without absorbs unions
mdbetancourt opened this issue ยท 6 comments
๐ Search Terms
metadata, any object, any union
โ Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
โญ Suggestion
a way to mark an object as "any" without lose all the know type information
๐ Motivating Example
some type as follow would be possible
const obj: unknown & { name: string } = getUser()
// or
const obj: AnyObject & { name: string } = getUser()
//instead of
const obj: { name: string; [k: string]: unknown } = getUser()
// add metadata to objects
type Throws<Err> = {
[errorSymbol]: Err;
} & uknown
function connectDb(): Db | Throws<ConnectionError> {}
function getUsers(db: Db): User[] {}
getUsers(connectDb()) // currently error because Throws dont have Db properties
connectDb().connect // autocomplete and everything else works well๐ป Use Cases
could be used to #46142
maybe related with this issue #46548 (dont need an additional type just a way to narrow instead of wide the union when any is present)
or typing some Proxies behaviors without lose type
type AnyObject = {
[k: string]: never
[i: number]: never
[s: symbol]: never
}
type FlexibleObject = { lastGet: Date | null } & AnyObject; // workaround allow autocomplete but...
function createFlexObject<T>(obj: T): T | FlexibleObject {
obj['lastGet'] = null
return new Proxy(
obj,
{
get(target, prop) {
target.lastGet = new Date()
return prop in target ? target[prop] : '...'
},
}
);
}
type User = { name: string }
const user: User = { name: '' }
const userWithLastGet = createFlexObject(user)
const targetUser: User = userWithLastGet
// ^ error Property 'name' is missing in type 'FlexibleObject' but required in type 'User'i'm using a workaround in https://www.npmjs.com/package/@kraftr/errors to add metadata error to the return
function couldThrow(): string | Throws<Error> & string {} i used a type to convert it to
function couldThrow(): Return<string, Error> {} but with this feature i can use
function couldThrow(): string | Throws<Error>{}It's very unclear to me what's being proposed here.
It's very unclear to me what's being proposed here.
check line 22 Playground "'connect' is declared here." even when i typed Throws as almost any
@RyanCavanaugh i have another e.g
function defaultize<T>(value: T): Record<string, T> {
return new Proxy({}, {
get(target, prop) {
if(target[prop] !== value) {
target[prop] = value
}
return target[prop]
}
})
}
const { name } = defaultize('Jean') // currently with --noUncheckedIndexedAccess name is "string | undefined"what is my idea?
function defaultize<T>(value: T): Record<string, T> & AnyObject {
....
}
const { name } = defaultize('Jean') // now is just "string" even with --noUncheckedIndexedAccessThe concept of a JS object that appears to give itself any available property seems very rare in practice. I don't think this is something the type system needs to support.
i dont think so, is not so rare, i listed some uses cases:
- objs with default values (like defaultdict in python)
- metadata (e.g bring metadata about what errors a function could throw)
function fn(): User | Throws<NotExists> - an option to allow any in an union without widening the original value e.g
User | anyresult inanyi know solutions could beUser | User & Record<string, uknown>orUser | User & { [k: string]: uknown }but it's awkward specially when there is more than just one type(User & Lead & Contact) | (User & Lead & Contact & Record<string, unknown>)or you just ending doing a type alias - i say could be related with #46548 maybe
{ name: string } |& anycould be a solution if that issue is aproved - collect variables names from destructuring, i'm creating a package to create cli in a more typed way:
const cli = createCLI()
cli.command('build', buildCmd => {
const { config, root } = buildCmd.namedArg() // namedArg returns a Proxy with get trap to know the var name
return () => {
console.log(config.value)
console.log(root.value)
}
})
cli.parse(['xx', 'xx', 'build', '--config', 'config.ts', '--root', '.', '--watch']) // error watch is not a possible argumentor request handler with validation
// server.ts
constroller(() => {
const { name, host } = urlQueries()
return () => {
// logic
}
})
// client.ts
get('http://xxxx/users?name=xx&host=xx')this last one could be helpful to some frameworks like vue to declare props e.g:
currently in vue 3 to declare props with setup we do
<script setup>
const props = defineProps({
modelValue: {
type: String,
default: ''
}
})
</script>but one possible aproach when all props has the same type could be
<script setup>
const { modelValue } = defineProps(String, '')
</script>The concept of a JS object that appears to give itself any available property seems very rare in practice
@RyanCavanaugh Are you sure? that concept is precisely the proxy concept there is libs which could use this feature i think you closed this issue too fast there is a lot of uses cases check my previous comment