@fabriktor/fx 0.0.118 → 0.0.120

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.
@@ -1,5 +1,6 @@
1
1
  import type { ChatOperator } from "@fabriktor/client";
2
2
  import type { Chat, InChat } from "@fabriktor/schema";
3
+ import { type EventListener } from "../core/events.js";
3
4
  import { type QueueRetry, type QueueStore, type QueueLock } from "../core/queue.js";
4
5
  export declare const ChatSendStatus: {
5
6
  readonly Saving: "saving";
@@ -55,7 +56,8 @@ export declare class ChatSendQueue {
55
56
  private entries;
56
57
  private confirmed;
57
58
  private confirmedSequences;
58
- private listeners;
59
+ private readonly events;
60
+ private readonly changed;
59
61
  private readonly queue;
60
62
  private uploads;
61
63
  private confirmations;
@@ -72,7 +74,7 @@ export declare class ChatSendQueue {
72
74
  constructor(opts: ChatSendQueueOptions);
73
75
  get hasUnsavedMessages(): boolean;
74
76
  get snapshot(): ChatSendSnapshot;
75
- subscribe(listener: (snapshot: ChatSendSnapshot) => void): () => void;
77
+ subscribe(listener: EventListener<ChatSendSnapshot>): () => void;
76
78
  start(ownerID: string): Promise<void>;
77
79
  pause(): void;
78
80
  enqueue(input: InChat, attachments: ChatSendAttachments): string;
@@ -4,6 +4,7 @@
4
4
  // not use this file except in compliance with the License. You may obtain
5
5
  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
6
  import { requiredOperationID } from "../core/id.js";
7
+ import { createEvent, EventBus } from "../core/events.js";
7
8
  import { PersistentQueue, QueueWakeup, nextQueueRetry, } from "../core/queue.js";
8
9
  import { newOptimisticChat } from "./optimistic.js";
9
10
  export const ChatSendStatus = {
@@ -27,7 +28,8 @@ export class ChatSendQueue {
27
28
  entries = [];
28
29
  confirmed = new Map();
29
30
  confirmedSequences = new Map();
30
- listeners = new Set();
31
+ events = new EventBus();
32
+ changed = createEvent();
31
33
  queue;
32
34
  uploads = new Set();
33
35
  confirmations = new Set();
@@ -59,9 +61,7 @@ export class ChatSendQueue {
59
61
  };
60
62
  }
61
63
  subscribe(listener) {
62
- this.listeners.add(listener);
63
- listener(this.snapshot);
64
- return () => this.listeners.delete(listener);
64
+ return this.events.subscribe(this.changed, listener, () => this.snapshot);
65
65
  }
66
66
  async start(ownerID) {
67
67
  if (this.enabled && this.owner === ownerID) {
@@ -690,9 +690,7 @@ export class ChatSendQueue {
690
690
  }
691
691
  publish() {
692
692
  this.revision++;
693
- for (const listener of this.listeners) {
694
- listener(this.snapshot);
695
- }
693
+ this.events.publish(this.changed, this.snapshot);
696
694
  }
697
695
  }
698
696
  function chatSendCanConfirm(entry) {
@@ -0,0 +1,13 @@
1
+ declare const eventPayload: unique symbol;
2
+ export type EventKey<T> = symbol & {
3
+ readonly [eventPayload]: (value: T) => T;
4
+ };
5
+ export type EventListener<T> = (value: T) => void | Promise<void>;
6
+ export declare function createEvent<T>(): EventKey<T>;
7
+ export declare function notifyObserver(run: () => unknown): void;
8
+ export declare class EventBus {
9
+ private readonly listeners;
10
+ subscribe<T>(event: EventKey<T>, listener: EventListener<NoInfer<T>>, initial?: () => T): () => void;
11
+ publish<T>(event: EventKey<T>, value: NoInfer<T>): void;
12
+ }
13
+ export {};
@@ -0,0 +1,47 @@
1
+ // Copyright (C) Fabriktor, Inc. 2025-present.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+ // not use this file except in compliance with the License. You may obtain
5
+ // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ export function createEvent() {
7
+ return Symbol();
8
+ }
9
+ export function notifyObserver(run) {
10
+ try {
11
+ void Promise.resolve(run()).catch(observerError);
12
+ }
13
+ catch (err) {
14
+ observerError(err);
15
+ }
16
+ }
17
+ function observerError(err) {
18
+ console.error("Event observer failed.", err);
19
+ }
20
+ export class EventBus {
21
+ listeners = new Map();
22
+ subscribe(event, listener, initial) {
23
+ let listeners = this.listeners.get(event);
24
+ if (!listeners) {
25
+ listeners = new Set();
26
+ this.listeners.set(event, listeners);
27
+ }
28
+ const observer = listener;
29
+ listeners.add(observer);
30
+ if (initial)
31
+ notifyObserver(() => listener(initial()));
32
+ return () => {
33
+ listeners.delete(observer);
34
+ if (!listeners.size && this.listeners.get(event) === listeners)
35
+ this.listeners.delete(event);
36
+ };
37
+ }
38
+ publish(event, value) {
39
+ const listeners = this.listeners.get(event);
40
+ if (!listeners)
41
+ return;
42
+ for (const listener of [...listeners]) {
43
+ if (listeners.has(listener))
44
+ notifyObserver(() => listener(value));
45
+ }
46
+ }
47
+ }
@@ -1,3 +1,4 @@
1
+ import { type EventListener } from "./events.js";
1
2
  export declare const multiCreateMaximumEntries = 40;
2
3
  export declare const multiCreateConcurrency = 5;
3
4
  export declare const MultiCreateStatus: {
@@ -52,13 +53,14 @@ export type MultiCreateOptions<Input, Result> = {
52
53
  export declare class MultiCreator<Input, Result> {
53
54
  private readonly opts;
54
55
  private entries;
55
- private listeners;
56
+ private readonly events;
57
+ private readonly changed;
56
58
  private running;
57
59
  private controller;
58
60
  private completion;
59
61
  constructor(opts: MultiCreateOptions<Input, Result>);
60
62
  get snapshot(): MultiCreateSnapshot<Input, Result>;
61
- subscribe(listener: (snapshot: MultiCreateSnapshot<Input, Result>) => void): () => void;
63
+ subscribe(listener: EventListener<MultiCreateSnapshot<Input, Result>>): () => void;
62
64
  add(input: Input): string;
63
65
  edit(id: string, input: Input): boolean;
64
66
  remove(id: string): boolean;
@@ -70,5 +72,4 @@ export declare class MultiCreator<Input, Result> {
70
72
  private run;
71
73
  private replace;
72
74
  private publish;
73
- private notify;
74
75
  }
@@ -3,6 +3,7 @@
3
3
  // Licensed under the Apache License, Version 2.0 (the "License"); you may
4
4
  // not use this file except in compliance with the License. You may obtain
5
5
  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ import { createEvent, EventBus, notifyObserver, } from "./events.js";
6
7
  export const multiCreateMaximumEntries = 40;
7
8
  export const multiCreateConcurrency = 5;
8
9
  export const MultiCreateStatus = {
@@ -47,7 +48,8 @@ const sharedMultiCreatePool = new MultiCreatePool();
47
48
  export class MultiCreator {
48
49
  opts;
49
50
  entries = [];
50
- listeners = new Set();
51
+ events = new EventBus();
52
+ changed = createEvent();
51
53
  running = false;
52
54
  controller = new AbortController();
53
55
  completion = Promise.resolve();
@@ -58,9 +60,7 @@ export class MultiCreator {
58
60
  return { entries: this.entries, running: this.running };
59
61
  }
60
62
  subscribe(listener) {
61
- this.listeners.add(listener);
62
- listener(this.snapshot);
63
- return () => this.listeners.delete(listener);
63
+ return this.events.subscribe(this.changed, listener, () => this.snapshot);
64
64
  }
65
65
  add(input) {
66
66
  if (this.running || this.entries.length >= multiCreateMaximumEntries)
@@ -170,14 +170,14 @@ export class MultiCreator {
170
170
  catch (error) {
171
171
  current = { ...current, status: MultiCreateStatus.Failed, error };
172
172
  this.replace(current);
173
- this.notify(() => this.opts.on_failed?.(current));
173
+ notifyObserver(() => this.opts.on_failed?.(current));
174
174
  }
175
175
  })));
176
176
  this.running = false;
177
177
  this.publish();
178
178
  const ids = new Set(pending.map((entry) => entry.id));
179
179
  const entries = this.entries.filter((entry) => ids.has(entry.id));
180
- this.notify(() => this.opts.on_settled?.({
180
+ notifyObserver(() => this.opts.on_settled?.({
181
181
  entries,
182
182
  created: entries.filter((entry) => entry.status === MultiCreateStatus.Created).length,
183
183
  failed: entries.filter((entry) => entry.status === MultiCreateStatus.Failed).length,
@@ -188,16 +188,6 @@ export class MultiCreator {
188
188
  this.publish();
189
189
  }
190
190
  publish() {
191
- for (const listener of this.listeners)
192
- this.notify(() => listener(this.snapshot));
193
- }
194
- notify(run) {
195
- // Presentation errors cannot turn a successful creation into a failed POST.
196
- try {
197
- run();
198
- }
199
- catch (err) {
200
- console.error("Multi-creation observer failed.", err);
201
- }
191
+ this.events.publish(this.changed, this.snapshot);
202
192
  }
203
193
  }
package/dist/src/fx.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { BillableFX, BillableFXOperator } from "./billable/billable.js";
4
4
  import type { CallsFX, CallsFXOperator } from "./calls/calls.js";
5
5
  import type { ChatFX, ChatFXOperator } from "./chat/chat.js";
6
6
  import type { ContactsFX, ContactsFXOperator } from "./contacts/contacts.js";
7
+ import { EventBus } from "./core/events.js";
7
8
  import type { FeedFX, FeedFXOperator } from "./feed/feed.js";
8
9
  import type { FulfillmentsFX, FulfillmentsFXOperator } from "./fulfillments/fulfillments.js";
9
10
  import type { JobsFX, JobsFXOperator } from "./jobs/jobs.js";
@@ -56,6 +57,7 @@ export type DefaultFXOptions = {
56
57
  marketplace_keep_alive_interval_ms: number;
57
58
  };
58
59
  export interface FXOperator {
60
+ readonly events: EventBus;
59
61
  billable: BillableFXOperator;
60
62
  calls: CallsFXOperator;
61
63
  chat: ChatFXOperator;
@@ -78,6 +80,7 @@ export interface FXOperator {
78
80
  users: UsersFXOperator;
79
81
  }
80
82
  export declare class FX implements FXOperator {
83
+ readonly events: EventBus;
81
84
  billable: BillableFX;
82
85
  calls: CallsFX;
83
86
  chat: ChatFX;
package/dist/src/fx.js CHANGED
@@ -8,6 +8,7 @@ import { newBillableFX } from "./billable/billable.js";
8
8
  import { newCallsFX } from "./calls/calls.js";
9
9
  import { newChatFX } from "./chat/chat.js";
10
10
  import { newContactsFX } from "./contacts/contacts.js";
11
+ import { EventBus } from "./core/events.js";
11
12
  import { newFeedFX } from "./feed/feed.js";
12
13
  import { newFulfillmentsFX } from "./fulfillments/fulfillments.js";
13
14
  import { newJobsFX } from "./jobs/jobs.js";
@@ -25,6 +26,7 @@ import { newStorageFX } from "./storage/storage.js";
25
26
  import { newTimesheetsFX } from "./timesheets/timesheets.js";
26
27
  import { newUsersFX } from "./users/users.js";
27
28
  export class FX {
29
+ events = new EventBus();
28
30
  billable;
29
31
  calls;
30
32
  chat;
@@ -83,6 +85,7 @@ export class FX {
83
85
  });
84
86
  this.preferences = newPreferencesFX({
85
87
  preferences: opts.preferences,
88
+ events: this.events,
86
89
  });
87
90
  this.privacy = newPrivacyFX({
88
91
  privacy: opts.privacy,
@@ -8,6 +8,7 @@ export * from "./contacts/contacts.js";
8
8
  export * from "./contacts/scope.js";
9
9
  export * from "./core/date.js";
10
10
  export * from "./core/err.js";
11
+ export * from "./core/events.js";
11
12
  export * from "./core/id.js";
12
13
  export * from "./core/multi-create.js";
13
14
  export * from "./core/pre-action.js";
package/dist/src/index.js CHANGED
@@ -8,6 +8,7 @@ export * from "./contacts/contacts.js";
8
8
  export * from "./contacts/scope.js";
9
9
  export * from "./core/date.js";
10
10
  export * from "./core/err.js";
11
+ export * from "./core/events.js";
11
12
  export * from "./core/id.js";
12
13
  export * from "./core/multi-create.js";
13
14
  export * from "./core/pre-action.js";
@@ -1,5 +1,9 @@
1
1
  import type { PreferencesOperator } from "@fabriktor/client";
2
2
  import { type InPrefClient, type InPrefCompany, type InPrefEmployee, type OperationResult, type PrefPayment, type PrefClient, type PrefEmployee, type PrefCompany, type PrefVisibility, type PrefLanguage, type PrefNotif, type PrefEmail, type PrefTutorial } from "@fabriktor/schema";
3
+ import { EventBus } from "../core/events.js";
4
+ export declare const PreferencesEvents: {
5
+ readonly CompanySaved: import("../index.js").EventKey<PrefCompany>;
6
+ };
3
7
  type PrefPaymentInsertOptions = Parameters<PreferencesOperator["insertOnePrefPayment"]>[0];
4
8
  type PrefPaymentReplaceOptions = Parameters<PreferencesOperator["replaceOnePrefPayment"]>[0];
5
9
  type PrefClientInsertOptions = Parameters<PreferencesOperator["insertOnePrefClient"]>[0];
@@ -112,9 +116,11 @@ export interface PreferencesFXOperator {
112
116
  }
113
117
  export type PreferencesFXOptions = {
114
118
  preferences: PreferencesOperator;
119
+ events?: EventBus;
115
120
  };
116
121
  export declare class PreferencesFX implements PreferencesFXOperator {
117
122
  private preferences;
123
+ private readonly events;
118
124
  constructor(opts: PreferencesFXOptions);
119
125
  createPrefPayment(opts: CreatePrefPaymentOptions): Promise<PrefPayment>;
120
126
  replacePrefPayment(opts: ReplacePrefPaymentOptions): Promise<PrefPayment>;
@@ -6,7 +6,11 @@
6
6
  import { queryParam } from "@fabriktor/client";
7
7
  import { OwnerIDKey, } from "@fabriktor/schema";
8
8
  import { errResolveFailed } from "../core/err.js";
9
+ import { createEvent, EventBus } from "../core/events.js";
9
10
  import { requiredOperationID } from "../core/id.js";
11
+ export const PreferencesEvents = {
12
+ CompanySaved: createEvent(),
13
+ };
10
14
  const resourcePrefPayment = "pref_payment";
11
15
  const msgCreatedPrefPaymentIDMissing = "Created preference payment response did not contain an ID.";
12
16
  const msgPrefPaymentMissing = "Preference payment could not be resolved after the mutation.";
@@ -40,8 +44,10 @@ export const PrefCompanyDocumentNote = {
40
44
  };
41
45
  export class PreferencesFX {
42
46
  preferences;
47
+ events;
43
48
  constructor(opts) {
44
49
  this.preferences = opts.preferences;
50
+ this.events = opts.events ?? new EventBus();
45
51
  }
46
52
  async createPrefPayment(opts) {
47
53
  const out = await this.preferences.insertOnePrefPayment(opts);
@@ -102,11 +108,15 @@ export class PreferencesFX {
102
108
  async createPrefCompany(opts) {
103
109
  const out = await this.preferences.insertOnePrefCompany(opts);
104
110
  const id = requiredOperationID(out, msgCreatedPrefCompanyIDMissing);
105
- return await this.findPrefCompany(id);
111
+ const pref = await this.findPrefCompany(id);
112
+ this.events.publish(PreferencesEvents.CompanySaved, pref);
113
+ return pref;
106
114
  }
107
115
  async replacePrefCompany(opts) {
108
116
  await this.preferences.replaceOnePrefCompany(opts);
109
- return await this.findPrefCompany(opts.id);
117
+ const pref = await this.findPrefCompany(opts.id);
118
+ this.events.publish(PreferencesEvents.CompanySaved, pref);
119
+ return pref;
110
120
  }
111
121
  async getPrefCompany(opts) {
112
122
  const pref = await this.queryPrefCompany(opts.owner_id);
@@ -97,8 +97,9 @@ export class TimesheetsWorkspace {
97
97
  start,
98
98
  end,
99
99
  });
100
- requireState(!!result.value?.ints.length, TimesheetsBlock.InvalidDay);
101
- return result.value.ints;
100
+ const indexes = result.value?.periods?.period_indexes;
101
+ requireState(!!indexes?.length, TimesheetsBlock.InvalidDay);
102
+ return indexes;
102
103
  }
103
104
  async punchState(opts) {
104
105
  requireState(!!opts.owner_id && !!opts.timezone, TimesheetsBlock.InvalidDay);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabriktor/fx",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
4
  "description": "![Fabriktor Logo](./img/fabriktor-character.gif)",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,8 +17,8 @@
17
17
  "typescript": "^6.0.3"
18
18
  },
19
19
  "dependencies": {
20
- "@fabriktor/client": "0.0.87",
20
+ "@fabriktor/client": "0.0.88",
21
21
  "@fabriktor/fx": "0.0.94",
22
- "@fabriktor/schema": "0.0.63"
22
+ "@fabriktor/schema": "0.0.64"
23
23
  }
24
24
  }