akanjs 3.0.0-alpha.44 → 3.0.0-alpha.46

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.
@@ -209,12 +209,12 @@ declare global {
209
209
  [DEFAULT_VALUE]: boolean;
210
210
  [PURIFIED_VALUE]: boolean;
211
211
  [EXAMPLE_VALUE]: boolean;
212
- validate(value: boolean | number): boolean;
213
- parseValue(input: boolean | number): boolean | number;
214
- serializeValue(value: boolean | number): boolean | number;
215
- _parse(input: boolean | number): boolean;
216
- _serialize(value: boolean | number): boolean;
217
- _checkValue(value: boolean | number): void;
212
+ validate(value: boolean | number | string): boolean;
213
+ parseValue(input: boolean | number | string): boolean | number | string;
214
+ serializeValue(value: boolean | number | string): boolean | number | string;
215
+ _parse(input: boolean | number | string): boolean;
216
+ _serialize(value: boolean | number | string): boolean;
217
+ _checkValue(value: boolean | number | string): void;
218
218
  }
219
219
  interface DateConstructor {
220
220
  refName: "Date";
@@ -305,10 +305,14 @@ Object.assign(String, scalarPrimitiveStatics, {
305
305
  });
306
306
  PrimitiveRegistry.register(String);
307
307
 
308
- const normalizeBooleanPrimitiveValue = (value: boolean | number): boolean | null => {
308
+ const normalizeBooleanPrimitiveValue = (value: boolean | number | string): boolean | null => {
309
309
  if (typeof value === "boolean") return value;
310
310
  if (value === 1) return true;
311
311
  if (value === 0) return false;
312
+ if (typeof value !== "string") return null;
313
+ const text = value.trim().toLowerCase();
314
+ if (text === "true" || text === "1") return true;
315
+ if (text === "false" || text === "0") return false;
312
316
  return null;
313
317
  };
314
318
 
@@ -317,13 +321,13 @@ Object.assign(Boolean, {
317
321
  refName: "Boolean",
318
322
  [DEFAULT_VALUE]: false,
319
323
  [EXAMPLE_VALUE]: true,
320
- validate(value: boolean | number) {
324
+ validate(value: boolean | number | string) {
321
325
  return normalizeBooleanPrimitiveValue(value) !== null;
322
326
  },
323
- parseValue(input: boolean | number) {
327
+ parseValue(input: boolean | number | string) {
324
328
  return normalizeBooleanPrimitiveValue(input) ?? input;
325
329
  },
326
- serializeValue(value: boolean | number) {
330
+ serializeValue(value: boolean | number | string) {
327
331
  return normalizeBooleanPrimitiveValue(value) ?? value;
328
332
  },
329
333
  });
package/common/index.ts CHANGED
@@ -45,6 +45,7 @@ export { randomPicks } from "./randomPicks";
45
45
  export {
46
46
  assertUniqueRoutePatterns,
47
47
  compareRouteSpecificity,
48
+ getPageSourceFileViolation,
48
49
  getRouteExports,
49
50
  isRouteSourceFile,
50
51
  isSpecialRouteLeaf,
@@ -67,27 +67,37 @@ export function isRouteSourceFile(filePath: string): boolean {
67
67
  return tryParseRouteModuleKey(key) !== null;
68
68
  }
69
69
 
70
- export function validatePageSourceFile(filePath: string, options: ValidatePageSourceFileOptions = {}): boolean {
71
- if (!SOURCE_EXT_RE.test(filePath)) return false;
70
+ /**
71
+ * Why a `page/` file breaks the route convention, or null when it is fine. Null also covers a non-source
72
+ * asset, which `page/` tolerates. `akan sync <lib>` reports these alongside its other layout violations,
73
+ * so the rule stays in one place instead of being restated where it cannot afford to throw.
74
+ */
75
+ export function getPageSourceFileViolation(filePath: string): string | null {
76
+ if (!SOURCE_EXT_RE.test(filePath)) return null;
72
77
 
73
78
  const key = filePath.startsWith("./") ? filePath : `./${filePath.split(/[\\/]/).join("/")}`;
74
79
  const match = ROUTE_SOURCE_RE.exec(key);
75
- const displayPath = options.filePath ?? key;
76
- if (!match) throw new Error(`[route-convention] invalid page source file: ${displayPath}`);
80
+ if (!match) return "invalid page source file";
77
81
 
78
82
  const file = match[1] as string;
79
83
  const ext = match[2] as string;
80
84
  const leaf = file.split("/").filter(Boolean).at(-1);
81
- if (!leaf) throw new Error(`[route-convention] invalid page source file: ${displayPath}`);
85
+ if (!leaf) return "invalid page source file";
82
86
 
83
- if (ext !== "tsx") throw new Error(`[route-convention] route source files under page/ must use .tsx: ${displayPath}`);
87
+ if (ext !== "tsx") return "route source files under page/ must use .tsx";
84
88
  if (leaf.startsWith("_") && !RESERVED_ROUTE_FILES.has(leaf) && leaf !== INTERNAL_ROOT_LAYOUT_LEAF)
85
- throw new Error(
86
- `[route-convention] only _index.tsx, _layout.tsx and _overrides.tsx are allowed as reserved route files under page/: ${displayPath}`,
87
- );
88
- if (/^[A-Z]/.test(leaf))
89
- throw new Error(`[route-convention] route page filenames must not start with an uppercase letter: ${displayPath}`);
90
- return true;
89
+ return "only _index.tsx, _layout.tsx and _overrides.tsx are allowed as reserved route files under page/";
90
+ if (/^[A-Z]/.test(leaf)) return "route page filenames must not start with an uppercase letter";
91
+ return null;
92
+ }
93
+
94
+ export function validatePageSourceFile(filePath: string, options: ValidatePageSourceFileOptions = {}): boolean {
95
+ if (!SOURCE_EXT_RE.test(filePath)) return false;
96
+
97
+ const violation = getPageSourceFileViolation(filePath);
98
+ if (!violation) return true;
99
+ const key = filePath.startsWith("./") ? filePath : `./${filePath.split(/[\\/]/).join("/")}`;
100
+ throw new Error(`[route-convention] ${violation}: ${options.filePath ?? key}`);
91
101
  }
92
102
 
93
103
  export function validateSubRoutePageKey(
@@ -81,6 +81,7 @@ export class FetchClient {
81
81
  readonly handler: Record<string, FetchHandler>;
82
82
  readonly slice: Record<string, SliceMeta> = {};
83
83
  readonly sortKeyMap = new Map<string, string[]>();
84
+ readonly #originWs = new Map<string, WsClient>();
84
85
  readonly #handlerStore: Record<string, FetchHandler> = {};
85
86
  readonly #handlerFactory = new Map<string, FetchHandlerFactory>();
86
87
  #sharedRegistryAppliedVersion = 0;
@@ -95,8 +96,7 @@ export class FetchClient {
95
96
  ) {
96
97
  this.origin = origin;
97
98
  this.http = new HttpClient(origin, ErrorCls);
98
- const wsUri = `${origin.replace("http://", "ws://").replace("https://", "wss://")}/ws`;
99
- this.ws = new WsClient(wsUri, ErrorCls);
99
+ this.ws = new WsClient(FetchClient.#makeWsUri(origin), ErrorCls);
100
100
  Object.assign(this.#handlerStore, handler);
101
101
  this.handler = this.#makeHandlerProxy();
102
102
  this.applySignal(serializedSignal);
@@ -158,6 +158,7 @@ export class FetchClient {
158
158
  this.ErrorCls = ErrorCls;
159
159
  this.http.setErrorConstructor(ErrorCls);
160
160
  this.ws.setErrorConstructor(ErrorCls);
161
+ for (const ws of this.#originWs.values()) ws.setErrorConstructor(ErrorCls);
161
162
  }
162
163
  applySignal(serializedSignal: { [key: string]: SerializedSignal }, { share = true }: { share?: boolean } = {}) {
163
164
  if (share && Object.keys(serializedSignal).length > 0) {
@@ -232,6 +233,8 @@ export class FetchClient {
232
233
  }
233
234
  disconnect() {
234
235
  this.ws.destroy();
236
+ for (const ws of this.#originWs.values()) ws.destroy();
237
+ this.#originWs.clear();
235
238
  }
236
239
  clone({ origin, connect = true, jwt }: { origin?: string; connect?: boolean; jwt?: string } = {}) {
237
240
  const instance = new FetchClient(origin ?? this.origin, {}, this.serializedSignal, this.ErrorCls);
@@ -245,6 +248,20 @@ export class FetchClient {
245
248
  setJwt(jwt: string | null) {
246
249
  this.jwt = jwt;
247
250
  this.ws.setJwt(jwt);
251
+ for (const ws of this.#originWs.values()) ws.setJwt(jwt);
252
+ }
253
+
254
+ #resolveWs(origin?: string) {
255
+ if (!origin) return this.ws;
256
+ const target = origin.replace(/\/+$/, "");
257
+ if (target === this.origin.replace(/\/+$/, "")) return this.ws;
258
+ const cached = this.#originWs.get(target);
259
+ if (cached) return cached;
260
+ const ws = new WsClient(FetchClient.#makeWsUri(target), this.ErrorCls);
261
+ this.#originWs.set(target, ws);
262
+ ws.setJwt(this.jwt);
263
+ ws.connect();
264
+ return ws;
248
265
  }
249
266
  #makeAuthHeaders(option?: FetchPolicy): Record<string, string> {
250
267
  if (option?.token) return { Authorization: `Bearer ${option.token}` };
@@ -338,13 +355,13 @@ export class FetchClient {
338
355
  handleEvent(parsedReturn);
339
356
  };
340
357
  wrappedListeners.set(handleEvent, wrapped);
341
- this.ws.subscribe({
358
+ const ws = this.#resolveWs(fetchPolicy?.origin);
359
+ ws.subscribe({
342
360
  key,
343
361
  data,
344
362
  handleEvent: wrapped,
345
363
  });
346
- return () =>
347
- this.ws.unsubscribe({ key, data, handleEvent: wrappedListeners.get(handleEvent) ?? handleEvent });
364
+ return () => ws.unsubscribe({ key, data, handleEvent: wrappedListeners.get(handleEvent) ?? handleEvent });
348
365
  };
349
366
  });
350
367
  return;
@@ -356,8 +373,9 @@ export class FetchClient {
356
373
  const serializerMap = this.#makeArgSerializer(endpoint.args);
357
374
  return (...argData: unknown[]) => {
358
375
  const args = argData.slice(0, msgArgLength);
376
+ const fetchPolicy = argData[msgArgLength] as FetchPolicy | undefined;
359
377
  const data = msgArgs.map((arg, idx) => serializerMap.get(arg.name)?.(args[idx]) ?? null);
360
- this.ws.emit(key, data);
378
+ this.#resolveWs(fetchPolicy?.origin).emit(key, data);
361
379
  };
362
380
  });
363
381
  this.#setHandlerFactory(`listen${capitalize(key)}`, () => {
@@ -369,8 +387,9 @@ export class FetchClient {
369
387
  handleEvent(parsedReturn);
370
388
  };
371
389
  wrappedListeners.set(handleEvent, wrapped);
372
- this.ws.on(key, wrapped);
373
- return () => this.ws.off(key, wrappedListeners.get(handleEvent) ?? handleEvent);
390
+ const ws = this.#resolveWs(fetchPolicy.origin);
391
+ ws.on(key, wrapped);
392
+ return () => ws.off(key, wrappedListeners.get(handleEvent) ?? handleEvent);
374
393
  }) as FetchHandler;
375
394
  });
376
395
  return;
@@ -380,6 +399,10 @@ export class FetchClient {
380
399
  break;
381
400
  }
382
401
  }
402
+ static #makeWsUri(origin: string) {
403
+ return `${origin.replace("http://", "ws://").replace("https://", "wss://")}/ws`;
404
+ }
405
+
383
406
  static paginationArgs: SerializedArg[] = [
384
407
  { type: "search", name: "skip", refName: "Int" },
385
408
  { type: "search", name: "limit", refName: "Int" },
@@ -41,6 +41,7 @@ export class WsClient {
41
41
  #listenerMap = new Map<string, Set<Listener>>();
42
42
  #destroyed = false;
43
43
  #connectRequested = false;
44
+ #outbox: string[] = [];
44
45
  #unconnectedWarnTimers = new Map<string, ReturnType<typeof setTimeout>>();
45
46
  #jwt: string | null = null;
46
47
  connected = false;
@@ -94,6 +95,9 @@ export class WsClient {
94
95
  const data: WebsocketReqData = { key: option.key, data: option.data, subscribe: true };
95
96
  this.#ws?.send(JSON.stringify(data));
96
97
  });
98
+ const queued = this.#outbox;
99
+ this.#outbox = [];
100
+ for (const frame of queued) this.#ws?.send(frame);
97
101
  };
98
102
  this.#ws.onmessage = (e) => {
99
103
  try {
@@ -197,6 +201,7 @@ export class WsClient {
197
201
  }
198
202
  for (const timer of this.#unconnectedWarnTimers.values()) clearTimeout(timer);
199
203
  this.#unconnectedWarnTimers.clear();
204
+ this.#outbox = [];
200
205
  this.#ws?.close();
201
206
  this.#ws = null;
202
207
  }
@@ -240,28 +245,31 @@ export class WsClient {
240
245
  `[akanjs] WebSocket is not connected. Call fetch.instance.connect(), or drop the root layout "wsConnect = false", before ${action} "${key}".`,
241
246
  );
242
247
  }
243
- #warnUnconnectedSubscribe(key: string) {
244
- if (this.#connectRequested || this.#unconnectedWarnTimers.has(key)) return;
248
+ #warnUnconnected(action: "emit" | "subscribe", key: string) {
249
+ const timerKey = `${action}:${key}`;
250
+ if (this.#connectRequested || this.#unconnectedWarnTimers.has(timerKey)) return;
245
251
  const timer = setTimeout(() => {
246
- this.#unconnectedWarnTimers.delete(key);
252
+ this.#unconnectedWarnTimers.delete(timerKey);
247
253
  if (this.#connectRequested || this.#destroyed) return;
248
- this.#warnNotConnected("subscribe", key);
254
+ this.#warnNotConnected(action, key);
249
255
  }, 0);
250
- this.#unconnectedWarnTimers.set(key, timer);
256
+ this.#unconnectedWarnTimers.set(timerKey, timer);
251
257
  }
252
258
  emit(key: string, data: WsRequestPayload) {
259
+ const payload: WebsocketReqData = { key, data: Array.isArray(data) ? data : [data] };
260
+ const frame = JSON.stringify(payload);
261
+
253
262
  if (this.#ws?.readyState !== WebSocket.OPEN) {
254
- this.logger.warn("WebSocket not connected");
255
- this.#warnNotConnected("emit", key);
263
+ this.#outbox.push(frame);
264
+ this.#warnUnconnected("emit", key);
256
265
  return this;
257
266
  }
258
- const payload: WebsocketReqData = { key, data: Array.isArray(data) ? data : [data] };
259
- this.#ws.send(JSON.stringify(payload));
267
+ this.#ws.send(frame);
260
268
  return this;
261
269
  }
262
270
  subscribe(option: { key: string; data: unknown[]; handleEvent: (data: unknown) => void }) {
263
271
  const roomId = WsClient.makeRoomId(option.key, option.data);
264
- if (!this.#ws) this.#warnUnconnectedSubscribe(option.key);
272
+ if (!this.#ws) this.#warnUnconnected("subscribe", option.key);
265
273
  if (!this.#roomSubscribeMap.has(roomId)) {
266
274
  this.#roomSubscribeMap.set(roomId, { key: option.key, data: option.data, listener: new Set() });
267
275
  if (this.#ws?.readyState === WebSocket.OPEN) {
@@ -44,7 +44,7 @@ type QueryOrMutationFetchFn<E, SlceCls extends SliceCls | never> = (
44
44
  /** Typed off `PromptResult` rather than the endpoint's return ref, which is the `Any` carrier a prompt rides on. */
45
45
  type PromptFetchFn<E> = (...args: [...EndpInfoArgs<E>, fetchPolicy?: FetchPolicy]) => Promise<PromptMessage[]>;
46
46
 
47
- type MessageEmitFn<E> = (...args: EndpInfoArgs<E>) => void;
47
+ type MessageEmitFn<E> = (...args: [...EndpInfoArgs<E>, fetchPolicy?: FetchPolicy]) => void;
48
48
 
49
49
  type MessageListenFn<E, SlceCls extends SliceCls | never> = (
50
50
  handleEvent: (data: EndpInfoReturns<E, SlceCls>) => PromiseOrObject<void>,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-alpha.44",
3
+ "version": "3.0.0-alpha.46",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
package/service/adapt.ts CHANGED
@@ -4,8 +4,8 @@ import { type ExtractInjectInfoObject, type InjectBuilder, type InjectInfo, inje
4
4
 
5
5
  export interface Adaptor {
6
6
  readonly logger: Logger;
7
- onInit(): Promise<void>;
8
- onDestroy(): Promise<void>;
7
+ onInit(): Promise<void> | void;
8
+ onDestroy(): Promise<void> | void;
9
9
  }
10
10
 
11
11
  export type AdaptorCls<
@@ -30,9 +30,9 @@ export function adapt(name: string, injectBuilder?: InjectBuilder) {
30
30
  readonly logger = new Logger(name);
31
31
  static readonly [INJECT_META] = injectInfoMap;
32
32
  static readonly refName = name;
33
- async onInit() {
33
+ onInit(): Promise<void> | void {
34
34
  }
35
- async onDestroy() {
35
+ onDestroy(): Promise<void> | void {
36
36
  }
37
37
  }
38
38
  return Adaptor;
@@ -18,6 +18,7 @@ import {
18
18
  type DocumentUpdateOptions,
19
19
  documentQueryHelper,
20
20
  encodeDocumentValue,
21
+ isDocumentId,
21
22
  isDocumentUpdateNode,
22
23
  NoDocumentError,
23
24
  resolveDocumentUpdate,
@@ -1033,6 +1034,7 @@ export class SqlDocumentStore {
1033
1034
  async create(data: DocumentRecord, { runSaveHooks = true }: WriteHookOptions = {}) {
1034
1035
  const now = Date.now();
1035
1036
  const id = data.id ?? createDocumentId(now);
1037
+ if (!isDocumentId(id)) throw new Error(`Invalid ID value: ${id}`);
1036
1038
  const doc = this.hydrate(
1037
1039
  this.prepareDocument({
1038
1040
  ...data,
package/service/serve.ts CHANGED
@@ -43,9 +43,9 @@ const avoidKeys = new Set([
43
43
  export interface Service {
44
44
  readonly logger: Logger;
45
45
 
46
- onInit(): Promise<void>;
46
+ onInit(): Promise<void> | void;
47
47
  _libsOnInit(): Promise<void>;
48
- onDestroy(): Promise<void>;
48
+ onDestroy(): Promise<void> | void;
49
49
  _libsOnDestroy(): Promise<void>;
50
50
  }
51
51
 
@@ -121,9 +121,9 @@ export function serve(
121
121
  }
122
122
  static [INJECT_META] = {};
123
123
  readonly logger = new Logger(this.constructor.name);
124
- async onInit() {
124
+ onInit(): Promise<void> | void {
125
125
  }
126
- async onDestroy() {
126
+ onDestroy(): Promise<void> | void {
127
127
  }
128
128
  };
129
129
  applyMixins(srvRef, extSrvs, avoidKeys);
@@ -132,10 +132,11 @@ export function serve(
132
132
  const onDestroyFns = extSrvs.map((srv) => srv.prototype.onDestroy);
133
133
  Object.assign(srvRef.prototype, {
134
134
  async _libsOnInit(this: Service) {
135
- await Promise.all([...onInitFns.map((onInit) => onInit?.call(this)), this.onInit()]);
135
+
136
+ await Promise.all([...onInitFns, this.onInit].map(async (onInit) => await onInit?.call(this)));
136
137
  },
137
138
  async _libsOnDestroy(this: Service) {
138
- await Promise.all([...onDestroyFns.map((onDestroy) => onDestroy?.call(this)), this.onDestroy()]);
139
+ await Promise.all([...onDestroyFns, this.onDestroy].map(async (onDestroy) => await onDestroy?.call(this)));
139
140
  },
140
141
  });
141
142
 
@@ -7,8 +7,8 @@ export type InterceptorCls<Methods = {}, InjectMap extends { [key: string]: Inje
7
7
  Methods &
8
8
  ExtractInjectInfoObject<InjectMap> & {
9
9
  readonly logger: Logger;
10
- onInit(): Promise<void>;
11
- onDestroy(): Promise<void>;
10
+ onInit(): Promise<void> | void;
11
+ onDestroy(): Promise<void> | void;
12
12
  intercept(context: SignalContext): AsyncGenerator<unknown> | Promise<unknown>;
13
13
  },
14
14
  { readonly [INJECT_META]: InjectMap; readonly refName: string }
@@ -31,9 +31,9 @@ export function intercept(refName: string, injectBuilder?: InjectBuilder) {
31
31
  intercept(context: SignalContext): AsyncGenerator | Promise<(res: Response) => Promise<Response>> {
32
32
  return Promise.resolve((res: Response) => Promise.resolve(res));
33
33
  }
34
- async onInit() {
34
+ onInit(): Promise<void> | void {
35
35
  }
36
- async onDestroy() {
36
+ onDestroy(): Promise<void> | void {
37
37
  }
38
38
  };
39
39
  }
@@ -114,12 +114,12 @@ declare global {
114
114
  [DEFAULT_VALUE]: boolean;
115
115
  [PURIFIED_VALUE]: boolean;
116
116
  [EXAMPLE_VALUE]: boolean;
117
- validate(value: boolean | number): boolean;
118
- parseValue(input: boolean | number): boolean | number;
119
- serializeValue(value: boolean | number): boolean | number;
120
- _parse(input: boolean | number): boolean;
121
- _serialize(value: boolean | number): boolean;
122
- _checkValue(value: boolean | number): void;
117
+ validate(value: boolean | number | string): boolean;
118
+ parseValue(input: boolean | number | string): boolean | number | string;
119
+ serializeValue(value: boolean | number | string): boolean | number | string;
120
+ _parse(input: boolean | number | string): boolean;
121
+ _serialize(value: boolean | number | string): boolean;
122
+ _checkValue(value: boolean | number | string): void;
123
123
  }
124
124
  interface DateConstructor {
125
125
  refName: "Date";
@@ -24,7 +24,7 @@ export { pathGet } from "./pathGet.d.ts";
24
24
  export { pathSet } from "./pathSet.d.ts";
25
25
  export { randomPick } from "./randomPick.d.ts";
26
26
  export { randomPicks } from "./randomPicks.d.ts";
27
- export { assertUniqueRoutePatterns, compareRouteSpecificity, getRouteExports, isRouteSourceFile, isSpecialRouteLeaf, LAYOUT_ROUTE_EXPORTS, matchRoutePattern, normalizeRoutePattern, PAGE_ROUTE_EXPORTS, type ParsedRouteModuleKey, parseRouteModuleKey, RESERVED_ROUTE_CONFIG_EXPORTS, ROOT_LAYOUT_ROUTE_EXPORTS, type RouteModuleKind, routeSegmentToPatternPart, routeSegmentToTreePath, tryParseRouteModuleKey, type ValidatePageSourceFileOptions, type ValidateSubRoutePageKeyOptions, validatePageSourceFile, validateSubRoutePageKey, } from "./routeConvention.d.ts";
27
+ export { assertUniqueRoutePatterns, compareRouteSpecificity, getPageSourceFileViolation, getRouteExports, isRouteSourceFile, isSpecialRouteLeaf, LAYOUT_ROUTE_EXPORTS, matchRoutePattern, normalizeRoutePattern, PAGE_ROUTE_EXPORTS, type ParsedRouteModuleKey, parseRouteModuleKey, RESERVED_ROUTE_CONFIG_EXPORTS, ROOT_LAYOUT_ROUTE_EXPORTS, type RouteModuleKind, routeSegmentToPatternPart, routeSegmentToTreePath, tryParseRouteModuleKey, type ValidatePageSourceFileOptions, type ValidateSubRoutePageKeyOptions, validatePageSourceFile, validateSubRoutePageKey, } from "./routeConvention.d.ts";
28
28
  export { sleep } from "./sleep.d.ts";
29
29
  export { splitVersion } from "./splitVersion.d.ts";
30
30
  export { getBasePathFromPathname, parseBasePaths, parseSubRouteHosts, resolveSubRouteHosts } from "./subRoute.d.ts";
@@ -27,6 +27,12 @@ export interface ValidatePageSourceFileOptions {
27
27
  filePath?: string;
28
28
  }
29
29
  export declare function isRouteSourceFile(filePath: string): boolean;
30
+ /**
31
+ * Why a `page/` file breaks the route convention, or null when it is fine. Null also covers a non-source
32
+ * asset, which `page/` tolerates. `akan sync <lib>` reports these alongside its other layout violations,
33
+ * so the rule stays in one place instead of being restated where it cannot afford to throw.
34
+ */
35
+ export declare function getPageSourceFileViolation(filePath: string): string | null;
30
36
  export declare function validatePageSourceFile(filePath: string, options?: ValidatePageSourceFileOptions): boolean;
31
37
  export declare function validateSubRoutePageKey(key: string, basePaths: Iterable<string>, options?: ValidateSubRoutePageKeyOptions): void;
32
38
  export declare function parseRouteModuleKey(key: string): ParsedRouteModuleKey;
@@ -7,7 +7,7 @@ type EndpInfoReturns<E, SlceCls extends SliceCls | never> = EndpointClientReturn
7
7
  type QueryOrMutationFetchFn<E, SlceCls extends SliceCls | never> = (...args: [...EndpInfoArgs<E>, fetchPolicy?: FetchPolicy]) => Promise<EndpInfoReturns<E, SlceCls>>;
8
8
  /** Typed off `PromptResult` rather than the endpoint's return ref, which is the `Any` carrier a prompt rides on. */
9
9
  type PromptFetchFn<E> = (...args: [...EndpInfoArgs<E>, fetchPolicy?: FetchPolicy]) => Promise<PromptMessage[]>;
10
- type MessageEmitFn<E> = (...args: EndpInfoArgs<E>) => void;
10
+ type MessageEmitFn<E> = (...args: [...EndpInfoArgs<E>, fetchPolicy?: FetchPolicy]) => void;
11
11
  type MessageListenFn<E, SlceCls extends SliceCls | never> = (handleEvent: (data: EndpInfoReturns<E, SlceCls>) => PromiseOrObject<void>, options?: FetchPolicy) => () => void;
12
12
  type PubsubSubscribeFn<E, SlceCls extends SliceCls | never> = (...args: [
13
13
  ...EndpInfoArgs<E>,
@@ -3,8 +3,8 @@ import { Logger } from "akanjs/common";
3
3
  import { type ExtractInjectInfoObject, type InjectBuilder, type InjectInfo } from "./injectInfo.d.ts";
4
4
  export interface Adaptor {
5
5
  readonly logger: Logger;
6
- onInit(): Promise<void>;
7
- onDestroy(): Promise<void>;
6
+ onInit(): Promise<void> | void;
7
+ onDestroy(): Promise<void> | void;
8
8
  }
9
9
  export type AdaptorCls<Methods = any, InjectMap extends Record<string, InjectInfo> = {}> = Cls<Methods & ExtractInjectInfoObject<InjectMap> & Adaptor, {
10
10
  readonly [INJECT_META]: InjectMap;
@@ -10,9 +10,9 @@ interface ServiceOptions {
10
10
  export type ServiceType = "database" | "plain";
11
11
  export interface Service {
12
12
  readonly logger: Logger;
13
- onInit(): Promise<void>;
13
+ onInit(): Promise<void> | void;
14
14
  _libsOnInit(): Promise<void>;
15
- onDestroy(): Promise<void>;
15
+ onDestroy(): Promise<void> | void;
16
16
  _libsOnDestroy(): Promise<void>;
17
17
  }
18
18
  export type ServiceCls<RefName extends string = string, Methods = {}, InjectMap extends {
@@ -6,8 +6,8 @@ export type InterceptorCls<Methods = {}, InjectMap extends {
6
6
  [key: string]: InjectInfo;
7
7
  } = {}> = Cls<Methods & ExtractInjectInfoObject<InjectMap> & {
8
8
  readonly logger: Logger;
9
- onInit(): Promise<void>;
10
- onDestroy(): Promise<void>;
9
+ onInit(): Promise<void> | void;
10
+ onDestroy(): Promise<void> | void;
11
11
  intercept(context: SignalContext): AsyncGenerator<unknown> | Promise<unknown>;
12
12
  }, {
13
13
  readonly [INJECT_META]: InjectMap;