meadow-endpoints 4.0.7 → 4.0.10

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.
@@ -0,0 +1,203 @@
1
+ # Update Endpoints
2
+
3
+ > Modify existing records with automatic conflict detection
4
+
5
+ The Update endpoints handle single-record updates, bulk updates, upserts (insert-or-update), and bulk upserts. The Update operation reads the existing record before writing, providing a checkpoint for authorization.
6
+
7
+ ## Routes
8
+
9
+ | Method | Route | Description |
10
+ |--------|-------|-------------|
11
+ | PUT | `/{v}/{entity}` | Update a single record |
12
+ | PUT | `/{v}/{entity}/s` | Bulk update (array of records) |
13
+ | PUT | `/{v}/{entity}/Upsert` | Insert or update a record |
14
+ | PUT | `/{v}/{entity}/Upserts` | Bulk upsert (array of records) |
15
+
16
+ ## Single Update
17
+
18
+ **Request:** `PUT /1.0/Book`
19
+
20
+ The request body must include the record's default identifier (e.g., `IDBook`):
21
+
22
+ ```json
23
+ {
24
+ "IDBook": 42,
25
+ "Title": "Dune (Revised Edition)",
26
+ "Author": "Frank Herbert"
27
+ }
28
+ ```
29
+
30
+ **Response:** The complete updated record, re-read from the database:
31
+
32
+ ```json
33
+ {
34
+ "IDBook": 42,
35
+ "GUIDBook": "0x12ab34cd...",
36
+ "Title": "Dune (Revised Edition)",
37
+ "Author": "Frank Herbert",
38
+ "CreateDate": "2024-01-15T10:30:00.000Z",
39
+ "CreatingIDUser": 1,
40
+ "UpdateDate": "2024-03-20T14:15:00.000Z",
41
+ "UpdatingIDUser": 1,
42
+ "Deleted": 0
43
+ }
44
+ ```
45
+
46
+ ## Bulk Update
47
+
48
+ **Request:** `PUT /1.0/Book/s`
49
+
50
+ Send an array of records, each including its identifier:
51
+
52
+ ```json
53
+ [
54
+ { "IDBook": 42, "Title": "Dune (Revised Edition)" },
55
+ { "IDBook": 43, "Title": "Neuromancer (2nd Edition)" }
56
+ ]
57
+ ```
58
+
59
+ **Response:** An array of updated records. Each record passes through the full Update lifecycle individually.
60
+
61
+ ## Upsert
62
+
63
+ **Request:** `PUT /1.0/Book/Upsert`
64
+
65
+ If the record has a valid identifier and that record exists, it is updated. Otherwise, it is created:
66
+
67
+ ```json
68
+ {
69
+ "IDBook": 0,
70
+ "Title": "New Book",
71
+ "Author": "New Author"
72
+ }
73
+ ```
74
+
75
+ The Upsert endpoint first attempts a read. If the record is not found, it falls through to the Create operation. If found, it performs an Update.
76
+
77
+ ## Bulk Upsert
78
+
79
+ **Request:** `PUT /1.0/Book/Upserts`
80
+
81
+ An array of records to upsert:
82
+
83
+ ```json
84
+ [
85
+ { "IDBook": 42, "Title": "Updated Title" },
86
+ { "IDBook": 0, "Title": "Brand New Book", "Author": "New Author" }
87
+ ]
88
+ ```
89
+
90
+ ## Request Lifecycle
91
+
92
+ The Update operation delegates to `Meadow-Operation-Update`, which runs the following waterfall:
93
+
94
+ ```
95
+ PUT /{v}/{entity}
96
+ |
97
+ v
98
+ Validate request body is an object
99
+ Validate record has a valid ID (> 0)
100
+ |
101
+ v
102
+ Read existing record (DAL.doRead) <-- loads current state for security checks
103
+ |
104
+ v
105
+ Build update query: addRecord(record), setIDUser(SessionData.UserID)
106
+ |
107
+ v
108
+ DAL.doUpdate(query) <-- execute update + re-read
109
+ |
110
+ v
111
+ [Update-PostOperation] <-- audit, notify, transform
112
+ |
113
+ v
114
+ Send response (updated record)
115
+ ```
116
+
117
+ ## Behavior Injection Points
118
+
119
+ ### Update-PostOperation
120
+
121
+ The Update operation has **only a post-operation hook** -- there is no pre-operation or query-configuration injection point. The existing record is loaded and used for the update automatically.
122
+
123
+ The hook runs after the record has been updated and re-read from the database. The updated record is available on `pRequestState.Record`.
124
+
125
+ ```javascript
126
+ tmpEndpoints.controller.BehaviorInjection.setBehavior('Update-PostOperation',
127
+ (pRequest, pRequestState, fCallback) =>
128
+ {
129
+ // Audit the update
130
+ let tmpAuditRecord = {
131
+ Action: 'Update',
132
+ EntityType: 'Book',
133
+ EntityID: pRequestState.Record.IDBook,
134
+ UserID: pRequestState.SessionData.UserID
135
+ };
136
+ _Fable.log.info(`Book updated: ${JSON.stringify(tmpAuditRecord)}`);
137
+ return fCallback();
138
+ });
139
+ ```
140
+
141
+ ### Why No Pre-Operation Hook?
142
+
143
+ The Update operation reads the existing record before performing the update as an inherent part of its workflow. The original record is accessible via `pRequestState.OriginalRecord` (when using cached records) or loaded via `DAL.doRead()`. Authorization checks that need the original record state can use the `Update-PostOperation` hook to compare the original and updated records, or use a custom endpoint for more complex scenarios.
144
+
145
+ ## Automatic Field Handling
146
+
147
+ | Field | Behavior |
148
+ |-------|----------|
149
+ | `UpdateDate` | Set automatically by the DAL on every update |
150
+ | `UpdatingIDUser` | Set from `Query.IDUser` (from `SessionData.UserID`) |
151
+ | `CreateDate` | Preserved from original record |
152
+ | `CreatingIDUser` | Preserved from original record |
153
+
154
+ ## Error Handling
155
+
156
+ The Update endpoint validates two conditions before attempting the update:
157
+
158
+ 1. **Request body must be an object** - Returns `400` if not
159
+ 2. **Record ID must be valid (> 0)** - Returns `400` if missing or invalid
160
+
161
+ If the existing record is not found during the pre-update read, a `404` error is returned.
162
+
163
+ For bulk updates, individual record errors are captured per-record without halting the batch:
164
+
165
+ ```json
166
+ [
167
+ { "IDBook": 42, "Title": "Updated Successfully", ... },
168
+ { "IDBook": 999, "Error": { "Code": 404, "Message": "Record not Found" } }
169
+ ]
170
+ ```
171
+
172
+ ## Real-World Example: Computed Field Update
173
+
174
+ ```javascript
175
+ tmpEndpoints.controller.BehaviorInjection.setBehavior('Update-PostOperation',
176
+ (pRequest, pRequestState, fCallback) =>
177
+ {
178
+ // After updating an order line item, recalculate the order total
179
+ if (pRequestState.Record.IDOrder)
180
+ {
181
+ let tmpQuery = _OrderDAL.query;
182
+ tmpQuery.addFilter('IDOrder', pRequestState.Record.IDOrder);
183
+
184
+ _OrderLineDAL.doReads(tmpQuery,
185
+ (pError, pQuery, pRecords) =>
186
+ {
187
+ if (pError) return fCallback();
188
+
189
+ let tmpTotal = pRecords.reduce(
190
+ (pSum, pLine) => pSum + (pLine.Quantity * pLine.UnitPrice), 0);
191
+
192
+ let tmpUpdateQuery = _OrderDAL.query;
193
+ tmpUpdateQuery.addRecord({ IDOrder: pRequestState.Record.IDOrder, Total: tmpTotal });
194
+ _OrderDAL.doUpdate(tmpUpdateQuery,
195
+ (pError) => fCallback());
196
+ });
197
+ }
198
+ else
199
+ {
200
+ return fCallback();
201
+ }
202
+ });
203
+ ```
@@ -0,0 +1,73 @@
1
+ /* ============================================================================
2
+ Pict Docuserve - Base Styles
3
+ ============================================================================ */
4
+
5
+ /* Reset and base */
6
+ *, *::before, *::after {
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ html, body {
11
+ margin: 0;
12
+ padding: 0;
13
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
14
+ font-size: 16px;
15
+ line-height: 1.5;
16
+ color: #423D37;
17
+ background-color: #fff;
18
+ -webkit-font-smoothing: antialiased;
19
+ -moz-osx-font-smoothing: grayscale;
20
+ }
21
+
22
+ /* Typography */
23
+ h1, h2, h3, h4, h5, h6 {
24
+ margin-top: 0;
25
+ line-height: 1.3;
26
+ }
27
+
28
+ a {
29
+ color: #2E7D74;
30
+ text-decoration: none;
31
+ }
32
+
33
+ a:hover {
34
+ color: #256861;
35
+ }
36
+
37
+ /* Application container */
38
+ #Docuserve-Application-Container {
39
+ min-height: 100vh;
40
+ }
41
+
42
+ /* Utility: scrollbar styling */
43
+ ::-webkit-scrollbar {
44
+ width: 8px;
45
+ }
46
+
47
+ ::-webkit-scrollbar-track {
48
+ background: #F5F0E8;
49
+ }
50
+
51
+ ::-webkit-scrollbar-thumb {
52
+ background: #D4CCBE;
53
+ border-radius: 4px;
54
+ }
55
+
56
+ ::-webkit-scrollbar-thumb:hover {
57
+ background: #B5AA9A;
58
+ }
59
+
60
+ /* Responsive adjustments */
61
+ @media (max-width: 768px) {
62
+ html {
63
+ font-size: 14px;
64
+ }
65
+
66
+ #Docuserve-Sidebar-Container {
67
+ display: none;
68
+ }
69
+
70
+ .docuserve-body {
71
+ flex-direction: column;
72
+ }
73
+ }
@@ -0,0 +1,39 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7
+ <meta name="description" content="Documentation powered by pict-docuserve">
8
+
9
+ <title>Documentation</title>
10
+
11
+ <!-- Application Stylesheet -->
12
+ <link href="css/docuserve.css" rel="stylesheet">
13
+ <!-- KaTeX stylesheet for LaTeX equation rendering -->
14
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css">
15
+ <!-- PICT Dynamic View CSS Container -->
16
+ <style id="PICT-CSS"></style>
17
+
18
+ <!-- Load the PICT library from jsDelivr CDN -->
19
+ <script src="https://cdn.jsdelivr.net/npm/pict@1/dist/pict.min.js" type="text/javascript"></script>
20
+ <!-- Bootstrap the Application -->
21
+ <script type="text/javascript">
22
+ //<![CDATA[
23
+ Pict.safeOnDocumentReady(() => { Pict.safeLoadPictApplication(PictDocuserve, 2)});
24
+ //]]>
25
+ </script>
26
+ </head>
27
+ <body>
28
+ <!-- The root container for the Pict application -->
29
+ <div id="Docuserve-Application-Container"></div>
30
+
31
+ <!-- Mermaid diagram rendering -->
32
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
33
+ <script>mermaid.initialize({ startOnLoad: false, theme: 'default' });</script>
34
+ <!-- KaTeX for LaTeX equation rendering -->
35
+ <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js"></script>
36
+ <!-- Load the Docuserve PICT Application Bundle from jsDelivr CDN -->
37
+ <script src="https://cdn.jsdelivr.net/npm/pict-docuserve@0/dist/pict-docuserve.min.js" type="text/javascript"></script>
38
+ </body>
39
+ </html>
@@ -0,0 +1,177 @@
1
+ {
2
+ "Generated": "2026-02-18T03:27:45.413Z",
3
+ "GitHubOrg": "stevenvelozo",
4
+ "DefaultBranch": "master",
5
+ "Groups": [
6
+ {
7
+ "Name": ".config",
8
+ "Key": ".config",
9
+ "Description": "",
10
+ "Modules": [
11
+ {
12
+ "Name": "code-server",
13
+ "Repo": "code-server",
14
+ "Group": ".config",
15
+ "Branch": "master",
16
+ "HasDocs": false,
17
+ "HasCover": false,
18
+ "Sidebar": [],
19
+ "DocFiles": []
20
+ },
21
+ {
22
+ "Name": "configstore",
23
+ "Repo": "configstore",
24
+ "Group": ".config",
25
+ "Branch": "master",
26
+ "HasDocs": false,
27
+ "HasCover": false,
28
+ "Sidebar": [],
29
+ "DocFiles": []
30
+ },
31
+ {
32
+ "Name": "luxury-extras",
33
+ "Repo": "luxury-extras",
34
+ "Group": ".config",
35
+ "Branch": "master",
36
+ "HasDocs": false,
37
+ "HasCover": false,
38
+ "Sidebar": [],
39
+ "DocFiles": []
40
+ },
41
+ {
42
+ "Name": "vscode-sqltools",
43
+ "Repo": "vscode-sqltools",
44
+ "Group": ".config",
45
+ "Branch": "master",
46
+ "HasDocs": false,
47
+ "HasCover": false,
48
+ "Sidebar": [],
49
+ "DocFiles": []
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "Name": ".github",
55
+ "Key": ".github",
56
+ "Description": "",
57
+ "Modules": [
58
+ {
59
+ "Name": "workflows",
60
+ "Repo": "workflows",
61
+ "Group": ".github",
62
+ "Branch": "master",
63
+ "HasDocs": false,
64
+ "HasCover": false,
65
+ "Sidebar": [],
66
+ "DocFiles": []
67
+ }
68
+ ]
69
+ },
70
+ {
71
+ "Name": "Dist",
72
+ "Key": "dist",
73
+ "Description": "",
74
+ "Modules": [
75
+ {
76
+ "Name": "indoctrinate_content_staging",
77
+ "Repo": "indoctrinate_content_staging",
78
+ "Group": "dist",
79
+ "Branch": "master",
80
+ "HasDocs": false,
81
+ "HasCover": false,
82
+ "Sidebar": [],
83
+ "DocFiles": []
84
+ }
85
+ ]
86
+ },
87
+ {
88
+ "Name": "Docs",
89
+ "Key": "docs",
90
+ "Description": "",
91
+ "Modules": [
92
+ {
93
+ "Name": "crud",
94
+ "Repo": "crud",
95
+ "Group": "docs",
96
+ "Branch": "master",
97
+ "HasDocs": true,
98
+ "HasCover": false,
99
+ "Sidebar": [],
100
+ "DocFiles": [
101
+ "crud/README.md",
102
+ "crud/count.md",
103
+ "crud/create.md",
104
+ "crud/delete.md",
105
+ "crud/read.md",
106
+ "crud/schema.md",
107
+ "crud/update.md"
108
+ ]
109
+ }
110
+ ]
111
+ },
112
+ {
113
+ "Name": "Source",
114
+ "Key": "source",
115
+ "Description": "",
116
+ "Modules": [
117
+ {
118
+ "Name": "controller",
119
+ "Repo": "controller",
120
+ "Group": "source",
121
+ "Branch": "master",
122
+ "HasDocs": false,
123
+ "HasCover": false,
124
+ "Sidebar": [],
125
+ "DocFiles": []
126
+ },
127
+ {
128
+ "Name": "endpoints",
129
+ "Repo": "endpoints",
130
+ "Group": "source",
131
+ "Branch": "master",
132
+ "HasDocs": false,
133
+ "HasCover": false,
134
+ "Sidebar": [],
135
+ "DocFiles": []
136
+ }
137
+ ]
138
+ },
139
+ {
140
+ "Name": "Test_support",
141
+ "Key": "test_support",
142
+ "Description": "",
143
+ "Modules": [
144
+ {
145
+ "Name": "data",
146
+ "Repo": "data",
147
+ "Group": "test_support",
148
+ "Branch": "master",
149
+ "HasDocs": false,
150
+ "HasCover": false,
151
+ "Sidebar": [],
152
+ "DocFiles": []
153
+ },
154
+ {
155
+ "Name": "model",
156
+ "Repo": "model",
157
+ "Group": "test_support",
158
+ "Branch": "master",
159
+ "HasDocs": false,
160
+ "HasCover": false,
161
+ "Sidebar": [],
162
+ "DocFiles": []
163
+ },
164
+ {
165
+ "Name": "test_old",
166
+ "Repo": "test_old",
167
+ "Group": "test_support",
168
+ "Branch": "master",
169
+ "HasDocs": false,
170
+ "HasCover": false,
171
+ "Sidebar": [],
172
+ "DocFiles": []
173
+ }
174
+ ]
175
+ }
176
+ ]
177
+ }