@arcote.tech/arc 0.7.16 → 0.7.18

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.
@@ -10,6 +10,8 @@
10
10
  */
11
11
  export { AuthAdapter } from "./auth-adapter";
12
12
  export type { DecodedToken } from "./auth-adapter";
13
+ export { awaitModuleSync, hasModuleSyncProvider, registerModuleSyncProvider, triggerModuleSync, } from "./module-sync-coordinator";
14
+ export type { ModuleSyncProvider } from "./module-sync-coordinator";
13
15
  export { CommandWire } from "./command-wire";
14
16
  export { EventWire } from "./event-wire";
15
17
  export type { QuerySubscriptionCallbacks, ReceivedEvent, SyncableEvent, } from "./event-wire";
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Module-sync coordinator — neutral seam letting token-setters (core/react)
3
+ * await the platform module loader WITHOUT core/react depending on platform.
4
+ *
5
+ * Token-gated code-splitting: setting a scope token must dynamically import
6
+ * that token's module chunks before routes inside them exist. The loader
7
+ * lives in the platform layer; setToken lives in core/react. This singleton
8
+ * is the bridge: platform's useModuleLoader registers a provider (runs
9
+ * syncModules), and setToken triggers a sync + returns a promise that
10
+ * resolves when the LATEST sync finishes.
11
+ *
12
+ * With no provider registered (server, SSR, tests, app-without-platform)
13
+ * every call resolves immediately — preserving the historical synchronous
14
+ * semantics. It NEVER rejects: a hung/failed sync resolves after a bounded
15
+ * timeout so navigation always proceeds.
16
+ */
17
+ export type ModuleSyncProvider = (scope?: string) => Promise<void>;
18
+ /**
19
+ * Register the function that performs a module sync. Returns an unregister fn.
20
+ * Last registration wins (only one platform loader is ever mounted).
21
+ */
22
+ export declare function registerModuleSyncProvider(fn: ModuleSyncProvider): () => void;
23
+ export declare function hasModuleSyncProvider(): boolean;
24
+ /**
25
+ * Trigger a sync through the registered provider; resolve when it finishes
26
+ * (or after `timeoutMs`). Coalesces concurrent calls — the published
27
+ * `latestSync` tracks the newest invocation.
28
+ *
29
+ * - No provider → resolves immediately (sync semantics preserved).
30
+ * - Provider hangs → resolves (never rejects) after `timeoutMs` + a warning.
31
+ * - Provider throws → resolves (never rejects) + a warning; the loader
32
+ * surfaces its own error state. setToken must not throw on module-load fail.
33
+ */
34
+ export declare function triggerModuleSync(scope?: string, timeoutMs?: number): Promise<void>;
35
+ /**
36
+ * Await the most recent sync without triggering a new one. Resolves
37
+ * immediately when nothing is in flight.
38
+ */
39
+ export declare function awaitModuleSync(): Promise<void>;
40
+ //# sourceMappingURL=module-sync-coordinator.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=module-sync-coordinator.test.d.ts.map
package/dist/index.js CHANGED
@@ -119,6 +119,56 @@ class AuthAdapter {
119
119
  this.scopes.clear();
120
120
  }
121
121
  }
