Skip to content

Instantly share code, notes, and snippets.

@williammartin
Created March 25, 2021 18:18
Show Gist options
  • Save williammartin/f999569e02e692c3697c9cd26c9bfb07 to your computer and use it in GitHub Desktop.
Save williammartin/f999569e02e692c3697c9cd26c9bfb07 to your computer and use it in GitHub Desktop.
// Helpers
// optional
type Some<T> = {
_tag: "some";
value: T;
};
type None = {
_tag: "none";
};
type Optional<T> = Some<T> | None;
const some = <T>(value: T): Some<T> => ({ _tag: "some", value });
const none: None = { _tag: "none" };
const isSome = <T>(optional: Optional<T>): optional is Some<T> => optional._tag === "some";
const isNone = <T>(optional: Optional<T>): optional is None => optional._tag === "none";
function applyOptional<T, R>(fns: { Some: (value: T) => R; None: () => R }): (optional: Optional<T>) => R {
return function (optional: Optional<T>): R {
if (optional._tag === "some") {
return fns.Some(optional.value);
} else if (optional._tag === "none") {
return fns.None();
} else {
return unreachable(optional);
}
};
}
function matchOptional<T, R>(optional: Optional<T>, fns: { Some: (value: T) => R; None: () => R }): R {
return applyOptional(fns)(optional);
}
// result
type Ok<O> = {
_tag: "ok";
value: O;
};
type Err<E> = {
_tag: "err";
value: E;
};
type Result<O, E> = Ok<O> | Err<E>;
const ok = <O>(value: O): Ok<O> => ({ _tag: "ok", value });
const err = <E>(value: E): Err<E> => ({ _tag: "err", value });
function applyResult<O, E, R>(fns: { Ok: (ok: O) => R; Err: (err: E) => R }): (result: Result<O, E>) => R {
return function (result: Result<O, E>): R {
if (result._tag === "ok") {
return fns.Ok(result.value);
} else if (result._tag === "err") {
return fns.Err(result.value);
} else {
return unreachable(result);
}
};
}
function matchResult<O, E, R>(
result: Result<O, E>,
fns: {
Ok: (ok: O) => R;
Err: (err: E) => R;
}
): R {
return applyResult(fns)(result);
}
// patterns
const unreachable = (x: never): never => {
throw new Error("unreachable encountered value which was supposed to be never");
};
// log
const log = (message: string) => console.log(message);
const warn = (message: string) => console.warn(message);
const error = (message: string) => console.error(message);
// pages
type PUID = string;
type PConstant = {
Constant: {
display_text: string;
value_type: string;
value: unknown;
};
};
type PExpressionValue = null | [PConstant, unknown];
function applyPExpressionValue<R>(fns: {
None: () => R;
Some: (value: [PConstant, unknown]) => R;
}): (expressionValue: PExpressionValue) => R {
return (expressionValue: PExpressionValue) => {
if (expressionValue === null) {
return fns.None();
} else if (expressionValue !== null) {
return fns.Some(expressionValue);
} else {
// Clearly unreachable right now when there are only two states...
return unreachable(expressionValue);
}
};
}
function matchPExpressionValue<R>(
expressionValue: PExpressionValue,
fns: {
None: () => R;
Some: (value: [PConstant, unknown]) => R;
}
): R {
return applyPExpressionValue(fns)(expressionValue);
}
type PServiceParam = { ServiceParam: { depends_on_atom: PUID[]; field_registry_id: PUID } };
type PExpressionKind = PServiceParam;
function applyPExpressionKind<R>(fns: {
PServiceParam: (serviceParam: PServiceParam) => R;
}): (kind: PExpressionKind) => R {
return (kind: PExpressionKind) => {
if ("ServiceParam" in kind) {
return fns.PServiceParam(kind);
} else {
return unreachable(kind);
}
};
}
function matchPExpressionKind<R>(
kind: PExpressionKind,
fns: {
PServiceParam: (serviceParam: PServiceParam) => R;
}
): R {
return applyPExpressionKind(fns)(kind);
}
type PExpression = {
Expression: {
uid: PUID;
expression: {
value: PExpressionValue;
text: string;
};
kind: PExpressionKind;
};
};
type PMarkKind = "Bold" | "Italics" | "Mono" | "Underline" | "Strikethrough";
type PText = {
Text: [string, PMarkKind[]];
};
type PServiceAction = {
ServiceAction: {
action_registry_id: PUID;
depends_on_atom: PUID;
};
};
type PService = {
Service: {
service_local_id: PUID;
service_registry_id: PUID;
};
};
type PAtomKind = PService | PServiceAction;
function applyPAtomKind<R>(fns: {
PService: (service: PService) => R;
PServiceAction: (serviceAction: PServiceAction) => R;
}): (kind: PAtomKind) => R {
return (kind: PAtomKind) => {
if ("Service" in kind) {
return fns.PService(kind);
} else if ("ServiceAction" in kind) {
return fns.PServiceAction(kind);
} else {
return unreachable(kind);
}
};
}
function matchPAtomKind<R>(
kind: PAtomKind,
fns: {
PService: (service: PService) => R;
PServiceAction: (serviceAction: PServiceAction) => R;
}
): R {
return applyPAtomKind(fns)(kind);
}
type PAtom = {
Atom: {
uid: PUID;
kind: PAtomKind;
};
};
type PToken = PAtom | PText | PExpression;
function applyPToken<R>(fns: {
PAtom: (atom: PAtom) => R;
PText: (text: PText) => R;
PExpression: (expression: PExpression) => R;
}): (token: PToken) => R {
return (token: PToken) => {
if ("Atom" in token) {
return fns.PAtom(token);
} else if ("Text" in token) {
return fns.PText(token);
} else if ("Expression" in token) {
return fns.PExpression(token);
} else {
return unreachable(token);
}
};
}
function matchPToken<R>(
token: PToken,
fns: {
PAtom: (atom: PAtom) => R;
PText: (text: PText) => R;
PExpression: (expression: PExpression) => R;
}
): R {
return applyPToken(fns)(token);
}
type PTokenLine = {
style: "Paragraph";
tokens: PToken[];
};
type PBlockKind = { TokenLine: PTokenLine };
type PBlock = {
uid: PUID;
kind: PBlockKind;
};
function applyPBlockKind<R>(fns: { TokenLine(tokenLine: PTokenLine): R }): (blockKind: PBlockKind) => R {
return (blockKind: PBlockKind) => {
if ("TokenLine" in blockKind) {
return fns.TokenLine(blockKind.TokenLine);
} else {
return unreachable(blockKind);
}
};
}
function matchPBlockKind<R>(
blockKind: PBlockKind,
fns: {
TokenLine(tokenLine: PTokenLine): R;
}
): R {
return applyPBlockKind(fns)(blockKind);
}
type PSkill = {
uid: PUID;
blocks: PBlock[];
};
// Drivers
type ServiceCredentials = unknown;
type ServiceArgs = unknown;
type ServiceShares = unknown;
interface ServiceAction {
perform: (credentials: ServiceCredentials, args: ServiceArgs) => Promise<Result<ServiceShares, Error>>;
}
interface ServiceEvent {
subscribe: (credentials: ServiceCredentials, args: ServiceArgs) => void;
}
namespace slack {
type ParsedSlackCredentials = { onegraphAppId: string; accessToken: string };
const parseCredentials = (credentials: ServiceCredentials): Result<ParsedSlackCredentials, Error> => {
// Proper validation would happen here
return ok({
onegraphAppId: (credentials as any).onegraphAppId,
accessToken: (credentials as any).accessToken,
});
};
// Janky types that match prisma GraphQLClient just to prove out e2e
class GraphQLClient {
constructor(private uri: string, private options: any) {}
public async request(query: string, variables: any) {
log(
`Faking GraphQL Request: ${JSON.stringify({
uri: this.uri,
options: this.options,
query,
variables,
})}`
);
}
}
const createClient = (credentials: ParsedSlackCredentials) => {
const graphQLClient = new GraphQLClient(
`https://serve.onegraph.com/graphql?app_id=${credentials.onegraphAppId}`,
{
headers: {
authorization: `Bearer ${credentials.accessToken}`,
},
}
);
return {
// Silly types
// Which layer holds responsibility for transforming vessels and representations?
// For example, `slack send to:Message author`
// Something needs to turn Message author into a representation that satisfies
// the slack send action interface.
// What about `slack send to:#bot`
// How will we bubble errors?
send: async (to: string, contents: string) => {
const query = "query contents or whatever";
// How does error handling work?
await graphQLClient.request(query, {
channel: to,
text: contents,
});
},
};
};
export namespace send {
// Very silly types for this
type ParsedSendArgs = { to: string; contents: string };
const parseArgs = (credentials: ServiceArgs): Result<ParsedSendArgs, Error> => {
// Proper validation would happen here
return ok({
to: (credentials as any).to,
contents: (credentials as any).contents,
});
};
export const slackSendAction = {
// Not sure whether we would hand the unknown types directly to perform or chain perform
// after a `parse` step that is offered by the service / action e.g.
// pipe(
//. zip(
// parseCredentials(credentials),
// parseArgs(args)
// ),
// perform,
// )
// Or something like that...
// Hmmm Promises and Results don't play that well together
perform: async (
credentials: ServiceCredentials,
args: ServiceArgs
): Promise<Result<ServiceShares, Error>> =>
// Railway Oriented Programming would probably help here but oh well
matchResult(parseCredentials(credentials), {
// Need a return type on the branch here to make TS understand that Ok and Err are both part of Result?
// Nothing shared quite yet...
Ok: async (parsedCredentials): Promise<Result<ServiceShares, Error>> =>
matchResult(parseArgs(args), {
Ok: async (parsedArgs): Promise<Result<ServiceShares, Error>> => {
// This will be repetitive, maybe actions should just be given the prepared client?
const slackClient = createClient(parsedCredentials);
// We always resolve this promise and let the layer above handle the Result type
// which might be confusing I guess?
return new Promise((resolve, reject) => {
slackClient
.send(parsedArgs.to, parsedArgs.contents)
.then(() => resolve(ok({})))
.catch((e) => resolve(err(e)));
});
},
Err: async (e) => err(e),
}),
// we never actually allow it in parseCredentials yet but might as well pass it on
Err: async (e) => err(e),
}),
};
}
}
// Runtime
// We should be able to be smarter here than a general interpreter
// because we know the UID that shared things into scope, and we know
// the UID that is being referenced as a lookup.
// Is a list of Expression the right type here?
// Can we just accrete scope over time since we don't need to worry about
// shadowing?
type Scope = Record<UID, Expression[]>;
type Diagnostics = {};
type Context = { scope: Scope; diagnostics: Diagnostics };
// Early modelling of diagnostics
// Runtime relationships?
type UID = string;
type LocalServiceID = string;
type DriverID = string;
// Hmm, what information do we actually need in here to make a decision? DriverID?
type Constant = {
_tag: "constant";
id: DriverID;
value: unknown;
};
const constant = (input: { id: DriverID; value: unknown }): Constant => ({
_tag: "constant",
id: input.id,
value: input.value,
});
type Reference = {
_tag: "reference";
};
type Value = Constant | Reference;
type Refinement = {
id: DriverID;
};
type Expression = {
value: Optional<[Value, Refinement[]]>;
text: string;
};
type ArgID = string;
type Args = Record<ArgID, Expression>;
type Iter = { next: () => Optional<Expression> };
type Block = {
_tag: "block";
// uid: UID;
instructions: Instruction[];
};
type Action = {
_tag: "action";
// uid: UID;
local_service_id: LocalServiceID;
service_id: DriverID; // Is this needed?
service_action_id: DriverID;
args: Args;
};
const action = (inputs: {
local_service_id: LocalServiceID;
service_id: DriverID;
service_action_id: DriverID;
args: Args;
}): Action => ({
_tag: "action",
local_service_id: inputs.local_service_id,
service_id: inputs.service_id,
service_action_id: inputs.service_action_id,
args: inputs.args,
});
type Subscription = {
_tag: "subscription";
// uid: UID;
local_service_id: LocalServiceID;
service_registry_id: DriverID; // Is this needed?
service_event_id: DriverID;
args: Args;
// Hmmm, I'm not sure this is going to work very well...
// We sort of need an exact 1:1 mapping of UIDs if we're going to do things like
// "hey, this particular line failed"
block: Block;
};
// How to model else if?
type Conditional = {
_tag: "conditional";
// uid: UID;
expression: Expression;
left: Block;
right: Optional<Block>;
};
// How to model the iteration subject?
type Iteration = {
_tag: "iteration";
// uid: UID;
iterable: Iter;
block: Block;
};
type Effect = Action | Subscription;
function applyEffect<R>(fns: {
Action: (action: Action) => R;
Subscription: (subscription: Subscription) => R;
}): (effect: Effect) => R {
return function (effect: Effect): R {
if (effect._tag === "action") {
return fns.Action(effect);
} else if (effect._tag === "subscription") {
return fns.Subscription(effect);
} else {
return unreachable(effect);
}
};
}
function matchEffect<R>(
effect: Effect,
fns: {
Action: (action: Action) => R;
Subscription: (subscription: Subscription) => R;
}
): R {
return applyEffect(fns)(effect);
}
type Instruction = Effect | Conditional | Iteration;
function applyInstruction<R>(fns: {
Effect: (effect: Effect) => R;
Conditional: (conditional: Conditional) => R;
Iteration: (iteration: Iteration) => R;
}): (instruction: Instruction) => R {
return function (instruction: Instruction): R {
if (instruction._tag === "action") {
return fns.Effect(instruction);
} else if (instruction._tag === "subscription") {
return fns.Effect(instruction);
} else if (instruction._tag === "conditional") {
return fns.Conditional(instruction);
} else if (instruction._tag === "iteration") {
return fns.Iteration(instruction);
} else {
return unreachable(instruction);
}
};
}
function matchInstruction<R>(
instruction: Instruction,
fns: {
Effect: (effect: Effect) => R;
Conditional: (conditional: Conditional) => R;
Iteration: (iteration: Iteration) => R;
}
): R {
return applyInstruction(fns)(instruction);
}
type CredentialLoader = (id: LocalServiceID) => ServiceCredentials;
type ActionLoader = (id: DriverID) => ServiceAction;
type EventLoader = (id: DriverID) => ServiceEvent;
// Too much closing over mutable state here e.g. scope when that's just initial scope
// Can we use an immutable library like ImmutableJS?
// Can we make a reducer for Promises here?
// preduce should act like reduce but with Promises
function preduce<A, C>(
collection: C[],
initialValue: A,
callbackFn: (accumulator: A, current: C) => Promise<A>
): Promise<A> {
return collection.reduce(
(accP: Promise<A>, curr: C): Promise<A> =>
// each subsequent accumulation should depend upon the resolution of the previous step
accP.then((acc) => callbackFn(acc, curr)),
Promise.resolve(initialValue)
);
}
type Tracer = (block: Block) => void;
type TraceID = string;
const tracer = (credentialsFor: CredentialLoader, loadAction: ActionLoader, loadEvent: EventLoader) => (
id: TraceID,
initialScope: Scope,
block: Block
) =>
preduce(
block.instructions,
initialScope,
(accumulatingScope, instruction): Promise<Scope> =>
applyInstruction({
Effect: applyEffect({
Action: async (actionInstruction: Action): Promise<Scope> => {
log(`Tracing ${id}: In Action`);
// Probably this could be pretty functional with fp-ts or something.
// Maybe we shouldn't do the action in here but pass it back up so this
// could remain effect-free and easily testable, but I dunno, seems ok.
// Don't look too closely at the wiring, maybe the credentials are used
// at loading time or something. Also these have error conditions e.g.
// no credentials with service id (which may be an error or not) or
// no action with id
const credentials = credentialsFor(actionInstruction.local_service_id);
const action = loadAction(actionInstruction.service_action_id);
const performResult = await action.perform(credentials, actionInstruction.args);
// This should probably have a way to signal failure?
return applyResult({
Ok: (shares) => {
// convert shares to expressions?
// should this return scope + new scope or just new scope?
// Let's pretend that we have a way to create new scope from
// shares...
return {
...accumulatingScope,
// Need to accumulate scope perhaps by UID here?
// [actionInstruction.uid]: [],
};
},
Err: (err) => {
return accumulatingScope;
},
})(performResult);
// And don't look too closely at these args. There's probably a better
// way to map them into the driver interface. For example, given some
// constant value or reference, we probably need to "resolve"/"evaluate"
// them before passing them through?
},
Subscription: async (subscriptionInstruction: Subscription): Promise<Scope> => {
return accumulatingScope;
},
}),
// Shouldn't really be able to return any new scope from these?
Conditional: async (conditionalInstruction: Conditional): Promise<Scope> => {
return accumulatingScope;
},
Iteration: async (iterationInstruction: Iteration): Promise<Scope> => {
return accumulatingScope;
},
})(instruction)
);
namespace transformation {
type QualifiedService = {
_tag: "qualifiedService";
service_local_id: LocalServiceID;
service_id: DriverID;
};
const qualifiedService = (inputs: {
service_local_id: LocalServiceID;
service_id: DriverID;
}): QualifiedService => ({
_tag: "qualifiedService",
service_local_id: inputs.service_local_id,
service_id: inputs.service_id,
});
type QualifiedServiceAction = {
_tag: "qualifiedServiceAction";
service_local_id: LocalServiceID;
service_id: DriverID;
service_action_id: DriverID;
args: Args;
};
const qualifiedServiceAction = (inputs: {
service_local_id: LocalServiceID;
service_id: DriverID;
service_action_id: DriverID;
args: Args;
}): QualifiedServiceAction => ({
_tag: "qualifiedServiceAction",
service_local_id: inputs.service_local_id,
service_id: inputs.service_id,
service_action_id: inputs.service_action_id,
args: inputs.args,
});
type Unqualified = {
_tag: "unqualified";
};
const unqualified: Unqualified = { _tag: "unqualified" };
type Disqualified = {
_tag: "disqualified";
reason: string;
};
const disqualified = (reason: string): Disqualified => ({ _tag: "disqualified", reason });
type Qualification = Unqualified | QualifiedService | QualifiedServiceAction | Disqualified;
function applyQualification<R>(fns: {
Unqualified: (unqualified: Unqualified) => R;
QualifiedService: (qualifiedService: QualifiedService) => R;
QualifiedServiceAction: (qualifiedServiceAction: QualifiedServiceAction) => R;
Disqualified: (disqualified: Disqualified) => R;
}): (qualification: Qualification) => R {
return (qualification: Qualification) => {
if (qualification._tag === "unqualified") {
return fns.Unqualified(qualification);
} else if (qualification._tag === "qualifiedService") {
return fns.QualifiedService(qualification);
} else if (qualification._tag === "qualifiedServiceAction") {
return fns.QualifiedServiceAction(qualification);
} else if (qualification._tag === "disqualified") {
return fns.Disqualified(qualification);
} else {
return unreachable(qualification);
}
};
}
function matchQualification<R>(
qualification: Qualification,
fns: {
Unqualified: (unqualified: Unqualified) => R;
QualifiedService: (qualifiedService: QualifiedService) => R;
QualifiedServiceAction: (qualifiedServiceAction: QualifiedServiceAction) => R;
Disqualified: (disqualified: Disqualified) => R;
}
): R {
return applyQualification(fns)(qualification);
}
export const transformSkill = (skill: PSkill): Block => {
const extendBlock = (block: Block, instruction: Instruction): Block => ({
_tag: "block",
instructions: [...block.instructions, instruction],
});
const startingBlock: Block = { _tag: "block", instructions: [] };
const qualifyTokenline = (tokenLine: PTokenLine) =>
tokenLine.tokens
.filter(
applyPToken({
PAtom: () => true,
PText: () => false,
PExpression: () => true,
})
)
.reduce(
(qualification: Qualification, token): Qualification =>
matchQualification(qualification, {
Unqualified: (): Qualification =>
matchPToken(token, {
PAtom: (pAtom): Qualification =>
matchPAtomKind(pAtom.Atom.kind, {
PService: (service): Qualification =>
qualifiedService({
service_local_id: service.Service.service_local_id,
service_id: service.Service.service_registry_id,
}),
PServiceAction: () =>
disqualified("the token line began with a service action"),
}),
PText: () => disqualified("the token line began with text"),
PExpression: () => disqualified("the token line began with an expression"),
}),
QualifiedService: (service) =>
matchPToken(token, {
PAtom: (pAtom): Qualification =>
matchPAtomKind(pAtom.Atom.kind, {
PService: (): Qualification =>
disqualified("the token line had two sequential services"),
PServiceAction: (serviceAction) =>
qualifiedServiceAction({
service_local_id: service.service_local_id,
service_id: service.service_id,
service_action_id: serviceAction.ServiceAction.action_registry_id,
args: {},
}),
}),
PText: () => disqualified("the token line had text following the service"),
PExpression: () =>
disqualified("the token line had an expression following the service"),
}),
QualifiedServiceAction: (serviceAction) =>
matchPToken(token, {
PAtom: (): Qualification =>
disqualified("the token line had an atom following the service action"),
PText: () => disqualified("the token line had text following the service atom"),
PExpression: (pExpression): Qualification =>
matchPExpressionKind(pExpression.Expression.kind, {
PServiceParam: (serviceParam): Qualification =>
qualifiedServiceAction({
service_local_id: serviceAction.service_local_id,
service_id: serviceAction.service_id,
service_action_id: serviceAction.service_action_id,
args: {
...serviceAction.args,
// Only handling constants right now
[serviceParam.ServiceParam.field_registry_id]: {
text: pExpression.Expression.expression.text,
value: matchPExpressionValue(
pExpression.Expression.expression.value,
{
None: (): Optional<[Value, Refinement[]]> => none,
// Can't really do anything with refinements right here because they are unknown type hmmm
Some: (value) =>
some([
constant({
id: value[0].Constant.value_type,
value: value[0].Constant.value,
}),
[],
]),
}
),
},
},
}),
}),
}),
Disqualified: (disqualification) => disqualification,
}),
unqualified
);
const transformTokenLine = (tokenLine: PTokenLine): Optional<Instruction> => {
const qualification = qualifyTokenline(tokenLine);
return matchQualification(qualification, {
Unqualified: (): Optional<Instruction> => none,
Disqualified: () => none,
QualifiedService: () => none,
QualifiedServiceAction: (qualification) =>
some(
action({
local_service_id: qualification.service_local_id,
service_id: qualification.service_id,
service_action_id: qualification.service_action_id,
args: qualification.args,
})
),
});
};
return skill.blocks.reduce(
(acc, curr): Block =>
matchPBlockKind(curr.kind, {
TokenLine: (tokenLine): Block =>
matchOptional(transformTokenLine(tokenLine), {
Some: (instruction): Block => extendBlock(acc, instruction),
None: () => acc,
}),
}),
startingBlock
);
};
}
namespace examples {
type Example = {
id: string;
page: PSkill;
runnable: Block;
};
// Slack send to:#general content:"Hello" -> Instruction.Effect.Action
export const e1: Example = {
id: "simple slack send",
page: {
uid: "JytJZuF4TTW_9D9Fbxgqjw",
blocks: [
{
uid: "!blank--token-line-1",
kind: {
TokenLine: {
style: "Paragraph",
tokens: [
{
Atom: {
uid: "2UpjJw2tTLiJGcbK2ZG0rA",
kind: {
Service: {
service_local_id: "my-slack",
service_registry_id: "service:slack",
},
},
},
},
{
Text: [" ", []],
},
{
Atom: {
uid: "gHKaeAzMRTmiJOSEnZLRLA",
kind: {
ServiceAction: {
action_registry_id: "service/action:slack/send",
depends_on_atom: "2UpjJw2tTLiJGcbK2ZG0rA",
},
},
},
},
{
Text: [" ", []],
},
{
Expression: {
uid: "K0wZfh31RdO1Zdhp9TYiQQ",
expression: {
value: [
{
Constant: {
display_text: "#general",
value_type: "service/type:slack/Channel",
value: "#general",
},
},
[],
],
text: "",
},
kind: {
ServiceParam: {
depends_on_atom: ["2UpjJw2tTLiJGcbK2ZG0rA", "gHKaeAzMRTmiJOSEnZLRLA"],
field_registry_id: "service/action/field:slack/send/recipient",
},
},
},
},
{
// Why is this an empty space?
Text: [" ", []],
},
{
Expression: {
uid: "qR_UDyFVQTu-UopVrY6Z9g",
expression: {
value: null,
text: "Hello",
},
kind: {
ServiceParam: {
depends_on_atom: ["2UpjJw2tTLiJGcbK2ZG0rA", "gHKaeAzMRTmiJOSEnZLRLA"],
field_registry_id: "service/action/field:slack/send/message",
},
},
},
},
],
},
},
},
],
},
runnable: {
_tag: "block",
instructions: [
{
_tag: "action",
local_service_id: "my-slack",
service_id: "service:slack",
service_action_id: "service/action:slack/send",
args: {
"service/action/field:slack/send/recipient": {
text: "",
value: {
_tag: "some",
value: [
{
_tag: "constant",
id: "service/type:slack/Channel",
// Probably not quite the right value here...
// it should really be a channel id, only the display
// is #bot
value: "#general",
},
[],
],
},
},
"service/action/field:slack/send/message": {
text: "Hello",
value: { _tag: "none" },
},
},
},
],
},
};
// Other Examples to come:
// Slack send to:#bot content:"Hello" -> Instruction.Effect.Action
// Slack send to:@Will content:"Hello" -> Instruction.Effect.Action
// when Slack hears in:#echo -> Instruction.Effect.Subscription
//. slack reply to:Message content:Message Contents -> Instruction.Effect.Action
// when Slack hears in:#engineering -> Instruction.Effect.Subscription
//. if Message contents contains "will" -> Instruction.Conditional
//. slack reply to:Message content:"mmm TDD"
// when Slack hears in:#engineering -> Instruction.Effect.Subscription
//. if Message contents contains "will" -> Instruction.Conditional
//. slack reply to:Message content:"mmm TDD" -> Instruction.Effect.Action
//. else -> No Construct
//. slack reply to:Message content:"Testing sucks" -> Instruction.Effect.Action
// when Event is cancelled -> Instruction.Effect.Subscription
//. for each Event attendee -> -> Instruction.Iteration
//. email to:Attendee message:"Event cancelled due to Covid" -> Instruction.Effect.Action
// when Stripe invoice create -> Instruction.Effect.Subscription
//. after 30 days -> Instruction.Effect.Subscription ?? How to model dropping back in here ??
//. if Invoice is unpaid -> Instruction.Conditional
// email to:Invoice recipient message:"Pay up" -> Instruction.Effect.Action
export const all = [e1];
}
const main = async () => {
const credentialsFor = (id: LocalServiceID): ServiceCredentials => {
if (id === "my-slack") {
return { accessToken: "fake-access-token", onegraphAppId: "fake-onegraph-app-id" };
} else {
throw new Error(`unknown driver id: ${id}`);
}
};
const loadAction = (id: DriverID): ServiceAction => {
if (id === "service/action:slack/send") {
return slack.send.slackSendAction;
} else {
throw new Error(`unknown driver id: ${id}`);
}
};
const loadEvent = (id: DriverID): ServiceEvent => ({
subscribe: (credentials: ServiceCredentials, args: ServiceArgs) => {},
});
const trace = tracer(credentialsFor, loadAction, loadEvent);
examples.all.forEach(async (e) => {
log(`starting Trace: ${e.id}`);
const scope = trace(e.id, {}, transformation.transformSkill(e.page));
log(`Finishing Trace: ${e.id} with scope: ${JSON.stringify(scope)}`);
});
};
const testTransformation = () => {
log("beginning transformation tests");
examples.all.forEach((e) => {
log(`testing transformation for "${e.id}"`);
log(`transformation equality: ${JSON.stringify(transformation.transformSkill(e.page)) === JSON.stringify(e.runnable)}`);
log(`finished testing transformation for "${e.id}"`);
});
log("finished transformation tests");
};
testTransformation();
main()
.then(() => log("complete"))
.catch((e) => console.error(e));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment