Add a utils/standard library package to Joystick
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)
See also #49
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.
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.
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.