122
+ // src/adapters/module-sync-coordinator.ts
123
+ var DEFAULT_TIMEOUT_MS = 8000;
124
+ var provider = null;
125
+ var latestSync = null;
126
+ var syncSeq = 0;
127
+ function registerModuleSyncProvider(fn) {
128
+ provider = fn;
129
+ return () => {
130
+ if (provider === fn)
131
+ provider = null;
132
+ };
133
+ }
134
+ function hasModuleSyncProvider() {
135
+ return provider !== null;
136
+ }
137
+ function triggerModuleSync(scope, timeoutMs = DEFAULT_TIMEOUT_MS) {
138
+ if (!provider) {
139
+ latestSync = Promise.resolve();
140
+ return latestSync;
141
+ }
142
+ const seq = ++syncSeq;
143
+ const run = (async () => {
144
+ try {
145
+ await provider(scope);
146
+ } catch (err) {
147
+ console.warn("[arc] module sync failed during setToken:", err);
148
+ }
149
+ })();
150
+ const guarded = new Promise((resolve) => {
151
+ let settled = false;
152
+ const done = () => {
153
+ if (settled)
154
+ return;
155
+ settled = true;
156
+ resolve();
157
+ };
158
+ const timer = setTimeout(() => {
159
+ console.warn(`[arc] module sync did not complete within ${timeoutMs}ms; proceeding anyway.`);
160
+ done();
161
+ }, timeoutMs);
162
+ timer?.unref?.();
163
+ run.then(done, done).finally(() => clearTimeout(timer));
164
+ });
165
+ if (seq === syncSeq)
166
+ latestSync = guarded;
167
+ return guarded;
168
+ }
169
+ function awaitModuleSync() {
170
+ return latestSync ?? Promise.resolve();
171
+ }
122
172
  // src/adapters/wire.ts
123
173
  class Wire {
124
174
  baseUrl;
@@ -4452,6 +4502,7 @@ class ScopedModel {
4452
4502
  for (const listener4 of this.tokenListeners) {
4453
4503
  listener4();
4454
4504
  }
4505
+ return triggerModuleSync(this.scopeName);
4455
4506
  }
4456
4507
  getToken() {
4457
4508
  return this.authAdapter.getToken();
@@ -5250,6 +5301,7 @@ class TokenCache {
5250
5301
  }
5251
5302
  export {
5252
5303
  view,
5304
+ triggerModuleSync,
5253
5305
  token,
5254
5306
  stringEnum,
5255
5307
  string,
@@ -5257,6 +5309,7 @@ export {
5257
5309
  secureDataStorage,
5258
5310
  route,
5259
5311
  resolveQueryChange,
5312
+ registerModuleSyncProvider,
5260
5313
  record,
5261
5314
  or,
5262
5315
  observeQueries,
@@ -5267,6 +5320,7 @@ export {
5267
5320
  mergeUnsafe,
5268
5321
  listener,
5269
5322
  id,
5323
+ hasModuleSyncProvider,
5270
5324
  file,
5271
5325
  extractDatabaseAgnosticSchema,
5272
5326
  executeDescriptor,
@@ -5285,6 +5339,7 @@ export {
5285
5339
  buildContextAccessor,
5286
5340
  boolean,
5287
5341
  blob,
5342
+ awaitModuleSync,
5288
5343
  array,
5289
5344
  arcFunctionWithCtx,
5290
5345
  arcFunction,
@@ -21,7 +21,15 @@ export declare class ScopedModel<Context extends ArcContextAny> implements Model
21
21
  private readonly scopedAdapters;
22
22
  private tokenListeners;
23
23
  constructor(parent: ModelLike<Context>, scopeName: string);
24
- setToken(token: string | null): void;
24
+ /**
25
+ * Set this scope's token. Returns a promise that resolves when the platform
26
+ * module loader has synced the chunks for the new token state (so routes in
27
+ * newly-gated modules exist before the caller navigates). With no module-sync
28
+ * provider registered (server, SSR, tests, app-without-platform) it resolves
29
+ * immediately — preserving the historical synchronous semantics for the many
30
+ * server-side `setToken(rawToken)` callers that ignore the return value.
31
+ */
32
+ setToken(token: string | null): Promise<void>;
25
33
  getToken(): string | null;
26
34
  getDecoded(): DecodedToken | null;
27
35
  getParams(): Record<string, any> | null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc",
3
3
  "type": "module",
4
- "version": "0.7.16",
4
+ "version": "0.7.18",
5
5
  "private": false,
6
6
  "author": "Przemysław Krasiński [arcote.tech]",
7
7
  "description": "Arc framework core rewrite with improved event emission and type safety",