Keep your Business Logic appart from your actions/loaders plumbing

Overview

Remix Domains

Remix Domains helps you to keep your Business Logic appart from your actions/loaders plumbing. It does this by enforcing the parameters' types in runtime (through zod schemas) and always wrapping results (even exceptions) into a Promise<Result<Output>> type.

Benefits

  • End-to-End typesafety all the way from the Backend to the UI
  • Keep your domain functions decoupled from the framework, with the assurance that your values conform to your types
  • Easier to test and maintain business logic
  • Business Logic can be expressed in the type system
  • Removes the plumbing of extracting and parsing structured data from your actions

Quickstart

npm i remix-domains zod
import { makeDomainFunction, inputFromForm } from 'remix-domains'
import * as z from 'zod'

const schema = z.object({ number: z.preprocess(Number, z.number()) })
const increment = makeDomainFunction(schema)(async ({ number }) => number + 1)

const result = await increment({ number: 1 }) // result = { data: 2, success: true }
const failedResult = await increment({ number: 'foo' })
/*
failedResult = {
  success: false,
  inputErrors: [{ path: ['number'], message: 'Expected number, received nan' }]
}
*/

To understand how to build the schemas, refer to Zod documentation.

Create your first action with Remix

import type { ActionFunction } from 'remix'
import { useActionData, redirect } from 'remix'
import { makeDomainFunction, inputFromForm } from 'remix-domains'
import * as z from 'zod'

const schema = z.object({ number: z.preprocess(Number, z.number()) })
const increment = makeDomainFunction(schema)(({ number }) => number + 1)

export const action: ActionFunction = async ({ request }) => {
  const result = await increment(await inputFromForm(request))

  if (!result.success) return result

  return redirect('/')
}

export default function Index() {
  const actionData = useActionData()

  return (
    <Form method="post">
      <input name="number" type="number" />
      {actionData.inputErrors && (
        <span role="alert">{actionData.inputErrors[0].message}</span>
      )}
      <button type="submit">
        Submit
      </button>
    </Form>
  )
}

Taking parameters that are not user input

Sometimes you want to ensure the safety of certain values that weren't explicitly sent by the user. We call them environment:

const sendEmail = makeDomainFunction(
  z.object({ email: z.string().email() }), // user input schema
  z.object({ origin: z.string() }) // environment schema
)(
  async ({ email }, { origin }) => {
    mailer.send({
      email,
      message: `Link to reset password: ${origin}/reset-password`
    })
  }
)

// In your Remix action:
export const action = async ({ request }) => {
  const environment = (request: Request) => ({
    origin: new URL(request.url).origin,
  })

  await sendResetToken(
    await inputFromForm(request),
    environment(request),
  )
}

We usually use the environment for ensuring authenticated requests. In this case, assume you have a currentUser function that returns the authenticated user:

const dangerousFunction = makeDomainFunction(
  someInputSchema,
  z.object({ user: z.object({ id: z.string(), admin: z.literal(true) }) })
)(async (input, { user }) => {
  // do something that only the admin can do
})

Dealing with errors

The error result has the following structure:

type ErrorResult = {
  success: false
  errors: z.ZodIssue[] | { message: string }
  inputErrors: z.ZodIssue[]
}

Where inputErrors will be the errors from parsing the user input and errors will be either from parsing the environment or any exceptions thrown inside the domain function:

const alwaysFails = makeDomainFunction(input, environment)(async () => {
  throw new Error('Some error')
})

const failedResult = await alwaysFails(someInput)
/*
failedResult = {
  success: false,
  errors: [{ message: 'Some error' }],
  inputErrors: []
}
*/

Type Utilities

UnpackData

It infers the returned data of a successful domain function:

const fn = makeDomainFunction()(async () => '')

type LoaderData = UnpackData<typeof fn>
// LoaderData = string

Input Utilities

We export some functions to help you extract values out of your requests before sending them as user input.

inputFromForm

Extracts values sent in a request through the FormData as an object of values:

// Given the following form:
function Form() {
  return (
    <form method="post">
      <input name="email" value="[email protected]" />
      <input name="password" value="1234" />
      <button type="submit">
        Submit
      </button>
    </form>
  )
}

export const action = async ({ request }) => {
  const values = await inputFromForm(request)
  // values = { email: '[email protected]', password: '1234' }
}

inputFromUrl

Extracts values sent in a request through the URL as an object of values:

// Given the following form:
function Form() {
  return (
    <form method="get">
      <button name="page" value="2">
        Change URL
      </button>
    </form>
  )
}

export const action = async ({ request }) => {
  const values = inputFromUrl(request)
  // values = { page: '2' }
}

Both of these functions will parse the input using qs:

// Given the following form:
function Form() {
  return (
    <form method="post">
      <input name="numbers[]" value="1" />
      <input name="numbers[]" value="2" />
      <input name="person[0][email]" value="[email protected]" />
      <input name="person[0][password]" value="1234" />
      <button type="submit">
        Submit
      </button>
    </form>
  )
}

export const action = async ({ request }) => {
  const values = await inputFromForm(request)
  /*
  values = {
    numbers: ['1', '2'],
    person: [{ email: '[email protected]', password: '1234' }]
  }
  */
}

To better understand how to structure your data, refer to qs documentation

Acknowlegements

We are grateful for Zod as it is a great library and informed our design. It's worth mentioning two other projects that inspired remix domains:

Comments
  • Add pattern matching

    Add pattern matching

    Hi!

    I was just about to build something like this myself when I stumbled on this library haha. Great work! I love how you approached it.

    A common pattern in Remix when having multiple forms on one page, is to add a hidden input named action to your form. It would be great to be able to combine domain functions with pattern matching to handle this. For example:

    type Input =
      | {
          action: 'update';
          name: string;
        }
      | {
          action: 'delete';
          id: string;
        };
    
    // Or possibly:
    // type Input =
    //   | UnpackInput<typeof updateProjectName>
    //   | UnpackInput<typeof deleteStuff>;
    
    export const action: ActionFunction = async ({ request }) => {
      const input = (await inputFromForm(request)) as Input;
    
      return match(input.action)
        .with({ action: 'update' }, (input) => updateProjectName(input))
        .with({ action: 'delete' }, (input) => deleteStuff(input))
        .otherwise(() => {
          throw new Error('Unexpected action');
        });
    };
    

    ts-pattern immediately comes to mind. Maybe this could be integrated or serve as inspiration.

    Also, quick question; is this library actually specific to Remix? By the looks of it I can use this in any other framework, as long as it gets a Request as input?

    opened by waspeer 8
  • Creates a 'sequence' function which is like a pipe function but savin…

    Creates a 'sequence' function which is like a pipe function but savin…

    …g the results along the way

    We may need a better name for the function.

    The proposal here is to have the ability to pipe domain functions but keeping the output of every step along the way. The Result is going to be a tuple, similar to the output of all.

    Result<[
      UnpackData<typeof firstDomainFunction>,
      UnpackData<typeof secondDomainFunction>,
      UnpackData<typeof thirdDomainFunction>
    ]>
    
    opened by gustavoguichard 8
  • A way to access errors for fields not in the schema when using `errorMessagesForSchema`

    A way to access errors for fields not in the schema when using `errorMessagesForSchema`

    Scenario with Remix Forms, let's say we're updating a user at /users/$userId/edit:

    const formSchema = z.object({
      firstName: z.string().min(1),
      email: z.string().min(1).email(),
    })
    
    const mutationSchema = formSchema.extend({ userId: z.string() })
    
    const mutation = makeDomainFunction(mutationSchema)(async (values) => values)
    
    export const action: ActionFunction = async ({ request, params }) =>
      formAction({
        request,
        schema,
        mutation,
        transformValues: (values) => ({ ...params, ...values }),
      })
    
    export default () => <Form schema={formSchema} />
    

    If for any reason the userId is not present in the params, the mutation will not succeed but formAction will not return an error for the missing userId because it is using errorMessagesForSchema under the hood.

    It would be nice if we could access those errors that are not part of the schema. Either as part of the return value of errorMessagesForSchema or through another helper function.

    That way Remix Forms could show them as global errors and give feedback to the developer that something is off with the wiring between Form and action. Right now, the form submission will not succeed but no error will be shown.

    opened by danielweinmann 7
  • errorMessagesForSchema returns wrong data

    errorMessagesForSchema returns wrong data

      const securityUpdateAction = useActionData<typeof action>() as any;
      if (securityUpdateAction) {
        const errorMessagesFormattedByDf = errorMessagesForSchema(
          securityUpdateAction.inputErrors,
          Schema
        );
      }
    
    

    The securityUpdateAction returns inputErrors with this shape:

    [
        {
            "path": [
                "newPassword"
            ],
            "message": "String must contain at least 8 character(s)"
        },
        {
            "path": [
                "newPasswordRepeat"
            ],
            "message": "String must contain at least 8 character(s)"
        },
        {
            "path": [
                "newPassword",
                "newPasswordRepeat"
            ],
            "message": "Passwords don't match"
        }
    ]
    

    The Schema looks like this:

    const Schema = z
      .object({
        oldPassword: z.string(),
        newPassword: z.string().min(8),
        newPasswordRepeat: z.string().min(8),
      })
      .refine((data) => data.newPassword === data.newPasswordRepeat, {
        message: "Passwords don't match",
        path: ["newPassword", "newPasswordRepeat"], // path of error
      })
    

    The output after parsing looks like this in the browser console:

    Bildschirmfoto 2022-12-12 um 20 01 38

    In the docs it shows the shape what is to be expected: https://github.com/SeasonedSoftware/domain-functions#errormessagesforschema

    errorForSchema(result.inputErrors, schema)
    /*
    {
      email: ['Must not be empty', 'Must be a string'],
      password: ['Must not be empty']
    }
    */
    

    Basically: Record<string,string[]>

    bug 
    opened by phifa 6
  • The path

    The path "domain-functions" is imported in "..." but "domain-functions" was not found in your node_modules. Did you forget to install it?

    Getting this error in Remix project, when building, when starting it gives this: Error: No "exports" main defined in .../node_modules/domain-functions/package.json

    opened by alexbu92 6
  • Allow the user to define DFs that will take no input parameters

    Allow the user to define DFs that will take no input parameters

    Purpose

    It seems very akward to be forced to pass parameters when they are not going to be used. Currently a DF without any parameters look something like this:

    const parser = z.undefined()
    const handler = makeDomainFunction(parser)(async () => 'no input!')
    const result = await handler(undefined)
    //                               ^ looks silly
    

    this PR makes the input parameter optional:

    const parser = z.undefined()
    const handler = makeDomainFunction(parser)(async () => 'no input!')
    const result = await handler()
    //                          ^ not quite so silly anymore
    

    I checked the type inference of the results and compositions and nothing changes after this change is applied.

    opened by diogob 5
  • Implement nested errors in errorMessagesForSchema

    Implement nested errors in errorMessagesForSchema

    Purpose

    When a domain function received nested input data, errorMessagesForSchema should mirror that structure so we do not lose nested error information. This should address #24

    opened by diogob 5
  • [Bug]: failed parse FormData with Cloudflare

    [Bug]: failed parse FormData with Cloudflare

    What version

    0.0.2

    What happend

    input-resolvers.ts#L6-L7 failed parse FormData with Cloudflare Pages functions.

    Steps to Reproduce

    I found that new URLSearchParams().toString() return "" with Cloudflare Pages functions at ergofriend/remix-cloudflare-pages

    debug code

    const formData = await request.clone().formData()
    const data = new URLSearchParams(formData as URLSearchParams).toString()
    const json = JSON.stringify(Object.fromEntries(formData.entries()))
    sentry.captureMessage(`action URLSearchParams: ${JSON.stringify(data)} json: ${json}`)
    

    sentry.io console

    // local (wrangler2)
    action URLSearchParams: "like=takenoko" json: {"like":"takenoko"}
    
    // cloudflare pages functions
    action URLSearchParams: "" json: {"like":"takenoko"}
    
    opened by ergofriend 5
  • Throw multiple input errors at once

    Throw multiple input errors at once

    There are use cases such as this one, where throwing a single exception with multiple input errors is desirable. We should add a custom error constructor for that. Perhaps just have a plural InputErrors that will take a non-empty list as argument. If we are fine breaking backwards compatibility we could also just change the InputError type.

    opened by diogob 4
  • Preserve original exception when domain function is handling an unknown thrown value

    Preserve original exception when domain function is handling an unknown thrown value

    Purpose

    The main use case is to handle exceptions thrown in 3rd party code without having to resort to try/catch.

    Let's say you have a library called ApiClient. This library is used directly in domain functions, but sometimes throw errors containing details information on why a certain api call failed.

    const thisMightFail = makeDomainFunction(z.object({ id: z.number() }))(
      async () => {
        const externalData = ApiClient.call('doSomething')
        ...
      },
    )
    

    Since we don't know what the 3rd party code does, we could capture a failure in the errors field of our ErrorResult. However, in the current version, the exception would be discarded and you would only have access to the message field of the exception.

    The current work-around is to use a try/catch within the domain function:

    const thisMightFail = makeDomainFunction(z.object({ id: z.number() }))(
      async () => {
        try {
          const externalData = ApiClient.call('doSomething')
          ...
        } catch (error) {
          throw new Error("Additional error details: " + String(error.data.someNestedDetail))
        }
        ...
      },
    )
    

    Since we already have a combinator to map errors (mapError), it would be handy to have access to all exception data and just map the error:

    const thisMightFail = mapError(makeDomainFunction(z.object({ id: z.number() }))(
      async () => {
        const externalData = ApiClient.call('doSomething')
        ...
      }
      (result: ErrorData) => ({
          ...result,
          errors: result.errors.map((e) => { message: 'Additional error details: ' + String(e.?.exception?.data?.someNestedDetail) })
        })
    )
    

    The approach enabled by this PR has the advantage of always bubbling up the exception details, so one can always call mapError regardless of how the domain function is composed.

    Caveat

    I used a reference to the exception instead of cloning the object. Since anything can bee thrown, I decided to keep the type unknown. So we keep the original exception opaque and avoid the complexities and risks of cloning something that we know nothing about.

    I consider this a minor inconvenience since mutating the exception in place would be a terrible practice.

    Bonus points

    • This enables the user to make error mappers that take advantage of the name in the Error type and create a custom domain error from 3rd party APIs. This could extremely useful to handle database errors automatically and turn them into domain errors for instance.
    • This would also enable automatic instrumentation to track full exceptions from domain functions applying mapError to already created DFs.
    opened by diogob 3
  • Nested errors for schema - take 2

    Nested errors for schema - take 2

    Purpose

    When a domain function received nested input data, errorMessagesForSchema should mirror that structure so we do not lose nested error information. This should address #24

    Technical details

    This approach seems simpler than the one on #26 since it does not require any info from the schema (other than its type). Using this code we could keep backwards compatibility or break it just to drop the schema parameter (since we can use just the generic type parameter).

    opened by diogob 3
Releases(v1.5.1)
  • v1.5.1(Dec 19, 2022)

    What's Changed

    • 🐛 Fix type bug with trace function by @gustavoguichard in https://github.com/SeasonedSoftware/domain-functions/pull/59
    • Type tests by @gustavoguichard in https://github.com/SeasonedSoftware/domain-functions/pull/58
    • Simplify Unpack types by @gustavoguichard in https://github.com/SeasonedSoftware/domain-functions/pull/60
    • Add legacy types back with deprecation messages 👴🏼 by @gustavoguichard in https://github.com/SeasonedSoftware/domain-functions/pull/61

    Full Changelog: https://github.com/SeasonedSoftware/domain-functions/compare/v1.5.0...v1.5.1

    Source code(tar.gz)
    Source code(zip)
  • v1.5.0(Dec 15, 2022)

    Highlights

    • Allow the user to define DFs that will take no input parameters by @diogob in https://github.com/SeasonedSoftware/domain-functions/pull/52
    • Zod as peer dependency by @diogob in https://github.com/SeasonedSoftware/domain-functions/pull/56
    • Create a trace function that will capture the inputs and outputs of any DF and call an arbitraty function. by @diogob in https://github.com/SeasonedSoftware/domain-functions/pull/57

    Other changes

    • Update README to show original exception after PR #47 by @diogob in https://github.com/SeasonedSoftware/domain-functions/pull/48
    • Correct return type of errorMessageFor example by @yurilaguardia in https://github.com/SeasonedSoftware/domain-functions/pull/50
    • Fix bug in "errorMessagesFor" example by @yurilaguardia in https://github.com/SeasonedSoftware/domain-functions/pull/49
    • doc: correct-function-name by @phifa in https://github.com/SeasonedSoftware/domain-functions/pull/53
    • Update remix example for more modern techniques by @gustavoguichard in https://github.com/SeasonedSoftware/domain-functions/pull/54

    New Contributors

    • @yurilaguardia made their first contribution in https://github.com/SeasonedSoftware/domain-functions/pull/50
    • @phifa made their first contribution in https://github.com/SeasonedSoftware/domain-functions/pull/53

    Full Changelog: https://github.com/SeasonedSoftware/domain-functions/compare/v1.4.0...v1.5.0

    Source code(tar.gz)
    Source code(zip)
  • v1.4.0(Nov 11, 2022)

    What's Changed

    • Replace npm with deno task by @diogob in https://github.com/SeasonedSoftware/domain-functions/pull/46
    • Preserve original exception when domain function is handling an unknown thrown value by @diogob in https://github.com/SeasonedSoftware/domain-functions/pull/47

    Possible breaking change

    If you test the error results with strict equality, the additional data in the ErrorResult will break some tests. A quick fix is to use toMatchObject instead.

    Full Changelog: https://github.com/SeasonedSoftware/domain-functions/compare/v1.3.0...v1.4.0

    Source code(tar.gz)
    Source code(zip)
  • v1.3.0(Oct 19, 2022)

    What's Changed

    • First combinator by @gustavoguichard and @diogob in https://github.com/SeasonedSoftware/domain-functions/pull/43
    • New primitives: fromSuccess and ResultError by @gustavoguichard and @diogob in https://github.com/SeasonedSoftware/domain-functions/pull/44
    • change function type declarations by @diogob and @gustavoguichard in https://github.com/SeasonedSoftware/domain-functions/pull/45

    For more information: check the updated README for ResultError, first, and fromSuccess.

    Full Changelog: https://github.com/SeasonedSoftware/domain-functions/compare/v1.2.0...v1.3.0

    Source code(tar.gz)
    Source code(zip)
  • v1.2.0(Sep 28, 2022)

    What's Changed

    • Creates a 'sequence' function which is like a pipe function but saving results by @gustavoguichard in https://github.com/SeasonedSoftware/domain-functions/pull/33

    Full Changelog: https://github.com/SeasonedSoftware/domain-functions/compare/v1.1.0...v1.2.0

    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Sep 21, 2022)

    New utility for domain function compositions

    Check the merge documentation on README to learn more about it.

    What's Changed

    • Add merge utility to domainFunctions by @gustavoguichard in https://github.com/SeasonedSoftware/domain-functions/pull/39

    Full Changelog: https://github.com/SeasonedSoftware/domain-functions/compare/v1.0.2...v1.1.0

    Source code(tar.gz)
    Source code(zip)
  • v1.0.2(Sep 21, 2022)

    What's Changed

    • Refactor Types by @gustavoguichard in https://github.com/SeasonedSoftware/domain-functions/pull/38
    • Reintroduce CommonJS module by @diogob in https://github.com/SeasonedSoftware/domain-functions/pull/41

    Full Changelog: https://github.com/SeasonedSoftware/domain-functions/compare/v1.0.1...v1.0.2

    Source code(tar.gz)
    Source code(zip)
  • v1.0.1(Sep 21, 2022)

    New name

    We changed the name of this lib from remix-domains to domain-functions and made a few changes to ensure this package can run everywhere 🔥!

    • It can be used with any JS framework! Beyond Remix, make sure you also try it in NextJS, CRA, etc.
    • It can be used with any non-React framework as well: Solidjs, Vuejs, and so on.
    • It can even be used on Deno now 🤯! We already tried it out in Fresh and it feels goooood! 🤤

    To use it with Deno start with:

    import { makeDomainFunction } from "https://deno.land/x/domain_functions/mod.ts";
    

    Send a PR

    If you happen to play with domain-functions in your non-remix framework, we'd be happy to accept a PR to our ./examples folder. 🫱🏾‍🫲🏽

    What's Changed

    • Remove most references to Remix and create a remix folder inside exam… by @gustavoguichard in https://github.com/SeasonedSoftware/domain-functions/pull/35
    • Use GitHub repo as homepage by @danielweinmann in https://github.com/SeasonedSoftware/domain-functions/pull/36
    • Deno version by @diogob and @gustavoguichard in https://github.com/SeasonedSoftware/domain-functions/pull/37

    Full Changelog: https://github.com/SeasonedSoftware/domain-functions/compare/v0.3.2...v1.0.1

    Source code(tar.gz)
    Source code(zip)
  • v0.3.2(Jul 4, 2022)

    What's Changed

    • Make errorMessagesForSchema input type coherent with makeDomainFunction by @diogob in https://github.com/SeasonedSoftware/remix-domains/pull/32

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/compare/v0.3.1...v0.3.2

    Source code(tar.gz)
    Source code(zip)
  • v0.3.1(Jun 30, 2022)

    What's Changed

    • fix typo where "object" was written as "objecto" by @kory-smith in https://github.com/SeasonedSoftware/remix-domains/pull/27
    • Fix typo by @stivaugoin in https://github.com/SeasonedSoftware/remix-domains/pull/28
    • Update Zod to 3.17.3 by @diogob in https://github.com/SeasonedSoftware/remix-domains/pull/29
    • Nested errors for schema by @diogob in https://github.com/SeasonedSoftware/remix-domains/pull/31

    New Contributors

    • @kory-smith made their first contribution in https://github.com/SeasonedSoftware/remix-domains/pull/27
    • @stivaugoin made their first contribution in https://github.com/SeasonedSoftware/remix-domains/pull/28

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/compare/v0.3.0...v0.3.1

    Source code(tar.gz)
    Source code(zip)
  • v0.3.0(Jun 30, 2022)

    What's Changed

    • Add InputErrors exception so we can return multiple input errors at once by @diogob in https://github.com/SeasonedSoftware/remix-domains/pull/23

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/compare/v0.2.0...v0.3.0

    Source code(tar.gz)
    Source code(zip)
  • v0.2.0(May 2, 2022)

    What's Changed

    • Use safeParseAsync to allow for custom async validation by @danielweinmann in https://github.com/SeasonedSoftware/remix-domains/pull/20

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/compare/v0.1.0...v0.2.0

    Source code(tar.gz)
    Source code(zip)
  • v0.1.0(Apr 25, 2022)

    What's Changed

    • Allow a domain function to throw custom error data by @danielweinmann in https://github.com/SeasonedSoftware/remix-domains/pull/19

    New Contributors

    • @danielweinmann made their first contribution in https://github.com/SeasonedSoftware/remix-domains/pull/19

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/compare/v0.0.2...v0.1.0

    Source code(tar.gz)
    Source code(zip)
  • v0.0.2(Apr 14, 2022)

    What's Changed

    • Error messages for deep property by @gustavoguichard in https://github.com/SeasonedSoftware/remix-domains/pull/16

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/compare/v0.0.1-rc.11...v0.0.2

    Source code(tar.gz)
    Source code(zip)
  • v0.0.1-rc.11(Apr 14, 2022)

    What's Changed

    • Create some examples in a remix app by @gustavoguichard in https://github.com/SeasonedSoftware/remix-domains/pull/12
    • Add mapError combinator by @diogob in https://github.com/SeasonedSoftware/remix-domains/pull/13
    • New error format by @gustavoguichard in https://github.com/SeasonedSoftware/remix-domains/pull/14
    • New "inputFrom..." utilities by @gustavoguichard in https://github.com/SeasonedSoftware/remix-domains/pull/15

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/compare/v0.0.1-rc.10...v0.0.1-rc.11

    Source code(tar.gz)
    Source code(zip)
  • v0.0.1-rc.10(Apr 8, 2022)

    What's Changed

    • Map combinator by @diogob in https://github.com/SeasonedSoftware/remix-domains/pull/10
    • Accept JS literals as inputs or environment by @gustavoguichard in https://github.com/SeasonedSoftware/remix-domains/pull/11

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/compare/v0.0.1-rc.9...v0.0.1-rc.10

    Source code(tar.gz)
    Source code(zip)
  • v0.0.1-rc.9(Apr 6, 2022)

    What's Changed

    • Domain function combinator all by @gustavoguichard in https://github.com/SeasonedSoftware/remix-domains/pull/8
    • Domain function combinator pipe by @diogob in https://github.com/SeasonedSoftware/remix-domains/pull/9

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/compare/v0.0.1-rc.7...v0.0.1-rc.9

    Source code(tar.gz)
    Source code(zip)
  • v0.0.1-rc.7(Mar 25, 2022)

    What's Changed

    • Give credit where credit is due by @diogob in https://github.com/SeasonedSoftware/remix-domains/pull/6
    • Change some result types by @gustavoguichard in https://github.com/SeasonedSoftware/remix-domains/pull/7

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/compare/v0.0.1-rc.6...v0.0.1-rc.7

    Source code(tar.gz)
    Source code(zip)
  • v0.0.1-rc.6(Mar 19, 2022)

  • v0.0.1-rc.4(Mar 19, 2022)

  • v0.0.1-rc.2(Mar 19, 2022)

    What's Changed

    • Improve README by @gustavoguichard in https://github.com/SeasonedSoftware/remix-domains/pull/4
    • Fix ErrorResult type by @gustavoguichard in https://github.com/SeasonedSoftware/remix-domains/pull/5

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/compare/v0.0.1-rc.0...v0.0.1-rc.2

    Source code(tar.gz)
    Source code(zip)
  • v0.0.1-rc.0(Mar 19, 2022)

    New Contributors

    • @diogob made their first contribution in https://github.com/SeasonedSoftware/remix-domains/pull/1

    Full Changelog: https://github.com/SeasonedSoftware/remix-domains/commits/v0.0.1-rc.0

    Source code(tar.gz)
    Source code(zip)
