@pistonite/pure 0.26.2 → 0.26.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pistonite/pure",
3
- "version": "0.26.2",
3
+ "version": "0.26.4",
4
4
  "type": "module",
5
5
  "description": "Pure TypeScript libraries for my projects",
6
6
  "homepage": "https://github.com/Pistonite/pure",
@@ -26,11 +26,11 @@
26
26
  "url": "git+https://github.com/Pistonite/pure.git",
27
27
  "directory": "packages/pure"
28
28
  },
29
- "dependencies": {
30
- "denque": "2.1.0"
31
- },
32
29
  "devDependencies": {
33
- "vitest": "^3.1.1",
34
- "mono-dev": "0.1.1"
30
+ "vitest": "^3.2.4",
31
+ "mono-dev": "0.2.2"
32
+ },
33
+ "dependencies": {
34
+ "denque": "^2.1.0"
35
35
  }
36
36
  }
package/src/log/index.ts CHANGED
@@ -1,56 +1,154 @@
1
1
  /**
2
2
  * Client side log util
3
3
  *
4
+ * This is rather simple logging stuff with the primary focus
5
+ * being easy to debug.
6
+ *
7
+ * Use {@link logger} to create a logger with a name and a color,
8
+ * then use one of the {@link LoggerFactory} methods to setup the logging level.
9
+ *
10
+ * There are 3 levels of logging:
11
+ * 1. (Default) Warnings and Errors only
12
+ * 2. Also log info messages
13
+ * 3. Also log debug messages.
14
+ *
15
+ * Each logger can turn on info and debug logging separately, or it can
16
+ * be turned on at the global level for debugging.
17
+ *
18
+ * You can also turn off each logger individually or at global level for debugging.
19
+ *
20
+ * Due to the nature of JS, all logging calls, even when turned off, will incur
21
+ * some small runtime overhead. While we could remove debug calls
22
+ * for release build, that's currently not done (and it would require
23
+ * bundler to inline the call to remove the call completely, which might
24
+ * not be the case)
25
+ *
4
26
  * @module
5
27
  */
6
28
 
7
- import Denque from "denque";
8
29
  import { errstr } from "../result/index.ts";
9
30
 
10
- const LIMIT = 500;
31
+ export const LogLevel = {
32
+ Off: 0,
33
+ High: 1,
34
+ Info: 2,
35
+ Debug: 3,
36
+ } as const;
37
+ export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];
11
38
 
12
- /** Global log queue */
13
- const LogQueue = new Denque<string>();
14
- function pushLog(msg: string) {
15
- if (LogQueue.length > LIMIT) {
16
- LogQueue.shift();
17
- }
18
- LogQueue.push(`[${new Date().toISOString()}]${msg}`);
19
- }
39
+ let globalLevel: LogLevel = LogLevel.High;
40
+ /**
41
+ * Suppress ALL logging.
42
+ *
43
+ * This overrides logger-level settings
44
+ */
45
+ export const globalLogOff = () => (globalLevel = 0);
46
+ /**
47
+ * Enable +info logging for ALL loggers
48
+ *
49
+ * This overrides logger-level settings
50
+ */
51
+ export const globalLogInfo = () => (globalLevel = 2);
52
+ /**
53
+ * Enable +debug logging for ALL loggers
54
+ *
55
+ * This overrides logger-level settings
56
+ */
57
+ export const globalLogDebug = () => (globalLevel = 3);
20
58
 
21
- /** Get the current log */
22
- export function getLogLines(): string[] {
23
- return LogQueue.toArray();
24
- }
59
+ /** Create a logger creator. Use the factory methods to finish making the logger */
60
+ export const logger = (name: string, color?: string): LoggerFactory => {
61
+ return {
62
+ default: () => new LoggerImpl(name, color, LogLevel.High),
63
+ debug: () => new LoggerImpl(name, color, LogLevel.Debug),
64
+ info: () => new LoggerImpl(name, color, LogLevel.Info),
65
+ off: () => new LoggerImpl(name, color, LogLevel.Off),
66
+ };
67
+ };
68
+
69
+ export type LoggerFactory = {
70
+ /** Standard important logger (warning and errors) */
71
+ default(): Logger;
72
+ /** Enable +info +debug logging for this logger */
73
+ debug(): Logger;
74
+ /** Enable +info logging for this logger */
75
+ info(): Logger;
76
+ /** Stop all logging, including warn and error */
77
+ off(): Logger;
78
+ };
79
+
80
+ export type Logger = {
81
+ /** Log a debug message */
82
+ debug(obj: unknown): void;
83
+ /** Log an info message */
84
+ info(obj: unknown): void;
85
+ /** Log a warning message */
86
+ warn(obj: unknown): void;
87
+ /** Log an error message */
88
+ error(obj: unknown): void;
89
+ };
25
90
 
