@teacss/engine 0.0.0-stage → 0.8.5-alpha.1

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.
@@ -0,0 +1,179 @@
1
+ // @ts-check
2
+ // Token collections the facade shares with `@teacss/core`: CountableSet (core/utils/countable-set.ts),
3
+ // expandInputGroups (core/generator.ts:517-535) and the FIFO caches of core/bounded-cache.ts.
4
+
5
+ import { expandClassGroups, splitClassTokens } from "../../binding.js";
6
+
7
+ export const MAX_RUNTIME_CACHE_ENTRIES = 10_000;
8
+
9
+ /** @template K @extends {Set<K>} */
10
+ export class CountableSet extends Set {
11
+ /** @type {Map<K, number>} */
12
+ _map = new Map();
13
+
14
+ /** @param {Iterable<K>} [values] */
15
+ constructor(values) {
16
+ super();
17
+ if (values) for (const key of values) this.add(key);
18
+ }
19
+
20
+ /** @param {K} key */
21
+ add(key) {
22
+ this._map.set(key, (this._map.get(key) ?? 0) + 1);
23
+ return super.add(key);
24
+ }
25
+
26
+ /** @param {K} key */
27
+ delete(key) {
28
+ if (!this._map.has(key)) return false;
29
+ this._map.delete(key);
30
+ return super.delete(key);
31
+ }
32
+
33
+ clear() {
34
+ this._map.clear();
35
+ super.clear();
36
+ }
37
+
38
+ /** @param {K} key */
39
+ getCount(key) {
40
+ return this._map.get(key) ?? 0;
41
+ }
42
+
43
+ /** @param {K} key @param {number} count */
44
+ setCount(key, count) {
45
+ this._map.set(key, count);
46
+ return super.add(key);
47
+ }
48
+ }
49
+
50
+ /** @param {unknown} value @returns {value is CountableSet<string>} */
51
+ export function isCountableSet(value) {
52
+ return value instanceof CountableSet;
53
+ }
54
+
55
+ /** @template K @param {CountableSet<K>} input */
56
+ export function copyCountableSet(input) {
57
+ const copied = new CountableSet();
58
+ for (const key of input) copied.setCount(key, input.getCount(key));
59
+ return copied;
60
+ }
61
+
62
+ /**
63
+ * Expands the tokens holding a `{…}` group in place; the parts go to the end, and a counted
64
+ * group's count carries over to each part.
65
+ * @template {Set<string>} T @param {T} tokens @returns {T}
66
+ */
67
+ export function expandInputGroups(tokens) {
68
+ const expanded = [...tokens].filter((token) => token.includes("{"));
69
+ if (expanded.length === 0) return tokens;
70
+ for (const token of expanded) {
71
+ const parts = splitClassTokens(expandClassGroups(token));
72
+ if (parts.length === 1 && parts[0] === token) continue;
73
+ const count = isCountableSet(tokens) ? tokens.getCount(token) : 1;
74
+ tokens.delete(token);
75
+ for (const part of parts) {
76
+ if (isCountableSet(tokens)) tokens.setCount(part, tokens.getCount(part) + count);
77
+ else tokens.add(part);
78
+ }
79
+ }
80
+ return tokens;
81
+ }
82
+
83
+ /**
84
+ * The part of the oracle `BoundedMap` the facade uses: ordinary keys are evicted oldest first,
85
+ * retained keys never count toward the limit.
86
+ * @template V @extends {Map<string, V>}
87
+ */
88
+ export class BoundedTokenCache extends Map {
89
+ /** @param {number} [maxEntries] */
90
+ constructor(maxEntries = MAX_RUNTIME_CACHE_ENTRIES) {
91
+ super();
92
+ this.maxEntries = maxEntries;
93
+ /** @type {Set<string>} */
94
+ this.retained = new Set();
95
+ /** Ordinary keys in insertion order, the order the native map iterates them. @type {Set<string>} */
96
+ this.order = new Set();
97
+ }
98
+
99
+ get retainedSize() {
100
+ return this.retained.size;
101
+ }
102
+
103
+ /** @param {string} key */
104
+ isRetained(key) {
105
+ return this.retained.has(key);
106
+ }
107
+
108
+ /** @param {string} key @param {V} value */
109
+ set(key, value) {
110
+ const exists = super.has(key);
111
+ super.set(key, value);
112
+ if (!exists && !this.retained.has(key)) this.order.add(key);
113
+ this.#enforceLimit();
114
+ return this;
115
+ }
116
+
117
+ /** @param {string} key */
118
+ delete(key) {
119
+ const deleted = super.delete(key);
120
+ this.order.delete(key);
121
+ this.retained.delete(key);
122
+ return deleted;
123
+ }
124
+
125
+ clear() {
126
+ super.clear();
127
+ this.order.clear();
128
+ this.retained.clear();
129
+ }
130
+
131
+ /**
132
+ * Replaces the retained entries (bounded-cache.ts:188-205 without the revision check).
133
+ * @param {Iterable<readonly [string, V]>} entries
134
+ */
135
+ replaceRetained(entries) {
136
+ const next = new Map(entries);
137
+ for (const key of this.retained) {
138
+ if (next.has(key)) continue;
139
+ super.delete(key);
140
+ this.retained.delete(key);
141
+ }
142
+ for (const [key, value] of next) {
143
+ this.order.delete(key);
144
+ super.set(key, value);
145
+ this.retained.add(key);
146
+ }
147
+ this.#enforceLimit();
148
+ }
149
+
150
+ #enforceLimit() {
151
+ while (this.size - this.retained.size > this.maxEntries) {
152
+ const oldest = this.order.values().next();
153
+ if (oldest.done) break;
154
+ this.order.delete(oldest.value);
155
+ super.delete(oldest.value);
156
+ }
157
+ }
158
+ }
159
+
160
+ /** FIFO bounded Set (the oracle `BoundedSet` without its `assumeNoPrototypeBypass` fast path). @extends {Set<string>} */
161
+ export class BoundedSet extends Set {
162
+ /** @param {number} [maxEntries] @param {Iterable<string>} [values] */
163
+ constructor(maxEntries = MAX_RUNTIME_CACHE_ENTRIES, values) {
164
+ super();
165
+ this.maxEntries = maxEntries;
166
+ if (values) for (const value of values) this.add(value);
167
+ }
168
+
169
+ /** @param {string} value */
170
+ add(value) {
171
+ super.add(value);
172
+ while (this.size > this.maxEntries) {
173
+ const oldest = this.values().next();
174
+ if (oldest.done) break;
175
+ super.delete(oldest.value);
176
+ }
177
+ return this;
178
+ }
179
+ }
@@ -0,0 +1,22 @@
1
+ // @ts-check
2
+
3
+ /** Errors the facade raises itself; an `Error` from napi (code `E_*`) passes through unwrapped. */
4
+ export class TeacssEngineError extends Error {
5
+ /** @param {string} code @param {string} message @param {Record<string, unknown>} [details] */
6
+ constructor(code, message, details = {}) {
7
+ super(message);
8
+ this.name = "TeacssEngineError";
9
+ this.code = code;
10
+ this.details = details;
11
+ }
12
+ }
13
+
14
+ /** v02.9 §2.1: the member needs `@teacss/core`, which is not installed. @param {string} member */
15
+ export function tsRequiredError(member) {
16
+ return new TeacssEngineError("E_TS_REQUIRED", `[@teacss/engine] ${member} requires @teacss/core (optional peer) to be installed`, { member });
17
+ }
18
+
19
+ /** v02.9 §3.3: the member needs the TypeScript engine, which has not been materialized. @param {string} member */
20
+ export function tsNotReadyError(member) {
21
+ return new TeacssEngineError("E_TS_REQUIRED", `[@teacss/engine] ${member} needs the TypeScript engine; await generator[Symbol.for('@teacss/engine/materialize')]() first or set engine: 'ts'`, { member });
22
+ }