Owner
Seasoned
Enabling non-technical founders
Seasoned
Automatically document all of your Remix loaders and actions typings per each route. 📚

About remix-docs-gen parses all of your Remix loaders and actions and automatically documents all the typings per each route. Installation First, you

Stratulat Alexandru 50 Nov 9, 2022
Zod utilities for Remix loaders and actions.

Zodix Zodix is a collection of Zod utilities for Remix loaders and actions. It abstracts the complexity of parsing and validating FormData and URLSear

Riley Tomasek 172 Dec 22, 2022
Deploy an Architect project from GitHub Actions with keys gathered from aws-actions/configure-aws-credentials

Deploy an Architect project from GitHub Actions with keys gathered from a specific AWS IAM Role federated by an IAM OIDCProvider. CloudFormation to cr

Taylor Beseda 4 Apr 6, 2022
Zenload - "Load couple loaders and apply transform one-by-one

Zenload Load couple loaders and apply transforms one-by-one. Install npm i zenload -g How to use? With env vairable ZENLOAD: NODE_OPTIONS='"--loader

coderaiser 1 Jan 25, 2022
Node.js ESM loader for chaining multiple custom loaders.

ESMultiloader Node.js ESM loader for chaining multiple custom loaders. Fast and lightweight No configuration required, but configurable if needed Usag