26
- /** A general-purpose client side logger */
27
- export class Logger {
28
- /** The prefix of the logger */
29
- private prefix: string;
91
+ class LoggerImpl implements Logger {
92
+ name: string;
93
+ color: string | undefined;
94
+ level: LogLevel;
30
95
 
31
- constructor(prefix: string) {
32
- this.prefix = prefix;
96
+ constructor(name: string, color: string | undefined, level: LogLevel) {
97
+ this.name = name;
98
+ this.color =
99
+ "padding:0 3x;color:white" + (color ? `;background:${color}` : "");
100
+ this.level = level;
33
101
  }
34
102
 
35
- /** Log an info message */
36
- public info(msg: string) {
37
- const msgWithPrefix = `[${this.prefix}] ${msg}`;
38
- self.console.info(msgWithPrefix);
39
- pushLog(msgWithPrefix);
103
+ debug(obj: unknown) {
104
+ if (globalLevel !== LogLevel.Debug && this.level !== LogLevel.Debug) {
105
+ return;
106
+ }
107
+ console.log(
108
+ `%cDEBUG%c${this.name}%c ${obj}`,
109
+ "background:gray;color:white;padding:0 3px",
110
+ this.color,
111
+ "color:inherit;background:inherit",
112
+ );
40
113
  }
41
114
 
42
- /** Log a warning message */
43
- public warn(msg: string) {
44
- const msgWithPrefix = `[${this.prefix}] ${msg}`;
45
- self.console.warn(msgWithPrefix);
46
- pushLog(msgWithPrefix);
115
+ info(obj: unknown) {
116
+ if (globalLevel < LogLevel.Info && this.level < LogLevel.Info) {
117
+ return;
118
+ }
119
+ console.log(
120
+ `%cINFO%c${this.name}%c ${obj}`,
121
+ "background:green;color:white;padding:0 3px",
122
+ this.color,
123
+ "color:inherit;background:inherit",
124
+ );
47
125
  }
48
126
 
49
- /** Log an error message */
50
- public error(msg: unknown) {
51
- const msgWithPrefix = `[${this.prefix}] ${errstr(msg)}`;
52
- self.console.error(msgWithPrefix);
53
- self.console.error(msg);
54
- pushLog(msgWithPrefix);
127
+ warn(obj: unknown) {
128
+ if (globalLevel < LogLevel.High || this.level < LogLevel.High) {
129
+ return;
130
+ }
131
+ console.warn(
132
+ `%cWARN%c${this.name}%c ${obj}`,
133
+ "background:orange;color:white;padding:0 3px",
134
+ this.color,
135
+ "color:inherit;background:inherit",
136
+ );
137
+ }
138
+
139
+ error(obj: unknown) {
140
+ if (globalLevel < LogLevel.High || this.level < LogLevel.High) {
141
+ return;
142
+ }
143
+ const msg = errstr(obj);
144
+ console.error(
145
+ `%cERROR%c${this.name}%c ${msg}`,
146
+ "background:orange;color:white;padding:0 3px",
147
+ this.color,
148
+ "color:inherit;background:inherit",
149
+ );
150
+ if (msg !== obj) {
151
+ console.error(obj);
152
+ }
55
153
  }
56
154
  }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * A holder for an externally ref-counted object, where free and addref
3
+ * operations are asynchronous.
4
+ *
5
+ * See {@link makeErcType} for how to use
6
+ */
7
+ export type AsyncErc<TName, TRepr = number> = {
8
+ readonly type: TName;
9
+
10
+ /**
11
+ * Underlying object representation.
12
+ *
13
+ * The repr should not be undefinable. undefined means nullptr
14
+ */
15
+ readonly value: TRepr | undefined;
16
+
17
+ /**
18
+ * Free the underlying object.
19
+ *
20
+ * All weak references will be immediately invalidated, and this Erc becomes
21
+ * empty. Awaiting on the promise will ensure that the object is freed externally
22
+ */
23
+ free: () => Promise<void>;
24
+
25
+ /**
26
+ * Assign a new value to this Erc.
27
+ *
28
+ * The old value will be freed, and all weak references will be invalidated.
29
+ */
30
+ assign: (value: TRepr | undefined) => Promise<void>;
31
+
32
+ /**
33
+ * Take the inner value without freeing it.
34
+ *
35
+ * All weak references will be invalidated, and this Erc will become
36
+ * empty
37
+ */
38
+ take: () => TRepr | undefined;
39
+
40
+ /**
41
+ * Create a weak reference to the inner value.
42
+ *
43
+ * When this Erc is freed, all weak references will be invalidated.
44
+ */
45
+ getWeak: () => AsyncErcRef<TName, TRepr>;
46
+
47
+ /**
48
+ * Create a strong reference to the inner value, essentially
49
+ * incrementing the ref count.
50
+ */
51
+ getStrong: () => Promise<AsyncErc<TName, TRepr>>;
52
+ };
53
+
54
+ /**
55
+ * Weak reference to an externally ref-counted object.
56
+ *
57
+ * See {@link makeErcType} for how to use
58
+ */
59
+ export type AsyncErcRef<TName, TRepr = number> = {
60
+ readonly type: TName;
61
+
62
+ /**
63
+ * The underlying object representation.
64
+ *
65
+ * This may become undefined across async calls if the weak reference
66
+ * is invalidated
67
+ */
68
+ readonly value: TRepr | undefined;
69
+
70
+ /**
71
+ * Create a strong reference to the inner value, essentially
72
+ * incrementing the ref count.
73
+ */
74
+ getStrong: () => Promise<AsyncErc<TName, TRepr>>;
75
+ };
76
+
77
+ export type AsyncErcRefType<T> =
78
+ T extends AsyncErc<infer TName, infer TRepr>
79
+ ? AsyncErcRef<TName, TRepr>
80
+ : never;
81
+
82
+ export type AsyncErcTypeConstructor<TName, TRepr> = {
83
+ /**
84
+ * A marker value for the underlying object type.
85
+ *
86
+ * This is commonly a string literal or a symbol.
87
+ */
88
+ marker: TName;
89
+
90
+ /**
91
+ * The function to free the underlying object.
92
+ */
93
+ free: (value: TRepr) => Promise<void> | void;
94
+
95
+ /**
96
+ * Given a value, increase the ref count and return the new reference.
97
+ * The returned representation should be a different value if double indirection
98
+ * is used (each value is a pointer to the smart pointer), or the same value
99
+ * if single indirection is used (the value is pointing to the object itself).
100
+ */
101
+ addRef: (value: TRepr) => Promise<TRepr> | TRepr;
102
+ };
103
+
104
+ /** See {@link makeErcType} */
105
+ export const makeAsyncErcType = <TName, TRepr>({
106
+ marker,
107
+ free,
108
+ addRef,
109
+ }: AsyncErcTypeConstructor<TName, TRepr>): ((
110
+ value: TRepr | undefined,
111
+ ) => AsyncErc<TName, TRepr>) => {
112
+ const createStrongRef = (
113
+ value: TRepr | undefined,
114
+ ): AsyncErc<TName, TRepr> => {
115
+ let weakRef:
116
+ | (AsyncErcRef<TName, TRepr> & { invalidate: () => void })
117
+ | undefined = undefined;
118
+ const invalidateWeakRef = () => {
119
+ if (!weakRef) {
120
+ return;
121
+ }
122
+ const oldWeakRef = weakRef;
123
+ weakRef = undefined;
124
+ oldWeakRef.invalidate();
125
+ };
126
+ const createWeakRef = (initialValue: TRepr | undefined) => {
127
+ const weak = {
128
+ type: marker,
129
+ value: initialValue,
130
+ invalidate: () => {
131
+ weak.value = undefined;
132
+ },
133
+ getStrong: async () => {
134
+ if (weak.value === undefined) {
135
+ return createStrongRef(undefined);
136
+ }
137
+ return createStrongRef(await addRef(weak.value));
138
+ },
139
+ };
140
+ return weak;
141
+ };
142
+ const erc = {
143
+ type: marker,
144
+ value,
145
+ free: async () => {
146
+ if (erc.value !== undefined) {
147
+ invalidateWeakRef();
148
+ const freePromise = free(erc.value);
149
+ erc.value = undefined;
150
+ await freePromise;
151
+ }
152
+ },
153
+ assign: async (newValue: TRepr | undefined) => {
154
+ await erc.free();
155
+ erc.value = newValue;
156
+ },
157
+ take: () => {
158
+ invalidateWeakRef();
159
+ const oldValue = erc.value;
160
+ erc.value = undefined;
161
+ return oldValue;
162
+ },
163
+ getWeak: () => {
164
+ if (!weakRef) {
165
+ weakRef = createWeakRef(erc.value);
166
+ }
167
+ return weakRef;
168
+ },
169
+ getStrong: async () => {
170
+ if (erc.value === undefined) {
171
+ return createStrongRef(undefined);
172
+ }
173
+ return createStrongRef(await addRef(erc.value));
174
+ },
175
+ };
176
+ return erc;
177
+ };
178
+ return createStrongRef;
179
+ };
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { idgen, safeidgen } from "./idgen.ts";
4
+
5
+ describe("idgen", () => {
6
+ it("idgen generates ids", () => {
7
+ const idgen1 = idgen();
8
+ const idgen2 = idgen();
9
+ expect(idgen1()).toBe(2);
10
+ expect(idgen2()).toBe(2);
11
+ expect(idgen1()).toBe(3);
12
+ expect(idgen2()).toBe(3);
13
+ expect(idgen1()).toBe(4);
14
+ expect(idgen2()).toBe(4);
15
+ });
16
+ it("safeidgen wraps", () => {
17
+ const x = safeidgen(5);
18
+ expect(x()).toBe(2);
19
+ expect(x()).toBe(3);
20
+ expect(x()).toBe(4);
21
+ expect(x()).toBe(1);
22
+ expect(x()).toBe(2);
23
+ expect(x()).toBe(3);
24
+ expect(x()).toBe(4);
25
+ });
26
+ });
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Return an id generator that will generate ids in order
3
+ * from 1 to n, wrapping around to 1 when it's n
4
+ */
5
+ export const safeidgen = (n: number): (() => number) => {
6
+ let x = 1;
7
+ return () => {
8
+ x += 1;
9
+ if (x === n) {
10
+ x = 1;
11
+ }
12
+ return x;
13
+ };
14
+ };
15
+
16
+ /**
17
+ * Return an id generator that returns bigint,
18
+ * starting from 1n, and will always return a new bigint
19
+ */
20
+ export const bidgen = (): (() => bigint) => {
21
+ let x = 1n;
22
+ return () => {
23
+ x += 1n;
24
+ return x;
25
+ };
26
+ };
27
+
28
+ /**
29
+ * Returns an id generator that returns number staring from 1
30
+ * and always increasing. The number could become inaccurate
31
+ * (not integer) when exceeding Number.MAX_SAFE_INTEGER (which
32
+ * is 2^53 - 1
33
+ */
34
+ export const idgen = (): (() => number) => {
35
+ let x = 1;
36
+ return () => {
37
+ x += 1;
38
+ return x;
39
+ };
40
+ };
@@ -7,3 +7,4 @@
7
7
  export { cell, type CellConstructor, type Cell } from "./cell.ts";
8
8
  export { persist, type PersistConstructor, type Persist } from "./persist.ts";
9
9
  export * from "./erc.ts";
10
+ export * from "./async_erc.ts";
package/src/pref/dark.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { persist } from "../memory/persist.ts";
2
- import { injectStyle } from "./injectStyle.ts";
2
+ import { injectStyle } from "./inject_style.ts";
3
3
 
4
4
  const dark = persist({
5
5
  initial: false,
package/src/pref/index.ts CHANGED
@@ -8,5 +8,5 @@
8
8
  * @module
9
9
  */
10
10
  export * from "./dark.ts";
11
- export * from "./injectStyle.ts";
11
+ export * from "./inject_style.ts";
12
12
  export * from "./locale.ts";
@@ -189,7 +189,7 @@ export const setLocale = (newLocale: string): boolean => {
189
189
  return true;
190
190
  }
191
191
  settingLocale = supported;
192
- onBeforeChangeHook(supported).then((result) => {
192
+ void onBeforeChangeHook(supported).then((result) => {
193
193
  if (result.err) {
194
194
  return;
195
195
  }
package/src/sync/batch.ts CHANGED
@@ -214,7 +214,7 @@ class BatchImpl<TFn extends AnyFn> {
214
214
  let done = this.disregardExecutionTime;
215
215
  setTimeout(() => {
216
216
  if (done) {
217
- this.scheduleNext();
217
+ void this.scheduleNext();
218
218
  } else {
219
219
  done = true;
220
220
  }
@@ -225,7 +225,7 @@ class BatchImpl<TFn extends AnyFn> {
225
225
  if (!this.disregardExecutionTime) {
226
226
  if (done) {
227
227
  // interval already passed, we need to call it
228
- this.scheduleNext();
228
+ void this.scheduleNext();
229
229
  } else {
230
230
  done = true;
231
231
  }
File without changes