akanjs 2.0.5 → 2.0.7
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/README.ko.md +1 -1
- package/README.md +1 -1
- package/cli/application/application.command.ts +4 -1
- package/cli/application/application.runner.ts +6 -8
- package/cli/build.ts +3 -1
- package/cli/cloud/cloud.runner.ts +7 -8
- package/cli/index.js +288 -115
- package/cli/library/library.runner.ts +2 -2
- package/cli/module/module.runner.ts +2 -2
- package/cli/npmRegistry.ts +13 -0
- package/cli/openBrowser.ts +15 -0
- package/cli/pluralizeName.ts +5 -0
- package/cli/scalar/scalar.prompt.ts +2 -2
- package/cli/scalar/scalar.runner.ts +2 -2
- package/cli/semver.ts +18 -0
- package/cli/templates/lib/sig.ts +2 -2
- package/cli/workspace/workspace.runner.ts +3 -3
- package/client/cookie.ts +10 -15
- package/common/index.ts +1 -0
- package/common/jwtDecode.ts +17 -0
- package/constant/serialize.ts +1 -1
- package/devkit/akanApp/akanApp.host.ts +46 -9
- package/devkit/akanConfig/akanConfig.ts +2 -1
- package/devkit/capacitor.base.config.ts +18 -4
- package/devkit/capacitorApp.ts +118 -64
- package/devkit/incrementalBuilder/incrementalBuilder.host.ts +83 -9
- package/devkit/mobile/mobileTarget.ts +2 -1
- package/devkit/scanInfo.ts +1 -0
- package/document/dataLoader.ts +140 -6
- package/document/database.ts +1 -1
- package/package.json +7 -13
- package/server/akanApp.ts +250 -44
- package/server/di/diLifecycle.ts +1 -1
- package/server/processMetricsCollector.ts +79 -1
- package/server/proxy/localeWebProxy.ts +29 -12
- package/server/resolver/database.resolver.ts +82 -31
- package/server/resolver/signal.resolver.ts +67 -28
- package/service/ipcTypes.ts +5 -0
- package/service/predefinedAdaptor/database.adaptor.ts +95 -27
- package/service/predefinedAdaptor/solidSqlite.ts +7 -7
- package/service/predefinedAdaptor/storage.adaptor.ts +35 -9
- package/service/serviceModule.ts +1 -6
- package/signal/base.signal.ts +1 -1
- package/signal/index.ts +1 -0
- package/signal/middleware.ts +5 -1
- package/signal/signalContext.ts +85 -31
- package/signal/signalRegistry.ts +35 -10
- package/signal/trace.ts +279 -0
- package/types/cli/npmRegistry.d.ts +1 -0
- package/types/cli/openBrowser.d.ts +1 -0
- package/types/cli/pluralizeName.d.ts +1 -0
- package/types/cli/semver.d.ts +1 -0
- package/types/client/cookie.d.ts +6 -1
- package/types/common/index.d.ts +1 -0
- package/types/common/jwtDecode.d.ts +2 -0
- package/types/devkit/capacitorApp.d.ts +14 -5
- package/types/devkit/incrementalBuilder/incrementalBuilder.host.d.ts +9 -5
- package/types/document/dataLoader.d.ts +21 -2
- package/types/document/database.d.ts +1 -1
- package/types/server/processMetricsCollector.d.ts +2 -0
- package/types/service/ipcTypes.d.ts +5 -0
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +26 -32
- package/types/service/predefinedAdaptor/solidSqlite.d.ts +3 -3
- package/types/service/predefinedAdaptor/storage.adaptor.d.ts +8 -2
- package/types/service/serviceModule.d.ts +1 -1
- package/types/signal/index.d.ts +1 -0
- package/types/signal/signalContext.d.ts +4 -1
- package/types/signal/signalRegistry.d.ts +25 -4
- package/types/signal/trace.d.ts +97 -0
- package/types/ui/Signal/style.d.ts +15 -0
- package/ui/Signal/Arg.tsx +22 -15
- package/ui/Signal/Doc.tsx +30 -24
- package/ui/Signal/Listener.tsx +15 -39
- package/ui/Signal/Message.tsx +32 -50
- package/ui/Signal/Object.tsx +16 -13
- package/ui/Signal/PubSub.tsx +29 -47
- package/ui/Signal/Response.tsx +7 -17
- package/ui/Signal/RestApi.tsx +41 -57
- package/ui/Signal/WebSocket.tsx +1 -1
- package/ui/Signal/style.ts +36 -0
- package/webkit/useCsrValues.ts +147 -37
package/signal/signalRegistry.ts
CHANGED
|
@@ -5,6 +5,18 @@ import type { ServerSignalCls } from "./serverSignal";
|
|
|
5
5
|
import type { SliceCls } from "./slice";
|
|
6
6
|
import type { SerializedSignal } from "./types";
|
|
7
7
|
|
|
8
|
+
type SignalBaseRef<SigCls> = SigCls extends { srv: { srv: { refName: infer RefName } } } ? RefName : never;
|
|
9
|
+
type SignalWithBase<RefName extends string, SigCls> = SignalBaseRef<SigCls> extends RefName ? SigCls : never;
|
|
10
|
+
|
|
11
|
+
function assertSignalBase(refName: string, signalKind: string, signalCls: { srv?: { srv?: { refName?: unknown } } }) {
|
|
12
|
+
const signalRefName = signalCls.srv?.srv?.refName;
|
|
13
|
+
if (signalRefName !== refName) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Signal base mismatch: ${signalKind} uses "${String(signalRefName)}", but registry expected "${refName}". Use srv.${refName}.with(...) when the signal needs another service.`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
export class DatabaseSignal<
|
|
9
21
|
IntlCls extends InternalCls = InternalCls,
|
|
10
22
|
EndpCls extends EndpointCls = EndpointCls,
|
|
@@ -46,34 +58,47 @@ export class ServiceSignal<
|
|
|
46
58
|
|
|
47
59
|
/** Registry for database and service signals used by routing and fetch serialization. */
|
|
48
60
|
export class SignalRegistry {
|
|
49
|
-
static readonly #database = new Map<string, DatabaseSignal<
|
|
50
|
-
static readonly #service = new Map<string, ServiceSignal<
|
|
61
|
+
static readonly #database = new Map<string, DatabaseSignal<InternalCls, EndpointCls, SliceCls, ServerSignalCls>>();
|
|
62
|
+
static readonly #service = new Map<string, ServiceSignal<InternalCls, EndpointCls, ServerSignalCls>>();
|
|
51
63
|
|
|
52
64
|
static registerDatabase<
|
|
65
|
+
RefName extends string,
|
|
53
66
|
IntlCls extends InternalCls,
|
|
54
67
|
EndpCls extends EndpointCls,
|
|
55
68
|
SlceCls extends SliceCls,
|
|
56
69
|
SrvrCls extends ServerSignalCls,
|
|
57
70
|
>(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
refName: RefName,
|
|
72
|
+
internal: SignalWithBase<RefName, IntlCls>,
|
|
73
|
+
endpoint: SignalWithBase<RefName, EndpCls>,
|
|
74
|
+
slice: SignalWithBase<RefName, SlceCls>,
|
|
61
75
|
server: SrvrCls,
|
|
62
76
|
): DatabaseSignal<IntlCls, EndpCls, SlceCls, SrvrCls> {
|
|
77
|
+
assertSignalBase(refName, "internal", internal);
|
|
78
|
+
assertSignalBase(refName, "endpoint", endpoint);
|
|
79
|
+
assertSignalBase(refName, "slice", slice);
|
|
63
80
|
const databaseSignal = new DatabaseSignal(internal, endpoint, slice, server);
|
|
64
|
-
SignalRegistry.#database.set(
|
|
81
|
+
SignalRegistry.#database.set(refName, databaseSignal);
|
|
65
82
|
return databaseSignal;
|
|
66
83
|
}
|
|
67
84
|
static getDatabase(refName: string) {
|
|
68
85
|
return SignalRegistry.#database.get(refName);
|
|
69
86
|
}
|
|
70
|
-
static registerService<
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
static registerService<
|
|
88
|
+
RefName extends string,
|
|
89
|
+
IntlCls extends InternalCls,
|
|
90
|
+
EndpCls extends EndpointCls,
|
|
91
|
+
SrvrCls extends ServerSignalCls,
|
|
92
|
+
>(
|
|
93
|
+
refName: RefName,
|
|
94
|
+
internal: SignalWithBase<RefName, IntlCls>,
|
|
95
|
+
endpoint: SignalWithBase<RefName, EndpCls>,
|
|
73
96
|
server: SrvrCls,
|
|
74
97
|
): ServiceSignal<IntlCls, EndpCls, SrvrCls> {
|
|
98
|
+
assertSignalBase(refName, "internal", internal);
|
|
99
|
+
assertSignalBase(refName, "endpoint", endpoint);
|
|
75
100
|
const serviceSignal = new ServiceSignal(internal, endpoint, server);
|
|
76
|
-
SignalRegistry.#service.set(
|
|
101
|
+
SignalRegistry.#service.set(refName, serviceSignal);
|
|
77
102
|
return serviceSignal;
|
|
78
103
|
}
|
|
79
104
|
static getService(refName: string) {
|
package/signal/trace.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight request tracing for Akan signals.
|
|
5
|
+
*
|
|
6
|
+
* Everything here is gated behind the `AKAN_TRACE=1` environment flag so that the
|
|
7
|
+
* production hot path pays nothing when tracing is disabled (see {@link isTraceEnabled}).
|
|
8
|
+
* The goal is internal performance work (per-stage latency breakdown, query counts,
|
|
9
|
+
* cache hit ratio) rather than full distributed tracing.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
let traceEnabledCache: boolean | null = null;
|
|
13
|
+
|
|
14
|
+
/** Whether request tracing is enabled. Cached after first read. */
|
|
15
|
+
export const isTraceEnabled = (): boolean => {
|
|
16
|
+
if (traceEnabledCache === null) traceEnabledCache = process.env.AKAN_TRACE === "1";
|
|
17
|
+
return traceEnabledCache;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Override the trace flag at runtime (tests / harness control). */
|
|
21
|
+
export const setTraceEnabled = (enabled: boolean): void => {
|
|
22
|
+
traceEnabledCache = enabled;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export interface SpanRecord {
|
|
26
|
+
name: string;
|
|
27
|
+
durationMs: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const MAX_SPANS_PER_TRACE = 64;
|
|
31
|
+
|
|
32
|
+
/** Per-request trace context. Threaded via {@link AsyncLocalStorage}. */
|
|
33
|
+
export class SignalTrace {
|
|
34
|
+
readonly traceId: string;
|
|
35
|
+
readonly endpointKey: string;
|
|
36
|
+
readonly endpointType: string;
|
|
37
|
+
readonly startedAt: number;
|
|
38
|
+
readonly spans: SpanRecord[] = [];
|
|
39
|
+
dbQueryCount = 0;
|
|
40
|
+
dbQueryMs = 0;
|
|
41
|
+
cacheHits = 0;
|
|
42
|
+
cacheMisses = 0;
|
|
43
|
+
dataLoaderBatchCount = 0;
|
|
44
|
+
dataLoaderKeyCount = 0;
|
|
45
|
+
#finalized = false;
|
|
46
|
+
|
|
47
|
+
constructor(endpointKey: string, endpointType: string) {
|
|
48
|
+
this.endpointKey = endpointKey;
|
|
49
|
+
this.endpointType = endpointType;
|
|
50
|
+
this.startedAt = performance.now();
|
|
51
|
+
this.traceId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
recordSpan(name: string, durationMs: number): void {
|
|
55
|
+
if (this.spans.length >= MAX_SPANS_PER_TRACE) return;
|
|
56
|
+
this.spans.push({ name, durationMs });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
countDbQuery(durationMs: number): void {
|
|
60
|
+
this.dbQueryCount += 1;
|
|
61
|
+
this.dbQueryMs += durationMs;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
countCache(hit: boolean): void {
|
|
65
|
+
if (hit) this.cacheHits += 1;
|
|
66
|
+
else this.cacheMisses += 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
countDataLoaderBatch(keyCount: number): void {
|
|
70
|
+
this.dataLoaderBatchCount += 1;
|
|
71
|
+
this.dataLoaderKeyCount += keyCount;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
finalize(): void {
|
|
75
|
+
if (this.#finalized) return;
|
|
76
|
+
this.#finalized = true;
|
|
77
|
+
const totalMs = performance.now() - this.startedAt;
|
|
78
|
+
this.recordSpan("total", totalMs);
|
|
79
|
+
traceAggregator.ingest(this);
|
|
80
|
+
maybeFlushTraceFile();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const traceProcessStore = process as unknown as {
|
|
85
|
+
__akanTraceAls?: AsyncLocalStorage<SignalTrace>;
|
|
86
|
+
__akanTraceAggregator?: TraceAggregator;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
let alsInstance = traceProcessStore.__akanTraceAls;
|
|
90
|
+
if (!alsInstance) {
|
|
91
|
+
alsInstance = new AsyncLocalStorage<SignalTrace>();
|
|
92
|
+
traceProcessStore.__akanTraceAls = alsInstance;
|
|
93
|
+
}
|
|
94
|
+
const als = alsInstance;
|
|
95
|
+
|
|
96
|
+
export const getCurrentTrace = (): SignalTrace | undefined => als.getStore();
|
|
97
|
+
|
|
98
|
+
/** Run `fn` with `trace` as the ambient request trace. */
|
|
99
|
+
export const runWithTrace = <T>(trace: SignalTrace, fn: () => T): T => als.run(trace, fn);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Time an async stage under the current trace. When tracing is off (or no trace is
|
|
103
|
+
* active) this is a thin passthrough with no measurement overhead.
|
|
104
|
+
*/
|
|
105
|
+
export const traceSpan = async <T>(name: string, fn: () => Promise<T>): Promise<T> => {
|
|
106
|
+
const trace = getCurrentTrace();
|
|
107
|
+
if (!trace) return await fn();
|
|
108
|
+
const start = performance.now();
|
|
109
|
+
try {
|
|
110
|
+
return await fn();
|
|
111
|
+
} finally {
|
|
112
|
+
trace.recordSpan(name, performance.now() - start);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** Record a DB query duration against the current trace (no-op when untraced). */
|
|
117
|
+
export const traceDbQuery = (durationMs: number): void => {
|
|
118
|
+
getCurrentTrace()?.countDbQuery(durationMs);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/** Record a cache hit/miss against the current trace (no-op when untraced). */
|
|
122
|
+
export const traceCache = (hit: boolean): void => {
|
|
123
|
+
getCurrentTrace()?.countCache(hit);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/** Record a DataLoader batch against the current trace (no-op when untraced). */
|
|
127
|
+
export const traceDataLoaderBatch = (keyCount: number): void => {
|
|
128
|
+
getCurrentTrace()?.countDataLoaderBatch(keyCount);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
interface SpanStat {
|
|
132
|
+
count: number;
|
|
133
|
+
sumMs: number;
|
|
134
|
+
maxMs: number;
|
|
135
|
+
samples: number[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface EndpointStat {
|
|
139
|
+
endpointKey: string;
|
|
140
|
+
endpointType: string;
|
|
141
|
+
requests: number;
|
|
142
|
+
spans: Map<string, SpanStat>;
|
|
143
|
+
dbQueryCount: number;
|
|
144
|
+
dbQueryMs: number;
|
|
145
|
+
cacheHits: number;
|
|
146
|
+
cacheMisses: number;
|
|
147
|
+
dataLoaderBatchCount: number;
|
|
148
|
+
dataLoaderKeyCount: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const RING_SIZE = 1024;
|
|
152
|
+
|
|
153
|
+
const percentile = (sorted: number[], p: number): number => {
|
|
154
|
+
if (sorted.length === 0) return 0;
|
|
155
|
+
const idx = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length));
|
|
156
|
+
return sorted[idx] ?? 0;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Process-wide aggregator. Keeps rolling per-endpoint, per-span statistics with a
|
|
161
|
+
* bounded sample ring per span so percentiles stay representative of steady state
|
|
162
|
+
* without unbounded memory growth.
|
|
163
|
+
*/
|
|
164
|
+
class TraceAggregator {
|
|
165
|
+
#endpoints = new Map<string, EndpointStat>();
|
|
166
|
+
|
|
167
|
+
ingest(trace: SignalTrace): void {
|
|
168
|
+
const id = `${trace.endpointType}:${trace.endpointKey}`;
|
|
169
|
+
let stat = this.#endpoints.get(id);
|
|
170
|
+
if (!stat) {
|
|
171
|
+
stat = {
|
|
172
|
+
endpointKey: trace.endpointKey,
|
|
173
|
+
endpointType: trace.endpointType,
|
|
174
|
+
requests: 0,
|
|
175
|
+
spans: new Map(),
|
|
176
|
+
dbQueryCount: 0,
|
|
177
|
+
dbQueryMs: 0,
|
|
178
|
+
cacheHits: 0,
|
|
179
|
+
cacheMisses: 0,
|
|
180
|
+
dataLoaderBatchCount: 0,
|
|
181
|
+
dataLoaderKeyCount: 0,
|
|
182
|
+
};
|
|
183
|
+
this.#endpoints.set(id, stat);
|
|
184
|
+
}
|
|
185
|
+
stat.requests += 1;
|
|
186
|
+
stat.dbQueryCount += trace.dbQueryCount;
|
|
187
|
+
stat.dbQueryMs += trace.dbQueryMs;
|
|
188
|
+
stat.cacheHits += trace.cacheHits;
|
|
189
|
+
stat.cacheMisses += trace.cacheMisses;
|
|
190
|
+
stat.dataLoaderBatchCount += trace.dataLoaderBatchCount;
|
|
191
|
+
stat.dataLoaderKeyCount += trace.dataLoaderKeyCount;
|
|
192
|
+
for (const span of trace.spans) {
|
|
193
|
+
let spanStat = stat.spans.get(span.name);
|
|
194
|
+
if (!spanStat) {
|
|
195
|
+
spanStat = { count: 0, sumMs: 0, maxMs: 0, samples: [] };
|
|
196
|
+
stat.spans.set(span.name, spanStat);
|
|
197
|
+
}
|
|
198
|
+
spanStat.count += 1;
|
|
199
|
+
spanStat.sumMs += span.durationMs;
|
|
200
|
+
spanStat.maxMs = Math.max(spanStat.maxMs, span.durationMs);
|
|
201
|
+
if (spanStat.samples.length < RING_SIZE) spanStat.samples.push(span.durationMs);
|
|
202
|
+
else spanStat.samples[spanStat.count % RING_SIZE] = span.durationMs;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
reset(): void {
|
|
207
|
+
this.#endpoints.clear();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Summarized snapshot suitable for JSON exposure on the metrics endpoint. */
|
|
211
|
+
snapshot() {
|
|
212
|
+
const endpoints = [...this.#endpoints.values()].map((stat) => {
|
|
213
|
+
const spans = [...stat.spans.entries()].map(([name, s]) => {
|
|
214
|
+
const sorted = [...s.samples].sort((a, b) => a - b);
|
|
215
|
+
return {
|
|
216
|
+
name,
|
|
217
|
+
count: s.count,
|
|
218
|
+
meanMs: round(s.sumMs / s.count),
|
|
219
|
+
p50Ms: round(percentile(sorted, 50)),
|
|
220
|
+
p95Ms: round(percentile(sorted, 95)),
|
|
221
|
+
p99Ms: round(percentile(sorted, 99)),
|
|
222
|
+
maxMs: round(s.maxMs),
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
spans.sort((a, b) => b.meanMs - a.meanMs);
|
|
226
|
+
const cacheTotal = stat.cacheHits + stat.cacheMisses;
|
|
227
|
+
return {
|
|
228
|
+
endpoint: `${stat.endpointType}:${stat.endpointKey}`,
|
|
229
|
+
requests: stat.requests,
|
|
230
|
+
avgDbQueriesPerRequest: round(stat.dbQueryCount / stat.requests),
|
|
231
|
+
avgDbQueryMsPerRequest: round(stat.dbQueryMs / stat.requests),
|
|
232
|
+
cacheHitRatio: cacheTotal ? round(stat.cacheHits / cacheTotal, 4) : null,
|
|
233
|
+
avgDataLoaderBatchSize: stat.dataLoaderBatchCount
|
|
234
|
+
? round(stat.dataLoaderKeyCount / stat.dataLoaderBatchCount)
|
|
235
|
+
: null,
|
|
236
|
+
spans,
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
endpoints.sort((a, b) => b.requests - a.requests);
|
|
240
|
+
return { enabled: isTraceEnabled(), endpoints };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const round = (value: number, digits = 3): number => {
|
|
245
|
+
const factor = 10 ** digits;
|
|
246
|
+
return Math.round(value * factor) / factor;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
let aggregatorInstance = traceProcessStore.__akanTraceAggregator;
|
|
250
|
+
if (!aggregatorInstance) {
|
|
251
|
+
aggregatorInstance = new TraceAggregator();
|
|
252
|
+
traceProcessStore.__akanTraceAggregator = aggregatorInstance;
|
|
253
|
+
}
|
|
254
|
+
export const traceAggregator: TraceAggregator = aggregatorInstance;
|
|
255
|
+
|
|
256
|
+
/** Snapshot of all aggregated trace stats. Safe to call when tracing is disabled. */
|
|
257
|
+
export const getTraceSnapshot = () => traceAggregator.snapshot();
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Optional file sink for the aggregated snapshot.
|
|
261
|
+
*
|
|
262
|
+
* The akan worker loads the app-server bundle and the framework metrics collector in
|
|
263
|
+
* separate module realms that share neither `globalThis` nor `process`, so the in-realm
|
|
264
|
+
* aggregator that records spans cannot be read by the metrics endpoint's collector. When
|
|
265
|
+
* `AKAN_TRACE_FILE` is set, the recording realm instead flushes the cumulative snapshot to
|
|
266
|
+
* that path (throttled), where any external reader (e.g. the benchmark harness) can pick it
|
|
267
|
+
* up. This is a benchmarking/diagnostics aid, not a production hot-path concern.
|
|
268
|
+
*/
|
|
269
|
+
const traceFilePath = process.env.AKAN_TRACE_FILE;
|
|
270
|
+
let lastTraceFlushAt = 0;
|
|
271
|
+
const TRACE_FLUSH_INTERVAL_MS = 1_000;
|
|
272
|
+
|
|
273
|
+
const maybeFlushTraceFile = (): void => {
|
|
274
|
+
if (!traceFilePath) return;
|
|
275
|
+
const now = Date.now();
|
|
276
|
+
if (now - lastTraceFlushAt < TRACE_FLUSH_INTERVAL_MS) return;
|
|
277
|
+
lastTraceFlushAt = now;
|
|
278
|
+
void Bun.write(traceFilePath, JSON.stringify(traceAggregator.snapshot())).catch(() => {});
|
|
279
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getLatestPackageVersion(packageName: string, tag?: string): Promise<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function openBrowser(url: string): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function pluralizeName(name: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function compareSemver(a: string, b: string): number;
|
package/types/client/cookie.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { Account } from "akanjs/fetch";
|
|
2
|
+
interface CookieOptions {
|
|
3
|
+
path?: string;
|
|
4
|
+
sameSite?: "strict" | "lax" | "none";
|
|
5
|
+
secure?: boolean;
|
|
6
|
+
}
|
|
2
7
|
export declare const cookies: () => Map<string, {
|
|
3
8
|
name: string;
|
|
4
9
|
value: string;
|
|
5
10
|
}>;
|
|
6
|
-
export declare const setCookie: (key: string, value: string, options?:
|
|
11
|
+
export declare const setCookie: (key: string, value: string, options?: CookieOptions) => void;
|
|
7
12
|
export declare const getCookie: (key: string) => string | undefined;
|
|
8
13
|
export declare const removeCookie: (key: string, options?: {
|
|
9
14
|
path: string;
|
package/types/common/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { isEmail } from "./isEmail.d.ts";
|
|
|
11
11
|
export { isPhoneNumber } from "./isPhoneNumber.d.ts";
|
|
12
12
|
export { isQueryEqual } from "./isQueryEqual.d.ts";
|
|
13
13
|
export { isValidDate } from "./isValidDate.d.ts";
|
|
14
|
+
export { decodeJwtPayload } from "./jwtDecode.d.ts";
|
|
14
15
|
export { Logger, type LoggerSink, type LoggerSinkEntry, type LogLevel } from "./Logger.d.ts";
|
|
15
16
|
export { type AkanI18nConfig, type AkanI18nConfigInput, DEFAULT_AKAN_I18N, parseAkanI18nEnv, resolveAkanI18nConfig, } from "./localeConfig.d.ts";
|
|
16
17
|
export { lowerlize } from "./lowerlize.d.ts";
|
|
@@ -8,6 +8,8 @@ interface RunConfig {
|
|
|
8
8
|
env: "local" | "debug" | "develop" | "main";
|
|
9
9
|
regenerate?: boolean;
|
|
10
10
|
}
|
|
11
|
+
interface PrepareConfig extends RunConfig {
|
|
12
|
+
}
|
|
11
13
|
export declare class CapacitorApp {
|
|
12
14
|
#private;
|
|
13
15
|
private readonly app;
|
|
@@ -18,19 +20,26 @@ export declare class CapacitorApp {
|
|
|
18
20
|
};
|
|
19
21
|
iosTargetName: string;
|
|
20
22
|
readonly targetRoot: string;
|
|
23
|
+
readonly targetRootPath: string;
|
|
24
|
+
readonly targetWebRoot: string;
|
|
25
|
+
readonly targetAssetRoot: string;
|
|
26
|
+
readonly iosRootPath = "ios";
|
|
27
|
+
readonly iosProjectPath = "ios/App";
|
|
28
|
+
readonly androidRootPath = "android";
|
|
21
29
|
constructor(app: AppExecutor, target: AkanMobileTargetConfig);
|
|
22
|
-
init({ platform, regenerate }?: {
|
|
30
|
+
init({ platform, operation, env, regenerate, }?: {
|
|
23
31
|
platform?: "ios" | "android";
|
|
24
|
-
|
|
25
|
-
}): Promise<this>;
|
|
32
|
+
} & Partial<PrepareConfig>): Promise<this>;
|
|
26
33
|
save(): Promise<void>;
|
|
27
|
-
buildIos(
|
|
34
|
+
buildIos({ env, regenerate }?: {
|
|
35
|
+
env?: RunConfig["env"];
|
|
28
36
|
regenerate?: boolean;
|
|
29
37
|
}): Promise<void>;
|
|
30
38
|
syncIos(): Promise<void>;
|
|
31
39
|
openIos(): Promise<void>;
|
|
32
40
|
runIos({ operation, env, regenerate }: RunConfig): Promise<void>;
|
|
33
|
-
buildAndroid(assembleType: "apk" | "aab",
|
|
41
|
+
buildAndroid(assembleType: "apk" | "aab", { env, regenerate }?: {
|
|
42
|
+
env?: RunConfig["env"];
|
|
34
43
|
regenerate?: boolean;
|
|
35
44
|
}): Promise<void>;
|
|
36
45
|
openAndroid(): Promise<void>;
|
|
@@ -7,6 +7,12 @@ interface IncrementalBuilderHostOptions {
|
|
|
7
7
|
env: Record<string, string>;
|
|
8
8
|
onMessage: (message: BuilderMessage) => void;
|
|
9
9
|
}
|
|
10
|
+
type IncrementalBuilderStatus = "starting" | "ready" | "restarting" | "stopped";
|
|
11
|
+
interface IncrementalBuilderStartOptions {
|
|
12
|
+
onExit?: () => void;
|
|
13
|
+
onReady?: () => void;
|
|
14
|
+
onRestartReady?: () => void;
|
|
15
|
+
}
|
|
10
16
|
export declare class IncrementalBuilderHost {
|
|
11
17
|
#private;
|
|
12
18
|
logger: Logger;
|
|
@@ -15,11 +21,9 @@ export declare class IncrementalBuilderHost {
|
|
|
15
21
|
app: App;
|
|
16
22
|
ready: boolean;
|
|
17
23
|
constructor({ app, entry, env, onMessage }: IncrementalBuilderHostOptions);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}): this;
|
|
22
|
-
send(message: BuilderMessage): void;
|
|
24
|
+
get status(): IncrementalBuilderStatus;
|
|
25
|
+
start(options?: IncrementalBuilderStartOptions): this;
|
|
26
|
+
send(message: BuilderMessage): boolean;
|
|
23
27
|
stop(): void;
|
|
24
28
|
static create(app: App, env: Record<string, string>, onMessage: (message: BuilderMessage) => void): Promise<IncrementalBuilderHost>;
|
|
25
29
|
}
|
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import type { QueryOf } from "akanjs/constant";
|
|
2
|
-
import DataLoader from "dataloader";
|
|
3
2
|
export declare const Id: StringConstructor;
|
|
4
3
|
export declare const ObjectId: StringConstructor;
|
|
5
4
|
export declare const Mixed: ObjectConstructor;
|
|
6
|
-
export { DataLoader };
|
|
7
5
|
type LoaderItem = Record<string, unknown>;
|
|
8
6
|
type LoaderModel = {
|
|
9
7
|
find: (query: QueryOf<unknown>) => Promise<LoaderItem[]> | {
|
|
10
8
|
then: Promise<LoaderItem[]>["then"];
|
|
11
9
|
};
|
|
12
10
|
};
|
|
11
|
+
type BatchLoadFn<Key, Value> = (keys: readonly Key[]) => PromiseLike<ReadonlyArray<Value | Error>> | ReadonlyArray<Value | Error>;
|
|
12
|
+
interface DataLoaderOptions<Key, CacheKey> {
|
|
13
|
+
cache?: boolean;
|
|
14
|
+
cacheKeyFn?: (key: Key) => CacheKey;
|
|
15
|
+
batch?: boolean;
|
|
16
|
+
batchScheduleFn?: (callback: () => void) => void;
|
|
17
|
+
maxBatchSize?: number;
|
|
18
|
+
name?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Minimal DataLoader-compatible batch loader used by Akan document resolvers. */
|
|
21
|
+
export declare class DataLoader<Key, Value, CacheKey = Key> {
|
|
22
|
+
#private;
|
|
23
|
+
readonly name?: string;
|
|
24
|
+
constructor(batchLoadFn: BatchLoadFn<Key, Value>, options?: DataLoaderOptions<Key, CacheKey>);
|
|
25
|
+
load(key: Key): Promise<Value>;
|
|
26
|
+
loadMany(keys: readonly Key[]): Promise<Array<Value | Error>>;
|
|
27
|
+
clear(key: Key): this;
|
|
28
|
+
clearAll(): this;
|
|
29
|
+
prime(key: Key, value: Value | Error): this;
|
|
30
|
+
}
|
|
13
31
|
export declare const createLoader: <Key, Value>(model: LoaderModel, fieldName?: string, defaultQuery?: QueryOf<unknown>) => DataLoader<Key, Value, Key>;
|
|
14
32
|
export declare const createArrayLoader: <K, V>(model: LoaderModel, fieldName?: string, defaultQuery?: QueryOf<unknown>) => DataLoader<K, V, K>;
|
|
15
33
|
export declare const createArrayElementLoader: <K, V>(model: LoaderModel, fieldName?: string, defaultQuery?: QueryOf<unknown>) => DataLoader<K, V, K>;
|
|
16
34
|
export declare const createQueryLoader: <Key, Value>(model: LoaderModel, queryKeys: string[], defaultQuery?: QueryOf<unknown>) => DataLoader<Key, Value, Key>;
|
|
17
35
|
export type Loader<Field, Value> = DataLoader<Field, Value | null>;
|
|
36
|
+
export {};
|
|
@@ -2,7 +2,7 @@ import type { Dayjs, MergedValues, PromiseOrObject } from "akanjs/base";
|
|
|
2
2
|
import { Logger } from "akanjs/common";
|
|
3
3
|
import type { DocumentModel, QueryOf } from "akanjs/constant";
|
|
4
4
|
import type { CacheAdaptor } from "akanjs/service";
|
|
5
|
-
import type DataLoader from "
|
|
5
|
+
import type { DataLoader } from "./dataLoader.d.ts";
|
|
6
6
|
import type { ExtractQuery, ExtractSort, FilterInstance } from "./filterMeta.d.ts";
|
|
7
7
|
import type { CRUDEventType, Mdl, SaveEventType } from "./into.d.ts";
|
|
8
8
|
import type { DataInputOf, FindQueryOption, ListQueryOption } from "./types.d.ts";
|
|
@@ -2,6 +2,8 @@ import type { AkanMetricsReport } from "akanjs/service";
|
|
|
2
2
|
export declare class ProcessMetricsCollector {
|
|
3
3
|
#private;
|
|
4
4
|
static parseMemoryLogIntervalMs(value?: string | undefined): number;
|
|
5
|
+
/** Begin sampling event-loop lag. Idempotent; safe to call from each server role. */
|
|
6
|
+
static startEventLoopLagMonitor(intervalMs?: number): void;
|
|
5
7
|
static collect(extra?: AkanMetricsReport): Promise<AkanMetricsReport>;
|
|
6
8
|
static formatBytes(bytes?: number): string;
|
|
7
9
|
static format(metrics: AkanMetricsReport): string;
|
|
@@ -108,6 +108,11 @@ export interface AkanMetricsReport {
|
|
|
108
108
|
httpHtmlCacheHits?: number;
|
|
109
109
|
httpHtmlCacheMisses?: number;
|
|
110
110
|
httpHtmlCacheBypass?: number;
|
|
111
|
+
eventLoopLagMeanMs?: number;
|
|
112
|
+
eventLoopLagP99Ms?: number;
|
|
113
|
+
eventLoopLagMaxMs?: number;
|
|
114
|
+
gcDurationMs?: number;
|
|
115
|
+
trace?: unknown;
|
|
111
116
|
}
|
|
112
117
|
export type AkanIpcMessage = {
|
|
113
118
|
type: "ready";
|
|
@@ -67,33 +67,20 @@ export interface DocumentStore {
|
|
|
67
67
|
modifiedCount: number;
|
|
68
68
|
upsertedId: string | null;
|
|
69
69
|
}>;
|
|
70
|
-
find(query?: DocumentQuery, options?:
|
|
71
|
-
sort?: SortOption;
|
|
72
|
-
skip?: number | null;
|
|
73
|
-
limit?: number | null;
|
|
74
|
-
sample?: number;
|
|
75
|
-
}): Promise<any[]>;
|
|
70
|
+
find(query?: DocumentQuery, options?: FindManyOptions): Promise<any[]>;
|
|
76
71
|
findIds(query?: DocumentQuery, options?: {
|
|
77
72
|
sort?: SortOption;
|
|
78
73
|
skip?: number | null;
|
|
79
74
|
limit?: number | null;
|
|
80
75
|
sample?: number;
|
|
81
76
|
}): Promise<string[]>;
|
|
82
|
-
findOne(query?: DocumentQuery, options?:
|
|
83
|
-
sort?: SortOption;
|
|
84
|
-
skip?: number | null;
|
|
85
|
-
sample?: boolean;
|
|
86
|
-
}): Promise<any | null>;
|
|
77
|
+
findOne(query?: DocumentQuery, options?: FindOneOptions): Promise<any | null>;
|
|
87
78
|
findId(query?: DocumentQuery, options?: {
|
|
88
79
|
sort?: SortOption;
|
|
89
80
|
skip?: number | null;
|
|
90
81
|
sample?: boolean;
|
|
91
82
|
}): Promise<string | null>;
|
|
92
|
-
pickOne(query?: DocumentQuery, options?:
|
|
93
|
-
sort?: SortOption;
|
|
94
|
-
skip?: number | null;
|
|
95
|
-
sample?: boolean;
|
|
96
|
-
}): Promise<any>;
|
|
83
|
+
pickOne(query?: DocumentQuery, options?: FindOneOptions): Promise<any>;
|
|
97
84
|
pickById(id: string): Promise<any>;
|
|
98
85
|
exists(query?: DocumentQuery): Promise<string | null>;
|
|
99
86
|
count(query?: DocumentQuery): Promise<number>;
|
|
@@ -128,6 +115,20 @@ type FieldMap = Record<string, {
|
|
|
128
115
|
[key: string]: unknown;
|
|
129
116
|
}>;
|
|
130
117
|
type SortOption = Record<string, 1 | -1> | null | undefined;
|
|
118
|
+
type ProjectionOption = Partial<Record<string, boolean>> | null | undefined;
|
|
119
|
+
type FindManyOptions = {
|
|
120
|
+
sort?: SortOption;
|
|
121
|
+
skip?: number | null;
|
|
122
|
+
limit?: number | null;
|
|
123
|
+
sample?: number;
|
|
124
|
+
select?: ProjectionOption;
|
|
125
|
+
};
|
|
126
|
+
type FindOneOptions = {
|
|
127
|
+
sort?: SortOption;
|
|
128
|
+
skip?: number | null;
|
|
129
|
+
sample?: boolean;
|
|
130
|
+
select?: ProjectionOption;
|
|
131
|
+
};
|
|
131
132
|
interface DocumentDatabaseOwner {
|
|
132
133
|
getConnection(): AkanSqlClient;
|
|
133
134
|
getMeta(key: string): Promise<string | undefined> | string | undefined;
|
|
@@ -214,33 +215,20 @@ export declare class SqliteDocumentStore {
|
|
|
214
215
|
modifiedCount: number;
|
|
215
216
|
upsertedId: string | null;
|
|
216
217
|
}>;
|
|
217
|
-
find(query?: DocumentQuery, options?:
|
|
218
|
-
sort?: SortOption;
|
|
219
|
-
skip?: number | null;
|
|
220
|
-
limit?: number | null;
|
|
221
|
-
sample?: number;
|
|
222
|
-
}): Promise<any[]>;
|
|
218
|
+
find(query?: DocumentQuery, options?: FindManyOptions): Promise<any[]>;
|
|
223
219
|
findIds(query?: DocumentQuery, options?: {
|
|
224
220
|
sort?: SortOption;
|
|
225
221
|
skip?: number | null;
|
|
226
222
|
limit?: number | null;
|
|
227
223
|
sample?: number;
|
|
228
224
|
}): Promise<string[]>;
|
|
229
|
-
findOne(query?: DocumentQuery, options?:
|
|
230
|
-
sort?: SortOption;
|
|
231
|
-
skip?: number | null;
|
|
232
|
-
sample?: boolean;
|
|
233
|
-
}): Promise<any>;
|
|
225
|
+
findOne(query?: DocumentQuery, options?: FindOneOptions): Promise<any>;
|
|
234
226
|
findId(query?: DocumentQuery, options?: {
|
|
235
227
|
sort?: SortOption;
|
|
236
228
|
skip?: number | null;
|
|
237
229
|
sample?: boolean;
|
|
238
230
|
}): Promise<string | null>;
|
|
239
|
-
pickOne(query?: DocumentQuery, options?:
|
|
240
|
-
sort?: SortOption;
|
|
241
|
-
skip?: number | null;
|
|
242
|
-
sample?: boolean;
|
|
243
|
-
}): Promise<any>;
|
|
231
|
+
pickOne(query?: DocumentQuery, options?: FindOneOptions): Promise<any>;
|
|
244
232
|
pickById(id: string): Promise<any>;
|
|
245
233
|
exists(query?: DocumentQuery): Promise<string | null>;
|
|
246
234
|
count(query?: DocumentQuery): Promise<number>;
|
|
@@ -259,6 +247,11 @@ export declare class SqliteDocumentStore {
|
|
|
259
247
|
private applyDocumentUpdate;
|
|
260
248
|
private toRow;
|
|
261
249
|
private fromRow;
|
|
250
|
+
private normalizeProjection;
|
|
251
|
+
private projectionSql;
|
|
252
|
+
private projectionAlias;
|
|
253
|
+
private fromProjectedRow;
|
|
254
|
+
private parseProjectedValue;
|
|
262
255
|
private decodeDocumentPayload;
|
|
263
256
|
private decodeFieldValue;
|
|
264
257
|
private decodeMapValue;
|
|
@@ -266,6 +259,7 @@ export declare class SqliteDocumentStore {
|
|
|
266
259
|
hydrate(data: DocumentRecord, originalData?: DocumentRecord): any;
|
|
267
260
|
private runHooks;
|
|
268
261
|
private insertStmt;
|
|
262
|
+
private prepareReadStmt;
|
|
269
263
|
private assertValidRefName;
|
|
270
264
|
}
|
|
271
265
|
declare const SqliteDatabase_base: import("..").AdaptorCls<{}, {
|
|
@@ -13,12 +13,12 @@ export interface SolidEnv extends BaseEnv {
|
|
|
13
13
|
workspaceRoot?: string;
|
|
14
14
|
solid?: SolidConfig;
|
|
15
15
|
}
|
|
16
|
-
export type SolidValueType = "string" | "number" | "buffer";
|
|
16
|
+
export type SolidValueType = "string" | "number" | "buffer" | "json";
|
|
17
17
|
export declare const getSolidConfig: (env: SolidEnv) => Required<SolidConfig>;
|
|
18
18
|
export declare const openSolidDatabase: (config: Required<SolidConfig>) => Promise<Database>;
|
|
19
|
-
export declare const encodeSolidValue: (value:
|
|
19
|
+
export declare const encodeSolidValue: (value: unknown) => {
|
|
20
20
|
type: SolidValueType;
|
|
21
21
|
value: string | Buffer;
|
|
22
22
|
};
|
|
23
|
-
export declare const decodeSolidValue: <T
|
|
23
|
+
export declare const decodeSolidValue: <T>(type: SolidValueType, value: string | Buffer | null) => T | undefined;
|
|
24
24
|
export declare const toEpochMs: (value?: Dayjs | number | null) => number | null;
|