diff --git a/noname/library/announce/index.d.ts b/noname/library/announce/index.d.ts index 7c41545d0..8e4af684b 100644 --- a/noname/library/announce/index.d.ts +++ b/noname/library/announce/index.d.ts @@ -1,3 +1,5 @@ +import { NonameAnnounceType } from "./interface.d.ts" + export interface IAnnounceSubscriber { subscribe(name: string): void; unsubscribe(name: string): void; @@ -21,7 +23,7 @@ export class Announce { * @param name - 要推送事件的名称 * @param values - 要推送的数据 */ - publish(name: string, values: T): T + publish(name: Name, values: Parameters[0]): Parameters[0] /** * 订阅给定名字的事件,并返回给定的函数 @@ -33,7 +35,7 @@ export class Announce { * @param name - 要订阅事件的名称 * @param method - 事件触发时执行的函数 */ - subscribe(name: string, method: (values: T) => void): (values: T) => void + subscribe(name: Name, method: Type[Name]): Type[Name] /** * 取消指定事件某一个函数的订阅,并返回该函数 @@ -43,7 +45,7 @@ export class Announce { * @param name - 要取消订阅事件的名称 * @param method - 订阅指定事件的函数 */ - unsubscribe(name: string, method: (values: T) => void): (values: T) => void + unsubscribe(name: Name, method: Type[Name]): Type[Name] } export class AnnounceSubscriber implements IAnnounceSubscriber { diff --git a/noname/library/announce/interface.d.ts b/noname/library/announce/interface.d.ts new file mode 100644 index 000000000..329a6be84 --- /dev/null +++ b/noname/library/announce/interface.d.ts @@ -0,0 +1,54 @@ + +export interface NonameAnnounceType { + // Apperaence 外观区域 + // 用于关于无名杀外观方面的通知 + + // Apperaence.Theme 无名杀主题区域 + /** + * 主题正在被切换时通知 + * + * @param values - 主题名称 + */ + "Noname.Apperaence.Theme.onChanging": AnnounceFunction + + /** + * 主题被切换时通知 + * + * @param values - 主题名称 + */ + "Noname.Apperaence.Theme.onChanged": AnnounceFunction + + /** + * 主题被切换时,已经显示完毕后通知 + * + * @param values - 主题名称 + */ + "Noname.Apperaence.Theme.onChangeFinished": AnnounceFunction + + // Game 游戏区域 + // 包含游戏对局下的通知 + + // Game.Event 事件区域 + /** + * 当游戏对局开始时进行通知 + * + * @param values - 空对象 + */ + "Noname.Game.Event.GameStart": AnnounceFunction<{}> + + + // Init 初始化区域 + // 用于关于初始化方面的通知 + + // Init.Extension 扩展初始化区域 + /** + * 当扩展初始化完成时通知 + * + * @param values - 扩展名称 + */ + "Noname.Init.Extension.onLoad": AnnounceFunction + + +} + +export type AnnounceFunction = (values: T) => void diff --git a/noname/library/assembly/buildin.js b/noname/library/assembly/buildin.js new file mode 100644 index 000000000..4c629547e --- /dev/null +++ b/noname/library/assembly/buildin.js @@ -0,0 +1,110 @@ +import { lib } from "../index.js" +import { ui } from "../../ui/index.js" +import { get } from "../../get/index.js" +import { _status } from "../../status/index.js" + +/** + * @type {(import("./interface.js").NonameAssemblyType["checkBegin"])} + */ +export const checkBegin = {} + +/** + * @type {(import("./interface.js").NonameAssemblyType["checkCard"])} + */ +export const checkCard = { + updateTempname(card, event) { + if (lib.config.cardtempname === 'off') return + if (get.name(card) === card.name && get.is.sameNature(get.nature(card), card.nature, true)) return + const node = ui.create.cardTempName(card) + if (lib.config.cardtempname !== 'default') node.classList.remove('vertical') + } +} + +/** + * @type {(import("./interface.js").NonameAssemblyType["checkTarget"])} + */ +export const checkTarget = { + updateInstance(target, event) { + // @ts-ignore + if (!target.instance) return; + ['selected', 'selectable'].forEach(className => { + if (target.classList.contains(className)) { + // @ts-ignore + target.instance.classList.add(className) + } else { + // @ts-ignore + target.instance.classList.remove(className) + } + }) + } +} + +/** + * @type {(import("./interface.js").NonameAssemblyType["checkButton"])} + */ +export const checkButton = {} + +/** + * @type {(import("./interface.js").NonameAssemblyType["checkEnd"])} + */ +export const checkEnd = { + autoConfirm(event, { ok, auto, autoConfirm }) { + if (!event.isMine()) return + const skillinfo = get.info(event.skill) || {} + if (ok && auto && (autoConfirm || skillinfo.direct) && !_status.touchnocheck + && !_status.mousedown && (!_status.mousedragging || !_status.mouseleft)) { + if (ui.confirm) ui.confirm.close() + // @ts-ignore + if (event.skillDialog === true) event.skillDialog = false + ui.click.ok() + _status.mousedragging = null + if (skillinfo.preservecancel) ui.create.confirm('c') + } + } +} + + + +/** + * @type {(import("./interface.js").NonameAssemblyType["uncheckBegin"])} + */ +export const uncheckBegin = {} + +/** + * @type {(import("./interface.js").NonameAssemblyType["uncheckCard"])} + */ +export const uncheckCard = { + removeTempname(card, event) { + // @ts-ignore + if (!card._tempName) return + // @ts-ignore + card._tempName.delete() + // @ts-ignore + delete card._tempName + } +} + +/** + * @type {(import("./interface.js").NonameAssemblyType["uncheckTarget"])} + */ +export const uncheckTarget = { + removeInstance(target, event) { + // @ts-ignore + if (!target.instance) return + // @ts-ignore + target.instance.classList.remove('selected') + // @ts-ignore + target.instance.classList.remove('selectable') + } +} + +/** + * @type {(import("./interface.js").NonameAssemblyType["uncheckButton"])} + */ +export const uncheckButton = {} + + +/** + * @type {(import("./interface.js").NonameAssemblyType["uncheckEnd"])} + */ +export const uncheckEnd = {} diff --git a/noname/library/assembly/index.js b/noname/library/assembly/index.js new file mode 100644 index 000000000..ea1796509 --- /dev/null +++ b/noname/library/assembly/index.js @@ -0,0 +1,134 @@ +import * as buildin from "./buildin.js" + +/** + * > 这玩意因为狂神还得是数组 + * + * @template {import("./interface.d.ts").NonameAssemblyType} AssemblyType + * @template {keyof AssemblyType} Name + * @extends {Array} + */ +export class NonameAssembly extends Array { + /** + * @type {Name} + */ + #name + + /** + * @type {Map} + */ + #record + + /** + * + * @param {Name} name + */ + constructor(name) { + super() + this.#name = name + this.#record = new Map() + + if (name in buildin) { + // @ts-ignore + for (const [key, item] of Object.entries(buildin[name])) { + // @ts-ignore + this.add(key, item) + } + } + } + + static get [Symbol.species]() { + return Array + } + + get name() { + return this.#name + } + + /** + * + * @param {keyof AssemblyType[Name]} name + * @param {AssemblyType[Name][keyof AssemblyType[Name]]} content + * @override + */ + // @ts-ignore + add(name, content) { + if (!content) { + // @ts-expect-error A + content = name + // @ts-expect-error A + name = content.name + } + if (typeof content !== "function") throw new Error("you can't add a non-function to assembly.") + if (typeof name !== "string" || name.length === 0) throw new Error("you can't add a anonymous function to assembly.") + + if (!this.has(name)) { + this.#record.set(name, this.length) + Array.prototype.push.call(this, content) + } + + return this + } + + /** + * + * @param {keyof AssemblyType[Name]} name + * @param {AssemblyType[Name][keyof AssemblyType[Name]]} content + * @override + */ + // @ts-ignore + push(name, content) { + return this.add(name, content).length + } + + /** + * + * @param {keyof AssemblyType[Name]} name + */ + has(name) { + return this.#record.has(name) + } + + /** + * + * @param {keyof AssemblyType[Name]} name + * @returns {AssemblyType[Name][keyof AssemblyType[Name]] | undefined} + */ + get(name) { + if (!this.has(name)) return void 0 + // @ts-ignore + return this[this.#record.get(name)] + } + + /** + * + * @param {keyof AssemblyType[Name]} name + * @param {AssemblyType[Name][keyof AssemblyType[Name]]} content + */ + update(name, content) { + if (!this.has(name)) return false + + try { + // @ts-ignore + this[this.#record.get(name)] = content + } catch (e) { + console.error(e) + return false + } + + return true + } +} + +export const defaultAssemblys = { + checkBegin: new NonameAssembly("checkBegin"), + checkCard: new NonameAssembly("checkCard"), + checkTarget: new NonameAssembly("checkTarget"), + checkButton: new NonameAssembly("checkButton"), + checkEnd: new NonameAssembly("checkEnd"), + + uncheckBegin: new NonameAssembly("uncheckBegin"), + uncheckCard: new NonameAssembly("uncheckCard"), + uncheckTarget: new NonameAssembly("uncheckTarget"), + uncheckButton: new NonameAssembly("uncheckButton"), + uncheckEnd: new NonameAssembly("uncheckEnd") +} diff --git a/noname/library/assembly/interface.d.ts b/noname/library/assembly/interface.d.ts new file mode 100644 index 000000000..1407a1c28 --- /dev/null +++ b/noname/library/assembly/interface.d.ts @@ -0,0 +1,58 @@ +import { Button, Card, GameEvent, GameEventPromise, Player } from "../element" + +export interface NonameAssemblyType { + checkBegin: {} + + checkCard: { + /** + * + * @param card - 检查的卡牌 + * @param event - 当前检查的事件 + */ + updateTempname(card: Card, event: GameEvent & GameEventPromise): void + } + + checkTarget: { + /** + * + * @param target - 检查的玩家 + * @param event - 当前检查的事件 + */ + updateInstance(target: Player, event: GameEvent & GameEventPromise): void + } + + checkButton: {} + + checkEnd: { + /** + * + * @param event - 当前检查的事件 + * @param config - 一些配置 + */ + autoConfirm(event: GameEvent & GameEventPromise, config: { ok: boolean, auto: boolean, autoConfirm: boolean }): void + } + + uncheckBegin: {} + + uncheckCard: { + /** + * + * @param card - 取消检查的卡牌 + * @param event - 当前检查的事件 + */ + removeTempname(card: Card, event: GameEvent & GameEventPromise): void + } + + uncheckTarget: { + /** + * + * @param target - 取消检查的玩家 + * @param event - 当前检查的事件 + */ + removeInstance(target: Player, event: GameEvent & GameEventPromise): void + } + + uncheckButton: {} + + uncheckEnd: {} +} diff --git a/noname/library/element/gameEvent.js b/noname/library/element/gameEvent.js index 28d9a0649..e4d77f1af 100644 --- a/noname/library/element/gameEvent.js +++ b/noname/library/element/gameEvent.js @@ -750,6 +750,7 @@ export class GameEvent { if ((this.name === 'gain' || this.name === 'lose') && !_status.gameDrawed) return; if (name === 'gameDrawEnd') _status.gameDrawed = true; if (name === 'gameStart') { + lib.announce.publish("Noname.Game.Event.GameStart", {}); lib.announce.publish('gameStart', {}); if (_status.brawl && _status.brawl.gameStart) _status.brawl.gameStart(); if (lib.config.show_cardpile) ui.cardPileButton.style.display = ''; diff --git a/noname/library/hooks/buildin.js b/noname/library/hooks/buildin.js index 961c13b03..f6f105b02 100644 --- a/noname/library/hooks/buildin.js +++ b/noname/library/hooks/buildin.js @@ -1,8 +1,5 @@ import { lib } from "../index.js" import { game } from "../../game/index.js" -import { ui } from "../../ui/index.js" -import { get } from "../../get/index.js" -import { _status } from "../../status/index.js" /** * @type {(import("./interface.js").NonameHookType["addGroup"])[]} @@ -213,112 +210,3 @@ export const addNature = [ } } ] - - - - -/** - * @type {(import("./interface.js").NonameHookType["checkBegin"])[]} - */ -export const checkBegin = [] - -/** - * @type {(import("./interface.js").NonameHookType["checkCard"])[]} - */ -export const checkCard = [ - function updateTempname(card, event) { - if (lib.config.cardtempname === 'off') return - if (get.name(card) === card.name && get.is.sameNature(get.nature(card), card.nature, true)) return - const node = ui.create.cardTempName(card) - if (lib.config.cardtempname !== 'default') node.classList.remove('vertical') - } -] - -/** - * @type {(import("./interface.js").NonameHookType["checkTarget"])[]} - */ -export const checkTarget = [ - function updateInstance(target, event) { - // @ts-ignore - if (!target.instance) return; - ['selected', 'selectable'].forEach(className => { - if (target.classList.contains(className)) { - // @ts-ignore - target.instance.classList.add(className) - } else { - // @ts-ignore - target.instance.classList.remove(className) - } - }) - }, -] - -/** - * @type {(import("./interface.js").NonameHookType["checkButton"])[]} - */ -export const checkButton = [] - -/** - * @type {(import("./interface.js").NonameHookType["checkEnd"])[]} - */ -export const checkEnd = [ - function autoConfirm(event, { ok, auto, autoConfirm }) { - if (!event.isMine()) return - const skillinfo = get.info(event.skill) || {} - if (ok && auto && (autoConfirm || skillinfo.direct) && !_status.touchnocheck - && !_status.mousedown && (!_status.mousedragging || !_status.mouseleft)) { - if (ui.confirm) ui.confirm.close() - // @ts-ignore - if (event.skillDialog === true) event.skillDialog = false - ui.click.ok() - _status.mousedragging = null - if (skillinfo.preservecancel) ui.create.confirm('c') - } - } -] - - - -/** - * @type {(import("./interface.js").NonameHookType["uncheckBegin"])[]} - */ -export const uncheckBegin = [] - -/** - * @type {(import("./interface.js").NonameHookType["uncheckCard"])[]} - */ -export const uncheckCard = [ - function removeTempname(card, event) { - // @ts-ignore - if (!card._tempName) return - // @ts-ignore - card._tempName.delete() - // @ts-ignore - delete card._tempName - }, -] - -/** - * @type {(import("./interface.js").NonameHookType["uncheckTarget"])[]} - */ -export const uncheckTarget = [ - function removeInstance(target, event) { - // @ts-ignore - if (!target.instance) return - // @ts-ignore - target.instance.classList.remove('selected') - // @ts-ignore - target.instance.classList.remove('selectable') - }, -] - -/** - * @type {(import("./interface.js").NonameHookType["uncheckButton"])[]} - */ -export const uncheckButton = [] - - -/** - * @type {(import("./interface.js").NonameHookType["uncheckEnd"])[]} - */ -export const uncheckEnd = [] diff --git a/noname/library/hooks/hook.js b/noname/library/hooks/hook.js index f9d29e6e9..777915c6d 100644 --- a/noname/library/hooks/hook.js +++ b/noname/library/hooks/hook.js @@ -3,49 +3,35 @@ import * as buildin from "./buildin.js" /** * @template {import("./interface.js").NonameHookType} HookType * @template {keyof HookType} Name + * @extends {Array} */ -export class NonameHook { +export class NonameHook extends Array { /** * @type {Name} */ #name - /** - * @type {HookType[Name][]} - */ - #methodList - /** * * @param {Name} name */ constructor(name) { + super() this.#name = name - this.#methodList = (name in buildin) ? [...buildin[name]] : [] + + if (name in buildin) { + // @ts-ignore + for (const item of buildin[name]) { + this.push(item) + } + } + } + + static get [Symbol.species]() { + return Array } get name() { return this.#name } - - /** - * - * @param {HookType[Name]} method - */ - add(method) { - return this.#methodList.add(method) - } - - /** - * - * @param {HookType[Name]} method - */ - push(method) { - return this.#methodList.push(method) - } - - - *[Symbol.iterator]() { - yield* this.#methodList - } } diff --git a/noname/library/hooks/index.js b/noname/library/hooks/index.js index 0fd3fa4a4..a6dfc7dac 100644 --- a/noname/library/hooks/index.js +++ b/noname/library/hooks/index.js @@ -1,18 +1,13 @@ import { NonameHook } from "./hook.js" +import { defaultAssemblys } from "../assembly/index.js" export const defaultHooks = { addGroup: new NonameHook("addGroup"), addNature: new NonameHook("addNature"), - checkBegin: new NonameHook("checkBegin"), - checkCard: new NonameHook("checkCard"), - checkTarget: new NonameHook("checkTarget"), - checkButton: new NonameHook("checkButton"), - checkEnd: new NonameHook("checkEnd"), - - uncheckBegin: new NonameHook("uncheckBegin"), - uncheckCard: new NonameHook("uncheckCard"), - uncheckTarget: new NonameHook("uncheckTarget"), - uncheckButton: new NonameHook("uncheckButton"), - uncheckEnd: new NonameHook("uncheckEnd") + ...defaultAssemblys +} + +export { + NonameHook } diff --git a/noname/library/hooks/interface.d.ts b/noname/library/hooks/interface.d.ts index b3f5954e5..0be0a6533 100644 --- a/noname/library/hooks/interface.d.ts +++ b/noname/library/hooks/interface.d.ts @@ -1,92 +1,94 @@ import { Button, Card, GameEvent, GameEventPromise, Player } from "../element" export interface NonameHookType { - /** - * - * @param id - 势力的id - * @param short - 势力的短名称(单字称呼) - * @param name - 势力的完整名称 - * @param config - 关于势力的配置情况 - */ - addGroup(id: string, short: string, name: string, config: Record) + /** + * + * @param id - 势力的id + * @param short - 势力的短名称(单字称呼) + * @param name - 势力的完整名称 + * @param config - 关于势力的配置情况 + */ + addGroup(id: string, short: string, name: string, config: Record): any - /** - * - * @param nature - 属性的id - * @param translation - 属性的名称 - * @param config - 关于属性的配置 - */ - addNature(nature: string, translation: string, config: Record) + /** + * + * @param nature - 属性的id + * @param translation - 属性的名称 + * @param config - 关于属性的配置 + */ + addNature(nature: string, translation: string, config: Record): any - /** - * - * @param event - 当前检查的事件 - */ - checkBegin(event: GameEvent & GameEventPromise) + // #region Assembly-Compatition + /** + * + * @param event - 当前检查的事件 + */ + checkBegin(event: GameEvent & GameEventPromise): any - /** - * - * @param event - 当前检查的事件 - * @param config - 一些配置 - */ - checkEnd(event: GameEvent & GameEventPromise, config: { ok: boolean, auto: boolean, autoConfirm: boolean }) + /** + * + * @param event - 当前检查的事件 + * @param config - 一些配置 + */ + checkEnd(event: GameEvent & GameEventPromise, config: { ok: boolean, auto: boolean, autoConfirm: boolean }): any - /** - * - * @param button - 检查的Button - * @param event - 当前检查的事件 - */ - checkButton(button: Button, event: GameEvent & GameEventPromise) + /** + * + * @param button - 检查的Button + * @param event - 当前检查的事件 + */ + checkButton(button: Button, event: GameEvent & GameEventPromise): any - /** - * - * @param card - 检查的卡牌 - * @param event - 当前检查的事件 - */ - checkCard(card: Card, event: GameEvent & GameEventPromise) + /** + * + * @param card - 检查的卡牌 + * @param event - 当前检查的事件 + */ + checkCard(card: Card, event: GameEvent & GameEventPromise): any - /** - * - * @param target - 检查的玩家 - * @param event - 当前检查的事件 - */ - checkTarget(target: Player, event: GameEvent & GameEventPromise) + /** + * + * @param target - 检查的玩家 + * @param event - 当前检查的事件 + */ + checkTarget(target: Player, event: GameEvent & GameEventPromise): any - /** - * - * @param event - 当前检查的事件 - * @param args - 要取消检查的对象 - */ - uncheckBegin(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"]) + /** + * + * @param event - 当前检查的事件 + * @param args - 要取消检查的对象 + */ + uncheckBegin(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"]): any - /** - * - * @param event - 当前检查的事件 - * @param args - 要取消检查的对象 - */ - uncheckEnd(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"]) + /** + * + * @param event - 当前检查的事件 + * @param args - 要取消检查的对象 + */ + uncheckEnd(event: GameEvent & GameEventPromise, args: ("button" | "card" | "target")[] = ["button", "card", "target"]): any - /** - * - * @param button - 取消检查的Button - * @param event - 当前检查的事件 - */ - uncheckButton(button: Button, event: GameEvent & GameEventPromise) + /** + * + * @param button - 取消检查的Button + * @param event - 当前检查的事件 + */ + uncheckButton(button: Button, event: GameEvent & GameEventPromise): any - /** - * - * @param card - 取消检查的卡牌 - * @param event - 当前检查的事件 - */ - uncheckCard(card: Card, event: GameEvent & GameEventPromise) + /** + * + * @param card - 取消检查的卡牌 + * @param event - 当前检查的事件 + */ + uncheckCard(card: Card, event: GameEvent & GameEventPromise): any - /** - * - * @param target - 取消检查的玩家 - * @param event - 当前检查的事件 - */ - uncheckTarget(target: Player, event: GameEvent & GameEventPromise) + /** + * + * @param target - 取消检查的玩家 + * @param event - 当前检查的事件 + */ + uncheckTarget(target: Player, event: GameEvent & GameEventPromise): any + // #endregion } diff --git a/noname/library/index.js b/noname/library/index.js index 34d37d76b..7fdf3cd31 100644 --- a/noname/library/index.js +++ b/noname/library/index.js @@ -24,6 +24,7 @@ import { Experimental } from "./experimental/index.js"; import * as Element from "./element/index.js"; import { updateURLs } from "./update-urls.js"; import { defaultHooks } from "./hooks/index.js" +import { freezeButExtensible } from "../util/index.js" export class Library extends Uninstantable { @@ -153,7 +154,7 @@ export class Library extends Uninstantable { * 这样当某个地方调用game.callHook(钩子名,[...函数参数])时,就会按顺序将对应数组中的每个函数运行一遍(传参为callHook的第二个参数)。 * 你可以将hook机制类比为event.trigger(),但是这里只能放同步代码 */ - static hooks = { ...defaultHooks }; + static hooks = freezeButExtensible({ ...defaultHooks }); /** * **无名杀频道推送机制** diff --git a/noname/util/index.js b/noname/util/index.js index 11face5a7..7bca77016 100644 --- a/noname/util/index.js +++ b/noname/util/index.js @@ -1,27 +1,47 @@ -/** @type { string } */ -// @ts-ignore -export const nonameInitialized = localStorage.getItem('noname_inited'); -export const assetURL = location.protocol.startsWith('http') || typeof nonameInitialized != 'string' || nonameInitialized == 'nodejs' ? '' : nonameInitialized; -export const GeneratorFunction = (function* () {}).constructor; -export const AsyncFunction = (async function () {}).constructor; -export const userAgent = navigator.userAgent.toLowerCase(); -export { Mutex } from './mutex.js'; -export const characterDefaultPicturePath = "image/character/default_silhouette_"; - -/** - * 不能被new的类 - */ -export class Uninstantable { - constructor() { - throw new TypeError(`${new.target.name} is not a constructor`); - } -} - -/** - * 暂停x毫秒 - * @param { number } ms - * @returns { Promise } - */ -export function delay(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} +/** @type { string } */ +// @ts-ignore +export const nonameInitialized = localStorage.getItem('noname_inited'); +export const assetURL = location.protocol.startsWith('http') || typeof nonameInitialized != 'string' || nonameInitialized == 'nodejs' ? '' : nonameInitialized; +export const GeneratorFunction = (function* () {}).constructor; +export const AsyncFunction = (async function () {}).constructor; +export const userAgent = navigator.userAgent.toLowerCase(); +export { Mutex } from './mutex.js'; +export const characterDefaultPicturePath = "image/character/default_silhouette_"; + +/** + * 不能被new的类 + */ +export class Uninstantable { + constructor() { + throw new TypeError(`${new.target.name} is not a constructor`); + } +} + +/** + * 暂停x毫秒 + * @param { number } ms + * @returns { Promise } + */ +export function delay(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** + * 将当前Record已有的普通项封装起来,但不阻止其继续扩展 + * + * @template {object} T + * @param {T} record - 要封装的Record + * @returns {Readonly} + */ +export function freezeButExtensible(record) { + const descriptors = Object.getOwnPropertyDescriptors(record) + if (descriptors) { + for (const [key, descriptor] of Object.entries(descriptors)) { + if ("value" in descriptor) descriptor.writable = false + descriptor.configurable = false + Reflect.defineProperty(record, key, descriptor) + } + } + + return record +}