joystick icon indicating copy to clipboard operation
joystick copied to clipboard

Add a utils/standard library package to Joystick

Open rglover opened this issue 2 years ago • 4 comments

Just came to mind looking at some functions I've copy/pasted across several projects. Would be great to just organize these into something like @joystick.js/utils. Easy enough to do. Just want to avoid recreating something like Lodash.

Keeping a list...

  • [ ] Types (could rely on Node util/types but want it to be isomorphic and thorough)
  • [ ] Delay/Debounce
  • [ ] Set Query Param
  • [ ] Delete Query Param
  • [ ] Date Format Helpers (maybe—could end up being @joystick.js/dates)
  • [ ] Wait (Promised)
  • [ ] Copy to Clipboard
  • [ ] Check if Valid JSON
  • [ ] Generate ID (technically in Joystick proper)
  • [ ] URL encode JSON body for POST
  • [ ] Input Validation
  • [ ] CLILog
  • [ ] loadSettings
  • [ ] serializeQueryParameters
  • [ ] trackFunctionCall (from joystick.js/test)

rglover avatar Apr 06 '23 16:04 rglover

See also #49

rglover avatar Aug 08 '23 18:08 rglover

For types, I want to have a type checker function that can be called anywhere to validate the type/shape of things. So, if I pass an object, I can validate the types like this:

const user = { firstName: 'Ryan' };

utils.types.validate(user, {
  firstName: {
    type: 'string',
    required: true,
  },
}):

I should be able to adapt the existing input validation from the server and just make it globally available via the utils package.

Note: The value passed as the second argument could be an object, or, a type string like this: utils.types.validate(user, 'object') for generic type checking.

rglover avatar Aug 24 '23 16:08 rglover

For dates, I'd like to have some basic Date-object based helpers like:

  • [ ] Add (unit) to timestamp (e.g., add seconds, hours, etc)
  • [ ] Exchange one ISO timestamp for another w/ a different offset
  • [ ] Get back a date object from an ISO string

Right now I use dayjs for this stuff but it's unnecessary. It'd be best to standardize it and have control over all dates running through the framework so there's zero confusion.

rglover avatar Nov 05 '23 19:11 rglover

For SQL:

const generate_sql_from_object = {
  insert: (options = {}) => {
    const column_names = Object.keys(options?.data)?.join(',');
    const value_placeholders = Object.keys(options?.data)?.map((_, index) => `$${index + 1}`)?.join(',');
    
    return {
      statement: `INSERT INTO ${options?.table} (${column_names}) VALUES (${value_placeholders})`,
      column_names,
      value_placeholders,
      values: Object.values(options?.data),
    };
  },
  update: (options = {}) => {
    const whereEntries = Object.entries(options?.where);
    const sets = Object.keys(options?.data).map((key, index) => {
      return `${key} = $${whereEntries.length + index + 1}`;
    })?.join(',');
    const where = whereEntries?.map(([key], index) => {
        return `${key} = $${index + 1}`;
    })?.join(',');
  
    return {
      statement: `UPDATE ${options?.table} SET ${sets} WHERE ${where}`,
      sets,
      where,
      values: [
        ...(Object.values(options?.where)),
        ...(Object.values(options?.data))
      ],
    };
  },
};

Usage:

const insert = generate_sql_from_object.insert({
  table: 'users',
  data: {
    user_id: 'abc123',
    pancakes: 'good',
    sausage: 'decent',
    pizza: 'oh baby',
  },
});

await process.databases.postgresql.query(insert.statement, insert.values);

const update = generate_sql_from_object.update({
  table: 'users',
  data: {
    pancakes: 'good',
    sausage: 'decent',
    pizza: 'oh baby',
  },
  where: { user_id: 'abc123', role: 'eater' }
});

await process.databases.postgresql.query(update.statement, update.values);

Consider offering the above as a single wrapper on the postgresql object like process.databases.postgresql.insert() or process.databases.postgresql.update() which takes in a table, data, and optional where object and wraps the example code above.

rglover avatar Nov 07 '23 16:11 rglover