@livenetworks/ashlar 1.3.2
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.md +177 -0
- package/js/COMPONENTS.md +1102 -0
- package/js/index.js +41 -0
- package/js/ln-accordion/README.md +137 -0
- package/js/ln-accordion/ln-accordion.js +1 -0
- package/js/ln-accordion/src/ln-accordion.js +41 -0
- package/js/ln-ajax/README.md +91 -0
- package/js/ln-ajax/ln-ajax.js +1 -0
- package/js/ln-ajax/src/ln-ajax.js +277 -0
- package/js/ln-api-connector/README.md +150 -0
- package/js/ln-api-connector/ln-api-connector.js +1 -0
- package/js/ln-api-connector/src/ln-api-connector.js +265 -0
- package/js/ln-autoresize/README.md +80 -0
- package/js/ln-autoresize/ln-autoresize.js +1 -0
- package/js/ln-autoresize/src/ln-autoresize.js +47 -0
- package/js/ln-autosave/README.md +92 -0
- package/js/ln-autosave/ln-autosave.js +1 -0
- package/js/ln-autosave/src/ln-autosave.js +147 -0
- package/js/ln-circular-progress/README.md +161 -0
- package/js/ln-circular-progress/ln-circular-progress.js +1 -0
- package/js/ln-circular-progress/src/ln-circular-progress.js +133 -0
- package/js/ln-confirm/README.md +86 -0
- package/js/ln-confirm/_ln-confirm.scss +13 -0
- package/js/ln-confirm/ln-confirm.js +1 -0
- package/js/ln-confirm/src/ln-confirm.js +131 -0
- package/js/ln-core/crypto.js +83 -0
- package/js/ln-core/helpers.js +411 -0
- package/js/ln-core/index.js +5 -0
- package/js/ln-core/persist.js +71 -0
- package/js/ln-core/positioning.js +207 -0
- package/js/ln-core/reactive.js +74 -0
- package/js/ln-couchdb-connector/README.md +156 -0
- package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -0
- package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +348 -0
- package/js/ln-data-coordinator/README.md +165 -0
- package/js/ln-data-coordinator/ln-data-coordinator.js +1 -0
- package/js/ln-data-coordinator/src/ln-data-coordinator.js +249 -0
- package/js/ln-data-store/README.md +94 -0
- package/js/ln-data-store/ln-data-store.js +1 -0
- package/js/ln-data-store/src/ln-data-store.js +699 -0
- package/js/ln-data-table/README.md +110 -0
- package/js/ln-data-table/ln-data-table.js +1 -0
- package/js/ln-data-table/ln-data-table.scss +10 -0
- package/js/ln-data-table/src/ln-data-table.js +1103 -0
- package/js/ln-date/README.md +151 -0
- package/js/ln-date/ln-date.js +1 -0
- package/js/ln-date/src/ln-date.js +442 -0
- package/js/ln-dropdown/README.md +117 -0
- package/js/ln-dropdown/ln-dropdown.js +1 -0
- package/js/ln-dropdown/ln-dropdown.scss +15 -0
- package/js/ln-dropdown/src/ln-dropdown.js +174 -0
- package/js/ln-external-links/README.md +341 -0
- package/js/ln-external-links/ln-external-links.js +1 -0
- package/js/ln-external-links/src/ln-external-links.js +116 -0
- package/js/ln-filter/README.md +99 -0
- package/js/ln-filter/ln-filter.js +1 -0
- package/js/ln-filter/ln-filter.scss +7 -0
- package/js/ln-filter/src/ln-filter.js +404 -0
- package/js/ln-form/README.md +101 -0
- package/js/ln-form/ln-form.js +1 -0
- package/js/ln-form/src/ln-form.js +199 -0
- package/js/ln-http/README.md +89 -0
- package/js/ln-http/ln-http.js +1 -0
- package/js/ln-http/src/ln-http.js +219 -0
- package/js/ln-icons/README.md +88 -0
- package/js/ln-icons/ln-icons.js +1 -0
- package/js/ln-icons/src/ln-icons.js +169 -0
- package/js/ln-link/README.md +303 -0
- package/js/ln-link/ln-link.js +1 -0
- package/js/ln-link/src/ln-link.js +196 -0
- package/js/ln-modal/README.md +154 -0
- package/js/ln-modal/ln-modal.js +1 -0
- package/js/ln-modal/ln-modal.scss +11 -0
- package/js/ln-modal/src/ln-modal.js +201 -0
- package/js/ln-nav/README.md +70 -0
- package/js/ln-nav/ln-nav.js +1 -0
- package/js/ln-nav/src/ln-nav.js +177 -0
- package/js/ln-number/README.md +122 -0
- package/js/ln-number/ln-number.js +1 -0
- package/js/ln-number/src/ln-number.js +302 -0
- package/js/ln-popover/README.md +127 -0
- package/js/ln-popover/ln-popover.js +1 -0
- package/js/ln-popover/src/ln-popover.js +288 -0
- package/js/ln-progress/README.md +442 -0
- package/js/ln-progress/ln-progress.js +1 -0
- package/js/ln-progress/src/ln-progress.js +150 -0
- package/js/ln-search/README.md +83 -0
- package/js/ln-search/ln-search.js +1 -0
- package/js/ln-search/ln-search.scss +7 -0
- package/js/ln-search/src/ln-search.js +114 -0
- package/js/ln-sortable/README.md +95 -0
- package/js/ln-sortable/ln-sortable.js +1 -0
- package/js/ln-sortable/src/ln-sortable.js +203 -0
- package/js/ln-table/README.md +101 -0
- package/js/ln-table/ln-table-sort.js +1 -0
- package/js/ln-table/ln-table.js +1 -0
- package/js/ln-table/ln-table.scss +11 -0
- package/js/ln-table/src/ln-table-sort.js +168 -0
- package/js/ln-table/src/ln-table.js +473 -0
- package/js/ln-tabs/README.md +137 -0
- package/js/ln-tabs/ln-tabs.js +1 -0
- package/js/ln-tabs/src/ln-tabs.js +171 -0
- package/js/ln-time/README.md +81 -0
- package/js/ln-time/ln-time.js +1 -0
- package/js/ln-time/src/ln-time.js +192 -0
- package/js/ln-toast/README.md +122 -0
- package/js/ln-toast/ln-toast.js +15 -0
- package/js/ln-toast/src/ln-toast.js +210 -0
- package/js/ln-toast/template.html +14 -0
- package/js/ln-toggle/README.md +137 -0
- package/js/ln-toggle/ln-toggle.js +1 -0
- package/js/ln-toggle/src/ln-toggle.js +139 -0
- package/js/ln-tooltip/README.md +58 -0
- package/js/ln-tooltip/ln-tooltip.js +1 -0
- package/js/ln-tooltip/ln-tooltip.scss +9 -0
- package/js/ln-tooltip/src/ln-tooltip.js +169 -0
- package/js/ln-translations/README.md +96 -0
- package/js/ln-translations/ln-translations.js +1 -0
- package/js/ln-translations/src/ln-translations.js +275 -0
- package/js/ln-upload/README.md +180 -0
- package/js/ln-upload/ln-upload.js +1 -0
- package/js/ln-upload/ln-upload.scss +20 -0
- package/js/ln-upload/src/ln-upload.js +407 -0
- package/js/ln-validate/README.md +108 -0
- package/js/ln-validate/ln-validate.js +1 -0
- package/js/ln-validate/src/ln-validate.js +160 -0
- package/package.json +55 -0
- package/scss/base/_global.scss +83 -0
- package/scss/base/_reset.scss +17 -0
- package/scss/base/_typography.scss +125 -0
- package/scss/components/_accordion.scss +34 -0
- package/scss/components/_ajax.scss +15 -0
- package/scss/components/_alert.scss +5 -0
- package/scss/components/_app-shell.scss +15 -0
- package/scss/components/_avatar.scss +6 -0
- package/scss/components/_breadcrumbs.scss +33 -0
- package/scss/components/_button.scss +20 -0
- package/scss/components/_card.scss +10 -0
- package/scss/components/_chip.scss +5 -0
- package/scss/components/_circular-progress.scss +29 -0
- package/scss/components/_confirm.scss +5 -0
- package/scss/components/_data-table.scss +83 -0
- package/scss/components/_dropdown.scss +25 -0
- package/scss/components/_empty-state.scss +22 -0
- package/scss/components/_form.scss +100 -0
- package/scss/components/_layout.scss +8 -0
- package/scss/components/_link.scss +11 -0
- package/scss/components/_ln-table.scss +60 -0
- package/scss/components/_loader.scss +6 -0
- package/scss/components/_modal.scss +20 -0
- package/scss/components/_nav.scss +9 -0
- package/scss/components/_page-header.scss +10 -0
- package/scss/components/_popover.scss +10 -0
- package/scss/components/_progress.scss +17 -0
- package/scss/components/_prose.scss +5 -0
- package/scss/components/_scrollbar.scss +32 -0
- package/scss/components/_sections.scss +12 -0
- package/scss/components/_sidebar.scss +5 -0
- package/scss/components/_stat-card.scss +5 -0
- package/scss/components/_status-badge.scss +4 -0
- package/scss/components/_stepper.scss +5 -0
- package/scss/components/_table.scss +19 -0
- package/scss/components/_tabs.scss +21 -0
- package/scss/components/_timeline.scss +14 -0
- package/scss/components/_toast.scss +41 -0
- package/scss/components/_toggle.scss +81 -0
- package/scss/components/_tooltip.scss +18 -0
- package/scss/components/_translations.scss +111 -0
- package/scss/components/_upload.scss +51 -0
- package/scss/config/_breakpoints.scss +72 -0
- package/scss/config/_density.scss +117 -0
- package/scss/config/_icons.scss +37 -0
- package/scss/config/_mixins.scss +13 -0
- package/scss/config/_theme.scss +216 -0
- package/scss/config/_tokens.scss +419 -0
- package/scss/config/mixins/_accordion.scss +52 -0
- package/scss/config/mixins/_ajax.scss +39 -0
- package/scss/config/mixins/_alert.scss +82 -0
- package/scss/config/mixins/_app-shell.scss +312 -0
- package/scss/config/mixins/_avatar.scss +109 -0
- package/scss/config/mixins/_borders.scss +36 -0
- package/scss/config/mixins/_breadcrumbs.scss +72 -0
- package/scss/config/mixins/_breakpoints.scss +62 -0
- package/scss/config/mixins/_btn.scss +179 -0
- package/scss/config/mixins/_card.scss +338 -0
- package/scss/config/mixins/_chip.scss +66 -0
- package/scss/config/mixins/_circular-progress.scss +71 -0
- package/scss/config/mixins/_collapsible.scss +24 -0
- package/scss/config/mixins/_colors.scss +46 -0
- package/scss/config/mixins/_confirm.scss +31 -0
- package/scss/config/mixins/_data-table.scss +346 -0
- package/scss/config/mixins/_display.scss +32 -0
- package/scss/config/mixins/_dropdown.scss +143 -0
- package/scss/config/mixins/_empty-state.scss +30 -0
- package/scss/config/mixins/_focus.scss +55 -0
- package/scss/config/mixins/_footer.scss +42 -0
- package/scss/config/mixins/_form.scss +601 -0
- package/scss/config/mixins/_index.scss +58 -0
- package/scss/config/mixins/_interaction.scss +15 -0
- package/scss/config/mixins/_kbd.scss +22 -0
- package/scss/config/mixins/_layout.scss +117 -0
- package/scss/config/mixins/_link.scss +55 -0
- package/scss/config/mixins/_ln-table.scss +420 -0
- package/scss/config/mixins/_loader.scss +26 -0
- package/scss/config/mixins/_modal.scss +66 -0
- package/scss/config/mixins/_motion.scss +19 -0
- package/scss/config/mixins/_nav.scss +273 -0
- package/scss/config/mixins/_page-header.scss +69 -0
- package/scss/config/mixins/_popover.scss +25 -0
- package/scss/config/mixins/_position.scss +32 -0
- package/scss/config/mixins/_progress.scss +56 -0
- package/scss/config/mixins/_prose.scss +127 -0
- package/scss/config/mixins/_shadows.scss +8 -0
- package/scss/config/mixins/_sidebar.scss +95 -0
- package/scss/config/mixins/_sizing.scss +6 -0
- package/scss/config/mixins/_spacing.scss +19 -0
- package/scss/config/mixins/_stat-card.scss +68 -0
- package/scss/config/mixins/_status-badge.scss +83 -0
- package/scss/config/mixins/_stepper.scss +78 -0
- package/scss/config/mixins/_table.scss +215 -0
- package/scss/config/mixins/_tabs.scss +64 -0
- package/scss/config/mixins/_timeline.scss +69 -0
- package/scss/config/mixins/_toast.scss +148 -0
- package/scss/config/mixins/_tooltip.scss +111 -0
- package/scss/config/mixins/_transitions.scss +10 -0
- package/scss/config/mixins/_translations.scss +124 -0
- package/scss/config/mixins/_typography.scss +57 -0
- package/scss/config/mixins/_upload.scss +168 -0
- package/scss/ln-ashlar.scss +62 -0
- package/scss/tabler-icons.txt +5039 -0
- package/scss/utilities/_animations.scss +83 -0
- package/scss/utilities/_utilities.scss +49 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { registerComponent, dispatch } from '../../ln-core';
|
|
2
|
+
|
|
3
|
+
(function () {
|
|
4
|
+
const DOM_SELECTOR = 'data-ln-data-coordinator';
|
|
5
|
+
const DOM_ATTRIBUTE = 'lnDataCoordinator';
|
|
6
|
+
const DOM_ALIAS = 'lnCoordinator';
|
|
7
|
+
|
|
8
|
+
if (window[DOM_ATTRIBUTE] !== undefined) return;
|
|
9
|
+
|
|
10
|
+
// ─── Component Constructor ─────────────────────────────
|
|
11
|
+
|
|
12
|
+
function _component(dom) {
|
|
13
|
+
this.dom = dom;
|
|
14
|
+
this._name = dom.getAttribute(DOM_SELECTOR);
|
|
15
|
+
dom[DOM_ATTRIBUTE] = this;
|
|
16
|
+
dom[DOM_ALIAS] = this;
|
|
17
|
+
|
|
18
|
+
this.mapper = null;
|
|
19
|
+
this._handlers = null;
|
|
20
|
+
|
|
21
|
+
this.refreshMapper();
|
|
22
|
+
_bindEvents(this);
|
|
23
|
+
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── Resolve and Refresh Mapper ──────────────────────────
|
|
28
|
+
|
|
29
|
+
_component.prototype.refreshMapper = function () {
|
|
30
|
+
this.mapper = null;
|
|
31
|
+
|
|
32
|
+
// 1. Check for deprecated/insecure inline script mapper
|
|
33
|
+
const inlineScript = this.dom.querySelector('script[data-ln-mapper]');
|
|
34
|
+
if (inlineScript) {
|
|
35
|
+
console.error('[ln-data-coordinator] Security Error: Inline script mappers using <script data-ln-mapper> are deprecated and disabled due to XSS vulnerability risks (unsafe-eval). Please register your mappers securely via window.lnCore.registerDataMapper() instead.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Resolve to registered external mapper
|
|
39
|
+
const mapperName = this.dom.getAttribute('data-ln-data-mapper') || this.dom.getAttribute('data-ln-data-coordinator');
|
|
40
|
+
if (mapperName && window.lnCore && typeof window.lnCore.getDataMapper === 'function') {
|
|
41
|
+
this.mapper = window.lnCore.getDataMapper(mapperName);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 3. Ultimate safe fallback: no-op mapper
|
|
45
|
+
if (!this.mapper) {
|
|
46
|
+
this.mapper = {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Ensure ingress and egress are safe callable functions
|
|
50
|
+
if (typeof this.mapper.ingress !== 'function') {
|
|
51
|
+
this.mapper.ingress = function (r) { return r; };
|
|
52
|
+
}
|
|
53
|
+
if (typeof this.mapper.egress !== 'function') {
|
|
54
|
+
this.mapper.egress = function (r) { return r; };
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ─── Dynamic Child Discovery ──────────────────────────────
|
|
59
|
+
|
|
60
|
+
_component.prototype.findChildren = function () {
|
|
61
|
+
const storeEl = this.dom.querySelector('[data-ln-data-store]');
|
|
62
|
+
const connectorEl = this.dom.querySelector('[data-ln-api-connector], [data-ln-couchdb-connector], [data-ln-websocket-connector], [data-ln-rest-connector]');
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
storeEl: storeEl,
|
|
66
|
+
connectorEl: connectorEl,
|
|
67
|
+
store: storeEl ? (storeEl.lnDataStore || storeEl.lnStore) : null,
|
|
68
|
+
connector: connectorEl ? (connectorEl.lnConnector || connectorEl.lnApiConnector || connectorEl.lnCouchDbConnector) : null
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// ─── Event Binding ────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function _bindEvents(self) {
|
|
75
|
+
self._handlers = {
|
|
76
|
+
sync: function (e) {
|
|
77
|
+
self.refreshMapper();
|
|
78
|
+
const children = self.findChildren();
|
|
79
|
+
if (!children.store || !children.connector) {
|
|
80
|
+
console.warn('[ln-data-coordinator] Cannot sync: store or connector not found in subtree');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const since = e.detail.since;
|
|
85
|
+
|
|
86
|
+
children.connector.fetchDelta(since)
|
|
87
|
+
.then(function (rawResponse) {
|
|
88
|
+
let rawRecords = [];
|
|
89
|
+
let deletedIds = [];
|
|
90
|
+
let syncedAt = null;
|
|
91
|
+
|
|
92
|
+
if (rawResponse && Array.isArray(rawResponse)) {
|
|
93
|
+
rawRecords = rawResponse;
|
|
94
|
+
syncedAt = Math.floor(Date.now() / 1000);
|
|
95
|
+
} else if (rawResponse) {
|
|
96
|
+
rawRecords = Array.isArray(rawResponse.data) ? rawResponse.data : [];
|
|
97
|
+
deletedIds = Array.isArray(rawResponse.deleted) ? rawResponse.deleted : [];
|
|
98
|
+
syncedAt = rawResponse.synced_at !== undefined ? rawResponse.synced_at : (rawResponse.since !== undefined ? rawResponse.since : null);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const normalizedData = rawRecords.map(r => self.mapper.ingress(r));
|
|
102
|
+
children.store.applySync(normalizedData, deletedIds, syncedAt);
|
|
103
|
+
})
|
|
104
|
+
.catch(function (err) {
|
|
105
|
+
console.error('[ln-data-coordinator] Sync failed:', err);
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
create: function (e) {
|
|
110
|
+
self.refreshMapper();
|
|
111
|
+
const children = self.findChildren();
|
|
112
|
+
if (!children.store || !children.connector) return;
|
|
113
|
+
|
|
114
|
+
const tempId = e.detail.tempId;
|
|
115
|
+
const data = e.detail.data || {};
|
|
116
|
+
|
|
117
|
+
// Apply egress mapping
|
|
118
|
+
const serverPayload = self.mapper.egress(data);
|
|
119
|
+
|
|
120
|
+
children.connector.create(serverPayload)
|
|
121
|
+
.then(function (serverRawResponse) {
|
|
122
|
+
const confirmedLocalRecord = self.mapper.ingress(serverRawResponse);
|
|
123
|
+
children.store.confirmMutation(tempId, confirmedLocalRecord, 'create');
|
|
124
|
+
})
|
|
125
|
+
.catch(function (err) {
|
|
126
|
+
console.error('[ln-data-coordinator] Create mutation failed:', err);
|
|
127
|
+
children.store.revertMutation(tempId, 'create', err.message || err);
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
update: function (e) {
|
|
132
|
+
self.refreshMapper();
|
|
133
|
+
const children = self.findChildren();
|
|
134
|
+
if (!children.store || !children.connector) return;
|
|
135
|
+
|
|
136
|
+
const id = e.detail.id;
|
|
137
|
+
const expectedVersion = e.detail.expected_version;
|
|
138
|
+
|
|
139
|
+
// Fetch complete merged local record to pass to egress for full PUT mapping
|
|
140
|
+
children.store.getById(id)
|
|
141
|
+
.then(function (localRecord) {
|
|
142
|
+
if (!localRecord) throw new Error('Record not found in cache store: ' + id);
|
|
143
|
+
|
|
144
|
+
const cleanRecord = Object.assign({}, localRecord);
|
|
145
|
+
delete cleanRecord._pending;
|
|
146
|
+
|
|
147
|
+
const serverPayload = self.mapper.egress(cleanRecord);
|
|
148
|
+
|
|
149
|
+
return children.connector.update(id, serverPayload, expectedVersion);
|
|
150
|
+
})
|
|
151
|
+
.then(function (serverRawResponse) {
|
|
152
|
+
const confirmedLocalRecord = self.mapper.ingress(serverRawResponse);
|
|
153
|
+
children.store.confirmMutation(id, confirmedLocalRecord, 'update');
|
|
154
|
+
})
|
|
155
|
+
.catch(function (err) {
|
|
156
|
+
console.error('[ln-data-coordinator] Update mutation failed:', err);
|
|
157
|
+
if (err.status === 409) {
|
|
158
|
+
const remoteRecord = err.data && err.data.remote ? self.mapper.ingress(err.data.remote) : null;
|
|
159
|
+
const fieldDiffs = err.data ? err.data.field_diffs : null;
|
|
160
|
+
children.store.resolveConflict(id, remoteRecord, fieldDiffs);
|
|
161
|
+
} else {
|
|
162
|
+
children.store.revertMutation(id, 'update', err.message || err);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
delete: function (e) {
|
|
168
|
+
self.refreshMapper();
|
|
169
|
+
const children = self.findChildren();
|
|
170
|
+
if (!children.store || !children.connector) return;
|
|
171
|
+
|
|
172
|
+
const id = e.detail.id;
|
|
173
|
+
|
|
174
|
+
children.connector.delete(id)
|
|
175
|
+
.then(function () {
|
|
176
|
+
children.store.confirmMutation(id, null, 'delete');
|
|
177
|
+
})
|
|
178
|
+
.catch(function (err) {
|
|
179
|
+
console.error('[ln-data-coordinator] Delete mutation failed:', err);
|
|
180
|
+
children.store.revertMutation(id, 'delete', err.message || err);
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
bulkDelete: function (e) {
|
|
185
|
+
self.refreshMapper();
|
|
186
|
+
const children = self.findChildren();
|
|
187
|
+
if (!children.store || !children.connector) return;
|
|
188
|
+
|
|
189
|
+
const ids = e.detail.ids || [];
|
|
190
|
+
const bulkKey = ids.join(',');
|
|
191
|
+
|
|
192
|
+
children.connector.bulkDelete(ids)
|
|
193
|
+
.then(function () {
|
|
194
|
+
children.store.confirmMutation(bulkKey, null, 'bulk-delete');
|
|
195
|
+
})
|
|
196
|
+
.catch(function (err) {
|
|
197
|
+
console.error('[ln-data-coordinator] Bulk delete mutation failed:', err);
|
|
198
|
+
children.store.revertMutation(bulkKey, 'bulk-delete', err.message || err);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Listen to all request-remote events bubbling up from the child store
|
|
204
|
+
self.dom.addEventListener('ln-store:request-remote-sync', self._handlers.sync);
|
|
205
|
+
self.dom.addEventListener('ln-store:request-remote-create', self._handlers.create);
|
|
206
|
+
self.dom.addEventListener('ln-store:request-remote-update', self._handlers.update);
|
|
207
|
+
self.dom.addEventListener('ln-store:request-remote-delete', self._handlers.delete);
|
|
208
|
+
self.dom.addEventListener('ln-store:request-remote-bulk-delete', self._handlers.bulkDelete);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ─── Destroy and Cleanup ──────────────────────────────────
|
|
212
|
+
|
|
213
|
+
_component.prototype.destroy = function () {
|
|
214
|
+
if (!this.dom[DOM_ATTRIBUTE]) return;
|
|
215
|
+
|
|
216
|
+
const self = this;
|
|
217
|
+
if (self._handlers) {
|
|
218
|
+
self.dom.removeEventListener('ln-store:request-remote-sync', self._handlers.sync);
|
|
219
|
+
self.dom.removeEventListener('ln-store:request-remote-create', self._handlers.create);
|
|
220
|
+
self.dom.removeEventListener('ln-store:request-remote-update', self._handlers.update);
|
|
221
|
+
self.dom.removeEventListener('ln-store:request-remote-delete', self._handlers.delete);
|
|
222
|
+
self.dom.removeEventListener('ln-store:request-remote-bulk-delete', self._handlers.bulkDelete);
|
|
223
|
+
self._handlers = null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
delete this.dom[DOM_ATTRIBUTE];
|
|
227
|
+
delete this.dom[DOM_ALIAS];
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// ─── Attribute Sync ────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
function _syncAttribute(el, attrName) {
|
|
233
|
+
const instance = el[DOM_ATTRIBUTE];
|
|
234
|
+
if (!instance) return;
|
|
235
|
+
|
|
236
|
+
if (attrName === 'data-ln-data-mapper') {
|
|
237
|
+
instance.refreshMapper();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─── Registration ──────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-data-coordinator', {
|
|
244
|
+
extraAttributes: [
|
|
245
|
+
'data-ln-data-mapper'
|
|
246
|
+
],
|
|
247
|
+
onAttributeChange: _syncAttribute
|
|
248
|
+
});
|
|
249
|
+
})();
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# ln-data-store
|
|
2
|
+
|
|
3
|
+
A zero-dependency, local-first **Database Cache Store** backed by standard browser `IndexedDB`. It acts as a pure client-side database cache, maintaining records locally, executing high-performance querying in-memory, and managing optimistic mutations with automatic snapshots for transaction safety.
|
|
4
|
+
|
|
5
|
+
It possesses no visual interface and is **completely blind to the network** (no fetch, status codes, paths, or urls). Instead, it communicates strictly via custom DOM events, allowing the parent **Data Coordinator** to orchestrate syncs and mutations.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📦 Declarative Setup in HTML
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<div data-ln-data-store="documents"
|
|
13
|
+
data-ln-data-store-stale="300"
|
|
14
|
+
data-ln-data-store-indexes="status,department,updated_at"
|
|
15
|
+
data-ln-data-store-search-fields="title,owner">
|
|
16
|
+
</div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 🛠️ JS API (On the element)
|
|
22
|
+
|
|
23
|
+
Access the database layer directly via the `lnDataStore` property on the store container:
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
const store = document.querySelector('[data-ln-data-store="documents"]');
|
|
27
|
+
|
|
28
|
+
// 1. Queries (returns Promise)
|
|
29
|
+
const { data, total, filtered } = await store.lnDataStore.getAll({
|
|
30
|
+
sort: { field: 'updated_at', direction: 'desc' },
|
|
31
|
+
filters: { status: ['Approved'] },
|
|
32
|
+
search: 'ISO 27001',
|
|
33
|
+
limit: 50
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const doc = await store.lnDataStore.getById(42);
|
|
37
|
+
|
|
38
|
+
// 2. Decorators / Computed Fields Configuration
|
|
39
|
+
store.lnDataStore.setPresenters({
|
|
40
|
+
computed: {
|
|
41
|
+
size_display: record => (record.file_size / 1024).toFixed(1) + ' MB'
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## ⚡ DOM Events
|
|
49
|
+
|
|
50
|
+
### Commands (Dispatched TO the store)
|
|
51
|
+
|
|
52
|
+
*Never invoke write methods directly.* Route all mutations through DOM commands:
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
// Create optimistically
|
|
56
|
+
store.dispatchEvent(new CustomEvent('ln-store:request-create', {
|
|
57
|
+
detail: { data: { title: 'New Document', status: 'Draft' } }
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// Update with version locks
|
|
61
|
+
store.dispatchEvent(new CustomEvent('ln-store:request-update', {
|
|
62
|
+
detail: { id: 42, data: { title: 'Updated title' }, expected_version: 3 }
|
|
63
|
+
}));
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Remote Request Events (Bubbles UP to the Coordinator)
|
|
67
|
+
|
|
68
|
+
*The Coordinator catches these events to run transport sync and mutations:*
|
|
69
|
+
|
|
70
|
+
* `ln-store:request-remote-sync` (detail: `{ since }`)
|
|
71
|
+
* `ln-store:request-remote-create` (detail: `{ tempId, data }`)
|
|
72
|
+
* `ln-store:request-remote-update` (detail: `{ id, data, expected_version }`)
|
|
73
|
+
* `ln-store:request-remote-delete` (detail: `{ id }`)
|
|
74
|
+
* `ln-store:request-remote-bulk-delete` (detail: `{ ids }`)
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 🚀 Public Synchronization APIs (Invoked by the Coordinator)
|
|
79
|
+
|
|
80
|
+
Once the network connector returns backend payloads, the Coordinator feeds the results back to the store using these public methods:
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// Feed delta updates or initial load data
|
|
84
|
+
store.lnDataStore.applySync(upsertedRecords, deletedIds, syncedAt);
|
|
85
|
+
|
|
86
|
+
// Confirm an optimistic transaction (swaps temp IDs, clears pending flag)
|
|
87
|
+
store.lnDataStore.confirmMutation(tempIdOrId, serverRecord, action);
|
|
88
|
+
|
|
89
|
+
// Revert an optimistic transaction (restores snapshot on error)
|
|
90
|
+
store.lnDataStore.revertMutation(tempIdOrId, action, errorMessage);
|
|
91
|
+
|
|
92
|
+
// Trigger conflict merge flow
|
|
93
|
+
store.lnDataStore.resolveConflict(id, remoteRecord, fieldDiffs);
|
|
94
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";function l(a,i,d){a.dispatchEvent(new CustomEvent(i,{bubbles:!0,detail:d||{}}))}function H(a,i){if(!document.body){document.addEventListener("DOMContentLoaded",function(){H(a,i)}),console.warn("["+i+'] Script loaded before <body> — add "defer" to your <script> tag');return}a()}function K(a,i,d,h){if(a.nodeType!==1)return;const _=i.indexOf("[")!==-1||i.indexOf(".")!==-1||i.indexOf("#")!==-1?i:"["+i+"]",m=Array.from(a.querySelectorAll(_));a.matches&&a.matches(_)&&m.push(a);for(const u of m)u[d]||(u[d]=new h(u))}function ee(a,i,d,h,p={}){const _=p.extraAttributes||[],m=p.onAttributeChange||null,u=p.onInit||null;function g(L){const A=L||document.body;K(A,a,i,d),u&&u(A)}return H(function(){const L=new MutationObserver(function(k){for(let w=0;w<k.length;w++){const y=k[w];if(y.type==="childList")for(let B=0;B<y.addedNodes.length;B++){const R=y.addedNodes[B];R.nodeType===1&&(K(R,a,i,d),u&&u(R))}else y.type==="attributes"&&(m&&y.target[i]?m(y.target,y.attributeName):(K(y.target,a,i,d),u&&u(y.target)))}});let A=[];if(a.indexOf("[")!==-1){const k=/\[([\w-]+)/g;let w;for(;(w=k.exec(a))!==null;)A.push(w[1])}else A.push(a);L.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:A.concat(_)})},h),window[i]=g,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){g(document.body)}):g(document.body),g}const V={};function te(a,i){V[a]=i}function ne(a){return V[a]||{ingress:i=>i,egress:i=>i}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=te,window.lnCore.getDataMapper=ne);let x=null;async function G(a){if(!a){x=null;return}try{const i=new TextEncoder,d=await crypto.subtle.digest("SHA-256",i.encode(a));x=await crypto.subtle.importKey("raw",d,{name:"AES-GCM"},!1,["encrypt","decrypt"])}catch(i){console.error("[ln-core/crypto] Key derivation failed:",i),x=null}}function j(){return x}async function re(a,i=x){const d=i||x;if(!d||a===void 0||a===null)return a;try{const h=new TextEncoder,p=crypto.getRandomValues(new Uint8Array(12)),_=typeof a=="string"?a:JSON.stringify(a),m=await crypto.subtle.encrypt({name:"AES-GCM",iv:p},d,h.encode(_)),u=btoa(String.fromCharCode(...p)),g=btoa(String.fromCharCode(...new Uint8Array(m)));return{encrypted:!0,iv:u,data:g}}catch(h){return console.error("[ln-core/crypto] Encryption failed:",h),a}}async function oe(a,i=x){const d=i||x;if(!a||!a.encrypted||!d)return a;try{const h=new TextDecoder,p=Uint8Array.from(atob(a.iv),g=>g.charCodeAt(0)),_=Uint8Array.from(atob(a.data),g=>g.charCodeAt(0)),m=await crypto.subtle.decrypt({name:"AES-GCM",iv:p},d,_),u=h.decode(m);try{return JSON.parse(u)}catch{return u}}catch(h){return console.error("[ln-core/crypto] Decryption failed. Key may be incorrect:",h),{...a,decryptionError:!0}}}(function(){const a="data-ln-data-store",i="lnDataStore";if(window[i]!==void 0)return;const d="ln_app_cache",h="_meta",p="1.0";let _=null,m=null;const u={};function g(){try{return crypto.randomUUID()}catch{return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{const r=Math.random()*16|0;return(t==="x"?r:r&3|8).toString(16)})}}function L(e){e&&e.name==="QuotaExceededError"&&l(document,"ln-store:quota-exceeded",{error:e})}function A(){const e={};for(const t of document.querySelectorAll(`[${a}]`)){const r=t.getAttribute(a);if(r){const n=t.getAttribute("data-ln-data-store-indexes")||t.getAttribute("data-ln-store-indexes")||"";e[r]={indexes:n.split(",").map(o=>o.trim()).filter(Boolean)}}}return e}function k(){return m||(m=new Promise(e=>{if(typeof indexedDB>"u")return console.warn("[ln-data-store] IndexedDB not available — falling back to in-memory store"),e(null);const t=A(),r=Object.keys(t),n=indexedDB.open(d);n.onerror=()=>{console.warn("[ln-data-store] IndexedDB open failed — falling back to in-memory store"),e(null)},n.onsuccess=o=>{const s=o.target.result,c=Array.from(s.objectStoreNames);if(!(!c.includes(h)||r.some(q=>!c.includes(q))))return w(s),_=s,e(s);const S=s.version;s.close();const v=indexedDB.open(d,S+1);v.onblocked=()=>{console.warn("[ln-data-store] Database upgrade blocked — waiting for other tabs to close connection")},v.onerror=()=>{console.warn("[ln-data-store] Database upgrade failed"),e(null)},v.onupgradeneeded=q=>{const P=q.target.result;P.objectStoreNames.contains(h)||P.createObjectStore(h,{keyPath:"key"});for(const $ of r)if(!P.objectStoreNames.contains($)){const ye=P.createObjectStore($,{keyPath:"id"});for(const I of t[$].indexes)ye.createIndex(I,I,{unique:!1})}},v.onsuccess=q=>{const P=q.target.result;w(P),_=P,e(P)}}}),m)}function w(e){e.onversionchange=()=>{e.close(),_=null,m=null}}function y(){return _?Promise.resolve(_):(m=null,k())}async function B(e){if(!j()||!e)return e;const t={...e},r=t.id,n=t._pending,o=await re(t);return!o||!o.encrypted?e:{id:r,_pending:n,encrypted:!0,iv:o.iv,data:o.data}}async function R(e){return!e||!e.encrypted||!j()?e:oe(e)}const C=(e,t)=>y().then(r=>r?r.transaction(e,t).objectStore(e):null);function E(e){return new Promise((t,r)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>{L(e.error),r(e.error)}})}const U=e=>C(e,"readonly").then(t=>t?E(t.getAll()):[]).then(t=>j()?Promise.all(t.map(r=>R(r))):t),N=(e,t)=>C(e,"readonly").then(r=>r?E(r.get(t)):null).then(r=>r?R(r):null),D=(e,t)=>(j()?B(t):Promise.resolve(t)).then(n=>C(e,"readwrite").then(o=>o?E(o.put(n)):null)),O=(e,t)=>C(e,"readwrite").then(r=>r?E(r.delete(t)):null),J=e=>C(e,"readwrite").then(t=>t?E(t.clear()):null),Q=e=>C(e,"readonly").then(t=>t?E(t.count()):0),se=e=>C(h,"readonly").then(t=>t?E(t.get(e)):null),z=(e,t)=>C(h,"readwrite").then(r=>{if(r)return t.key=e,E(r.put(t))});function b(e){this.dom=e,this._name=e.getAttribute(a);const t=e.getAttribute("data-ln-data-store-stale")||e.getAttribute("data-ln-store-stale"),r=parseInt(t,10);this._staleThreshold=t==="never"||t==="-1"?-1:isNaN(r)?300:r;const n=e.getAttribute("data-ln-data-store-search-fields")||e.getAttribute("data-ln-store-search-fields")||"";return this._searchFields=n.split(",").map(o=>o.trim()).filter(Boolean),this._handlers=null,this._pendingSnapshots={},this.isLoaded=!1,this.isSyncing=!1,this.lastSyncedAt=null,this.totalCount=0,this.presenters=null,u[this._name]=this,ae(this),de(this),this}function ae(e){e._handlers={create:t=>ie(e,t.detail),update:t=>ce(e,t.detail),delete:t=>le(e,t.detail),"bulk-delete":t=>ue(e,t.detail)};for(const[t,r]of Object.entries(e._handlers))e.dom.addEventListener(`ln-store:request-${t}`,r)}function ie(e,{data:t={}}={}){const r=`_temp_${g()}`,n={...t,id:r,_pending:!0};D(e._name,n).then(()=>{e.totalCount++,l(e.dom,"ln-store:created",{store:e._name,record:n,tempId:r}),l(e.dom,"ln-store:request-remote-create",{tempId:r,data:t})})}function ce(e,{id:t,data:r={},expected_version:n}={}){N(e._name,t).then(o=>{if(!o)throw new Error(`Record not found: ${t}`);e._pendingSnapshots[t]={...o};const s={...o,...r,_pending:!0};return D(e._name,s).then(()=>{l(e.dom,"ln-store:updated",{store:e._name,record:s,previous:e._pendingSnapshots[t]}),l(e.dom,"ln-store:request-remote-update",{id:t,data:r,expected_version:n})})}).catch(o=>console.error("[ln-data-store] Optimistic update failed:",o))}function le(e,{id:t}={}){N(e._name,t).then(r=>{if(r)return e._pendingSnapshots[t]={...r},O(e._name,t).then(()=>{e.totalCount--,l(e.dom,"ln-store:deleted",{store:e._name,id:t}),l(e.dom,"ln-store:request-remote-delete",{id:t})})}).catch(r=>console.error("[ln-data-store] Optimistic delete failed:",r))}function ue(e,{ids:t=[]}={}){t.length&&Promise.all(t.map(r=>N(e._name,r))).then(r=>{const n=r.filter(Boolean),o=n.map(s=>s.id);return e._pendingSnapshots[o.join(",")]=n,Y(e._name,o).then(()=>{e.totalCount-=o.length,l(e.dom,"ln-store:deleted",{store:e._name,ids:o}),l(e.dom,"ln-store:request-remote-bulk-delete",{ids:o})})}).catch(r=>console.error("[ln-data-store] Optimistic bulk delete failed:",r))}function de(e){k().then(()=>se(e._name)).then(t=>{t&&t.schema_version===p?(e.lastSyncedAt=t.last_synced_at||null,e.totalCount=t.record_count||0,e.totalCount>0?(e.isLoaded=!0,l(e.dom,"ln-store:ready",{store:e._name,count:e.totalCount,source:"cache"}),W(e)&&M(e)):M(e)):t&&t.schema_version!==p?J(e._name).then(()=>z(e._name,{schema_version:p,last_synced_at:null,record_count:0})).then(()=>M(e)):M(e)})}function W(e){return e._staleThreshold===-1?!1:e.lastSyncedAt?Math.floor(Date.now()/1e3)-e.lastSyncedAt>e._staleThreshold:!0}function M(e){e.isSyncing=!0,l(e.dom,"ln-store:request-remote-sync",{since:e.lastSyncedAt})}function X(e,t){return y().then(r=>r?(j()?Promise.all(t.map(o=>B(o))):Promise.resolve(t)).then(o=>new Promise((s,c)=>{const f=r.transaction(e,"readwrite"),S=f.objectStore(e);o.forEach(v=>S.put(v)),f.oncomplete=()=>s(),f.onerror=()=>{L(f.error),c(f.error)}})):void 0)}function Y(e,t){return y().then(r=>{if(r)return new Promise((n,o)=>{const s=r.transaction(e,"readwrite"),c=s.objectStore(e);t.forEach(f=>c.delete(f)),s.oncomplete=()=>n(),s.onerror=()=>o(s.error)})})}let T=()=>{document.visibilityState==="visible"&&Object.values(u).forEach(e=>{e.isLoaded&&!e.isSyncing&&W(e)&&M(e)})};document.addEventListener("visibilitychange",T);const he=new Intl.Collator(void 0,{numeric:!0,sensitivity:"base"});function fe(e,t){if(!t||!t.field)return e;const{field:r,direction:n}=t,o=n==="desc";return[...e].sort((s,c)=>{const f=s[r],S=c[r];if(f==null&&S==null)return 0;if(f==null)return o?1:-1;if(S==null)return o?-1:1;const v=typeof f=="string"&&typeof S=="string"?he.compare(f,S):f<S?-1:f>S?1:0;return o?-v:v})}function Z(e,t){if(!t)return e;const r=Object.keys(t).filter(n=>Array.isArray(t[n])&&t[n].length>0);return r.length?e.filter(n=>r.every(o=>t[o].map(String).includes(String(n[o])))):e}function me(e,t,r){if(!t||!r||!r.length)return e;const n=t.toLowerCase();return e.filter(o=>r.some(s=>{const c=o[s];return c!=null&&String(c).toLowerCase().includes(n)}))}function pe(e,t,r){if(!e.length)return 0;if(r==="count")return e.length;const n=e.map(s=>parseFloat(s[t])).filter(s=>!isNaN(s)),o=n.reduce((s,c)=>s+c,0);return r==="sum"?o:r==="avg"&&n.length?o/n.length:0}function F(e,t){if(!e.presenters||!e.presenters.computed)return t;const r=e.presenters.computed;return t.map(n=>{const o={...n};for(const[s,c]of Object.entries(r))try{o[s]=c(n)}catch(f){console.error(`[ln-data-store] Decorator computed field failed for ${s}`,f)}return o})}b.prototype.getAll=function(e={}){const t=this;return U(t._name).then(r=>{const n=r.length;e.filters&&(r=Z(r,e.filters)),e.search&&(r=me(r,e.search,t._searchFields));const o=r.length;if(e.sort&&(r=fe(r,e.sort)),e.offset||e.limit){const s=e.offset||0,c=e.limit||r.length;r=r.slice(s,s+c)}return{data:F(t,r),total:n,filtered:o}})},b.prototype.getById=function(e){return N(this._name,e).then(t=>t?F(this,[t])[0]:null)},b.prototype.count=function(e){return e?U(this._name).then(t=>Z(t,e).length):Q(this._name)},b.prototype.aggregate=function(e,t){return U(this._name).then(r=>pe(r,e,t))},b.prototype.setPresenters=function(e){this.presenters=e},b.prototype.applySync=function(e,t,r){const n=this,o=e.length>0||t.length>0;let s=Promise.resolve();return e.length>0&&(s=s.then(()=>X(n._name,e))),t.length>0&&(s=s.then(()=>Y(n._name,t))),s.then(()=>Q(n._name)).then(c=>(n.totalCount=c,z(n._name,{schema_version:p,last_synced_at:r,record_count:c}))).then(()=>{const c=!n.isLoaded;n.isLoaded=!0,n.isSyncing=!1,n.lastSyncedAt=r,c?(l(n.dom,"ln-store:loaded",{store:n._name,count:n.totalCount}),l(n.dom,"ln-store:ready",{store:n._name,count:n.totalCount,source:"server"})):l(n.dom,"ln-store:synced",{store:n._name,added:e.length,deleted:t.length,changed:o})}).catch(c=>{n.isSyncing=!1,console.error("[ln-data-store] applySync failed:",c)})},b.prototype.confirmMutation=function(e,t,r){const n=this,o={create:()=>O(n._name,e).then(()=>D(n._name,t)).then(()=>{delete n._pendingSnapshots[e],l(n.dom,"ln-store:confirmed",{store:n._name,record:t,tempId:e,action:"create"})}),update:()=>D(n._name,t).then(()=>{delete n._pendingSnapshots[e],l(n.dom,"ln-store:confirmed",{store:n._name,record:t,action:"update"})}),delete:()=>(delete n._pendingSnapshots[e],l(n.dom,"ln-store:confirmed",{store:n._name,record:null,action:"delete"}),Promise.resolve()),"bulk-delete":()=>(delete n._pendingSnapshots[e],l(n.dom,"ln-store:confirmed",{store:n._name,record:null,ids:e.split(","),action:"bulk-delete"}),Promise.resolve())};return o[r]?o[r]():Promise.resolve()},b.prototype.revertMutation=function(e,t,r){const n=this,o=r||`Server rejected ${t}`,s={create:()=>O(n._name,e).then(()=>{n.totalCount--,delete n._pendingSnapshots[e],l(n.dom,"ln-store:reverted",{store:n._name,record:null,action:"create",error:o})}),update:()=>{const c=n._pendingSnapshots[e];return c?D(n._name,c).then(()=>{delete n._pendingSnapshots[e],l(n.dom,"ln-store:reverted",{store:n._name,record:c,action:"update",error:o})}):Promise.resolve()},delete:()=>{const c=n._pendingSnapshots[e];return c?D(n._name,c).then(()=>{n.totalCount++,delete n._pendingSnapshots[e],l(n.dom,"ln-store:reverted",{store:n._name,record:c,action:"delete",error:o})}):Promise.resolve()},"bulk-delete":()=>{const c=n._pendingSnapshots[e];return!c||!c.length?Promise.resolve():X(n._name,c).then(()=>{n.totalCount+=c.length,delete n._pendingSnapshots[e],l(n.dom,"ln-store:reverted",{store:n._name,record:null,ids:e.split(","),action:"bulk-delete",error:o})})}};return s[t]?s[t]():Promise.resolve()},b.prototype.resolveConflict=function(e,t,r){const n=this._pendingSnapshots[e];return n?D(this._name,n).then(()=>{delete this._pendingSnapshots[e],l(this.dom,"ln-store:conflict",{store:this._name,local:n,remote:t,field_diffs:r||null})}):Promise.resolve()},b.prototype.forceSync=function(){M(this)},b.prototype.fullReload=function(){const e=this;return J(e._name).then(()=>{e.isLoaded=!1,e.lastSyncedAt=null,e.totalCount=0,M(e)})},b.prototype.destroy=function(){if(this._handlers){for(const[e,t]of Object.entries(this._handlers))this.dom.removeEventListener(`ln-store:request-${e}`,t);this._handlers=null}delete u[this._name],Object.keys(u).length===0&&T&&(document.removeEventListener("visibilitychange",T),T=null),delete this.dom[i],l(this.dom,"ln-store:destroyed",{store:this._name})};function _e(){return y().then(e=>{if(!e)return;const t=Array.from(e.objectStoreNames);return new Promise((r,n)=>{const o=e.transaction(t,"readwrite");t.forEach(s=>o.objectStore(s).clear()),o.oncomplete=()=>r(),o.onerror=()=>n(o.error)})}).then(()=>{Object.values(u).forEach(e=>{e.isLoaded=!1,e.isSyncing=!1,e.lastSyncedAt=null,e.totalCount=0})})}ee(a,i,b,"ln-data-store"),window[i].clearAll=_e,window[i].init=window[i],window[i].setStorageKey=G,typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.setStorageKey=G)})()})();
|