#Exclusively mix types

65 messages ยท Page 1 of 1 (latest)

dreamy swallow
#

I wonder how to make combined types in arrays. If the BaseScope is present it automatically grants all extension scopes, so they shouldn't be present in the array. Example cases:

type ExtensionScopes = "read" | "write" | "update" | "delete";
type BaseScopes = "products" | "categories";

// Should pass
const a: Scope = ["products"];
const b: Scope = ["categories", "products"];
const c: Scope = ["products:delete", "products:write", "categories:delete"];
const d: Scope = ["categories", "products:write"];
// Should fail
const e: Scope = ["products", "products:read"];
const f: Scope = ["products", "categories", "products:read"];
const g: Scope = ["categories", "categories:delete"];

So far I was only able to filter out one "baseScope" from all using something like this:

type FilteredScope<T extends BaseScopes> =
  (T | `${Exclude<BaseScopes, T>}:${ExtensionScopes}` | Exclude<BaseScopes, T>)[]
  | (`${T}:${ExtensionScopes}` | Exclude<BaseScopes, T> | `${Exclude<BaseScopes, T>}:${ExtensionScopes}`)[]

type Scope = FilteredScope<"products"> // Any merge with another FilteredScope<> will break it 
polar creek
#

It's gonna be tricky. An array type doesn't require all member types to be present, so you have very little control

#

Tuples are more precise, so we have more control, but tuples are ordered, and accounting for different orders gets very complicated

#

I would probably suggest not doing this, and trying a different approach

dreamy swallow
#

I just used something that is quick to construct when using it, Sets seem fine, but I couldn't figure it out

polar creek
#

for example

const permissions = {
  products: {
    delete: true,
    write: true
  }
}
#

This kind of structured object approach seems likely to be much easier to work with

dreamy swallow
#

Let me introduce the intended usage:

@MetadataDecorator(["products, categories:read"]) // Endpoint controller

Then when the request comes, it contains string with allowed scopes: products categories
I'll just check if it contains everything that is required for the endpoint.

polar creek
#

I'm not sure what that is meant to do. But yeah if you really want an array then doing runtime checking will be much easier than type level checks

dreamy swallow
#

Doesn't need to be array. It is just that I don't want to check during runtime if the required scopes were entered correctly, I just need to see while typing long scope permissions, if I don't repeat myself (will just prolong the checking for each request)

polar creek
#

So why don't you use the object style?

dreamy swallow
#

How would the default like? ๐Ÿค”, instead of typing out every true for each extensionScope, just mentioning products would be fine

polar creek
#

How about this?

#

Wait I misunderstood your need, I misread

dreamy swallow
#

Yeah, I imagined for something more like this: ["products, categories:read"] The top scopes automatically grants all subsequent

polar creek
#
type P = {
  products: 'all' | Set<'read' | 'write' | 'delete'>,
  category: 'all' | Set<'read' | 'write' | 'delete'>,
}
const p1: P = { products: 'all' }
const p2: P = { products: new Set('delete', 'write') }
#

This is one way

dreamy swallow
#

Well yes, but... ๐Ÿค”

polar creek
#

the syntax isn't so clean?

dreamy swallow
#

When specifying the individual ones I need to create Set each time, it works, but ain't the clearest

polar creek
#

I think your decorator will only run once, so if you do use runtime checking it won't have any noticably performance affect

#

You're only checking on load

dreamy swallow
#

Running tests takes forever ๐Ÿ˜‚ , that's why I am trying to avoid that

polar creek
#

huh?

#

unit tests to test the runtime check? Why would it take long?

dreamy swallow
#

It's in Nest.js module, they get recompiled in between each E2E test suit

polar creek
#

I dunno ๐Ÿ™‚

#
type Permission = 'all' | 'read' | 'write' | 'delete' | 'readwrite' | 'readdelete' | 'writedelete'
type Permissions = { product: Permission, category: Permission }

const p: Permissions = { product: 'all', category: 'readdelete' }
#

+update ๐Ÿ˜„

dreamy swallow
#

Now make the test when the request contains: ```ts
scope: "product categories:write categories:delete"

polar creek
#

I don't know what you mean

dreamy swallow
#
type P = {
  [K in BaseScopes]: 'all' | ExtensionScopes[]
}

Someething like that ,I just need to ensure uniqueness of the ExtensionScopes

dreamy swallow
polar creek
#

The simple answer is to use a set. You guess you could technically write an advanced recursive type to look for duplicates within a tuple, but that's super complex

dreamy swallow
polar creek
#

You can look at something like this

#

you'll need a const array / tuple. If it's a mutable array type then there is no capacity to prevent duplicates

dreamy swallow
#

My failed attempt

 type ValidScopes<T extends CombinedScopes[]> = T extends (infer U)[]
   ? U extends `${BaseScopes}:${ExtensionScopes}`
     ? Exclude<CombinedScopes, ExtractBaseScope<U>>[]
     : T
   : never;
 type ValidScopes<T extends CombinedScopes[]> = T extends (infer First extends CombinedScopes)[]
   ? First extends `${BaseScopes}:${ExtensionScopes}`
     ? Exclude<CombinedScopes, First>
     : First extends BaseScopes ?
       Exclude<CombinedScopes, `${First}:${ExtensionScopes}`>
       : never
   : never;```
polar creek
#

Well, I am not going to get into trying to solve this

dreamy swallow
polar creek
#

There are simple solutions, I wouldn't spend my time trying to write these types ๐Ÿ™‚

#

If someone wants a challenge then cool, but not me haha

dreamy swallow
#

Bro, I've been studying RFC standards for last month, finally some fun ๐Ÿ˜‚

#

@polar creek

type P = {
  [K in BaseScopes]: 'all' | [ExtensionScopes] | ["read", "write"] // and so on
}```
polar creek
#

I guess you could write out every option yeah

#

?:

dreamy swallow
#

Yeah ๐Ÿ˜…, sometimes I am an idiot

polar creek
#

But remember tuples are ordered. So you really need ["read", "write"] ["write", "read"] to be complete

#

Which is why I said I didn't like tuples for this ๐Ÿ˜„

bold adder
#

Then when the request comes, it contains string with allowed scopes
It is just that I don't want to check during runtime
by "request" do you mean HTTP request (or equivalent)? if so then i think these two things are contradictory. if the input comes from outside of your program at runtime then there's nothing you can do other than runtime validation. users could send you arbitrary inputs

polar creek
#

If you don't mind always following the same order you can ignore that

dreamy swallow
#

Do you think it is better to have the 'all' as string, or make it part of the tuple: ["all"]

polar creek
#

๐Ÿคท๐Ÿป

dreamy swallow
polar creek
#

maybe put it in a tuple, seems easier to remember

dreamy swallow
polar creek
#

Write whatever you think will be easier for your to remember

#

Because the only time it matters is when you come back 6 months later and are trying to remember what the frick you did 6 months ago

dreamy swallow
#

It's not about me, I will remember (also LSP helps anyway). I am just wondering what would be nicer for a some new into the project

polar creek
dreamy swallow
#

I'll make it tuple for consistency.
Okay, thanks for help overall though.

#

!resolved