diff --git a/frappe/common/index.js b/frappe/common/index.js index a32701ec..ac11c0ce 100644 --- a/frappe/common/index.js +++ b/frappe/common/index.js @@ -1,15 +1,13 @@ -const utils = require('../utils'); -const format = require('../utils/format'); -const errors = require('./errors'); -const BaseDocument = require('frappe/model/document'); -const BaseMeta = require('frappe/model/meta'); +export default async function initLibs(frappe) { + const utils = await import('../utils'); + const format = await import('../utils/format'); + const errors = await import('./errors'); + const BaseMeta = await import('frappe/model/meta'); + const BaseDocument = await import('frappe/model/document'); -module.exports = { - initLibs(frappe) { - Object.assign(frappe, utils); - Object.assign(frappe, format); - frappe.errors = errors; - frappe.BaseDocument = BaseDocument; - frappe.BaseMeta = BaseMeta; - }, -}; + Object.assign(frappe, utils.default); + Object.assign(frappe, format.default); + frappe.errors = errors.default; + frappe.BaseDocument = BaseDocument.default; + frappe.BaseMeta = BaseMeta.default; +} diff --git a/frappe/index.js b/frappe/index.js index 395eca73..454445f2 100644 --- a/frappe/index.js +++ b/frappe/index.js @@ -1,25 +1,25 @@ -const Observable = require('./utils/observable'); -const { T, t } = require('./utils/translation'); -const utils = require('./utils'); -const { getMoneyMaker } = require('pesa'); -const { - DEFAULT_INTERNAL_PRECISION, +import initLibs from 'frappe/common'; +import { getMoneyMaker } from 'pesa'; +import { markRaw } from 'vue'; +import utils from './utils'; +import { DEFAULT_DISPLAY_PRECISION, -} = require('./utils/consts'); -const { markRaw } = require('vue'); + DEFAULT_INTERNAL_PRECISION, +} from './utils/consts'; +import Observable from './utils/observable'; +import { t, T } from './utils/translation'; -module.exports = { - isElectron: false, - isServer: false, +class Frappe { + isElectron = false; + isServer = false; - initializeAndRegister(customModels = {}, force = false) { + async initializeAndRegister(customModels = {}, force = false) { this.init(force); - const common = require('frappe/common'); - this.registerLibs(common); - const coreModels = require('frappe/models'); - this.registerModels(coreModels); + await initLibs(this); + const coreModels = await import('frappe/models'); + this.registerModels(coreModels.default); this.registerModels(customModels); - }, + } async initializeMoneyMaker(currency) { currency ??= 'XXX'; @@ -65,26 +65,19 @@ module.exports = { display, wrapper: markRaw, }); - }, + } init(force) { if (this._initialized && !force) return; - this.initConfig(); - this.initGlobals(); - this.docs = new Observable(); - this.events = new Observable(); - this._initialized = true; - }, - initConfig() { + // Initialize Config this.config = { serverURL: '', backend: 'sqlite', port: 8000, }; - }, - initGlobals() { + // Initialize Globals this.metaCache = {}; this.models = {}; this.forms = {}; @@ -92,15 +85,15 @@ module.exports = { this.flags = {}; this.methods = {}; this.errorLog = []; + // temp params while calling routes this.temp = {}; this.params = {}; - }, - registerLibs(common) { - // add standard libs and utils to frappe - common.initLibs(this); - }, + this.docs = new Observable(); + this.events = new Observable(); + this._initialized = true; + } registerModels(models) { // register models from app/models/index.js @@ -126,7 +119,7 @@ module.exports = { this.models[doctype] = metaDefinition; } - }, + } getModels(filterFunction) { let models = []; @@ -134,12 +127,12 @@ module.exports = { models.push(this.models[doctype]); } return filterFunction ? models.filter(filterFunction) : models; - }, + } registerView(view, name, module) { if (!this.views[view]) this.views[view] = {}; this.views[view][name] = module; - }, + } registerMethod({ method, handler }) { this.methods[method] = handler; @@ -156,7 +149,7 @@ module.exports = { }) ); } - }, + } async call({ method, args }) { if (this.isServer) { @@ -177,7 +170,7 @@ module.exports = { body: JSON.stringify(args || {}), }); return await response.json(); - }, + } addToCache(doc) { if (!this.docs) return; @@ -199,7 +192,7 @@ module.exports = { this.docs.trigger('change', params); }); } - }, + } removeFromCache(doctype, name) { try { @@ -207,7 +200,7 @@ module.exports = { } catch (e) { console.warn(`Document ${doctype} ${name} does not exist`); } - }, + } isDirty(doctype, name) { return ( @@ -217,13 +210,13 @@ module.exports = { this.docs[doctype][name]._dirty) || false ); - }, + } getDocFromCache(doctype, name) { if (this.docs && this.docs[doctype] && this.docs[doctype][name]) { return this.docs[doctype][name]; } - }, + } getMeta(doctype) { if (!this.metaCache[doctype]) { @@ -231,12 +224,13 @@ module.exports = { if (!model) { throw new Error(`${doctype} is not a registered doctype`); } + let metaClass = model.metaClass || this.BaseMeta; this.metaCache[doctype] = new metaClass(model); } return this.metaCache[doctype]; - }, + } async getDoc(doctype, name, options = { skipDocumentCache: false }) { let doc = options.skipDocumentCache @@ -251,16 +245,16 @@ module.exports = { this.addToCache(doc); } return doc; - }, + } getDocumentClass(doctype) { const meta = this.getMeta(doctype); return meta.documentClass || this.BaseDocument; - }, + } async getSingle(doctype) { return await this.getDoc(doctype, doctype); - }, + } async getDuplicate(doc) { const newDoc = await this.getNewDoc(doc.doctype); @@ -277,7 +271,7 @@ module.exports = { } } return newDoc; - }, + } getNewDoc(doctype, cacheDoc = true) { let doc = this.newDoc({ doctype: doctype }); @@ -287,7 +281,7 @@ module.exports = { this.addToCache(doc); } return doc; - }, + } async newCustomDoc(fields) { let doc = new this.BaseDocument({ isCustom: 1, fields }); @@ -295,22 +289,22 @@ module.exports = { doc.name = this.getRandomString(); this.addToCache(doc); return doc; - }, + } createMeta(fields) { let meta = new this.BaseMeta({ isCustom: 1, fields }); return meta; - }, + } newDoc(data) { let doc = new (this.getDocumentClass(data.doctype))(data); doc.setDefaults(); return doc; - }, + } async insert(data) { return await this.newDoc(data).insert(); - }, + } async syncDoc(data) { let doc; @@ -322,7 +316,7 @@ module.exports = { doc = this.newDoc(data); await doc.insert(); } - }, + } // only for client side async login(email, password) { @@ -354,7 +348,7 @@ module.exports = { } return response; - }, + } async signup(email, fullName, password) { let response = await fetch(this.getServerURL() + '/api/signup', { @@ -371,11 +365,11 @@ module.exports = { } return response; - }, + } getServerURL() { return this.config.serverURL || ''; - }, + } close() { this.db.close(); @@ -383,11 +377,15 @@ module.exports = { if (this.server) { this.server.close(); } - }, - t, - T, - store: { + } + + store = { isDevelopment: false, appVersion: '', - }, -}; + }; + t = t; + T = T; +} + +export { T, t }; +export default new Frappe(); diff --git a/frappe/model/document.js b/frappe/model/document.js index e635f18c..93a262a0 100644 --- a/frappe/model/document.js +++ b/frappe/model/document.js @@ -1,12 +1,12 @@ -const frappe = require('frappe'); -const Observable = require('frappe/utils/observable'); -const naming = require('./naming'); -const { isPesa } = require('../utils/index'); -const { DEFAULT_INTERNAL_PRECISION } = require('../utils/consts'); -const { Verb } = require('@/telemetry/types'); -const { default: telemetry } = require('@/telemetry/telemetry'); +import telemetry from '@/telemetry/telemetry'; +import { Verb } from '@/telemetry/types'; +import frappe from 'frappe'; +import Observable from 'frappe/utils/observable'; +import { DEFAULT_INTERNAL_PRECISION } from '../utils/consts'; +import { isPesa } from '../utils/index'; +import { setName } from './naming'; -module.exports = class BaseDocument extends Observable { +export default class Document extends Observable { constructor(data) { super(); this.fetchValuesCache = {}; @@ -171,7 +171,7 @@ module.exports = class BaseDocument extends Observable { } _initChild(data, key) { - if (data instanceof BaseDocument) { + if (data instanceof Document) { return data; } @@ -189,7 +189,7 @@ module.exports = class BaseDocument extends Observable { data.name = frappe.getRandomString(); } - const childDoc = new BaseDocument(data); + const childDoc = new Document(data); childDoc.setDefaults(); return childDoc; } @@ -549,10 +549,6 @@ module.exports = class BaseDocument extends Observable { } } - async setName() { - await naming.setName(this); - } - async commit() { // re-run triggers this.setKeywords(); @@ -562,7 +558,7 @@ module.exports = class BaseDocument extends Observable { } async insert() { - await this.setName(); + await setName(this); this.setStandardValues(); await this.commit(); await this.validateInsert(); @@ -745,7 +741,7 @@ module.exports = class BaseDocument extends Observable { await doc.set(updateMap); await doc.insert(); } -}; +} function getPreDefaultValues(fieldtype) { switch (fieldtype) { diff --git a/frappe/model/meta.js b/frappe/model/meta.js index 6ec0cbcc..5b3dc5c4 100644 --- a/frappe/model/meta.js +++ b/frappe/model/meta.js @@ -1,16 +1,16 @@ -const BaseDocument = require('./document'); -const frappe = require('frappe'); -const model = require('./index'); -const { indicators: indicatorColor } = require('../../src/colors'); +import frappe from 'frappe'; +import { indicators as indicatorColor } from '../../src/colors'; +import Document from './document'; +import model from './index'; -module.exports = class BaseMeta extends BaseDocument { +export default class BaseMeta extends Document { constructor(data) { if (data.basedOn) { let config = frappe.models[data.basedOn]; Object.assign(data, config, { name: data.name, label: data.label, - filters: data.filters + filters: data.filters, }); } super(data); @@ -30,7 +30,7 @@ module.exports = class BaseMeta extends BaseDocument { processFields() { // add name field - if (!this.fields.find(df => df.fieldname === 'name') && !this.isSingle) { + if (!this.fields.find((df) => df.fieldname === 'name') && !this.isSingle) { this.fields = [ { label: frappe.t`ID`, @@ -42,7 +42,7 @@ module.exports = class BaseMeta extends BaseDocument { ].concat(this.fields); } - this.fields = this.fields.map(df => { + this.fields = this.fields.map((df) => { // name field is always required if (df.fieldname === 'name') { df.required = 1; @@ -75,7 +75,7 @@ module.exports = class BaseMeta extends BaseDocument { * dataFields = meta.getFieldsWith({ fieldtype: 'Data' }) */ getFieldsWith(filters) { - return this.fields.filter(df => { + return this.fields.filter((df) => { let match = true; for (const key in filters) { const value = filters[key]; @@ -93,7 +93,7 @@ module.exports = class BaseMeta extends BaseDocument { getTableFields() { if (this._tableFields === undefined) { this._tableFields = this.fields.filter( - field => field.fieldtype === 'Table' + (field) => field.fieldtype === 'Table' ); } return this._tableFields; @@ -101,7 +101,7 @@ module.exports = class BaseMeta extends BaseDocument { getFormulaFields() { if (this._formulaFields === undefined) { - this._formulaFields = this.fields.filter(field => field.formula); + this._formulaFields = this.fields.filter((field) => field.formula); } return this._formulaFields; } @@ -141,7 +141,7 @@ module.exports = class BaseMeta extends BaseDocument { this._validFields = []; this._validFieldsWithChildren = []; - const _add = field => { + const _add = (field) => { this._validFields.push(field); this._validFieldsWithChildren.push(field); }; @@ -160,7 +160,7 @@ module.exports = class BaseMeta extends BaseDocument { } }); - const doctypeFields = this.fields.map(field => field.fieldname); + const doctypeFields = this.fields.map((field) => field.fieldname); // standard fields for (let field of model.commonFields) { @@ -176,7 +176,7 @@ module.exports = class BaseMeta extends BaseDocument { _add({ fieldtype: 'Check', fieldname: 'submitted', - label: frappe.t`Submitted` + label: frappe.t`Submitted`, }); } @@ -241,8 +241,8 @@ module.exports = class BaseMeta extends BaseDocument { this._keywordFields = this.keywordFields; if (!(this._keywordFields && this._keywordFields.length && this.fields)) { this._keywordFields = this.fields - .filter(field => field.fieldtype !== 'Table' && field.required) - .map(field => field.fieldname); + .filter((field) => field.fieldtype !== 'Table' && field.required) + .map((field) => field.fieldname); } if (!(this._keywordFields && this._keywordFields.length)) { this._keywordFields = ['name']; @@ -253,7 +253,7 @@ module.exports = class BaseMeta extends BaseDocument { getQuickEditFields() { if (this.quickEditFields) { - return this.quickEditFields.map(fieldname => this.getField(fieldname)); + return this.quickEditFields.map((fieldname) => this.getField(fieldname)); } return this.getFieldsWith({ required: 1 }); } @@ -271,10 +271,12 @@ module.exports = class BaseMeta extends BaseDocument { // values given as string validValues = options.split('\n'); } + if (typeof options[0] === 'object') { // options as array of {label, value} pairs - validValues = options.map(o => o.value); + validValues = options.map((o) => o.value); } + if (!validValues.includes(value)) { throw new frappe.errors.ValueError( // prettier-ignore @@ -287,7 +289,7 @@ module.exports = class BaseMeta extends BaseDocument { async trigger(event, params = {}) { Object.assign(params, { doc: this, - name: event + name: event, }); await super.trigger(event, params); @@ -300,8 +302,8 @@ module.exports = class BaseMeta extends BaseDocument { key: 'submitted', colors: { 0: indicatorColor.GRAY, - 1: indicatorColor.BLUE - } + 1: indicatorColor.BLUE, + }, }; } } @@ -323,4 +325,4 @@ module.exports = class BaseMeta extends BaseDocument { } } } -}; +} diff --git a/frappe/model/naming.js b/frappe/model/naming.js index 09b0c9d2..1a333cbf 100644 --- a/frappe/model/naming.js +++ b/frappe/model/naming.js @@ -1,122 +1,119 @@ -const { getPaddedName } = require('@/utils'); -const frappe = require('frappe'); -const { getRandomString } = require('frappe/utils'); +import frappe from 'frappe'; +import { getRandomString } from 'frappe/utils'; -module.exports = { - async isNameAutoSet(doctype) { - const doc = frappe.getNewDoc(doctype); - if (doc.meta.naming === 'autoincrement') { - return true; - } - - if (!doc.meta.settings) { - return false; - } - - const { numberSeries } = await doc.getSettings(); - if (numberSeries) { - return true; - } +export async function isNameAutoSet(doctype) { + const doc = frappe.getNewDoc(doctype); + if (doc.meta.naming === 'autoincrement') { + return true; + } + if (!doc.meta.settings) { return false; - }, + } - async setName(doc) { - if (frappe.isServer) { - // if is server, always name again if autoincrement or other - if (doc.meta.naming === 'autoincrement') { - doc.name = await this.getNextId(doc.doctype); - return; - } + const { numberSeries } = await doc.getSettings(); + if (numberSeries) { + return true; + } - // Current, per doc number series - if (doc.numberSeries) { - doc.name = await this.getSeriesNext(doc.numberSeries, doc.doctype); - return; - } + return false; +} - // Legacy, using doc settings for number series - if (doc.meta.settings) { - const numberSeries = (await doc.getSettings()).numberSeries; - if (!numberSeries) { - return; - } - - doc.name = await this.getSeriesNext(numberSeries, doc.doctype); - return; - } - } - - if (doc.name) { +export async function setName(doc) { + if (frappe.isServer) { + // if is server, always name again if autoincrement or other + if (doc.meta.naming === 'autoincrement') { + doc.name = await getNextId(doc.doctype); return; } - // name === doctype for Single - if (doc.meta.isSingle) { - doc.name = doc.meta.name; + // Current, per doc number series + if (doc.numberSeries) { + doc.name = await getSeriesNext(doc.numberSeries, doc.doctype); return; } - // assign a random name by default - // override doc to set a name - if (!doc.name) { - doc.name = getRandomString(); - } - }, - - async getNextId(doctype) { - // get the last inserted row - let lastInserted = await this.getLastInserted(doctype); - let name = 1; - if (lastInserted) { - let lastNumber = parseInt(lastInserted.name); - if (isNaN(lastNumber)) lastNumber = 0; - name = lastNumber + 1; - } - return (name + '').padStart(9, '0'); - }, - - async getLastInserted(doctype) { - const lastInserted = await frappe.db.getAll({ - doctype: doctype, - fields: ['name'], - limit: 1, - order_by: 'creation', - order: 'desc', - }); - return lastInserted && lastInserted.length ? lastInserted[0] : null; - }, - - async getSeriesNext(prefix, doctype) { - let series; - - try { - series = await frappe.getDoc('NumberSeries', prefix); - } catch (e) { - if (!e.statusCode || e.statusCode !== 404) { - throw e; + // Legacy, using doc settings for number series + if (doc.meta.settings) { + const numberSeries = (await doc.getSettings()).numberSeries; + if (!numberSeries) { + return; } - await this.createNumberSeries(prefix, doctype); - series = await frappe.getDoc('NumberSeries', prefix); - } - - return await series.next(doctype); - }, - - async createNumberSeries(prefix, referenceType, start = 1001) { - const exists = await frappe.db.exists('NumberSeries', prefix); - if (exists) { + doc.name = await getSeriesNext(numberSeries, doc.doctype); return; } + } - const series = frappe.newDoc({ - doctype: 'NumberSeries', - name: prefix, - start, - referenceType, - }); + if (doc.name) { + return; + } - await series.insert(); - }, -}; + // name === doctype for Single + if (doc.meta.isSingle) { + doc.name = doc.meta.name; + return; + } + + // assign a random name by default + // override doc to set a name + if (!doc.name) { + doc.name = getRandomString(); + } +} + +export async function getNextId(doctype) { + // get the last inserted row + let lastInserted = await getLastInserted(doctype); + let name = 1; + if (lastInserted) { + let lastNumber = parseInt(lastInserted.name); + if (isNaN(lastNumber)) lastNumber = 0; + name = lastNumber + 1; + } + return (name + '').padStart(9, '0'); +} + +export async function getLastInserted(doctype) { + const lastInserted = await frappe.db.getAll({ + doctype: doctype, + fields: ['name'], + limit: 1, + order_by: 'creation', + order: 'desc', + }); + return lastInserted && lastInserted.length ? lastInserted[0] : null; +} + +export async function getSeriesNext(prefix, doctype) { + let series; + + try { + series = await frappe.getDoc('NumberSeries', prefix); + } catch (e) { + if (!e.statusCode || e.statusCode !== 404) { + throw e; + } + + await createNumberSeries(prefix, doctype); + series = await frappe.getDoc('NumberSeries', prefix); + } + + return await series.next(doctype); +} + +export async function createNumberSeries(prefix, referenceType, start = 1001) { + const exists = await frappe.db.exists('NumberSeries', prefix); + if (exists) { + return; + } + + const series = frappe.newDoc({ + doctype: 'NumberSeries', + name: prefix, + start, + referenceType, + }); + + await series.insert(); +} diff --git a/frappe/model/runPatches.js b/frappe/model/runPatches.js index 0901003e..dfdcceec 100644 --- a/frappe/model/runPatches.js +++ b/frappe/model/runPatches.js @@ -1,6 +1,6 @@ -const frappe = require('frappe'); +import frappe from 'frappe'; -module.exports = async function runPatches(patchList) { +export default async function runPatches(patchList) { const patchesAlreadyRun = ( await frappe.db.knex('PatchRun').select('name') ).map(({ name }) => name); @@ -12,7 +12,7 @@ module.exports = async function runPatches(patchList) { await runPatch(patch); } -}; +} async function runPatch({ patchName, patchFunction }) { try { diff --git a/frappe/models/doctype/NumberSeries/NumberSeries.js b/frappe/models/doctype/NumberSeries/NumberSeries.js index 9ae22a21..51b1282d 100644 --- a/frappe/models/doctype/NumberSeries/NumberSeries.js +++ b/frappe/models/doctype/NumberSeries/NumberSeries.js @@ -1,4 +1,5 @@ -const { t } = require('frappe'); +import { t } from 'frappe'; +import NumberSeries from './NumberSeriesDocument.js'; const referenceTypeMap = { SalesInvoice: t`Invoice`, @@ -13,9 +14,10 @@ const referenceTypeMap = { '-': t`None`, }; -module.exports = { +export default { name: 'NumberSeries', - documentClass: require('./NumberSeriesDocument.js'), + label: t`Number Series`, + documentClass: NumberSeries, doctype: 'DocType', isSingle: 0, isChild: 0, diff --git a/frappe/models/doctype/NumberSeries/NumberSeriesDocument.js b/frappe/models/doctype/NumberSeries/NumberSeriesDocument.js index 34118845..7b83ab6c 100644 --- a/frappe/models/doctype/NumberSeries/NumberSeriesDocument.js +++ b/frappe/models/doctype/NumberSeries/NumberSeriesDocument.js @@ -1,8 +1,8 @@ -const { getPaddedName } = require('@/utils'); -const frappe = require('frappe'); -const BaseDocument = require('frappe/model/document'); +import { getPaddedName } from '@/utils'; +import frappe from 'frappe'; +import BaseDocument from 'frappe/model/document'; -module.exports = class NumberSeries extends BaseDocument { +export default class NumberSeries extends BaseDocument { validate() { if (!this.current) { this.current = this.start; @@ -34,4 +34,4 @@ module.exports = class NumberSeries extends BaseDocument { getPaddedName(next) { return getPaddedName(this.name, next, this.padZeros); } -}; +} diff --git a/frappe/models/index.js b/frappe/models/index.js index df5752a1..f4b69e46 100644 --- a/frappe/models/index.js +++ b/frappe/models/index.js @@ -1,13 +1,25 @@ -module.exports = { - NumberSeries: require('./doctype/NumberSeries/NumberSeries.js'), - PrintFormat: require('./doctype/PrintFormat/PrintFormat.js'), - Role: require('./doctype/Role/Role.js'), - Session: require('./doctype/Session/Session.js'), - SingleValue: require('./doctype/SingleValue/SingleValue.js'), - SystemSettings: require('./doctype/SystemSettings/SystemSettings.js'), - ToDo: require('./doctype/ToDo/ToDo.js'), - User: require('./doctype/User/User.js'), - UserRole: require('./doctype/UserRole/UserRole.js'), - File: require('./doctype/File/File.js'), - PatchRun: require('./doctype/PatchRun/PatchRun.js') +import File from './doctype/File/File.js'; +import NumberSeries from './doctype/NumberSeries/NumberSeries.js'; +import PatchRun from './doctype/PatchRun/PatchRun.js'; +import PrintFormat from './doctype/PrintFormat/PrintFormat.js'; +import Role from './doctype/Role/Role.js'; +import Session from './doctype/Session/Session.js'; +import SingleValue from './doctype/SingleValue/SingleValue.js'; +import SystemSettings from './doctype/SystemSettings/SystemSettings.js'; +import ToDo from './doctype/ToDo/ToDo.js'; +import User from './doctype/User/User.js'; +import UserRole from './doctype/UserRole/UserRole.js'; + +export default { + NumberSeries, + PrintFormat, + Role, + Session, + SingleValue, + SystemSettings, + ToDo, + User, + UserRole, + File, + PatchRun, }; diff --git a/frappe/utils/format.js b/frappe/utils/format.js index dc915bfe..a7b0f55d 100644 --- a/frappe/utils/format.js +++ b/frappe/utils/format.js @@ -1,8 +1,8 @@ -const luxon = require('luxon'); -const frappe = require('frappe'); -const { DEFAULT_DISPLAY_PRECISION, DEFAULT_LOCALE } = require('./consts'); +import frappe from 'frappe'; +import { DateTime } from 'luxon'; +import { DEFAULT_DISPLAY_PRECISION, DEFAULT_LOCALE } from './consts'; -module.exports = { +export default { format(value, df, doc) { if (!df) { return value; @@ -25,10 +25,10 @@ module.exports = { if (typeof value === 'string') { // ISO String - value = luxon.DateTime.fromISO(value); + value = DateTime.fromISO(value); } else if (Object.prototype.toString.call(value) === '[object Date]') { // JS Date - value = luxon.DateTime.fromJSDate(value); + value = DateTime.fromJSDate(value); } value = value.toFormat(dateFormat); diff --git a/frappe/utils/observable.js b/frappe/utils/observable.js deleted file mode 100644 index 00124d47..00000000 --- a/frappe/utils/observable.js +++ /dev/null @@ -1,128 +0,0 @@ -module.exports = class Observable { - constructor() { - this._observable = { - isHot: {}, - eventQueue: {}, - listeners: {}, - onceListeners: {} - } - } - - // getter, setter stubs, so Observable can be used as a simple Document - get(key) { - return this[key]; - } - - set(key, value) { - this[key] = value; - this.trigger('change', { - doc: this, - fieldname: key - }); - } - - on(event, listener) { - this._addListener('listeners', event, listener); - if (this._observable.socketClient) { - this._observable.socketClient.on(event, listener); - } - } - - // remove listener - off(event, listener) { - for (let type of ['listeners', 'onceListeners']) { - let index = this._observable[type][event] && this._observable[type][event].indexOf(listener); - if (index) { - this._observable[type][event].splice(index, 1); - } - } - } - - once(event, listener) { - this._addListener('onceListeners', event, listener); - } - - async trigger(event, params, throttle = false) { - if (throttle) { - if (this._throttled(event, params, throttle)) return; - params = [params] - } - - await this._executeTriggers(event, params); - } - - async _executeTriggers(event, params) { - let response = await this._triggerEvent('listeners', event, params); - if (response === false) return false; - - response = await this._triggerEvent('onceListeners', event, params); - if (response === false) return false; - - // emit via socket - if (this._observable.socketServer) { - this._observable.socketServer.emit(event, params); - } - - // clear once-listeners - if (this._observable.onceListeners && this._observable.onceListeners[event]) { - delete this._observable.onceListeners[event]; - } - - } - - clearListeners() { - this._observable.listeners = {}; - this._observable.onceListeners = {}; - } - - bindSocketClient(socket) { - // also send events with sockets - this._observable.socketClient = socket; - } - - bindSocketServer(socket) { - // also send events with sockets - this._observable.socketServer = socket; - } - - _throttled(event, params, throttle) { - if (this._observable.isHot[event]) { - // hot, add to queue - if (!this._observable.eventQueue[event]) this._observable.eventQueue[event] = []; - this._observable.eventQueue[event].push(params); - - // aleady hot, quit - return true; - } - this._observable.isHot[event] = true; - - // cool-off - setTimeout(() => { - this._observable.isHot[event] = false; - - // flush queue - if (this._observable.eventQueue[event]) { - let _queuedParams = this._observable.eventQueue[event]; - this._observable.eventQueue[event] = null; - this._executeTriggers(event, _queuedParams); - } - }, throttle); - - return false; - } - - _addListener(type, event, listener) { - if (!this._observable[type][event]) { - this._observable[type][event] = []; - } - this._observable[type][event].push(listener); - } - - async _triggerEvent(type, event, params) { - if (this._observable[type][event]) { - for (let listener of this._observable[type][event]) { - await listener(params); - } - } - } -} diff --git a/frappe/utils/observable.ts b/frappe/utils/observable.ts new file mode 100644 index 00000000..dea4c940 --- /dev/null +++ b/frappe/utils/observable.ts @@ -0,0 +1,170 @@ +enum EventType { + Listeners = '_listeners', + OnceListeners = '_onceListeners', +} + +export default class Observable { + [key: string]: unknown; + _isHot: Map; + _eventQueue: Map; + _map: Map; + _listeners: Map; + _onceListeners: Map; + + constructor() { + this._map = new Map(); + this._isHot = new Map(); + this._eventQueue = new Map(); + this._listeners = new Map(); + this._onceListeners = new Map(); + } + + /** + * Getter to use Observable as a regular document. + * + * @param key + * @returns + */ + get(key: string): unknown { + return this[key]; + } + + /** + * Setter to use Observable as a regular document. + * + * @param key + * @param value + */ + set(key: string, value: unknown) { + this[key] = value; + this.trigger('change', { + doc: this, + fieldname: key, + }); + } + + /** + * Sets a `listener` that executes every time `event` is triggered + * + * @param event : name of the event for which the listener is set + * @param listener : listener that is executed when the event is triggered + */ + on(event: string, listener: Function) { + this._addListener(EventType.Listeners, event, listener); + } + + /** + * Sets a `listener` that execture `once`: executes once when `event` is + * triggered then deletes itself + * + * @param event : name of the event for which the listener is set + * @param listener : listener that is executed when the event is triggered + */ + once(event: string, listener: Function) { + this._addListener(EventType.OnceListeners, event, listener); + } + + /** + * Remove a listener from an event for both 'on' and 'once' + * + * @param event : name of the event from which to remove the listener + * @param listener : listener that was set for the event + */ + off(event: string, listener: Function) { + this._removeListener(EventType.Listeners, event, listener); + this._removeListener(EventType.OnceListeners, event, listener); + } + + /** + * Remove all the listeners. + */ + clear() { + this._listeners.clear(); + this._onceListeners.clear(); + } + + /** + * Triggers the event's listener function. + * + * @param event : name of the event to be triggered. + * @param params : params to pass to the listeners. + * @param throttle : wait time before triggering the event. + */ + + async trigger(event: string, params: unknown, throttle: number = 0) { + let isHot = false; + if (throttle > 0) { + isHot = this._throttled(event, params, throttle); + params = [params]; + } + + if (isHot) { + return; + } + + await this._executeTriggers(event, params); + } + + _removeListener(type: EventType, event: string, listener: Function) { + const listeners = (this[type].get(event) ?? []).filter( + (l) => l !== listener + ); + this[type].set(event, listeners); + } + + async _executeTriggers(event: string, params?: unknown) { + await this._triggerEvent(EventType.Listeners, event, params); + await this._triggerEvent(EventType.OnceListeners, event, params); + this._onceListeners.delete(event); + } + + _throttled(event: string, params: unknown, throttle: number) { + /** + * Throttled events execute after `throttle` ms, during this period + * isHot is true, i.e it's going to execute. + */ + + if (!this._eventQueue.has(event)) { + this._eventQueue.set(event, []); + } + + if (this._isHot.get(event)) { + this._eventQueue.get(event)!.push(params); + return true; + } + + this._isHot.set(event, true); + + setTimeout(() => { + this._isHot.set(event, false); + + const params = this._eventQueue.get(event); + if (params !== undefined) { + this._executeTriggers(event, params); + this._eventQueue.delete(event); + } + }, throttle); + + return false; + } + + _addListener(type: EventType, event: string, listener: Function) { + this._initLiseners(type, event); + this[type].get(event)!.push(listener); + } + + _initLiseners(type: EventType, event: string) { + if (this[type].has(event)) { + return; + } + + this[type].set(event, []); + } + + async _triggerEvent(type: EventType, event: string, params?: unknown) { + const listeners = this[type].get(event) ?? []; + for (const listener of listeners) { + await listener(params); + } + } +} diff --git a/server/postStart.js b/server/postStart.js index 5c98aa53..a7ea0bbe 100644 --- a/server/postStart.js +++ b/server/postStart.js @@ -1,5 +1,5 @@ import frappe from 'frappe'; -import naming from 'frappe/model/naming'; +import { createNumberSeries } from 'frappe/model/naming'; import GSTR3BServer from '../models/doctype/GSTR3B/GSTR3BServer.js'; import JournalEntryServer from '../models/doctype/JournalEntry/JournalEntryServer.js'; import PartyServer from '../models/doctype/Party/PartyServer.js'; @@ -20,10 +20,10 @@ export default async function postStart() { frappe.metaCache = {}; // init naming series if missing - await naming.createNumberSeries('SINV-', 'SalesInvoice'); - await naming.createNumberSeries('PINV-', 'PurchaseInvoice'); - await naming.createNumberSeries('PAY-', 'Payment'); - await naming.createNumberSeries('JV-', 'JournalEntry'); + await createNumberSeries('SINV-', 'SalesInvoice'); + await createNumberSeries('PINV-', 'PurchaseInvoice'); + await createNumberSeries('PAY-', 'Payment'); + await createNumberSeries('JV-', 'JournalEntry'); // await naming.createNumberSeries('QTN-', 'QuotationSettings'); // await naming.createNumberSeries('SO-', 'SalesOrderSettings'); // await naming.createNumberSeries('OF-', 'FulfillmentSettings'); diff --git a/src/App.vue b/src/App.vue index a557b01f..c8350880 100644 --- a/src/App.vue +++ b/src/App.vue @@ -33,9 +33,9 @@ import WindowsTitleBar from '@/components/WindowsTitleBar'; import config from '@/config'; import { -connectToLocalDatabase, -postSetup, -purgeCache + connectToLocalDatabase, + postSetup, + purgeCache, } from '@/initialization'; import { IPC_ACTIONS, IPC_MESSAGES } from '@/messages'; import { ipcRenderer } from 'electron'; @@ -131,10 +131,10 @@ export default { routeTo('/get-started'); } }, - changeDbFile() { + async changeDbFile() { config.set('lastSelectedFilePath', null); telemetry.stop(); - purgeCache(true); + await purgeCache(true); this.activeScreen = 'DatabaseSelector'; }, async setupCanceled() { diff --git a/src/errorHandling.ts b/src/errorHandling.ts index 777fce6e..3d1a4aba 100644 --- a/src/errorHandling.ts +++ b/src/errorHandling.ts @@ -6,7 +6,7 @@ import { MandatoryError, ValidationError, } from 'frappe/common/errors'; -import BaseDocument from 'frappe/model/document'; +import Document from 'frappe/model/document'; import config, { ConfigKeys, TelemetrySetting } from './config'; import { IPC_ACTIONS, IPC_MESSAGES } from './messages'; import telemetry from './telemetry/telemetry'; @@ -110,7 +110,7 @@ export function handleError( } } -export function getErrorMessage(e: Error, doc?: BaseDocument): string { +export function getErrorMessage(e: Error, doc?: Document): string { let errorMessage = e.message || t`An error occurred.`; const { doctype, name }: { doctype?: unknown; name?: unknown } = doc ?? {}; @@ -125,7 +125,7 @@ export function getErrorMessage(e: Error, doc?: BaseDocument): string { return errorMessage; } -export function handleErrorWithDialog(error: Error, doc?: BaseDocument) { +export function handleErrorWithDialog(error: Error, doc?: Document) { const errorMessage = getErrorMessage(error, doc); handleError(false, error, { errorMessage, doc }); diff --git a/src/initialization.js b/src/initialization.js index b486a853..237bb5f9 100644 --- a/src/initialization.js +++ b/src/initialization.js @@ -106,7 +106,7 @@ export async function connectToLocalDatabase(filePath) { return { connectionSuccess: true, reason: '' }; } -export function purgeCache(purgeAll = false) { +export async function purgeCache(purgeAll = false) { const filterFunction = purgeAll ? () => true : (d) => frappe.docs[d][d] instanceof frappe.BaseMeta; @@ -120,7 +120,7 @@ export function purgeCache(purgeAll = false) { if (purgeAll) { delete frappe.db; - frappe.initializeAndRegister(models, true); + await frappe.initializeAndRegister(models, true); } } diff --git a/src/main.js b/src/main.js index 7012b98d..def99b30 100644 --- a/src/main.js +++ b/src/main.js @@ -25,7 +25,7 @@ import { setLanguageMap, stringifyCircular } from './utils'; frappe.isServer = true; frappe.isElectron = true; - frappe.initializeAndRegister(models); + await frappe.initializeAndRegister(models); ipcRenderer.send = getErrorHandled(ipcRenderer.send); ipcRenderer.invoke = getErrorHandled(ipcRenderer.invoke); diff --git a/src/pages/SetupWizard/SetupWizard.vue b/src/pages/SetupWizard/SetupWizard.vue index 514b21c2..dec091fd 100644 --- a/src/pages/SetupWizard/SetupWizard.vue +++ b/src/pages/SetupWizard/SetupWizard.vue @@ -89,25 +89,23 @@