UNPKG

@storecraft/core

Version:
230 lines (195 loc) 5.52 kB
/** * @import { BaseType} from './types.api.js' * @import {RegularGetOptions, db_crud } from '../database/types.public.js' * @import {PubSubEvent} from '../pubsub/types.public.js' * @import {ApiQuery, ExpandQuery} from './types.api.query.js' */ import { ID, apply_dates, assert } from './utils.func.js' import { assert_zod } from './middle.zod-validate.js' import { create_search_index } from './utils.index.js' import { z, ZodSchema, } from 'zod' import { rewrite_media_from_storage, rewrite_media_to_storage } from './con.storage.logic.js' import { App } from '../index.js'; /** * @description This type of upsert might be uniform and re-occurring, so it is * refactored. There is a hook to add more functionality. The purpose is: * - Convert an API upsert schema of zod into Database Upsert type * * @template {Partial<BaseType>} DB_GET_TYPE Database `get` type * @template {Partial<BaseType>} DB_UPSERT_TYPE Database `upsert` type * @template {ZodSchema} [API_UPSERT_ZOD_SCHEMA=ZodSchema] zod schema is the api upsert type * * * @param {App} app app instance * @param {db_crud<DB_UPSERT_TYPE, DB_GET_TYPE>} db db instance * @param {string} id_prefix * @param {API_UPSERT_ZOD_SCHEMA} schema * @param {<H extends z.infer<API_UPSERT_ZOD_SCHEMA>>(final: H) => H} pre_hook Hook before validation, this is * your chance to fill gaps in data * @param {<H extends DB_UPSERT_TYPE>(final: DB_UPSERT_TYPE) => string[]} post_hook * hook into final state, returns extra search terms * @param {PubSubEvent} [event] keep * `undefined` to avoid event processing */ export const regular_upsert = ( app, db, id_prefix, schema, pre_hook=x=>x, post_hook=x=>[], event ) => { /** * @param {z.infer<API_UPSERT_ZOD_SCHEMA>} item * @returns {Promise<string>} id */ return async (item) => { const requires_event_processing = Boolean(event) && app.pubsub.has(event); /** @type {DB_GET_TYPE} */ let previous_item; // active by default if(item && (typeof item==='object') && !('active' in item)) { item.active = true; } item = pre_hook(item); schema && assert_zod( schema.transform(x => x ?? undefined), item ); // fetch previous item from the database if(requires_event_processing) { if(item?.id) { previous_item = await db.get( item.id, { expand: /** @type {ExpandQuery<DB_GET_TYPE>} */ (['*']) } ); } } // Check if exists const id = !Boolean(item.id) ? ID(id_prefix) : String(item.id); const final = apply_dates({ ...item, id }); const search = [ ...create_search_index(final), ...post_hook(final) ]; rewrite_media_to_storage(app)(item); // dispatch event if(requires_event_processing) { await app.pubsub.dispatch( String(event), { previous: previous_item, current: final, } ); } const success = await db.upsert(final, search); assert(success, 'upsert-failed', 400); return final.id; } } /** * @description a regular document fetch * @template {Partial<BaseType>} G * @template {Partial<BaseType>} U * @param {App} app * @param {db_crud<U, G>} db db instance * @param {PubSubEvent} [event] keep `undefined` * to avoid event processing */ export const regular_get = (app, db, event) => /** * * @param {string} handle_or_id * @param {RegularGetOptions} [options] */ async (handle_or_id, options={ expand: ['*'] }) => { const item = await db.get(handle_or_id, options); rewrite_media_from_storage(app)(item); if(Boolean(event)) { await app.pubsub.dispatch( String(event), { current: item, } ); } return item; }; /** * @description a regular document removal * * @template {Partial<BaseType>} G * @template {Partial<BaseType>} U * * @param {App} app * @param {db_crud<U, G>} db db instance * @param {PubSubEvent} [event] keep * `undefined` to avoid event processing */ export const regular_remove = (app, db, event) => /** * @param {string} id_or_handle */ async (id_or_handle) => { const requires_event_processing = Boolean(event) && app.pubsub.has(event); /** @type {G} */ let previous; // fetch item before removal if(requires_event_processing) { previous = await db.get(id_or_handle); } const success = await db.remove(id_or_handle); if(success && requires_event_processing) { await app.pubsub.dispatch( String(event), { previous, } ) } return success; } /** * @description a regular document list with query operation * * @template G * @template U * * @param {App} app * @param {db_crud<U, G>} db db instance * @param {PubSubEvent} [event] keep * `undefined` to avoid event processing */ export const regular_list = (app, db, event) => /** * @param {ApiQuery<G>} q */ async (q={}) => { // console.log('query', q); if(q.equals) { q.startAt = q.endAt = q.equals; } const items = await db.list( { ...q, // @ts-ignore expand: q.expand ?? ['*'] } ); rewrite_media_from_storage(app)(items); if(Boolean(event)) { await app.pubsub.dispatch( String(event), { current: items, } ); } return items; }