@things-factory/announcement 10.1.260 → 10.1.261

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,13 +29,7 @@ const announcement_rules_1 = require("./announcement-rules");
29
29
  exports.ANNOUNCEMENT_REF_TYPE = 'Announcement';
30
30
  /** Whether the request writes announcements - the one privilege of the module (`announcement.writing`). */
31
31
  async function isWriter(context) {
32
- const state = context?.state ?? {};
33
- const { domain, user, unsafeIP, prohibitedPrivileges } = state;
34
- if (!domain || !user)
35
- return false;
36
- /* Asked once per request - a list asks it for each row. */
37
- state.__announcementWriter ??= (0, auth_base_1.checkPermission)({ category: 'announcement', privilege: 'mutation', owner: true }, user, domain, unsafeIP, prohibitedPrivileges).then(Boolean);
38
- return await state.__announcementWriter;
32
+ return await (0, auth_base_1.requestHolds)(context, 'announcement', 'mutation');
39
33
  }
40
34
  /** The request's person as an audience sees them - asked once per request. */
41
35
  async function memberOf(context) {
@@ -1 +1 @@
1
- {"version":3,"file":"announcement-service.js","sourceRoot":"","sources":["../../../server/service/announcement/announcement-service.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What the doors of announcements do with the database (ADR-0163 decisions 1-5): who may read one, who is on its roster,\n * sending it out, telling people of it, and publishing the scheduled ones when their start comes.\n */\nimport { EntityManager, In, IsNull, LessThanOrEqual } from 'typeorm'\n\nimport { checkPermission, User, UserStatus } from '@things-factory/auth-base'\nimport { logger } from '@things-factory/env'\nimport { translatorFor } from '@things-factory/i18n-base/translator'\nimport { keepNoticeDelivery, noticeDeliveryOf, noticeLinkOf, sendNotice, sendNoticeWhenCommitted, type Notice } from '@things-factory/notification'\nimport { audienceMemberOf, audienceUserIds, type AudienceMember } from '@things-factory/organization'\nimport { Domain, getDataSource, getRepository } from '@things-factory/shell'\n\nimport { Announcement, AnnouncementAudience, AnnouncementReceipt, AnnouncementRevision, AnnouncementStatus } from './announcement'\nimport { broadcastOf, noticeWordsOf, viewAddressOf, type NoticeKind, type Say } from './announcement-rules'\n\n/** The attachments of an announcement hang on it under this `refType` (attachment-base). */\nexport const ANNOUNCEMENT_REF_TYPE = 'Announcement'\n\n/** Whether the request writes announcements - the one privilege of the module (`announcement.writing`). */\nexport async function isWriter(context: any): Promise<boolean> {\n const state = context?.state ?? {}\n const { domain, user, unsafeIP, prohibitedPrivileges } = state\n if (!domain || !user) return false\n /* Asked once per request - a list asks it for each row. */\n state.__announcementWriter ??= checkPermission({ category: 'announcement', privilege: 'mutation', owner: true } as any, user, domain, unsafeIP, prohibitedPrivileges).then(Boolean)\n return await state.__announcementWriter\n}\n\n/** The request's person as an audience sees them - asked once per request. */\nexport async function memberOf(context: any): Promise<AudienceMember> {\n const state = context.state\n if (!state.__announcementMember) {\n const { ownDepartments, ...member } = await audienceMemberOf(state.domain, state.user ?? {})\n state.__announcementMember = member\n }\n return state.__announcementMember\n}\n\n/** The announcements of the domain whose audience names `member` - by their targets, whatever their status. */\nexport async function reachingIds(member: AudienceMember, tx?: EntityManager): Promise<string[]> {\n if (!member.userId || !member.domainId) return []\n const domain = { id: member.domainId }\n const rows = await getRepository(AnnouncementAudience, tx).find({\n where: [\n { domain, scope: 'DOMAIN', targetId: member.domainId },\n { domain, scope: 'USER', targetId: member.userId },\n ...(member.departmentIds.length ? [{ domain, scope: 'DEPARTMENT', targetId: In([...member.departmentIds]) }] : []),\n ...(member.roleIds.length ? [{ domain, scope: 'ROLE', targetId: In([...member.roleIds]) }] : [])\n ] as any\n })\n return [...new Set(rows.map(one => one.announcementId!))]\n}\n\nexport async function audiencesOf(announcementId: string, tx?: EntityManager): Promise<AnnouncementAudience[]> {\n return await getRepository(AnnouncementAudience, tx).find({ where: { announcement: { id: announcementId } } as any, order: { createdAt: 'ASC' } as any })\n}\n\n/**\n * The announcement as the request may see it, or nothing - one answer for \"none\", \"not yours\" and \"not out yet\".\n * Its audience reads it once it is out; a writer reads every one that is not somebody else's draft.\n */\nexport async function visibleAnnouncement(id: string, context: any, tx?: EntityManager): Promise<{ announcement: Announcement; reader: boolean; writer: boolean } | undefined> {\n const { domain, user } = context?.state ?? {}\n if (!id || !domain || !user || !/^[0-9a-f-]{36}$/i.test(id)) return undefined\n const announcement = await getRepository(Announcement, tx).findOne({ where: { id, domain: { id: domain.id } } as any })\n if (!announcement) return undefined\n const reader = announcement.status === AnnouncementStatus.PUBLISHED && (await reachingIds(await memberOf(context), tx)).includes(id)\n const writer = (announcement.status !== AnnouncementStatus.DRAFT || announcement.creatorId === user.id) && (await isWriter(context))\n return reader || writer ? { announcement, reader, writer } : undefined\n}\n\n/**\n * Puts the roster in line with the audience as it stands: everyone it names now is listed, one it no longer names is\n * not (their reading stays). Answers those newly listed - the ones not yet told.\n */\nexport async function alignRoster(announcement: Announcement, domain: Domain, by: { id: string }, tx: EntityManager): Promise<string[]> {\n const named = await audienceUserIds(domain, await audiencesOf(announcement.id, tx), tx)\n const repository = getRepository(AnnouncementReceipt, tx)\n const receipts = await repository.find({ where: { announcement: { id: announcement.id } } as any })\n const byUser = new Map(receipts.map(one => [one.userId, one]))\n const now = new Date()\n const added: string[] = []\n for (const userId of named) {\n const receipt = byUser.get(userId)\n if (receipt?.listed) continue\n added.push(userId)\n if (receipt) await repository.update(receipt.id, { listed: true, notifiedAt: now, updater: by } as any)\n else await repository.save({ domain, announcement: { id: announcement.id }, user: { id: userId }, listed: true, notifiedAt: now, creator: by, updater: by } as any)\n }\n const gone = receipts.filter(one => one.listed && !named.includes(one.userId!))\n if (gone.length) await repository.update({ id: In(gone.map(one => one.id)) } as any, { listed: false, updater: by } as any)\n await getRepository(Announcement, tx).update(announcement.id, { audienceSize: named.length } as any)\n return added\n}\n\n/** The version as it stands, kept - the one that went out, and each change after. */\nexport async function keepRevision(announcement: Announcement, domain: Domain, by: { id: string }, tx: EntityManager): Promise<void> {\n const audiences = await audiencesOf(announcement.id, tx)\n const { title, body, category, importance, startsAt, endsAt, pinned, requiresAcknowledgement, acknowledgeBy, popup, links } = announcement\n await getRepository(AnnouncementRevision, tx).save({\n domain,\n announcement: { id: announcement.id },\n version: announcement.version,\n title,\n body,\n rest: {\n category,\n importance,\n startsAt,\n endsAt,\n pinned,\n requiresAcknowledgement,\n acknowledgeBy,\n popup,\n links,\n audiences: audiences.map(({ scope, targetId, targetName }) => ({ scope, targetId, targetName }))\n },\n creator: by,\n updater: by\n } as any)\n}\n\n/**\n * Sends it out: published now, the roster counted, this version kept. Answers the people to tell - the publisher is not\n * told of their own announcement.\n */\nexport async function sendOut(announcement: Announcement, domain: Domain, by: { id: string }, tx: EntityManager): Promise<string[]> {\n const now = new Date()\n await getRepository(Announcement, tx).update(announcement.id, {\n status: AnnouncementStatus.PUBLISHED,\n publishedAt: now,\n startsAt: announcement.startsAt ?? now,\n publisher: by,\n updater: by\n } as any)\n const fresh = await getRepository(Announcement, tx).findOneBy({ id: announcement.id })\n await keepRevision(fresh!, domain, by, tx)\n return (await alignRoster(fresh!, domain, by, tx)).filter(id => id !== by.id)\n}\n\n/** The sentences in `lng` from this module's translations, the English one where `lng` has none. */\nconst sayIn =\n (lng: string): Say =>\n (key, english, params = {}) =>\n translatorFor(lng)(key, { ...params, defaultValue: english.replace(/\\{(\\w+)\\}/g, (_, name) => String(params[name] ?? '')) })\n\n/** The people to tell, as notification takes them - only those who can sign in, each with their mail and language. */\nasync function receiversOf(userIds: string[], tx?: EntityManager) {\n if (!userIds.length) return []\n const people = await getRepository(User, tx).find({ where: { id: In(userIds) } as any })\n return people.filter(one => one.status === UserStatus.ACTIVATED).map(one => ({ id: one.id, email: one.email, locale: (one as any).locale }))\n}\n\n/**\n * The notice of an announcement (notification ADR-0164): in the app and by web push always - the `announcement` type is\n * what pops it up - and by mail when the announcement asks for it and the domain has mail on.\n */\nfunction noticeOf(one: Announcement, kind: NoticeKind, receivers: Notice['receivers'], broadcast: boolean, link?: string): Notice {\n return {\n type: 'announcement',\n level: one.importance === 'URGENT' ? 'warn' : 'info',\n receivers,\n words: lng => noticeWordsOf(one, kind, sayIn(lng)),\n address: viewAddressOf(one.id),\n link,\n label: `announcement ${kind} ${one.id}`,\n /* The mail carries the whole body, drawn from its Markdown; the inbox and push show the summary. */\n format: 'markdown',\n channels: { email: !!one.sendEmail },\n /* Said once to the domain's Teams channel too - only what is addressed to everyone, and only when it goes out or all are told again. */\n ...(broadcast ? { broadcast: true } : {})\n }\n}\n\n/**\n * Tells `userIds` of it once the request's transaction has committed - a reader told before it would not find it yet -\n * and keeps what went out. A notice that does not arrive does not undo the announcement: its list, badge and popup\n * still reach them.\n */\nexport async function tellWhenCommitted(context: any, one: Announcement, kind: NoticeKind, userIds: string[], toEveryone = false): Promise<void> {\n const receivers = await receiversOf(userIds, context.state.tx)\n if (!receivers.length) return\n const broadcast = toEveryone && broadcastOf(await audiencesOf(one.id, context.state.tx), context.state.domain?.id)\n sendNoticeWhenCommitted(context, noticeOf(one, kind, receivers, broadcast, noticeLinkOf(context, viewAddressOf(one.id))), result =>\n keepNoticeDelivery(Announcement, one.id, 'deliveries', noticeDeliveryOf(kind, result, receivers.length))\n )\n}\n\n/**\n * Publishes the scheduled announcements whose start has come. Each is taken by one update from SCHEDULED, so a server\n * among several that loses the race does not send it twice.\n */\nexport async function publishDue(now = new Date()): Promise<number> {\n const due = await getRepository(Announcement).find({\n where: { status: AnnouncementStatus.SCHEDULED, startsAt: LessThanOrEqual(now), deletedAt: IsNull() } as any,\n relations: ['domain', 'publisher', 'creator'],\n take: 50\n })\n let published = 0\n for (const one of due) {\n try {\n const by = { id: one.publisherId ?? one.creatorId! }\n const receivers = await getDataSource().transaction(async tx => {\n const taken = await tx.getRepository(Announcement).update({ id: one.id, status: AnnouncementStatus.SCHEDULED } as any, { status: AnnouncementStatus.DRAFT } as any)\n if (!taken.affected) return undefined\n return await sendOut({ ...one, status: AnnouncementStatus.DRAFT } as Announcement, one.domain!, by, tx)\n })\n if (!receivers) continue\n published++\n /* No request, so no address to put on a mail button - the mail says where to find it instead. */\n const people = await receiversOf(receivers)\n const broadcast = broadcastOf(await audiencesOf(one.id), one.domain?.id)\n if (people.length) await keepNoticeDelivery(Announcement, one.id, 'deliveries', noticeDeliveryOf('published', await sendNotice(one.domain, noticeOf(one, 'published', people, broadcast)), people.length))\n } catch (error) {\n logger.error(`announcement ${one.id}: publishing at its start failed`, error)\n }\n }\n return published\n}\n\nlet timer: ReturnType<typeof setInterval> | undefined\n\n/** Looks for scheduled announcements every minute. The timer never keeps the process alive. */\nexport function schedulePublishing(): void {\n if (timer) return\n timer = setInterval(() => void publishDue().catch(error => logger.error('scheduled announcements', error)), 60 * 1000)\n timer.unref?.()\n}\n"]}
1
+ {"version":3,"file":"announcement-service.js","sourceRoot":"","sources":["../../../server/service/announcement/announcement-service.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What the doors of announcements do with the database (ADR-0163 decisions 1-5): who may read one, who is on its roster,\n * sending it out, telling people of it, and publishing the scheduled ones when their start comes.\n */\nimport { EntityManager, In, IsNull, LessThanOrEqual } from 'typeorm'\n\nimport { requestHolds, User, UserStatus } from '@things-factory/auth-base'\nimport { logger } from '@things-factory/env'\nimport { translatorFor } from '@things-factory/i18n-base/translator'\nimport { keepNoticeDelivery, noticeDeliveryOf, noticeLinkOf, sendNotice, sendNoticeWhenCommitted, type Notice } from '@things-factory/notification'\nimport { audienceMemberOf, audienceUserIds, type AudienceMember } from '@things-factory/organization'\nimport { Domain, getDataSource, getRepository } from '@things-factory/shell'\n\nimport { Announcement, AnnouncementAudience, AnnouncementReceipt, AnnouncementRevision, AnnouncementStatus } from './announcement'\nimport { broadcastOf, noticeWordsOf, viewAddressOf, type NoticeKind, type Say } from './announcement-rules'\n\n/** The attachments of an announcement hang on it under this `refType` (attachment-base). */\nexport const ANNOUNCEMENT_REF_TYPE = 'Announcement'\n\n/** Whether the request writes announcements - the one privilege of the module (`announcement.writing`). */\nexport async function isWriter(context: any): Promise<boolean> {\n return await requestHolds(context, 'announcement', 'mutation')\n}\n\n/** The request's person as an audience sees them - asked once per request. */\nexport async function memberOf(context: any): Promise<AudienceMember> {\n const state = context.state\n if (!state.__announcementMember) {\n const { ownDepartments, ...member } = await audienceMemberOf(state.domain, state.user ?? {})\n state.__announcementMember = member\n }\n return state.__announcementMember\n}\n\n/** The announcements of the domain whose audience names `member` - by their targets, whatever their status. */\nexport async function reachingIds(member: AudienceMember, tx?: EntityManager): Promise<string[]> {\n if (!member.userId || !member.domainId) return []\n const domain = { id: member.domainId }\n const rows = await getRepository(AnnouncementAudience, tx).find({\n where: [\n { domain, scope: 'DOMAIN', targetId: member.domainId },\n { domain, scope: 'USER', targetId: member.userId },\n ...(member.departmentIds.length ? [{ domain, scope: 'DEPARTMENT', targetId: In([...member.departmentIds]) }] : []),\n ...(member.roleIds.length ? [{ domain, scope: 'ROLE', targetId: In([...member.roleIds]) }] : [])\n ] as any\n })\n return [...new Set(rows.map(one => one.announcementId!))]\n}\n\nexport async function audiencesOf(announcementId: string, tx?: EntityManager): Promise<AnnouncementAudience[]> {\n return await getRepository(AnnouncementAudience, tx).find({ where: { announcement: { id: announcementId } } as any, order: { createdAt: 'ASC' } as any })\n}\n\n/**\n * The announcement as the request may see it, or nothing - one answer for \"none\", \"not yours\" and \"not out yet\".\n * Its audience reads it once it is out; a writer reads every one that is not somebody else's draft.\n */\nexport async function visibleAnnouncement(id: string, context: any, tx?: EntityManager): Promise<{ announcement: Announcement; reader: boolean; writer: boolean } | undefined> {\n const { domain, user } = context?.state ?? {}\n if (!id || !domain || !user || !/^[0-9a-f-]{36}$/i.test(id)) return undefined\n const announcement = await getRepository(Announcement, tx).findOne({ where: { id, domain: { id: domain.id } } as any })\n if (!announcement) return undefined\n const reader = announcement.status === AnnouncementStatus.PUBLISHED && (await reachingIds(await memberOf(context), tx)).includes(id)\n const writer = (announcement.status !== AnnouncementStatus.DRAFT || announcement.creatorId === user.id) && (await isWriter(context))\n return reader || writer ? { announcement, reader, writer } : undefined\n}\n\n/**\n * Puts the roster in line with the audience as it stands: everyone it names now is listed, one it no longer names is\n * not (their reading stays). Answers those newly listed - the ones not yet told.\n */\nexport async function alignRoster(announcement: Announcement, domain: Domain, by: { id: string }, tx: EntityManager): Promise<string[]> {\n const named = await audienceUserIds(domain, await audiencesOf(announcement.id, tx), tx)\n const repository = getRepository(AnnouncementReceipt, tx)\n const receipts = await repository.find({ where: { announcement: { id: announcement.id } } as any })\n const byUser = new Map(receipts.map(one => [one.userId, one]))\n const now = new Date()\n const added: string[] = []\n for (const userId of named) {\n const receipt = byUser.get(userId)\n if (receipt?.listed) continue\n added.push(userId)\n if (receipt) await repository.update(receipt.id, { listed: true, notifiedAt: now, updater: by } as any)\n else await repository.save({ domain, announcement: { id: announcement.id }, user: { id: userId }, listed: true, notifiedAt: now, creator: by, updater: by } as any)\n }\n const gone = receipts.filter(one => one.listed && !named.includes(one.userId!))\n if (gone.length) await repository.update({ id: In(gone.map(one => one.id)) } as any, { listed: false, updater: by } as any)\n await getRepository(Announcement, tx).update(announcement.id, { audienceSize: named.length } as any)\n return added\n}\n\n/** The version as it stands, kept - the one that went out, and each change after. */\nexport async function keepRevision(announcement: Announcement, domain: Domain, by: { id: string }, tx: EntityManager): Promise<void> {\n const audiences = await audiencesOf(announcement.id, tx)\n const { title, body, category, importance, startsAt, endsAt, pinned, requiresAcknowledgement, acknowledgeBy, popup, links } = announcement\n await getRepository(AnnouncementRevision, tx).save({\n domain,\n announcement: { id: announcement.id },\n version: announcement.version,\n title,\n body,\n rest: {\n category,\n importance,\n startsAt,\n endsAt,\n pinned,\n requiresAcknowledgement,\n acknowledgeBy,\n popup,\n links,\n audiences: audiences.map(({ scope, targetId, targetName }) => ({ scope, targetId, targetName }))\n },\n creator: by,\n updater: by\n } as any)\n}\n\n/**\n * Sends it out: published now, the roster counted, this version kept. Answers the people to tell - the publisher is not\n * told of their own announcement.\n */\nexport async function sendOut(announcement: Announcement, domain: Domain, by: { id: string }, tx: EntityManager): Promise<string[]> {\n const now = new Date()\n await getRepository(Announcement, tx).update(announcement.id, {\n status: AnnouncementStatus.PUBLISHED,\n publishedAt: now,\n startsAt: announcement.startsAt ?? now,\n publisher: by,\n updater: by\n } as any)\n const fresh = await getRepository(Announcement, tx).findOneBy({ id: announcement.id })\n await keepRevision(fresh!, domain, by, tx)\n return (await alignRoster(fresh!, domain, by, tx)).filter(id => id !== by.id)\n}\n\n/** The sentences in `lng` from this module's translations, the English one where `lng` has none. */\nconst sayIn =\n (lng: string): Say =>\n (key, english, params = {}) =>\n translatorFor(lng)(key, { ...params, defaultValue: english.replace(/\\{(\\w+)\\}/g, (_, name) => String(params[name] ?? '')) })\n\n/** The people to tell, as notification takes them - only those who can sign in, each with their mail and language. */\nasync function receiversOf(userIds: string[], tx?: EntityManager) {\n if (!userIds.length) return []\n const people = await getRepository(User, tx).find({ where: { id: In(userIds) } as any })\n return people.filter(one => one.status === UserStatus.ACTIVATED).map(one => ({ id: one.id, email: one.email, locale: (one as any).locale }))\n}\n\n/**\n * The notice of an announcement (notification ADR-0164): in the app and by web push always - the `announcement` type is\n * what pops it up - and by mail when the announcement asks for it and the domain has mail on.\n */\nfunction noticeOf(one: Announcement, kind: NoticeKind, receivers: Notice['receivers'], broadcast: boolean, link?: string): Notice {\n return {\n type: 'announcement',\n level: one.importance === 'URGENT' ? 'warn' : 'info',\n receivers,\n words: lng => noticeWordsOf(one, kind, sayIn(lng)),\n address: viewAddressOf(one.id),\n link,\n label: `announcement ${kind} ${one.id}`,\n /* The mail carries the whole body, drawn from its Markdown; the inbox and push show the summary. */\n format: 'markdown',\n channels: { email: !!one.sendEmail },\n /* Said once to the domain's Teams channel too - only what is addressed to everyone, and only when it goes out or all are told again. */\n ...(broadcast ? { broadcast: true } : {})\n }\n}\n\n/**\n * Tells `userIds` of it once the request's transaction has committed - a reader told before it would not find it yet -\n * and keeps what went out. A notice that does not arrive does not undo the announcement: its list, badge and popup\n * still reach them.\n */\nexport async function tellWhenCommitted(context: any, one: Announcement, kind: NoticeKind, userIds: string[], toEveryone = false): Promise<void> {\n const receivers = await receiversOf(userIds, context.state.tx)\n if (!receivers.length) return\n const broadcast = toEveryone && broadcastOf(await audiencesOf(one.id, context.state.tx), context.state.domain?.id)\n sendNoticeWhenCommitted(context, noticeOf(one, kind, receivers, broadcast, noticeLinkOf(context, viewAddressOf(one.id))), result =>\n keepNoticeDelivery(Announcement, one.id, 'deliveries', noticeDeliveryOf(kind, result, receivers.length))\n )\n}\n\n/**\n * Publishes the scheduled announcements whose start has come. Each is taken by one update from SCHEDULED, so a server\n * among several that loses the race does not send it twice.\n */\nexport async function publishDue(now = new Date()): Promise<number> {\n const due = await getRepository(Announcement).find({\n where: { status: AnnouncementStatus.SCHEDULED, startsAt: LessThanOrEqual(now), deletedAt: IsNull() } as any,\n relations: ['domain', 'publisher', 'creator'],\n take: 50\n })\n let published = 0\n for (const one of due) {\n try {\n const by = { id: one.publisherId ?? one.creatorId! }\n const receivers = await getDataSource().transaction(async tx => {\n const taken = await tx.getRepository(Announcement).update({ id: one.id, status: AnnouncementStatus.SCHEDULED } as any, { status: AnnouncementStatus.DRAFT } as any)\n if (!taken.affected) return undefined\n return await sendOut({ ...one, status: AnnouncementStatus.DRAFT } as Announcement, one.domain!, by, tx)\n })\n if (!receivers) continue\n published++\n /* No request, so no address to put on a mail button - the mail says where to find it instead. */\n const people = await receiversOf(receivers)\n const broadcast = broadcastOf(await audiencesOf(one.id), one.domain?.id)\n if (people.length) await keepNoticeDelivery(Announcement, one.id, 'deliveries', noticeDeliveryOf('published', await sendNotice(one.domain, noticeOf(one, 'published', people, broadcast)), people.length))\n } catch (error) {\n logger.error(`announcement ${one.id}: publishing at its start failed`, error)\n }\n }\n return published\n}\n\nlet timer: ReturnType<typeof setInterval> | undefined\n\n/** Looks for scheduled announcements every minute. The timer never keeps the process alive. */\nexport function schedulePublishing(): void {\n if (timer) return\n timer = setInterval(() => void publishDue().catch(error => logger.error('scheduled announcements', error)), 60 * 1000)\n timer.unref?.()\n}\n"]}