jhmaster2000 2 Sep 12, 2022
Easy conditional if-else logic for your Cypress testsDo not use

cypress-if Easy conditional if-else logic for your Cypress tests Tested with cy.get, cy.contains, cy.find, .then, .within commands in Cypress v9 and v

Gleb Bahmutov 36 Dec 14, 2022
Build redux logic, without getting nervous 😬

Redux Cool Build redux logic, without getting nervous ?? Description Redux Cool is an awesome tiny package that allows you to easily and intuitively w

Redux Cool 24 Nov 3, 2022
An inheritable and strong logic template front-end mvvm framework.

Intact 文档 Documents 简介 Intact作为一个可继承,并且拥有强逻辑模板的前端MVVM框架,有着如下特色: 充分利用组合与继承的思想,来最高限度地复用代码 同时支持数据驱动和组件实例化调用,来最便捷地实现功能 强逻辑模板,赋予模板更多功能和职责,来完成业务逻辑和表现逻辑分离 安装

Javey 55 Oct 21, 2022
Kasada's p.js partially deobfuscated, still has VM logic

Kasada's p.js Deobfuscated The script was obfuscated by replacing most strings with a function to grab the string from an array and decode it. Ex: _0x

null 11 Nov 9, 2022
Play logic games and claim exclusive NFTs!

Bit Gaming Samruk Hackathon Winner ?? Play-to-earn DAO with exclusive NFT collection Idea We are bringing together curious minds and reward them with

Temirzhan Yussupov 3 Jun 21, 2022
Custom navigations for Solid written in Typescript. Implement custom page transition logic and ✨ animations ✨

solid-custom-navigation Get, Set, Go! Custom navigations for Solid, written in Typescript. Implement custom page transition logic and ✨ animations ✨ .

Dirag Biswas 8 Nov 27, 2022
Starter for Next.js projects with a basic page transition logic.

This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn dev Open http://

Antinomy Studio 23 Oct 27, 2022
Logic programming with JSON.

Cicada Whereabouts Logic programming with JSON. I asked the boy beneath the pines. He said, "The master’s gone alone Herb-picking, somewhere on the mo

Cicada Language 73 Dec 22, 2022
Google-reviews-crawler - A simple Playwright crawler that stores Google Maps Place/Business reviews to a JSON file.

google-reviews-crawler A simple Playwright crawler that stores Google Maps Place/Business reviews to a JSON file. Usage Clone the repo, install the de

￸A￸l￸e￸x D￸o￸m￸a￸k￸i￸d￸i￸s 6 Oct 26, 2022
NewsStation is a news app which can be used to grab daily news bites. If you are interested in news whether politics, business, entertainment, general, health, science, sports and technology news NewsStation is for you!

This is a NewsStation WebApp Project Using News API NewsStation is a news app which can be used to grab daily news bites. If you are interested in new

Ravi Chauhan 2 Feb 7, 2022
NHS Business Intelligence Platform

Cloud deployment of a Business Intelligence Application Suite, including modules for Population Health Management

Stewart Morgan 4 Aug 8, 2022
A calculation and tracker tool for one-person business operations

?? Taxemu This is the alpha version of Taxemu. A tracker tool for one-person business operations. The live project can be found here. Development Clon

John Raptis 7 Nov 30, 2022
An E-commerce website that allows to Buy/Sell products, designed to strengthen small vendors to enhance their business

Developed using MERN Stack, an E-commerce website that allows to Buy/Sell products, designed to strengthen small vendors to enhance their business, fu

Inderjit Shahi 5 Jun 25, 2022
Application for self-testing before exams covering some of the subject taught at the Prague University of Economics and Business

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: ya

Jiří Vrba 2 Jun 13, 2022