Skip to content

Instantly share code, notes, and snippets.

@colelawrence
Last active September 4, 2022 12:10
Show Gist options
  • Save colelawrence/fe797649a85cd8d3eece42fa2da0050b to your computer and use it in GitHub Desktop.
Save colelawrence/fe797649a85cd8d3eece42fa2da0050b to your computer and use it in GitHub Desktop.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type $IntentionalAny = any;
/**
* This special type can help generate pattern matchers for you!
* Just use it as so:
* // enum-ts
* type Result<Ok, Err> = Enum<{
* Ok: Ok,
* Err: Err,
* }>
*/
export type Enum<T extends { [Variant: string]: {} }> = {
[P in keyof T]: Expand<
Pick<T, P> &
Omit<
{
[K in keyof T]?: never;
},
P
>
>;
}[keyof T];
type Expand<T> = T extends T
? {
[K in keyof T]: T[K];
}
: never;
type U2I<U> = (U extends U ? (u: U) => 0 : never) extends (i: infer I) => 0 ? Extract<I, U> : never;
type ExcludeNevers<T> = {
[K in keyof T as Required<T>[K] extends never ? never : K]: T[K];
};
type EnumKeysObj<T> = U2I<{
-readonly [K in keyof T]-?: unknown;
}>;
type EnumKeys<T> = T extends T ? keyof ExcludeNevers<T> : never;
type EnumProp<T, K extends PropertyKey> = Required<Pick<Extract<T, Partial<Record<K, unknown>>>, K>>[K];
type EnumOmit<T, K extends PropertyKey> = Exclude<T, Record<K, unknown>>;
type Match<T, R = never, O = EnumKeysObj<ExcludeNevers<T>>> = EnumKeys<T> extends never
? { $(): R }
: [R] extends [never]
? {
[P in keyof O]: <R1>(cb: (value: EnumProp<T, P>) => R1) => Match<EnumOmit<T, P>, R1>;
}
: {
[P in keyof O]: (cb: (value: EnumProp<T, P>) => R) => Match<EnumOmit<T, P>, R>;
} & {
_(calculate: (values: T) => R): R;
};
interface UndefinedMatch<T extends object | undefined | null> {
nullish<R>(value: () => R): Match<NonNullable<T>, R>;
}
const empty = Symbol("match never");
const THEN = "then";
const EXH = "$";
const OTH = "_";
export function match<T extends object | undefined | null>(
value: T,
): undefined extends T ? UndefinedMatch<T> : null extends T ? UndefinedMatch<T> : Match<T> {
// throw new Error("todo")
console.log("variant", { value });
let found: unknown = empty;
const proxy = new Proxy(
{},
{
get(_, p): $IntentionalAny {
console.log(`get ${String(p)}`);
if (p === THEN) return undefined; // protect from Promise.resolve(...)
if (p === EXH) return () => found;
if (p === OTH) return found === empty ? (cb: (val: unknown) => unknown) => cb(value) : () => found;
if (found === empty && value && p in value) {
const inner = (value as $IntentionalAny)[p];
return (cb: (inner: unknown) => unknown) => {
found = cb(inner);
return proxy;
};
} else {
return () => proxy;
}
},
},
);
if (value == null) {
return {
nullish(cb: () => unknown) {
found = cb();
return proxy;
},
} as $IntentionalAny;
} else {
return proxy as $IntentionalAny;
}
}
import { z, match } from "@autoplay/utils";
import type { AnyID, ID, TypedStorage } from "artprompt-common";
export namespace Security {
export enum MVPAccess {
/** For now, must have created this item */
Owner,
}
export type Requirements<_TID extends AnyID> = MVPAccess; // | "anyone";
export function canAccess<TDebugName extends string = string>(
access: AccessSummary<ID<TDebugName>>,
// At the moment, all requirements are the same...
_requirements: Requirements<ID<TDebugName>>,
): boolean {
return access.data === true;
// hmm: if no requirements, then also permit access
//|| requirements === MVPAccess.Anyone
}
export const accessSummaryType = z.literal(true).or(z.undefined()); // true indicates complete owner of this resource
// FUTURE: Have a set of bitflags for different access kinds (e.g. `{ pmt: 1 | 2 | 4 }`, see _todo/scopes.ts)
// empty obj would indicate no permissions
// .or(z.record(z.string().length(3), z.number()).default({}));
export type AccessSummary<_ extends AnyID> = { data: typeof accessSummaryType["_output"] };
/** A data wrapping class for checking current access */
export class Authenticated implements Security.Authenticated {
_agent: AnyID[];
constructor(private _agentStorage: TypedStorage, agent: AnyID, chainOfCommand: AnyID[] = []) {
this._agent = [agent, ...chainOfCommand];
}
_resItem(resourceID: AnyID) {
return this._agentStorage.item(resourceID.prefixed(), Security.accessSummaryType);
}
async summarize<R extends AnyID>(resourceID: R): Promise<Security.AccessSummary<R>> {
const item = this._resItem(resourceID);
return {
data: await item
.get({ _op: "accessSummary requires get summary" })
.mapErrorTrpc("Error getting access summary", "INTERNAL_SERVER_ERROR"),
};
}
apply(update: SecurityAccessUpdate): Promise<void> {
return match(update)
.GrantOwnership(({ to }) =>
this._resItem(to)
.put(true)
.then(() => {}),
)
.Unlink(({ to }) => this._resItem(to).delete())
.$();
}
}
export type SecurityAccessUpdate =
| {
GrantOwnership: { to: AnyID };
}
| {
Unlink: { to: AnyID };
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment