@livenetworks/ashlar 1.5.1 → 1.6.1
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 +189 -177
- package/js/COMPONENTS.md +55 -49
- package/js/index.js +22 -5
- package/js/ln-accordion/README.md +186 -137
- package/js/ln-accordion/ln-accordion.js +1 -1
- package/js/ln-ajax/README.md +73 -4
- package/js/ln-ajax/ln-ajax.js +1 -1
- package/js/ln-ajax/src/ln-ajax.js +13 -13
- package/js/ln-api-connector/README.md +261 -150
- package/js/ln-api-connector/ln-api-connector.js +1 -1
- package/js/ln-api-connector/src/ln-api-connector.js +368 -265
- package/js/ln-api-queue/README.md +219 -0
- package/js/ln-api-queue/ln-api-queue.js +1 -0
- package/js/ln-api-queue/src/ln-api-queue.js +320 -0
- package/js/ln-api-queue/src/queue-storage.js +455 -0
- package/js/ln-autoresize/README.md +47 -0
- package/js/ln-autoresize/ln-autoresize.js +1 -1
- package/js/ln-autosave/README.md +33 -1
- package/js/ln-autosave/ln-autosave.js +1 -1
- package/js/ln-autosave/src/ln-autosave.js +4 -4
- package/js/ln-circular-progress/README.md +27 -13
- package/js/ln-circular-progress/ln-circular-progress.js +1 -1
- package/js/ln-circular-progress/src/ln-circular-progress.js +18 -5
- package/js/ln-confirm/README.md +165 -86
- package/js/ln-confirm/_ln-confirm.scss +11 -0
- package/js/ln-confirm/ln-confirm.js +1 -1
- package/js/ln-confirm/src/ln-confirm.js +82 -42
- package/js/ln-core/README.md +896 -0
- package/js/ln-core/hash.js +92 -0
- package/js/ln-core/helpers.js +337 -8
- package/js/ln-core/index.js +4 -2
- package/js/ln-core/persist.js +29 -3
- package/js/ln-core/positioning.js +1 -35
- package/js/ln-core/window-cache.js +213 -0
- package/js/ln-couchdb-connector/README.md +177 -156
- package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
- package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +398 -348
- package/js/ln-data-coordinator/README.md +536 -165
- package/js/ln-data-coordinator/ln-data-coordinator.js +1 -1
- package/js/ln-data-coordinator/src/data-read-policy.js +18 -0
- package/js/ln-data-coordinator/src/ln-data-coordinator.js +1042 -249
- package/js/ln-data-coordinator/src/mutation-receipts.js +46 -0
- package/js/ln-data-store/README.md +241 -94
- package/js/ln-data-store/ln-data-store.js +1 -1
- package/js/ln-data-store/src/ln-data-store.js +694 -699
- package/js/ln-date/README.md +39 -1
- package/js/ln-date/ln-date-dev.scss +14 -0
- package/js/ln-date/ln-date.js +1 -1
- package/js/ln-date/src/ln-date.js +236 -178
- package/js/ln-debug/README.md +58 -0
- package/js/ln-debug/ln-debug.js +1 -0
- package/js/ln-debug/src/ln-debug.js +19 -0
- package/js/ln-dropdown/README.md +31 -8
- package/js/ln-dropdown/ln-dropdown.js +1 -1
- package/js/ln-dropdown/src/ln-dropdown.js +30 -13
- package/js/ln-editor/README.md +274 -0
- package/js/ln-editor/ln-editor.js +1 -0
- package/js/ln-editor/ln-editor.scss +43 -0
- package/js/ln-editor/src/ln-editor.js +619 -0
- package/js/ln-external-links/README.md +44 -5
- package/js/ln-external-links/ln-external-links.js +1 -1
- package/js/ln-fill/README.md +199 -0
- package/js/ln-fill/ln-fill.js +1 -0
- package/js/ln-fill/src/ln-fill.js +87 -0
- package/js/ln-filter/README.md +92 -2
- package/js/ln-filter/ln-filter-dev.scss +8 -0
- package/js/ln-filter/ln-filter.js +1 -1
- package/js/ln-filter/src/ln-filter.js +22 -7
- package/js/ln-form/README.md +212 -49
- package/js/ln-form/ln-form.js +1 -1
- package/js/ln-form/src/ln-form.js +99 -199
- package/js/ln-http/README.md +125 -89
- package/js/ln-http/ln-http.js +1 -1
- package/js/ln-http/src/ln-http.js +32 -13
- package/js/ln-icon/README.md +130 -0
- package/js/ln-icon/ln-icon.js +1 -0
- package/js/{ln-icons/src/ln-icons.js → ln-icon/src/ln-icon.js} +20 -15
- package/js/ln-link/README.md +47 -9
- package/js/ln-link/ln-link.js +1 -1
- package/js/ln-link/src/ln-link.js +22 -6
- package/js/ln-list/README.md +129 -0
- package/js/ln-list/ln-list.js +1 -0
- package/js/ln-list/ln-list.scss +28 -0
- package/js/ln-list/src/ln-list.js +1216 -0
- package/js/ln-modal/README.md +185 -154
- package/js/ln-modal/ln-modal-dev.scss +13 -0
- package/js/ln-modal/ln-modal.js +1 -1
- package/js/ln-modal/ln-modal.scss +28 -4
- package/js/ln-modal/src/ln-modal.js +137 -201
- package/js/ln-modal-coordinator/README.md +99 -0
- package/js/ln-modal-coordinator/ln-modal-coordinator-dev.scss +8 -0
- package/js/ln-modal-coordinator/ln-modal-coordinator.js +1 -0
- package/js/ln-modal-coordinator/src/ln-modal-coordinator.js +345 -0
- package/js/ln-nav/README.md +46 -3
- package/js/ln-nav/ln-nav.js +1 -1
- package/js/ln-nav/src/ln-nav.js +92 -127
- package/js/ln-number/README.md +28 -1
- package/js/ln-number/ln-number-dev.scss +13 -0
- package/js/ln-number/ln-number.js +1 -1
- package/js/ln-number/src/ln-number.js +443 -350
- package/js/ln-options/README.md +66 -0
- package/js/ln-options/ln-options.js +1 -0
- package/js/ln-options/src/ln-options.js +74 -0
- package/js/ln-popover/README.md +40 -5
- package/js/ln-popover/ln-popover.js +1 -1
- package/js/ln-popover/src/ln-popover.js +38 -11
- package/js/ln-progress/README.md +413 -442
- package/js/ln-progress/ln-progress.js +1 -1
- package/js/ln-progress/src/ln-progress.js +98 -150
- package/js/ln-router/README.md +432 -0
- package/js/ln-router/ln-router.js +1 -0
- package/js/ln-router/src/ln-router.js +539 -0
- package/js/ln-search/README.md +158 -83
- package/js/ln-search/ln-search-dev.scss +8 -0
- package/js/ln-search/ln-search.js +1 -1
- package/js/ln-search/src/ln-search.js +12 -9
- package/js/ln-slug/README.md +110 -0
- package/js/ln-slug/ln-slug.js +1 -0
- package/js/ln-slug/src/ln-slug.js +74 -0
- package/js/ln-sort/README.md +170 -0
- package/js/ln-sort/ln-sort-dev.scss +8 -0
- package/js/ln-sort/ln-sort.js +1 -0
- package/js/ln-sort/src/ln-sort.js +178 -0
- package/js/ln-sortable/README.md +133 -95
- package/js/ln-sortable/ln-sortable.js +1 -1
- package/js/ln-stat/README.md +55 -0
- package/js/ln-stat/ln-stat.js +1 -0
- package/js/ln-stat/src/ln-stat.js +55 -0
- package/js/ln-table/README.md +285 -101
- package/js/ln-table/ln-table-dev.scss +17 -0
- package/js/ln-table/ln-table.js +1 -1
- package/js/ln-table/ln-table.scss +1 -1
- package/js/ln-table/src/ln-table.js +1493 -473
- package/js/ln-table-coordinator/README.md +73 -0
- package/js/ln-table-coordinator/ln-table-coordinator.js +1 -0
- package/js/ln-table-coordinator/src/ln-table-coordinator.js +160 -0
- package/js/ln-tabs/README.md +221 -137
- package/js/ln-tabs/ln-tabs-dev.scss +18 -0
- package/js/ln-tabs/ln-tabs.js +1 -1
- package/js/ln-tabs/src/ln-tabs.js +41 -24
- package/js/ln-time/README.md +14 -0
- package/js/ln-time/ln-time.js +1 -1
- package/js/ln-time/src/ln-time.js +223 -192
- package/js/ln-toast/README.md +178 -122
- package/js/ln-toast/ln-toast.js +1 -15
- package/js/ln-toast/src/ln-toast.js +232 -210
- package/js/ln-toast/template.html +16 -12
- package/js/ln-toggle/README.md +61 -2
- package/js/ln-toggle/ln-toggle-dev.scss +13 -0
- package/js/ln-toggle/ln-toggle.js +1 -1
- package/js/ln-toggle/src/ln-toggle.js +56 -43
- package/js/ln-tooltip/README.md +95 -58
- package/js/ln-tooltip/ln-tooltip-dev.scss +9 -0
- package/js/ln-tooltip/ln-tooltip.js +1 -1
- package/js/ln-tooltip/src/ln-tooltip.js +45 -6
- package/js/ln-translations/README.md +118 -96
- package/js/ln-translations/ln-translations.js +1 -1
- package/js/ln-translations/src/ln-translations.js +10 -5
- package/js/ln-upload/README.md +85 -10
- package/js/ln-upload/ln-upload.js +1 -1
- package/js/ln-upload/src/ln-upload.js +26 -10
- package/js/ln-validate/README.md +72 -0
- package/js/ln-validate/ln-validate-dev.scss +15 -0
- package/js/ln-validate/ln-validate.js +1 -1
- package/js/ln-validate/src/ln-validate.js +64 -0
- package/package.json +61 -56
- package/scss/base/_global.scss +2 -2
- package/scss/base/_typography.scss +1 -2
- package/scss/components/_date.scss +1 -0
- package/scss/components/_dropdown.scss +0 -5
- package/scss/components/_editor.scss +5 -0
- package/scss/components/_filter.scss +26 -25
- package/scss/components/_form.scss +7 -0
- package/scss/components/_ln-table.scss +13 -3
- package/scss/components/_sort.scss +10 -0
- package/scss/components/_table.scss +36 -23
- package/scss/components/_toast.scss +68 -41
- package/scss/components/_toggle.scss +16 -0
- package/scss/config/_density.scss +205 -117
- package/scss/config/_icons.scss +10 -9
- package/scss/config/_tokens.scss +69 -47
- package/scss/config/mixins/_accordion.scss +2 -7
- package/scss/config/mixins/_alert.scss +4 -4
- package/scss/config/mixins/_breadcrumbs.scss +2 -2
- package/scss/config/mixins/_btn.scss +1 -1
- package/scss/config/mixins/_card.scss +89 -14
- package/scss/config/mixins/_collapsible.scss +3 -0
- package/scss/config/mixins/_colors.scss +1 -1
- package/scss/config/mixins/_diagnostics.scss +11 -0
- package/scss/config/mixins/_display.scss +1 -0
- package/scss/config/mixins/_dropdown.scss +3 -5
- package/scss/config/mixins/_editor.scss +124 -0
- package/scss/config/mixins/_empty-state.scss +7 -0
- package/scss/config/mixins/_filter.scss +3 -5
- package/scss/config/mixins/_footer.scss +1 -2
- package/scss/config/mixins/_form.scss +207 -21
- package/scss/config/mixins/_index.scss +2 -0
- package/scss/config/mixins/_ln-table.scss +41 -2
- package/scss/config/mixins/_modal.scss +149 -66
- package/scss/config/mixins/_nav.scss +3 -4
- package/scss/config/mixins/_popover.scss +39 -0
- package/scss/config/mixins/_sidebar.scss +1 -1
- package/scss/config/mixins/_sort.scss +42 -0
- package/scss/config/mixins/_table.scss +85 -60
- package/scss/config/mixins/_toast.scss +179 -152
- package/scss/config/mixins/_tooltip.scss +17 -7
- package/scss/config/mixins/_typography.scss +4 -2
- package/scss/ln-ashlar-dev.scss +27 -9
- package/scss/ln-ashlar.scss +7 -1
- package/js/ln-data-table/README.md +0 -110
- package/js/ln-data-table/ln-data-table.js +0 -1
- package/js/ln-data-table/ln-data-table.scss +0 -10
- package/js/ln-data-table/src/ln-data-table.js +0 -1087
- package/js/ln-icons/README.md +0 -88
- package/js/ln-icons/ln-icons.js +0 -1
- package/js/ln-table/ln-table-sort.js +0 -1
- package/js/ln-table/src/ln-table-sort.js +0 -156
|
@@ -1,150 +1,261 @@
|
|
|
1
|
-
# `data-ln-api-connector`
|
|
2
|
-
|
|
3
|
-
A zero-dependency, Local-First sync transport component that implements the Transport Gateway pattern of `ln-ashlar`.
|
|
4
|
-
|
|
5
|
-
This component encapsulates all connection parameters (base URLs, auth tokens, headers, paths) and provides a declarative, event-driven, or programmatic way to talk to any RESTful or JSON API backend. It isolates networking concerns completely, making your cache store (`data-ln-data-store`) and visual presentation layers fully network-agnostic.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
connector
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
1
|
+
# `data-ln-api-connector`
|
|
2
|
+
|
|
3
|
+
A zero-dependency, Local-First sync transport component that implements the Transport Gateway pattern of `ln-ashlar`.
|
|
4
|
+
|
|
5
|
+
This component encapsulates all connection parameters (base URLs, auth tokens, headers, paths) and provides a declarative, event-driven, or programmatic way to talk to any RESTful or JSON API backend. It isolates networking concerns completely, making your cache store (`data-ln-data-store`) and visual presentation layers fully network-agnostic.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🔒 Forced `X-LN-Response: data` Header
|
|
10
|
+
|
|
11
|
+
Every request (`fetchDelta`, `create`, `update`, `delete`, `bulkDelete`) sends
|
|
12
|
+
`X-LN-Response: data` in addition to any configured `data-ln-api-headers`.
|
|
13
|
+
This header is **forced** — it rides on all five verbs via a single internal
|
|
14
|
+
helper and cannot be removed or overridden by consumer-supplied headers. The
|
|
15
|
+
backend uses it to select a JSON "data" response mode; it doubles as a CSRF
|
|
16
|
+
guard.
|
|
17
|
+
|
|
18
|
+
Queued mutation commands may also provide `idempotencyKey`. The connector
|
|
19
|
+
forwards it as `Idempotency-Key`; direct/non-queued calls omit it. Servers
|
|
20
|
+
should persist the first result for a key so lease recovery can safely retry a
|
|
21
|
+
request after a client crash.
|
|
22
|
+
|
|
23
|
+
## ⚠️ Unified 4xx / 5xx Body Parsing
|
|
24
|
+
|
|
25
|
+
Every non-2xx response (not just 409) has its JSON body parsed best-effort
|
|
26
|
+
and attached to the rejected error as `err.data` (`null` if the body isn't
|
|
27
|
+
parseable JSON), alongside `err.status`. This means server validation
|
|
28
|
+
messages (422, etc.) survive on every verb, not just conflict responses.
|
|
29
|
+
|
|
30
|
+
## `update(id, payload, expectedVersion, url)` — optional third/fourth argument
|
|
31
|
+
|
|
32
|
+
`update` accepts an optional third argument. When provided (not `null`/
|
|
33
|
+
`undefined`), the connector merges it into the outgoing PUT body as
|
|
34
|
+
`expected_version`: `payload = Object.assign({}, payload, { expected_version: expectedVersion })`.
|
|
35
|
+
This is a single merge point — the `ln-api-connector:request-update` event
|
|
36
|
+
path passes `detail.expected_version` straight through to it, so there is
|
|
37
|
+
no second place version-locking logic lives. The optional fourth argument,
|
|
38
|
+
`url`, when present, is treated as the COMPLETE resource URL for that call
|
|
39
|
+
— it already carries the record id (e.g. a form's resolved `action`
|
|
40
|
+
`/documents/42`), so `update()` does NOT append the id again (symmetric
|
|
41
|
+
with `create()`). Only the `path` fallback (no `url` given) appends the id.
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
// Programmatic call with version lock
|
|
45
|
+
connector.update(42, { title: 'Updated title' }, 3)
|
|
46
|
+
.then(record => console.log('Updated record:', record));
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Declarative DOM Setup
|
|
52
|
+
|
|
53
|
+
Place the connector inside your parent coordinator element alongside your store:
|
|
54
|
+
|
|
55
|
+
```html
|
|
56
|
+
<ul data-ln-data-coordinator="documents" hidden>
|
|
57
|
+
<!-- Storage Layer Cache (Blind to networking) -->
|
|
58
|
+
<li data-ln-data-store
|
|
59
|
+
data-ln-data-store-indexes="status,updated_at">
|
|
60
|
+
</li>
|
|
61
|
+
|
|
62
|
+
<!-- Transport Gateway (REST / API Connector) -->
|
|
63
|
+
<li data-ln-api-connector
|
|
64
|
+
data-ln-api-base-url="https://api.livenetworks.com/v1"
|
|
65
|
+
data-ln-api-path="/documents"
|
|
66
|
+
data-ln-api-headers='{"Authorization": "Bearer tok_123"}'>
|
|
67
|
+
</li>
|
|
68
|
+
</ul>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Attributes
|
|
74
|
+
|
|
75
|
+
All attributes are dynamically observed. Any runtime changes to these attributes instantly update the connector's internal configurations.
|
|
76
|
+
|
|
77
|
+
| Attribute | Category | Description |
|
|
78
|
+
|-----------|----------|-------------|
|
|
79
|
+
| `data-ln-api-connector` | Selector | Creates the component instance. Optional value serves as name. |
|
|
80
|
+
| `data-ln-api-base-url` | Connection | The base URL of the API gateway (e.g. `https://api.example.com/v1` or `/api`). Fallback: `data-ln-api-connector-base-url`, `data-ln-rest-base-url`. |
|
|
81
|
+
| `data-ln-api-path` | Connection | Resource path endpoint (e.g. `/documents`). Fallback: `data-ln-api-connector-path`, `data-ln-rest-path`. |
|
|
82
|
+
| `data-ln-api-headers` | Credentials | JSON-formatted string of request headers (e.g. `{"Authorization": "Bearer tok_123"}`). Fallback: `data-ln-api-connector-headers`, `data-ln-rest-headers`. |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## JavaScript API Methods
|
|
87
|
+
|
|
88
|
+
You can access the instance methods directly on the element via the `.lnApiConnector` or `.lnConnector` properties:
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
const connectorEl = document.querySelector('[data-ln-api-connector]');
|
|
92
|
+
const connector = connectorEl.lnApiConnector; // or connectorEl.lnConnector
|
|
93
|
+
|
|
94
|
+
// 1. Fetch changed records since a Unix timestamp (delta protocol)
|
|
95
|
+
connector.fetchDelta(1736952600)
|
|
96
|
+
.then(data => console.log('Sync arrays loaded:', data));
|
|
97
|
+
|
|
98
|
+
// 2. Create a new record
|
|
99
|
+
connector.create({ title: 'New Document', status: 'Draft' })
|
|
100
|
+
.then(record => console.log('Created record:', record));
|
|
101
|
+
|
|
102
|
+
// 3. Update an existing record
|
|
103
|
+
connector.update(42, { title: 'Updated Title' })
|
|
104
|
+
.then(record => console.log('Updated record:', record));
|
|
105
|
+
|
|
106
|
+
// 4. Delete a record
|
|
107
|
+
connector.delete(42)
|
|
108
|
+
.then(res => console.log('Deleted:', res));
|
|
109
|
+
|
|
110
|
+
// 5. Bulk Delete records
|
|
111
|
+
connector.bulkDelete([17, 23])
|
|
112
|
+
.then(res => console.log('Bulk deleted:', res));
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## DOM Events
|
|
118
|
+
|
|
119
|
+
### Commands (Dispatched TO the connector)
|
|
120
|
+
|
|
121
|
+
You can trigger mutations and fetches asynchronously by dispatching standard events directly on the connector DOM element. All events are supported in both `ln-api-connector` and legacy `ln-rest-connector` namespaces.
|
|
122
|
+
|
|
123
|
+
| Event | `detail` Payload | Description |
|
|
124
|
+
|-------|------------------|-------------|
|
|
125
|
+
| `ln-api-connector:request-sync` | `{ since, meta }` | Triggers a delta fetch request. |
|
|
126
|
+
| `ln-api-connector:request-create` | `{ data, tempId, url, idempotencyKey?, meta }` | Triggers a creation request. |
|
|
127
|
+
| `ln-api-connector:request-update` | `{ id, data, expected_version, url, idempotencyKey?, meta }` | Triggers a PUT update (supports 409 conflict checks). |
|
|
128
|
+
| `ln-api-connector:request-delete` | `{ id, url, idempotencyKey?, meta }` | Triggers a deletion request. |
|
|
129
|
+
| `ln-api-connector:request-bulk-delete` | `{ ids, url, idempotencyKey?, meta }` | Triggers a bulk-deletion request. |
|
|
130
|
+
|
|
131
|
+
`url` is optional — when present, it replaces the connector's own `path`
|
|
132
|
+
for that call (still joined with `data-ln-api-base-url`). For `update`,
|
|
133
|
+
`delete`, and `bulk-delete`, a supplied `url` is the complete resource URL
|
|
134
|
+
already carrying the id — the connector does not append the id a second
|
|
135
|
+
time (create has no id to append; delete/bulk-delete currently receive no
|
|
136
|
+
url in practice). `meta` is
|
|
137
|
+
opaque, default `null`, echoed untouched on the response.
|
|
138
|
+
|
|
139
|
+
`url` is same-origin-relative by convention (it originates from a form's
|
|
140
|
+
HTML `action` attribute) and always passes through the same `buildUrl()`
|
|
141
|
+
join as `path` — `data-ln-api-base-url` still prepends when configured.
|
|
142
|
+
|
|
143
|
+
### Notifications (Emitted BY the connector)
|
|
144
|
+
|
|
145
|
+
The connector dispatches bubbles-enabled events to notify parent coordinators of response states.
|
|
146
|
+
|
|
147
|
+
| Event | `detail` Payload | Description |
|
|
148
|
+
|-------|------------------|-------------|
|
|
149
|
+
| `ln-api-connector:fetched` | `{ data, since, meta }` | Dispatched upon successful fetch completion. Echoes `detail.meta` from the triggering request untouched, `null` if not provided. |
|
|
150
|
+
| `ln-api-connector:created` | `{ record, tempId, message, meta }` | Dispatched upon successful server record confirmation. `record` is the unwrapped `content` (or the bare body — see envelope below). `message` is opaque, `null` if absent. Echoes `detail.meta` untouched, `null` if not provided. |
|
|
151
|
+
| `ln-api-connector:updated` | `{ record, id, message, meta }` | Dispatched upon successful server update confirmation. Same `record`/`message` unwrap as `:created`. Echoes `detail.meta` untouched, `null` if not provided. |
|
|
152
|
+
| `ln-api-connector:deleted` | `{ response, id, message, meta }` | Dispatched upon successful server deletion. `response` is the (possibly null) parsed body; `message` is `null` on a 204. Echoes `detail.meta` untouched, `null` if not provided. |
|
|
153
|
+
| `ln-api-connector:bulk-deleted`| `{ response, ids, message, meta }` | Dispatched upon successful server bulk deletion. Same `message` pass-through as the others. Echoes `detail.meta` untouched, `null` if not provided. |
|
|
154
|
+
| `ln-api-connector:error` | `{ action, error, status, ..., meta }` | Dispatched on failures. Includes `conflictData` if status is 409. `status` is always present (`0` for network failure). Echoes `detail.meta` from the triggering request untouched, `null` if not provided. |
|
|
155
|
+
| `ln-api-connector:config-changed` | `{ baseUrl, path, headers }` | Dispatched when configuration attributes are mutated. |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Mutation Response Envelope
|
|
160
|
+
|
|
161
|
+
The three mutation response handlers (`create`/`update`/`delete`, and
|
|
162
|
+
`bulk-delete` for symmetry) presence-check the parsed JSON body for an
|
|
163
|
+
optional `{ message, content }` envelope, mirroring the unwrap `ln-ajax`
|
|
164
|
+
already does for HTML fragment responses (`ln-ajax.js:160` `if (data.content)`):
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"message": { "type": "success", "title": "Saved", "body": "Document created" },
|
|
169
|
+
"content": { "id": 42, "title": "New Document", "status": "Draft" }
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
- **`content`** — the record payload. `ln-api-connector:created`/`:updated`
|
|
174
|
+
unwrap `body.content` (presence-checked with `!== undefined`) into
|
|
175
|
+
`detail.record`; if the response has no `content` key, the bare body is
|
|
176
|
+
used as-is (back-compat with a plain-record response).
|
|
177
|
+
- **`content` duality** — this is the *same* envelope shape `ln-ajax` consumes,
|
|
178
|
+
just with a different content type: `ln-ajax` treats `content` as an
|
|
179
|
+
`{ targetId: html }` map for DOM fragment swaps, while `ln-api-connector`
|
|
180
|
+
treats it as JSON record data. One envelope, two consumers, two
|
|
181
|
+
interpretations of the payload.
|
|
182
|
+
- **`message` PRESENCE = toast opt-in.** If `message` is absent (or the whole
|
|
183
|
+
body has no envelope), `detail.message` is `null` and nothing renders. There
|
|
184
|
+
is no hardcoded toast text at the connector layer — the backend authors
|
|
185
|
+
the entire user-facing string.
|
|
186
|
+
- **The connector passes `message` opaquely** — it does not read `message.type`/
|
|
187
|
+
`title`/`body` itself. That happens one level up, in whichever consumer
|
|
188
|
+
turns it into an `ln-toast:enqueue` (see `js/ln-data-coordinator/README.md`
|
|
189
|
+
→ Toasts).
|
|
190
|
+
- **204 caveat.** `_resolve` returns `null` for a 204 response (no body to
|
|
191
|
+
parse), so a DELETE answering `204 No Content` has no envelope and no
|
|
192
|
+
`message` — no toast fires. A backend that wants a delete confirmation
|
|
193
|
+
toast must answer `200 OK` with the envelope (`content` can be `null`).
|
|
194
|
+
|
|
195
|
+
Nothing else in the connector is touched by this: the JS API methods
|
|
196
|
+
(`create`/`update`/`delete`/`bulkDelete`), `_resolve`, config, and the error
|
|
197
|
+
path are unchanged — the unwrap happens only in the DOM-event handlers, at
|
|
198
|
+
the point the parsed body arrives.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 3-Tier Integration Architecture (Coordinator Example)
|
|
203
|
+
|
|
204
|
+
Here is how a parent Coordinator links a Cache Store and the API Connector using
|
|
205
|
+
standard DOM events — parallel fan-out (local store write + remote connector
|
|
206
|
+
call happen from the same intake handler), no `_pending`/confirm/revert, and
|
|
207
|
+
success toasts riding the envelope's `message`:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
(function () {
|
|
211
|
+
const parent = document.querySelector('[data-ln-data-coordinator="documents"]');
|
|
212
|
+
const storeEl = parent.querySelector('[data-ln-data-store]');
|
|
213
|
+
const connectorEl = parent.querySelector('[data-ln-api-connector]');
|
|
214
|
+
|
|
215
|
+
if (!storeEl || !connectorEl) return;
|
|
216
|
+
|
|
217
|
+
// 1. Storage needs remote sync -> forward request to Connector
|
|
218
|
+
storeEl.addEventListener('ln-data-store:request-remote-sync', function (e) {
|
|
219
|
+
connectorEl.dispatchEvent(new CustomEvent('ln-api-connector:request-sync', {
|
|
220
|
+
detail: { since: e.detail.since }
|
|
221
|
+
}));
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// 2. Connector finishes sync -> feed delta back to Storage
|
|
225
|
+
connectorEl.addEventListener('ln-api-connector:fetched', function (e) {
|
|
226
|
+
const payload = e.detail.data;
|
|
227
|
+
storeEl.lnDataStore.applySync(payload.data, payload.deleted || [], payload.synced_at);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// 3. Intake fans out in parallel: local optimistic write AND remote call,
|
|
231
|
+
// from the same handler — not chained off each other.
|
|
232
|
+
const tempId = '_temp_' + crypto.randomUUID();
|
|
233
|
+
storeEl.dispatchEvent(new CustomEvent('ln-data-store:request-create', { detail: { tempId, data: { title: 'New Document' } } }));
|
|
234
|
+
connectorEl.dispatchEvent(new CustomEvent('ln-api-connector:request-create', {
|
|
235
|
+
detail: { data: { title: 'New Document' }, tempId: tempId }
|
|
236
|
+
}));
|
|
237
|
+
|
|
238
|
+
// 4. Connector confirms creation -> ordinary id-swap update (no confirmMutation)
|
|
239
|
+
connectorEl.addEventListener('ln-api-connector:created', function (e) {
|
|
240
|
+
storeEl.dispatchEvent(new CustomEvent('ln-data-store:request-update', {
|
|
241
|
+
detail: { id: e.detail.tempId, data: e.detail.record }
|
|
242
|
+
}));
|
|
243
|
+
// Success toast rides the envelope's message — no hardcoded string.
|
|
244
|
+
if (e.detail.message) {
|
|
245
|
+
window.dispatchEvent(new CustomEvent('ln-toast:enqueue', {
|
|
246
|
+
detail: { type: e.detail.message.type || 'success', title: e.detail.message.title || '', message: e.detail.message.body || '' }
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// 5. Errors are classified deterministic vs transient — never a blind revert.
|
|
252
|
+
// status 0/5xx -> transient (retry via queue, or leave local on single attempt);
|
|
253
|
+
// status 401/419 -> auth (pause queue); status 409/4xx -> deterministic (never retry).
|
|
254
|
+
connectorEl.addEventListener('ln-api-connector:error', function (e) {
|
|
255
|
+
if (e.detail.action === 'create' && e.detail.status >= 400 && e.detail.status < 500) {
|
|
256
|
+
storeEl.dispatchEvent(new CustomEvent('ln-data-store:request-delete', { detail: { id: e.detail.tempId } }));
|
|
257
|
+
}
|
|
258
|
+
// ... see ln-data-coordinator README's "Error reconciliation policy" for the full table
|
|
259
|
+
});
|
|
260
|
+
})();
|
|
261
|
+
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(){"use strict";function c(i,o,d){i.dispatchEvent(new CustomEvent(o,{bubbles:!0,detail:d||{}}))}function L(i,o){if(!document.body){document.addEventListener("DOMContentLoaded",function(){L(i,o)}),console.warn("["+o+'] Script loaded before <body> — add "defer" to your <script> tag');return}i()}function g(i,o,d,u){if(i.nodeType!==1)return;const m=o.indexOf("[")!==-1||o.indexOf(".")!==-1||o.indexOf("#")!==-1?o:"["+o+"]",e=Array.from(i.querySelectorAll(m));i.matches&&i.matches(m)&&e.push(i);for(const n of e)n[d]||(n[d]=new u(n))}function x(i,o,d,u,f={}){const m=f.extraAttributes||[],e=f.onAttributeChange||null,n=f.onInit||null;function t(r){const a=r||document.body;g(a,i,o,d),n&&n(a)}return L(function(){const r=new MutationObserver(function(s){for(let b=0;b<s.length;b++){const l=s[b];if(l.type==="childList"){for(let p=0;p<l.addedNodes.length;p++){const h=l.addedNodes[p];h.nodeType===1&&(g(h,i,o,d),n&&n(h))}for(let p=0;p<l.removedNodes.length;p++){const h=l.removedNodes[p];if(h.nodeType===1){const C=i.indexOf("[")!==-1||i.indexOf(".")!==-1||i.indexOf("#")!==-1?i:"["+i+"]",E=Array.from(h.querySelectorAll(C));h.matches&&h.matches(C)&&E.push(h);for(let v=0;v<E.length;v++){const O=E[v];if(!document.contains(O)){const T=O[o];T&&typeof T.destroy=="function"&&T.destroy()}}}}}else l.type==="attributes"&&(e&&l.target[o]?e(l.target,l.attributeName):(g(l.target,i,o,d),n&&n(l.target)))}});let a=[];if(i.indexOf("[")!==-1){const s=/\[([\w-]+)/g;let b;for(;(b=s.exec(i))!==null;)a.push(b[1])}else a.push(i);r.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:a.concat(m)})},u),window[o]=t,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){t(document.body)}):t(document.body),t}function y(...i){return i.filter(o=>o!=null&&o!=="").map((o,d)=>d===0?o.replace(/\/+$/,""):o.replace(/^\/+/,"").replace(/\/+$/,"")).filter(Boolean).join("/")}function w(i,o){return Object.assign({"Content-Type":"application/json",Accept:"application/json"},i,null)}function A(i,o="ln-core"){try{return i?JSON.parse(i):{}}catch(d){return console.error(`[${o}] Invalid headers JSON:`,d),{}}}const _={};function D(i,o){_[i]=o}function k(i){return _[i]||{ingress:o=>o,egress:o=>o}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=D,window.lnCore.getDataMapper=k),(function(){const i="data-ln-api-connector",o="lnApiConnector",d="lnConnector";if(window[o]!==void 0)return;function u(e){return this.dom=e,e[o]=this,e[d]=this,this.refreshConfig(),this._handlers=null,f(this),this}u.prototype.refreshConfig=function(){const e=this.dom;this.baseUrl=e.getAttribute("data-ln-api-base-url")||"",this.path=e.getAttribute("data-ln-api-path")||"",this.credentials="same-origin";const n=e.getAttribute("data-ln-api-headers")||"";this.headers=A(n,"ln-api-connector"),(n.toLowerCase().includes("authorization")||n.toLowerCase().includes("bearer")||n.toLowerCase().includes("basic"))&&console.warn("[ln-api-connector] Security Warning: Sensitive authorization credentials detected in data-ln-api-headers attribute. Storing secrets in HTML DOM attributes is highly discouraged and vulnerable to XSS credential extraction. Please use HttpOnly session cookies or a Backend Proxy Gateway instead."),c(e,"ln-api-connector:config-changed",{baseUrl:this.baseUrl,path:this.path,headers:this.headers})},u.prototype.fetchDelta=function(e){const n=this;let t=y(n.baseUrl,n.path);return e!=null&&e!==""&&(t+=(t.indexOf("?")!==-1?"&":"?")+"since="+encodeURIComponent(e)),window.fetch(t,{method:"GET",headers:w(n.headers),credentials:n.credentials}).then(r=>{if(!r.ok)throw new Error("HTTP "+r.status+": "+r.statusText);return r.json()})},u.prototype.create=function(e){const n=this;return window.fetch(y(n.baseUrl,n.path),{method:"POST",headers:w(n.headers),credentials:n.credentials,body:JSON.stringify(e)}).then(t=>{if(!t.ok)throw new Error("HTTP "+t.status+": "+t.statusText);return t.json()})},u.prototype.update=function(e,n){const t=this;return window.fetch(y(t.baseUrl,t.path,e),{method:"PUT",headers:w(t.headers),credentials:t.credentials,body:JSON.stringify(n)}).then(r=>{if(r.ok)return r.json();if(r.status===409)return r.json().then(a=>{const s=new Error("Conflict");throw s.status=409,s.data=a,s});throw new Error("HTTP "+r.status+": "+r.statusText)})},u.prototype.delete=function(e){const n=this;return window.fetch(y(n.baseUrl,n.path,e),{method:"DELETE",headers:w(n.headers),credentials:n.credentials}).then(t=>{if(!t.ok)throw new Error("HTTP "+t.status+": "+t.statusText);return t.json()})},u.prototype.bulkDelete=function(e){const n=this;return window.fetch(y(n.baseUrl,n.path)+"/bulk-delete",{method:"DELETE",headers:w(n.headers),credentials:n.credentials,body:JSON.stringify({ids:e})}).then(t=>{if(!t.ok)throw new Error("HTTP "+t.status+": "+t.statusText);return t.json()})};function f(e){e._handlers={sync:function(t){const r=t.detail||{};e.fetchDelta(r.since).then(function(a){c(e.dom,"ln-api-connector:fetched",{data:a,since:r.since})}).catch(function(a){c(e.dom,"ln-api-connector:error",{action:"sync",error:a.message,status:a.status||0,since:r.since})})},create:function(t){const r=t.detail||{};e.create(r.data).then(function(a){c(e.dom,"ln-api-connector:created",{record:a,tempId:r.tempId})}).catch(function(a){c(e.dom,"ln-api-connector:error",{action:"create",error:a.message,status:a.status||0,tempId:r.tempId})})},update:function(t){const r=t.detail||{},a=Object.assign({},r.data);r.expected_version!==void 0&&(a.expected_version=r.expected_version),e.update(r.id,a).then(function(s){c(e.dom,"ln-api-connector:updated",{record:s,id:r.id})}).catch(function(s){c(e.dom,"ln-api-connector:error",{action:"update",error:s.message,status:s.status||0,id:r.id,conflictData:s.status===409?s.data:null})})},delete:function(t){const r=t.detail||{};e.delete(r.id).then(function(a){c(e.dom,"ln-api-connector:deleted",{response:a,id:r.id})}).catch(function(a){c(e.dom,"ln-api-connector:error",{action:"delete",error:a.message,status:a.status||0,id:r.id})})},bulkDelete:function(t){const r=t.detail||{};e.bulkDelete(r.ids).then(function(a){c(e.dom,"ln-api-connector:bulk-deleted",{response:a,ids:r.ids})}).catch(function(a){c(e.dom,"ln-api-connector:error",{action:"bulk-delete",error:a.message,status:a.status||0,ids:r.ids})})}},["ln-api-connector","ln-rest-connector"].forEach(function(t){e.dom.addEventListener(t+":request-sync",e._handlers.sync),e.dom.addEventListener(t+":request-fetch",e._handlers.sync),e.dom.addEventListener(t+":request-create",e._handlers.create),e.dom.addEventListener(t+":request-update",e._handlers.update),e.dom.addEventListener(t+":request-delete",e._handlers.delete),e.dom.addEventListener(t+":request-bulk-delete",e._handlers.bulkDelete)})}u.prototype.destroy=function(){if(!this.dom[o])return;const e=this;e._handlers&&(["ln-api-connector","ln-rest-connector"].forEach(function(t){e.dom.removeEventListener(t+":request-sync",e._handlers.sync),e.dom.removeEventListener(t+":request-fetch",e._handlers.sync),e.dom.removeEventListener(t+":request-create",e._handlers.create),e.dom.removeEventListener(t+":request-update",e._handlers.update),e.dom.removeEventListener(t+":request-delete",e._handlers.delete),e.dom.removeEventListener(t+":request-bulk-delete",e._handlers.bulkDelete)}),e._handlers=null),c(this.dom,"ln-api-connector:destroyed",{target:this.dom}),delete this.dom[o],delete this.dom[d]};function m(e){const n=e[o];n&&n.refreshConfig()}x(i,o,u,"ln-api-connector",{extraAttributes:["data-ln-api-base-url","data-ln-api-path","data-ln-api-headers"],onAttributeChange:m})})()})();
|
|
1
|
+
(function(){"use strict";if(typeof window<"u"){const t=console.warn;console.warn=function(...i){typeof i[0]=="string"&&(i[0].startsWith("[ln-")||i[0].startsWith("[lnCore"))&&!(document.documentElement.hasAttribute("data-ln-debug")||document.body&&document.body.hasAttribute("data-ln-debug"))||t.apply(console,i)}}const b={};function j(t,i){b[t]||(b[t]=document.querySelector('[data-ln-template="'+t+'"]'));const s=b[t];return s?s.content.cloneNode(!0):(console.warn("["+(i||"ln-core")+'] Template "'+t+'" not found'),null)}function p(t,i,s){t.dispatchEvent(new CustomEvent(i,{bubbles:!0,detail:s||{}}))}function L(t,i){if(!t||!i)return t;const s=t.querySelectorAll("[data-ln-field]");for(let l=0;l<s.length;l++){const e=s[l],a=e.getAttribute("data-ln-field");i[a]!=null&&(e.textContent=i[a])}const f=t.querySelectorAll("[data-ln-attr]");for(let l=0;l<f.length;l++){const e=f[l],a=e.getAttribute("data-ln-attr").split(",");for(let r=0;r<a.length;r++){const n=a[r].trim().split(":");if(n.length!==2)continue;const o=n[0].trim(),c=n[1].trim();i[c]!=null&&e.setAttribute(o,i[c])}}const d=t.querySelectorAll("[data-ln-show]");for(let l=0;l<d.length;l++){const e=d[l],a=e.getAttribute("data-ln-show");a in i&&e.classList.toggle("hidden",!i[a])}const u=t.querySelectorAll("[data-ln-class]");for(let l=0;l<u.length;l++){const e=u[l],a=e.getAttribute("data-ln-class").split(",");for(let r=0;r<a.length;r++){const n=a[r].trim().split(":");if(n.length!==2)continue;const o=n[0].trim(),c=n[1].trim();c in i&&e.classList.toggle(o,!!i[c])}}return t}function U(t,i){t.matches&&t.matches("[data-ln-form], [data-ln-fillable]")&&t.dispatchEvent(new CustomEvent("ln-fill",{detail:i??null,bubbles:!0}));const s=t.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let f=0;f<s.length;f++)s[f].dispatchEvent(new CustomEvent("ln-fill",{detail:i??null,bubbles:!0}));return t}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(t){if(!(!t.target.matches||!t.target.matches("[data-ln-fillable]")))if(t.detail)L(t.target,t.detail);else{const i=t.target.querySelectorAll("[data-ln-field]");for(let s=0;s<i.length;s++)i[s].textContent=""}})));function E(t,i){if(!t||!i)return t;const s=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);for(;s.nextNode();){const u=s.currentNode;u.textContent.indexOf("{{")!==-1&&(u.textContent=u.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(l,e){return i[e]!==void 0?i[e]:""}))}const f=function(u,l){return i[l]!==void 0?i[l]:""},d=Array.from(t.querySelectorAll("*"));t.nodeType===1&&d.push(t);for(let u=0;u<d.length;u++){const l=d[u],e=l.attributes;for(let a=0;a<e.length;a++){const r=e[a];r.value.indexOf("{{")!==-1&&l.setAttribute(r.name,r.value.replace(/\{\{\s*(\w+)\s*\}\}/g,f))}}return t}function N(t,i,s,f,d,u){const l={};for(let a=0;a<t.children.length;a++){const r=t.children[a],n=r.getAttribute("data-ln-key");n&&(l[n]=r)}const e=document.createDocumentFragment();for(let a=0;a<i.length;a++){const r=i[a],n=String(f(r));let o=l[n];if(o)d(o,r,a);else{const c=j(s,u);if(!c||(E(c,r),o=c.firstElementChild,!o))continue;o.setAttribute("data-ln-key",n),d(o,r,a)}e.appendChild(o)}t.textContent="",t.appendChild(e)}function S(t,i){if(!document.body){document.addEventListener("DOMContentLoaded",function(){S(t,i)}),console.warn("["+i+'] Script loaded before <body> — add "defer" to your <script> tag');return}t()}function y(t,i,s,f){if(t.nodeType!==1)return;const u=i.indexOf("[")!==-1||i.indexOf(".")!==-1||i.indexOf("#")!==-1?i:"["+i+"]",l=Array.from(t.querySelectorAll(u));t.matches&&t.matches(u)&&l.push(t);for(const e of l)e[s]||(e[s]=new f(e))}function H(t,i,s,f,d={}){const u=d.extraAttributes||[],l=d.onAttributeChange||null,e=d.onInit||null;function a(r){const n=r||document.body;y(n,t,i,s),e&&e(n)}return S(function(){const r=new MutationObserver(function(o){for(let c=0;c<o.length;c++){const h=o[c];if(h.type==="childList"){for(let m=0;m<h.addedNodes.length;m++){const g=h.addedNodes[m];g.nodeType===1&&(y(g,t,i,s),e&&e(g))}for(let m=0;m<h.removedNodes.length;m++){const g=h.removedNodes[m];if(g.nodeType===1){const D=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",_=Array.from(g.querySelectorAll(D));g.matches&&g.matches(D)&&_.push(g);for(let v=0;v<_.length;v++){const T=_[v];if(!document.contains(T)){const A=T[i];A&&typeof A.destroy=="function"&&A.destroy()}}}}}else h.type==="attributes"&&(l&&h.target[i]?l(h.target,h.attributeName):(y(h.target,t,i,s),e&&e(h.target)))}});let n=[];if(t.indexOf("[")!==-1){const o=/\[([\w-]+)/g;let c;for(;(c=o.exec(t))!==null;)n.push(c[1])}else n.push(t);r.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:n.concat(u)})},f),window[i]=a,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){a(document.body)}):a(document.body),a}function w(...t){return t.filter(i=>i!=null&&i!=="").map((i,s)=>s===0?i.replace(/\/+$/,""):i.replace(/^\/+/,"").replace(/\/+$/,"")).filter(Boolean).join("/")}function I(t,i){return Object.assign({"Content-Type":"application/json",Accept:"application/json"},t,null)}function M(t,i="ln-core"){try{return t?JSON.parse(t):{}}catch(s){return console.error(`[${i}] Invalid headers JSON:`,s),{}}}const O={};function K(t,i){O[t]=i}function F(t){return O[t]||{ingress:i=>i,egress:i=>i}}const q={};function k(t,i){if(!t||typeof i!="object")return;const s=t.toLowerCase().split("-")[0];q[s]=i}function W(t){if(!t)return null;const i=t.toLowerCase().split("-")[0];return q[i]||null}k("mk",{monthsLong:["јануари","февруари","март","април","мај","јуни","јули","август","септември","октомври","ноември","декември"],monthsShort:["јан","фев","мар","апр","мај","јун","јул","авг","септ","окт","ноем","дек"],daysLong:["недела","понеделник","вторник","среда","четврток","петок","сабота"],daysShort:["нед","пон","вт","ср","чет","пет","саб"]}),typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=K,window.lnCore.getDataMapper=F,window.lnCore.registerLocaleFallback=k,window.lnCore.getLocaleFallback=W,window.lnCore.fillTemplate=E,window.lnCore.fill=L,window.lnCore.lnFill=U,window.lnCore.renderList=N);function x(t){return(t||"").replace(/^#/,"")}function C(t){const i=t===void 0?location.hash:t,s={},f=x(i);if(!f)return s;const d=f.split("&");for(let u=0;u<d.length;u++){const l=d[u];if(!l)continue;const e=l.indexOf(":"),a=e>-1?l.slice(0,e):l,r=e>-1?l.slice(e+1):"";if(a)try{s[a]=decodeURIComponent(r)}catch{s[a]=r}}return s}function B(t){if(!t)return null;const i=C();return t in i?i[t]:null}function G(t,i){if(!t)return;const s=C();i==null?delete s[t]:s[t]=String(i);const d=Object.keys(s).map(function(u){const l=s[u];return l===""?u:u+":"+encodeURIComponent(l)}).join("&");x(location.hash)!==d&&(location.hash=d)}function R(t){return t.button===1||t.ctrlKey||t.metaKey||t.shiftKey?!1:(t.preventDefault(),!0)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=C,window.lnCore.hashGet=B,window.lnCore.hashSet=G,window.lnCore.hashLinkClick=R),(function(){const t="data-ln-api-connector",i="lnApiConnector",s="lnConnector";if(window[i]!==void 0)return;function f(e){return e.ok?e.status===204?null:e.json():e.json().catch(()=>null).then(a=>{const r=new Error("HTTP "+e.status+": "+e.statusText);throw r.status=e.status,r.data=a,r})}function d(e){return this.dom=e,e[i]=this,e[s]=this,this.refreshConfig(),this._handlers=null,u(this),this}d.prototype.refreshConfig=function(){const e=this.dom;this.baseUrl=e.getAttribute("data-ln-api-base-url")||"",this.path=e.getAttribute("data-ln-api-path")||"",this.credentials="same-origin",this.paramKeys={offset:e.getAttribute("data-ln-api-param-offset")||"offset",limit:e.getAttribute("data-ln-api-param-limit")||"limit",search:e.getAttribute("data-ln-api-param-search")||"search",sortField:e.getAttribute("data-ln-api-param-sort-field")||"sort_field",sortDir:e.getAttribute("data-ln-api-param-sort-dir")||"sort_dir"};const a=e.getAttribute("data-ln-api-headers")||"";this.headers=M(a,"ln-api-connector"),(a.toLowerCase().includes("authorization")||a.toLowerCase().includes("bearer")||a.toLowerCase().includes("basic"))&&console.warn("[ln-api-connector] Security Warning: Sensitive authorization credentials detected in data-ln-api-headers attribute. Storing secrets in HTML DOM attributes is highly discouraged and vulnerable to XSS credential extraction. Please use HttpOnly session cookies or a Backend Proxy Gateway instead."),p(e,"ln-api-connector:config-changed",{baseUrl:this.baseUrl,path:this.path,headers:this.headers,paramKeys:this.paramKeys})},d.prototype._reqHeaders=function(e){const a=Object.assign({},I(this.headers),{"X-LN-Response":"data"});return e&&(a["Idempotency-Key"]=e),a},d.prototype.fetchDelta=function(e){const a=this;let r=w(a.baseUrl,a.path);return e!=null&&e!==""&&(r+=(r.indexOf("?")!==-1?"&":"?")+"since="+encodeURIComponent(e)),window.fetch(r,{method:"GET",headers:a._reqHeaders(),credentials:a.credentials}).then(f)},d.prototype.query=function(e){const a=this;e=e||{};let r=w(a.baseUrl,a.path);const n=a.paramKeys||{offset:"offset",limit:"limit",search:"search",sortField:"sort_field",sortDir:"sort_dir"},o=new URLSearchParams;e.search&&o.append(n.search,e.search),e.offset!=null&&o.append(n.offset,e.offset),e.limit!=null&&o.append(n.limit,e.limit),e.sort&&e.sort.field&&e.sort.direction&&(o.append(n.sortField,e.sort.field),o.append(n.sortDir,e.sort.direction)),e.filters&&typeof e.filters=="object"&&Object.keys(e.filters).forEach(h=>{const m=e.filters[h];Array.isArray(m)&&m.length>0&&o.append(h,m.join(","))});const c=o.toString();return c&&(r+=(r.indexOf("?")!==-1?"&":"?")+c),window.fetch(r,{method:"GET",headers:a._reqHeaders(),credentials:a.credentials}).then(f)},d.prototype.create=function(e,a,r){const n=this;return window.fetch(w(n.baseUrl,a||n.path),{method:"POST",headers:n._reqHeaders(r),credentials:n.credentials,body:JSON.stringify(e)}).then(f)},d.prototype.update=function(e,a,r,n,o){const c=this;r!=null&&(a=Object.assign({},a,{expected_version:r}));const h=n?w(c.baseUrl,n):w(c.baseUrl,c.path,e);return window.fetch(h,{method:"PUT",headers:c._reqHeaders(o),credentials:c.credentials,body:JSON.stringify(a)}).then(f)},d.prototype.delete=function(e,a,r){const n=this;return window.fetch(w(n.baseUrl,a||n.path,e),{method:"DELETE",headers:n._reqHeaders(r),credentials:n.credentials}).then(f)},d.prototype.bulkDelete=function(e,a,r){const n=this;return window.fetch(w(n.baseUrl,a||n.path)+"/bulk-delete",{method:"DELETE",headers:n._reqHeaders(r),credentials:n.credentials,body:JSON.stringify({ids:e})}).then(f)};function u(e){e._handlers={sync:function(r){const n=r.detail||{};e.fetchDelta(n.since).then(function(o){p(e.dom,"ln-api-connector:fetched",{data:o,since:n.since,meta:n.meta||null})}).catch(function(o){p(e.dom,"ln-api-connector:error",{action:"sync",error:o.message,status:o.status||0,data:o.data||null,since:n.since,meta:n.meta||null})})},query:function(r){const n=r.detail||{},o=n.query||n;e.query(o).then(function(c){const h=c||{};p(e.dom,"ln-api-connector:fetched",{data:h.data||(Array.isArray(h)?h:[]),total:h.total,filtered:h.filtered,offset:o.offset,queryGen:o.queryGen,meta:n.meta||null})}).catch(function(c){p(e.dom,"ln-api-connector:error",{action:"query",error:c.message,status:c.status||0,data:c.data||null,meta:n.meta||null})})},create:function(r){const n=r.detail||{};e.create(n.data,n.url,n.idempotencyKey).then(function(o){const c=o&&o.content!==void 0?o.content:o,h=o&&o.message?o.message:null;p(e.dom,"ln-api-connector:created",{record:c,tempId:n.tempId,message:h,meta:n.meta||null})}).catch(function(o){p(e.dom,"ln-api-connector:error",{action:"create",error:o.message,status:o.status||0,data:o.data||null,tempId:n.tempId,meta:n.meta||null})})},update:function(r){const n=r.detail||{};e.update(n.id,n.data,n.expected_version,n.url,n.idempotencyKey).then(function(o){const c=o&&o.content!==void 0?o.content:o,h=o&&o.message?o.message:null;p(e.dom,"ln-api-connector:updated",{record:c,id:n.id,message:h,meta:n.meta||null})}).catch(function(o){p(e.dom,"ln-api-connector:error",{action:"update",error:o.message,status:o.status||0,data:o.data||null,id:n.id,conflictData:o.status===409?o.data:null,meta:n.meta||null})})},delete:function(r){const n=r.detail||{};e.delete(n.id,n.url,n.idempotencyKey).then(function(o){const c=o&&o.message?o.message:null;p(e.dom,"ln-api-connector:deleted",{response:o,id:n.id,message:c,meta:n.meta||null})}).catch(function(o){p(e.dom,"ln-api-connector:error",{action:"delete",error:o.message,status:o.status||0,data:o.data||null,id:n.id,meta:n.meta||null})})},bulkDelete:function(r){const n=r.detail||{};e.bulkDelete(n.ids,n.url,n.idempotencyKey).then(function(o){const c=o&&o.message?o.message:null;p(e.dom,"ln-api-connector:bulk-deleted",{response:o,ids:n.ids,message:c,meta:n.meta||null})}).catch(function(o){p(e.dom,"ln-api-connector:error",{action:"bulk-delete",error:o.message,status:o.status||0,data:o.data||null,ids:n.ids,meta:n.meta||null})})}},["ln-api-connector","ln-rest-connector"].forEach(function(r){e.dom.addEventListener(r+":request-sync",e._handlers.sync),e.dom.addEventListener(r+":request-query",e._handlers.query),e.dom.addEventListener(r+":request-fetch",e._handlers.query),e.dom.addEventListener(r+":request-create",e._handlers.create),e.dom.addEventListener(r+":request-update",e._handlers.update),e.dom.addEventListener(r+":request-delete",e._handlers.delete),e.dom.addEventListener(r+":request-bulk-delete",e._handlers.bulkDelete)})}d.prototype.destroy=function(){if(!this.dom[i])return;const e=this;e._handlers&&(["ln-api-connector","ln-rest-connector"].forEach(function(r){e.dom.removeEventListener(r+":request-sync",e._handlers.sync),e.dom.removeEventListener(r+":request-query",e._handlers.query),e.dom.removeEventListener(r+":request-fetch",e._handlers.query),e.dom.removeEventListener(r+":request-create",e._handlers.create),e.dom.removeEventListener(r+":request-update",e._handlers.update),e.dom.removeEventListener(r+":request-delete",e._handlers.delete),e.dom.removeEventListener(r+":request-bulk-delete",e._handlers.bulkDelete)}),e._handlers=null),p(this.dom,"ln-api-connector:destroyed",{target:this.dom}),delete this.dom[i],delete this.dom[s]};function l(e){const a=e[i];a&&a.refreshConfig()}H(t,i,d,"ln-api-connector",{extraAttributes:["data-ln-api-base-url","data-ln-api-path","data-ln-api-headers","data-ln-api-param-offset","data-ln-api-param-limit","data-ln-api-param-search","data-ln-api-param-sort-field","data-ln-api-param-sort-dir"],onAttributeChange:l})})()})();
|