UNPKG

@statezero/core

Version:

The type-safe frontend client for StateZero - connect directly to your backend models with zero boilerplate

232 lines (231 loc) 9.94 kB
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _Operation__instances, _Operation__frozenInstances; import { MAX, v7 as uuidv7 } from "uuid"; import { isNil } from 'lodash-es'; import mitt from 'mitt'; import { tempPkMap } from "../../flavours/django/tempPk"; import { modelStoreRegistry } from "../registries/modelStoreRegistry.js"; export const operationEvents = mitt(); export const Status = { CREATED: 'operation:created', UPDATED: 'operation:updated', CONFIRMED: 'operation:confirmed', REJECTED: 'operation:rejected', CLEAR: 'clear:all', MUTATED: 'operation:mutated' }; export const Type = { CREATE: 'create', BULK_CREATE: 'bulk_create', UPDATE: 'update', DELETE: 'delete', UPDATE_INSTANCE: 'update_instance', DELETE_INSTANCE: 'delete_instance', GET_OR_CREATE: 'get_or_create', UPDATE_OR_CREATE: 'update_or_create', CHECKPOINT: 'checkpoint', // Aggregation operations COUNT: 'count', MIN: 'min', MAX: 'max', AVG: 'avg', SUM: 'sum', AGGREGATE: 'aggregate', }; export class Operation { constructor(data, restore = false) { _Operation__instances.set(this, void 0); _Operation__frozenInstances.set(this, void 0); if (!data || typeof data !== 'object') { throw new Error("Operation constructor requires a data object."); } if (!data.type) { throw new Error("Operation data must include a 'type'."); } if (!data.instances) { throw new Error("Operation data must include 'instances'."); } this.operationId = data.operationId || `op_${uuidv7()}`; this.type = data.type; this.status = data.status || Status.CREATED; this.queryset = data.queryset; this.args = data.args; this.doNotPropagate = data.doNotPropagate || false; this.localOnly = data.localOnly || false; let ModelClass = this.queryset.ModelClass; let instances = data.instances; // guarantee instances is an array if (!isNil(instances)) { instances = Array.isArray(data.instances) ? data.instances : [data.instances]; } // coerce to object format if its not already let pkField = ModelClass.primaryKeyField; if (instances.some(instance => isNil(instance) || typeof instance !== 'object' || !(pkField in instance))) { throw new Error(`All operation instances must be objects with the '${pkField}' field`); } __classPrivateFieldSet(this, _Operation__instances, instances, "f"); // Batch render to warm the cache, then serialize each instance to get plain objects const pks = instances.map(i => i[pkField]); const store = modelStoreRegistry.getStore(ModelClass); store.render(pks, true, false); __classPrivateFieldSet(this, _Operation__frozenInstances, instances.map(i => ModelClass.fromPk(i[pkField]).serialize()), "f"); this.timestamp = data.timestamp || Date.now(); if (restore) return; operationRegistry.register(this); // Emit operation created event with the entire operation operationEvents.emit(Status.CREATED, this); } /** * Getter for instances that replaces any temporary PKs with real PKs */ get instances() { if (tempPkMap.size === 0) return __classPrivateFieldGet(this, _Operation__instances, "f"); const ModelClass = this.queryset.ModelClass; const pkField = ModelClass.primaryKeyField; return __classPrivateFieldGet(this, _Operation__instances, "f").map(instance => { const pk = instance[pkField]; if (typeof pk === 'string' && tempPkMap.has(pk.replace(/[{}]/g, ''))) { // Return a new instance with the real PK return { ...instance, [pkField]: tempPkMap.get(pk.replace(/[{}]/g, '')) }; } return instance; }); } /** * Setter for instances */ set instances(value) { __classPrivateFieldSet(this, _Operation__instances, Array.isArray(value) ? value : [value], "f"); } /** * Getter for frozenInstances that replaces any temporary PKs with real PKs */ get frozenInstances() { if (tempPkMap.size === 0) return __classPrivateFieldGet(this, _Operation__frozenInstances, "f"); const ModelClass = this.queryset.ModelClass; const pkField = ModelClass.primaryKeyField; return __classPrivateFieldGet(this, _Operation__frozenInstances, "f").map(instance => { const pk = instance[pkField]; if (typeof pk === 'string' && tempPkMap.has(pk.replace(/[{}]/g, ''))) { // Return a new instance with the real PK return { ...instance, [pkField]: tempPkMap.get(pk.replace(/[{}]/g, '')) }; } return instance; }); } /** * Setter for frozenInstances */ set frozenInstances(value) { __classPrivateFieldSet(this, _Operation__frozenInstances, Array.isArray(value) ? value : [value], "f"); } /** * Get primary keys of all instances in this operation * Returns primary keys as simple values (not objects) */ get instancePks() { const pkField = this.queryset.ModelClass.primaryKeyField; return this.instances.map(instance => instance[pkField]); } /** * Update this operation's status and emit an event * @param {string} status - New status ('confirmed', 'rejected', etc.) * @param {Array|Object|null} [instances=null] - New instances for the operation */ updateStatus(status, instances = null) { this.status = status; this.timestamp = Date.now(); if (instances !== null) { this.instances = Array.isArray(instances) ? instances : [instances]; } operationEvents.emit(status, this); } /** * Updates this operation with new data and emits the appropriate event * @param {Object} newData - New data to update the operation with * @returns {Operation} - Returns this operation instance for chaining */ mutate(newData) { // Ensure instances is always an array if (newData.instances && !Array.isArray(newData.instances)) { newData.instances = [newData.instances]; } // Use Object.assign to update all properties at once Object.assign(this, newData); // Update timestamp this.timestamp = Date.now(); // Emit the mutated event with the updated operation operationEvents.emit(Status.MUTATED, this); return this; } } _Operation__instances = new WeakMap(), _Operation__frozenInstances = new WeakMap(); class OperationRegistry { constructor() { this._operations = new Map(); } /** * Registers a pre-constructed Operation instance in the registry. * Ensures the operationId is unique within the registry. * Throws an Error if the operationId already exists. * * @param {Operation} operation - The fully instantiated Operation object to register. * @throws {Error} If the input is not a valid operation object or if an operation with the same operationId already exists. */ register(operation) { if (!(operation instanceof Operation)) { throw new Error("OperationRegistry.register requires an Operation object."); } // Ensure the ID is unique before registering if (this._operations.has(operation.operationId)) { // Throw an error if the ID is already used. console.warn(`OperationId ${operation.operationId} is already used, overriding existing operation!`); } // Register the provided operation object this._operations.set(operation.operationId, operation); } /** * Retrieves an operation by its ID. * @param {string} operationId - The ID of the operation. * @returns {Operation | undefined} The operation instance or undefined if not found. */ get(operationId) { return this._operations.get(operationId); } /** * Checks if an operation with the given ID exists in the registry. * @param {string} operationId - The ID of the operation to check. * @returns {boolean} True if the operation exists, false otherwise. */ has(operationId) { return this._operations.has(operationId); } /** * Clears all operations from the registry. */ clear() { console.log("OperationRegistry: Clearing all operations."); this._operations.clear(); operationEvents.emit(Status.CLEAR); } } export const operationRegistry = new OperationRegistry();