@absolutejs/absolute 0.20.0-beta.16 → 0.20.0-beta.17

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/dist/cli/index.js CHANGED
@@ -6304,7 +6304,7 @@ var init_materializedBundle = __esm(() => {
6304
6304
  });
6305
6305
 
6306
6306
  // node_modules/@absolutejs/sync/dist/client/index.js
6307
- var RUNTIME_TRANSPORT, host, isRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")), registry, SyncLocalDataPolicyError, SyncLocalStoreSchemaError, positiveVersion = (value, label) => {
6307
+ var RUNTIME_TRANSPORT, host, isRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients")), registry, SyncLocalDataPolicyError, SyncLocalStoreSchemaError, positiveVersion = (value, label) => {
6308
6308
  if (!Number.isSafeInteger(value) || value < 1)
6309
6309
  throw new SyncLocalStoreSchemaError("INVALID_PLAN", `${label} must be a positive safe integer`);
6310
6310
  return value;
@@ -6325,6 +6325,12 @@ var RUNTIME_TRANSPORT, host, isRegistry = (value) => typeof value === "object" &
6325
6325
  }
6326
6326
  for (const [index, rule] of (policy.mutations ?? []).entries()) {
6327
6327
  validatePolicyMatch(rule.match, `${label}.mutations[${index}]`);
6328
+ if (rule.conflict !== undefined && rule.conflict.strategy !== "client-wins" && rule.conflict.strategy !== "manual" && rule.conflict.strategy !== "server-wins")
6329
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.strategy is invalid.`);
6330
+ if (rule.conflict?.maxAttempts !== undefined && (!Number.isSafeInteger(rule.conflict.maxAttempts) || rule.conflict.maxAttempts < 1))
6331
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts must be a positive safe integer.`);
6332
+ if (rule.conflict?.maxAttempts !== undefined && rule.conflict.strategy !== "client-wins")
6333
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts is only valid for client-wins.`);
6328
6334
  if (rule.persistence === "memory-only" && rule.protection === "required")
6329
6335
  throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}] cannot require at-rest protection when it is memory-only.`);
6330
6336
  if (rule.sensitivity !== undefined && rule.sensitivity !== "public" && rule.protection !== "required" && rule.persistence !== "memory-only")
@@ -6390,7 +6396,11 @@ var init_client2 = __esm(() => {
6390
6396
  const existing = host[RUNTIME_TRANSPORT];
6391
6397
  if (isRegistry(existing))
6392
6398
  return existing;
6393
- const created = { installations: [] };
6399
+ if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
6400
+ Reflect.set(existing, "clients", []);
6401
+ return existing;
6402
+ }
6403
+ const created = { clients: [], installations: [] };
6394
6404
  Object.defineProperty(host, RUNTIME_TRANSPORT, {
6395
6405
  configurable: false,
6396
6406
  enumerable: false,
@@ -6578,6 +6588,7 @@ var object = (value) => typeof value === "object" && value !== null && !Array.is
6578
6588
  const mutations = Array.isArray(mutationRules) ? mutationRules.map((entry, index) => {
6579
6589
  const rule = requireObject(entry, id, `localData.mutations[${index}] must be an object.`);
6580
6590
  const allowedRuleKeys = new Set([
6591
+ "conflict",
6581
6592
  "match",
6582
6593
  "onProtectionUnavailable",
6583
6594
  "persistence",
@@ -6591,6 +6602,26 @@ var object = (value) => typeof value === "object" && value !== null && !Array.is
6591
6602
  const sensitivity = unknownField(rule, "sensitivity");
6592
6603
  const persistence = unknownField(rule, "persistence");
6593
6604
  const onProtectionUnavailable = unknownField(rule, "onProtectionUnavailable");
6605
+ const declaredConflict = unknownField(rule, "conflict");
6606
+ let conflict;
6607
+ if (declaredConflict !== undefined) {
6608
+ const conflictRecord = requireObject(declaredConflict, id, `localData.mutations[${index}].conflict must be an object.`);
6609
+ const unsupportedConflictKey = Object.keys(conflictRecord).find((key) => key !== "maxAttempts" && key !== "strategy");
6610
+ if (unsupportedConflictKey)
6611
+ throw metadataError(id, `localData.mutations[${index}].conflict.${unsupportedConflictKey} is not supported.`);
6612
+ const strategy = unknownField(conflictRecord, "strategy");
6613
+ if (strategy !== "client-wins" && strategy !== "manual" && strategy !== "server-wins")
6614
+ throw metadataError(id, `localData.mutations[${index}].conflict.strategy is invalid.`);
6615
+ const maxAttempts = unknownField(conflictRecord, "maxAttempts");
6616
+ if (maxAttempts !== undefined && strategy !== "client-wins")
6617
+ throw metadataError(id, `localData.mutations[${index}].conflict.maxAttempts requires client-wins.`);
6618
+ conflict = {
6619
+ strategy,
6620
+ ...maxAttempts === undefined ? {} : {
6621
+ maxAttempts: positiveVersion2(maxAttempts, id, `localData.mutations[${index}].conflict.maxAttempts`)
6622
+ }
6623
+ };
6624
+ }
6594
6625
  if (protection !== undefined && protection !== "none" && protection !== "required")
6595
6626
  throw metadataError(id, `localData.mutations[${index}].protection is invalid.`);
6596
6627
  if (sensitivity !== undefined && sensitivity !== "public" && sensitivity !== "private" && sensitivity !== "secret")
@@ -6601,6 +6632,7 @@ var object = (value) => typeof value === "object" && value !== null && !Array.is
6601
6632
  throw metadataError(id, `localData.mutations[${index}].persistence is invalid.`);
6602
6633
  return {
6603
6634
  match: nonEmpty(Reflect.get(rule, "match"), id, `localData.mutations[${index}].match`),
6635
+ ...conflict ? { conflict } : {},
6604
6636
  ...sensitivity ? { sensitivity } : {},
6605
6637
  ...onProtectionUnavailable ? { onProtectionUnavailable } : {},
6606
6638
  ...persistence ? {
@@ -16978,8 +17010,13 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
16978
17010
  const mutationRules = schema.components.flatMap((component2) => component2.localData?.mutations ?? []);
16979
17011
  const protectedCount = [...collectionRules, ...mutationRules].filter((rule) => rule.protection === "required").length;
16980
17012
  const memoryOnlyCount = [...collectionRules, ...mutationRules].filter((rule) => rule.persistence === "memory-only" || rule.onProtectionUnavailable === "memory-only").length;
17013
+ const conflictCounts = {
17014
+ clientWins: mutationRules.filter((rule) => rule.conflict?.strategy === "client-wins").length,
17015
+ manual: mutationRules.filter((rule) => rule.conflict?.strategy === "manual").length,
17016
+ serverWins: mutationRules.filter((rule) => rule.conflict?.strategy === "server-wins").length
17017
+ };
16981
17018
  const quotas = schema.components.map((component2) => component2.localData?.maxBytesPerNamespace).filter((value) => value !== undefined);
16982
- const policy = `${collectionRules.length} collection rule(s), ${mutationRules.length} mutation rule(s), ${protectedCount} encryption-required, ${memoryOnlyCount} memory-only fallback(s)${quotas.length > 0 ? `, ${Math.min(...quotas)}-byte effective quota` : ", no logical quota"}`;
17019
+ const policy = `${collectionRules.length} collection rule(s), ${mutationRules.length} mutation rule(s), ${protectedCount} encryption-required, ${memoryOnlyCount} memory-only fallback(s), conflicts ${conflictCounts.clientWins} client-wins/${conflictCounts.serverWins} server-wins/${conflictCounts.manual} manual${quotas.length > 0 ? `, ${Math.min(...quotas)}-byte effective quota` : ", no logical quota"}`;
16983
17020
  return pass("sync.storage-schema", `Generated offline schema is compatible: ${versions}; ${policy}.`, manifestPath);
16984
17021
  } catch (error) {
16985
17022
  return fail5("sync.storage-schema", error instanceof Error ? error.message : "Generated offline schema metadata is invalid.", manifestPath, "Fix absolutejs.sync.localSchema metadata in the named app or package before releasing.");
@@ -18512,7 +18549,7 @@ var init_mobile = __esm(() => {
18512
18549
  "@absolutejs/devices-capacitor@0.1.3"
18513
18550
  ];
18514
18551
  CAPACITOR_SYNC_PACKAGE_SPECS = [
18515
- "@absolutejs/sync-capacitor@0.7.0",
18552
+ "@absolutejs/sync-capacitor@0.8.0",
18516
18553
  "@capacitor-community/sqlite@8.1.1"
18517
18554
  ];
18518
18555
  });
@@ -18,6 +18,7 @@ import {
18
18
  sendAbsoluteHmrTiming
19
19
  } from './hmrTiming';
20
20
  import { hideErrorOverlay, showErrorOverlay } from './errorOverlay';
21
+ import { installAbsoluteNativeSyncDevtools } from './syncDevtools';
21
22
  import {
22
23
  dispatchAngularComponentRemount,
23
24
  dispatchAngularComponentUpdate
@@ -45,6 +46,10 @@ const isStringRecord = (value: unknown): value is Record<string, string> =>
45
46
  Object.values(value).every((entry) => typeof entry === 'string');
46
47
 
47
48
  restoreAbsoluteHmrApply();
49
+ const removeNativeSyncDevtools =
50
+ absoluteHmrClientTarget() === 'web'
51
+ ? () => undefined
52
+ : installAbsoluteNativeSyncDevtools();
48
53
 
49
54
  /* Lightweight "server disconnected" banner. When the dev server is
50
55
  * genuinely down (process restarting or crashed) the browser would
@@ -165,15 +170,15 @@ type HMRMessage = {
165
170
 
166
171
  const handleStylesheetUpdate = (message: HMRMessage) => {
167
172
  const clientStart = performance.now();
168
- void reloadCSSStylesheets(message.data.manifest ?? {}).then((applied) => {
173
+ void reloadCSSStylesheets(message.data.manifest ?? {}).then((applied) =>
169
174
  sendAbsoluteHmrTiming({
170
175
  clientStart,
171
176
  kind: 'css',
172
177
  outcome: applied ? 'applied' : 'failed',
173
178
  serverMs: message.data.serverDuration,
174
179
  updateId: message.timestamp
175
- });
176
- });
180
+ })
181
+ );
177
182
  };
178
183
 
179
184
  const handleHMRMessage = (message: HMRMessage) => {
@@ -413,6 +418,7 @@ if (!(window.__HMR_WS__ && window.__HMR_WS__.readyState === WebSocket.OPEN)) {
413
418
  };
414
419
 
415
420
  window.addEventListener('beforeunload', () => {
421
+ removeNativeSyncDevtools();
416
422
  if (hmrState.isHMRUpdating) {
417
423
  if (hmrState.pingInterval) clearInterval(hmrState.pingInterval);
418
424
  if (hmrState.reconnectTimeout)
@@ -0,0 +1,237 @@
1
+ import type {} from '../../types/globals';
2
+ import {
3
+ discardSyncRuntimeDeadLetter,
4
+ inspectSyncRuntime,
5
+ rebaseSyncRuntimeDeadLetter,
6
+ retrySyncRuntimeDeadLetter,
7
+ type SyncRuntimeInspection
8
+ } from '@absolutejs/sync/client/runtime';
9
+
10
+ export type SyncDevtoolsBridge = {
11
+ discard: (operationId: string) => Promise<void>;
12
+ inspect: () => Promise<SyncRuntimeInspection>;
13
+ rebase: (operationId: string, args: unknown) => Promise<string>;
14
+ retry: (operationId: string) => Promise<void>;
15
+ };
16
+
17
+ const DEVTOOLS_ID = 'absolutejs-sync-devtools';
18
+ const REFRESH_INTERVAL_MS = 1_500;
19
+ const bridge: SyncDevtoolsBridge = {
20
+ discard: discardSyncRuntimeDeadLetter,
21
+ inspect: inspectSyncRuntime,
22
+ rebase: rebaseSyncRuntimeDeadLetter,
23
+ retry: retrySyncRuntimeDeadLetter
24
+ };
25
+
26
+ const time = (value: number | undefined) =>
27
+ value === undefined ? '—' : new Date(value).toLocaleTimeString();
28
+
29
+ const styles = `
30
+ :host { all: initial; color-scheme: light dark; }
31
+ button { font: inherit; }
32
+ .trigger { position:fixed;right:max(12px,env(safe-area-inset-right));bottom:max(12px,env(safe-area-inset-bottom));z-index:2147483645;border:0;border-radius:999px;padding:9px 13px;background:#111827;color:#fff;box-shadow:0 4px 18px #0006;font:600 12px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace; }
33
+ .trigger[data-alert=true] { background:#b91c1c; }
34
+ .panel { position:fixed;inset:max(12px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right)) max(56px,env(safe-area-inset-bottom)) auto;z-index:2147483645;width:min(420px,calc(100vw - 24px));max-height:calc(100vh - 80px);overflow:auto;border:1px solid #64748b66;border-radius:14px;background:#fffffff2;color:#111827;box-shadow:0 16px 48px #0008;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;backdrop-filter:blur(12px); }
35
+ [hidden] { display:none!important; }
36
+ header { position:sticky;top:0;display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid #64748b44;background:inherit; }
37
+ h2 { margin:0;font-size:14px; }
38
+ .close,.action { border:1px solid #64748b66;border-radius:7px;background:transparent;color:inherit;padding:5px 8px; }
39
+ .body { padding:12px 14px; }
40
+ .metrics { display:grid;grid-template-columns:repeat(2,1fr);gap:7px;margin-bottom:12px; }
41
+ .metric { padding:8px;border-radius:8px;background:#64748b18; }
42
+ .metric strong { display:block;font-size:16px; }
43
+ .empty { color:#64748b; }
44
+ .letter { margin-top:9px;padding:10px;border:1px solid #ef444466;border-radius:9px;overflow-wrap:anywhere; }
45
+ .letter strong { display:block; }
46
+ .meta { color:#64748b;margin:4px 0 8px; }
47
+ .actions { display:flex;flex-wrap:wrap;gap:6px; }
48
+ .danger { color:#b91c1c; }
49
+ .notice { margin-bottom:9px;padding:8px;border-radius:7px;background:#f59e0b22; }
50
+ @media (prefers-color-scheme:dark) { .panel { background:#111827f2;color:#f8fafc; } .empty,.meta { color:#94a3b8; } .danger { color:#fca5a5; } }
51
+ `;
52
+
53
+ const renderInspection = (
54
+ body: HTMLElement,
55
+ inspection: SyncRuntimeInspection,
56
+ notice?: string
57
+ ) => {
58
+ body.replaceChildren();
59
+ if (notice) {
60
+ const message = document.createElement('div');
61
+ message.className = 'notice';
62
+ message.textContent = notice;
63
+ body.appendChild(message);
64
+ }
65
+ const metrics = document.createElement('div');
66
+ metrics.className = 'metrics';
67
+ for (const [label, value] of [
68
+ ['Pending', inspection.pending],
69
+ ['Dead letters', inspection.deadLetters.length],
70
+ ['Conflicts', inspection.conflicts],
71
+ ['Auto-resolved', inspection.automaticResolutions]
72
+ ] as const) {
73
+ const metric = document.createElement('div');
74
+ metric.className = 'metric';
75
+ const strong = document.createElement('strong');
76
+ strong.textContent = String(value);
77
+ metric.append(strong, label);
78
+ metrics.appendChild(metric);
79
+ }
80
+ body.appendChild(metrics);
81
+ const activity = document.createElement('div');
82
+ activity.className = 'meta';
83
+ activity.textContent = `Clients ${inspection.clients} · last push ${time(inspection.lastSuccessfulPushAt)} · last pull ${time(inspection.lastSuccessfulPullAt)}`;
84
+ body.appendChild(activity);
85
+ if (inspection.deadLetters.length === 0) {
86
+ const empty = document.createElement('div');
87
+ empty.className = 'empty';
88
+ empty.textContent = 'No mutations need manual remediation.';
89
+ body.appendChild(empty);
90
+
91
+ return;
92
+ }
93
+ for (const deadLetter of inspection.deadLetters) {
94
+ const item = document.createElement('section');
95
+ item.className = 'letter';
96
+ const title = document.createElement('strong');
97
+ title.textContent = deadLetter.name;
98
+ const metadata = document.createElement('div');
99
+ metadata.className = 'meta';
100
+ metadata.textContent = `${deadLetter.kind ?? 'rejected'}${deadLetter.code ? ` · ${deadLetter.code}` : ''} · attempts ${deadLetter.attempts} · ${time(deadLetter.deadLetteredAt)}`;
101
+ const detail = document.createElement('div');
102
+ detail.textContent =
103
+ deadLetter.message ?? 'The server rejected this mutation.';
104
+ const actions = document.createElement('div');
105
+ actions.className = 'actions';
106
+ for (const [action, label] of [
107
+ ['retry', 'Retry unchanged'],
108
+ ['rebase', 'Rebase with new args'],
109
+ ['discard', 'Discard']
110
+ ] as const) {
111
+ const button = document.createElement('button');
112
+ button.className = `action${action === 'discard' ? ' danger' : ''}`;
113
+ button.dataset.action = action;
114
+ button.dataset.operationId = deadLetter.operationId;
115
+ button.textContent = label;
116
+ actions.appendChild(button);
117
+ }
118
+ item.append(title, metadata, detail, actions);
119
+ body.appendChild(item);
120
+ }
121
+ };
122
+
123
+ /** Install the development-only, framework-neutral native Sync panel. */
124
+ export const installAbsoluteNativeSyncDevtools = (
125
+ devtoolsBridge: SyncDevtoolsBridge = bridge
126
+ ) => {
127
+ if (typeof document === 'undefined' || !document.body)
128
+ return () => undefined;
129
+ if (document.getElementById(DEVTOOLS_ID)) return () => undefined;
130
+ const host = document.createElement('aside');
131
+ host.id = DEVTOOLS_ID;
132
+ host.dataset.hmrOverlay = 'true';
133
+ const root = host.attachShadow({ mode: 'open' });
134
+ const style = document.createElement('style');
135
+ style.textContent = styles;
136
+ const trigger = document.createElement('button');
137
+ trigger.className = 'trigger';
138
+ trigger.textContent = 'Sync';
139
+ trigger.type = 'button';
140
+ const panel = document.createElement('section');
141
+ panel.className = 'panel';
142
+ panel.hidden = true;
143
+ const header = document.createElement('header');
144
+ const title = document.createElement('h2');
145
+ title.textContent = 'AbsoluteJS Sync';
146
+ const close = document.createElement('button');
147
+ close.className = 'close';
148
+ close.textContent = 'Close';
149
+ close.type = 'button';
150
+ header.append(title, close);
151
+ const body = document.createElement('div');
152
+ body.className = 'body';
153
+ panel.append(header, body);
154
+ root.append(style, trigger, panel);
155
+ document.body.appendChild(host);
156
+ let active = true;
157
+ let notice: string | undefined;
158
+ const refresh = async () => {
159
+ try {
160
+ const inspection = await devtoolsBridge.inspect();
161
+ if (!active) return;
162
+ trigger.dataset.alert = String(inspection.deadLetters.length > 0);
163
+ trigger.textContent = inspection.deadLetters.length
164
+ ? `Sync · ${inspection.deadLetters.length}`
165
+ : 'Sync';
166
+ if (!panel.hidden) renderInspection(body, inspection, notice);
167
+ notice = undefined;
168
+ } catch {
169
+ if (!active || panel.hidden) return;
170
+ notice = 'Sync diagnostics are temporarily unavailable.';
171
+ }
172
+ };
173
+ trigger.addEventListener('click', () => {
174
+ panel.hidden = !panel.hidden;
175
+ if (!panel.hidden) void refresh();
176
+ });
177
+ close.addEventListener('click', () => {
178
+ panel.hidden = true;
179
+ });
180
+ const remediate = async (
181
+ action: string | undefined,
182
+ operationId: string
183
+ ) => {
184
+ try {
185
+ if (action === 'retry') await devtoolsBridge.retry(operationId);
186
+ else if (action === 'discard') {
187
+ if (
188
+ !globalThis.confirm(
189
+ 'Discard this local mutation permanently?'
190
+ )
191
+ )
192
+ return;
193
+ await devtoolsBridge.discard(operationId);
194
+ } else if (action === 'rebase') {
195
+ const serialized = globalThis.prompt(
196
+ 'New mutation arguments as JSON. This creates a new operation intent:'
197
+ );
198
+ if (serialized === null) return;
199
+ let args: unknown;
200
+ try {
201
+ args = JSON.parse(serialized);
202
+ } catch {
203
+ notice = 'Rebase cancelled: arguments were not valid JSON.';
204
+ await refresh();
205
+
206
+ return;
207
+ }
208
+ if (
209
+ !globalThis.confirm(
210
+ 'Create a new mutation with these arguments?'
211
+ )
212
+ )
213
+ return;
214
+ await devtoolsBridge.rebase(operationId, args);
215
+ }
216
+ notice = 'Sync remediation applied.';
217
+ } catch {
218
+ notice =
219
+ 'Sync remediation failed. The local mutation was retained.';
220
+ }
221
+ await refresh();
222
+ };
223
+ body.addEventListener('click', (event) => {
224
+ if (!(event.target instanceof HTMLButtonElement)) return;
225
+ const { action, operationId } = event.target.dataset;
226
+ if (!operationId) return;
227
+ void remediate(action, operationId);
228
+ });
229
+ const interval = setInterval(() => void refresh(), REFRESH_INTERVAL_MS);
230
+ void refresh();
231
+
232
+ return () => {
233
+ active = false;
234
+ clearInterval(interval);
235
+ host.remove();
236
+ };
237
+ };
package/dist/index.js CHANGED
@@ -13199,7 +13199,7 @@ var isTestSourcePath = (file2) => {
13199
13199
  };
13200
13200
 
13201
13201
  // node_modules/@absolutejs/sync/dist/client/index.js
13202
- var RUNTIME_TRANSPORT, host, isRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")), registry, SyncLocalDataPolicyError, SyncLocalStoreSchemaError, positiveVersion = (value, label) => {
13202
+ var RUNTIME_TRANSPORT, host, isRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients")), registry, SyncLocalDataPolicyError, SyncLocalStoreSchemaError, positiveVersion = (value, label) => {
13203
13203
  if (!Number.isSafeInteger(value) || value < 1)
13204
13204
  throw new SyncLocalStoreSchemaError("INVALID_PLAN", `${label} must be a positive safe integer`);
13205
13205
  return value;
@@ -13220,6 +13220,12 @@ var RUNTIME_TRANSPORT, host, isRegistry = (value) => typeof value === "object" &
13220
13220
  }
13221
13221
  for (const [index, rule] of (policy.mutations ?? []).entries()) {
13222
13222
  validatePolicyMatch(rule.match, `${label}.mutations[${index}]`);
13223
+ if (rule.conflict !== undefined && rule.conflict.strategy !== "client-wins" && rule.conflict.strategy !== "manual" && rule.conflict.strategy !== "server-wins")
13224
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.strategy is invalid.`);
13225
+ if (rule.conflict?.maxAttempts !== undefined && (!Number.isSafeInteger(rule.conflict.maxAttempts) || rule.conflict.maxAttempts < 1))
13226
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts must be a positive safe integer.`);
13227
+ if (rule.conflict?.maxAttempts !== undefined && rule.conflict.strategy !== "client-wins")
13228
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts is only valid for client-wins.`);
13223
13229
  if (rule.persistence === "memory-only" && rule.protection === "required")
13224
13230
  throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}] cannot require at-rest protection when it is memory-only.`);
13225
13231
  if (rule.sensitivity !== undefined && rule.sensitivity !== "public" && rule.protection !== "required" && rule.persistence !== "memory-only")
@@ -13285,7 +13291,11 @@ var init_client = __esm(() => {
13285
13291
  const existing = host[RUNTIME_TRANSPORT];
13286
13292
  if (isRegistry(existing))
13287
13293
  return existing;
13288
- const created = { installations: [] };
13294
+ if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
13295
+ Reflect.set(existing, "clients", []);
13296
+ return existing;
13297
+ }
13298
+ const created = { clients: [], installations: [] };
13289
13299
  Object.defineProperty(host, RUNTIME_TRANSPORT, {
13290
13300
  configurable: false,
13291
13301
  enumerable: false,
@@ -13473,6 +13483,7 @@ var object = (value) => typeof value === "object" && value !== null && !Array.is
13473
13483
  const mutations = Array.isArray(mutationRules) ? mutationRules.map((entry, index) => {
13474
13484
  const rule = requireObject(entry, id, `localData.mutations[${index}] must be an object.`);
13475
13485
  const allowedRuleKeys = new Set([
13486
+ "conflict",
13476
13487
  "match",
13477
13488
  "onProtectionUnavailable",
13478
13489
  "persistence",
@@ -13486,6 +13497,26 @@ var object = (value) => typeof value === "object" && value !== null && !Array.is
13486
13497
  const sensitivity = unknownField(rule, "sensitivity");
13487
13498
  const persistence = unknownField(rule, "persistence");
13488
13499
  const onProtectionUnavailable = unknownField(rule, "onProtectionUnavailable");
13500
+ const declaredConflict = unknownField(rule, "conflict");
13501
+ let conflict;
13502
+ if (declaredConflict !== undefined) {
13503
+ const conflictRecord = requireObject(declaredConflict, id, `localData.mutations[${index}].conflict must be an object.`);
13504
+ const unsupportedConflictKey = Object.keys(conflictRecord).find((key) => key !== "maxAttempts" && key !== "strategy");
13505
+ if (unsupportedConflictKey)
13506
+ throw metadataError(id, `localData.mutations[${index}].conflict.${unsupportedConflictKey} is not supported.`);
13507
+ const strategy = unknownField(conflictRecord, "strategy");
13508
+ if (strategy !== "client-wins" && strategy !== "manual" && strategy !== "server-wins")
13509
+ throw metadataError(id, `localData.mutations[${index}].conflict.strategy is invalid.`);
13510
+ const maxAttempts = unknownField(conflictRecord, "maxAttempts");
13511
+ if (maxAttempts !== undefined && strategy !== "client-wins")
13512
+ throw metadataError(id, `localData.mutations[${index}].conflict.maxAttempts requires client-wins.`);
13513
+ conflict = {
13514
+ strategy,
13515
+ ...maxAttempts === undefined ? {} : {
13516
+ maxAttempts: positiveVersion2(maxAttempts, id, `localData.mutations[${index}].conflict.maxAttempts`)
13517
+ }
13518
+ };
13519
+ }
13489
13520
  if (protection !== undefined && protection !== "none" && protection !== "required")
13490
13521
  throw metadataError(id, `localData.mutations[${index}].protection is invalid.`);
13491
13522
  if (sensitivity !== undefined && sensitivity !== "public" && sensitivity !== "private" && sensitivity !== "secret")
@@ -13496,6 +13527,7 @@ var object = (value) => typeof value === "object" && value !== null && !Array.is
13496
13527
  throw metadataError(id, `localData.mutations[${index}].persistence is invalid.`);
13497
13528
  return {
13498
13529
  match: nonEmpty(Reflect.get(rule, "match"), id, `localData.mutations[${index}].match`),
13530
+ ...conflict ? { conflict } : {},
13499
13531
  ...sensitivity ? { sensitivity } : {},
13500
13532
  ...onProtectionUnavailable ? { onProtectionUnavailable } : {},
13501
13533
  ...persistence ? {
@@ -40387,5 +40419,5 @@ export {
40387
40419
  ANGULAR_INIT_TIMEOUT_MS
40388
40420
  };
40389
40421
 
40390
- //# debugId=FF975832AE5A306264756E2164756E21
40422
+ //# debugId=C0E84CA8E87CA8B764756E2164756E21
40391
40423
  //# sourceMappingURL=index.js.map