meadow 2.0.23 → 2.0.27
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 +110 -141
- package/docs/README.md +34 -230
- package/docs/_cover.md +14 -0
- package/docs/_sidebar.md +44 -12
- package/docs/_topbar.md +5 -0
- package/docs/api/doCount.md +109 -0
- package/docs/api/doCreate.md +132 -0
- package/docs/api/doDelete.md +101 -0
- package/docs/api/doRead.md +122 -0
- package/docs/api/doReads.md +136 -0
- package/docs/api/doUndelete.md +98 -0
- package/docs/api/doUpdate.md +129 -0
- package/docs/api/getRoleName.md +84 -0
- package/docs/api/loadFromPackage.md +153 -0
- package/docs/api/marshalRecordFromSourceToObject.md +92 -0
- package/docs/api/query.md +133 -0
- package/docs/api/rawQueries.md +197 -0
- package/docs/api/reference.md +117 -0
- package/docs/api/setAuthorizer.md +103 -0
- package/docs/api/setDefault.md +90 -0
- package/docs/api/setDefaultIdentifier.md +84 -0
- package/docs/api/setDomain.md +56 -0
- package/docs/api/setIDUser.md +91 -0
- package/docs/api/setJsonSchema.md +92 -0
- package/docs/api/setProvider.md +87 -0
- package/docs/api/setSchema.md +107 -0
- package/docs/api/setScope.md +68 -0
- package/docs/api/validateObject.md +119 -0
- package/docs/architecture.md +316 -0
- package/docs/audit-tracking.md +226 -0
- package/docs/configuration.md +317 -0
- package/docs/providers/meadow-endpoints.md +306 -0
- package/docs/providers/mongodb.md +319 -0
- package/docs/providers/postgresql.md +312 -0
- package/docs/providers/rocksdb.md +297 -0
- package/docs/query-dsl.md +269 -0
- package/docs/quick-start.md +384 -0
- package/docs/raw-queries.md +193 -0
- package/docs/retold-catalog.json +61 -1
- package/docs/retold-keyword-index.json +15860 -4839
- package/docs/soft-deletes.md +224 -0
- package/package.json +44 -13
- package/scripts/bookstore-seed-postgresql.sql +135 -0
- package/scripts/dgraph-test-db.sh +144 -0
- package/scripts/meadow-test-cleanup.sh +5 -1
- package/scripts/mongodb-test-db.sh +98 -0
- package/scripts/postgresql-test-db.sh +124 -0
- package/scripts/solr-test-db.sh +135 -0
- package/source/Meadow.js +5 -0
- package/source/providers/Meadow-Provider-DGraph.js +679 -0
- package/source/providers/Meadow-Provider-MeadowEndpoints.js +1 -1
- package/source/providers/Meadow-Provider-MongoDB.js +527 -0
- package/source/providers/Meadow-Provider-PostgreSQL.js +361 -0
- package/source/providers/Meadow-Provider-RocksDB.js +1300 -0
- package/source/providers/Meadow-Provider-Solr.js +726 -0
- package/test/Meadow-Provider-DGraph_tests.js +741 -0
- package/test/Meadow-Provider-MongoDB_tests.js +661 -0
- package/test/Meadow-Provider-PostgreSQL_tests.js +787 -0
- package/test/Meadow-Provider-RocksDB_tests.js +887 -0
- package/test/Meadow-Provider-SQLiteBrowser-Headless_tests.js +657 -0
- package/test/Meadow-Provider-SQLiteBrowser_tests.js +895 -0
- package/test/Meadow-Provider-Solr_tests.js +679 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This document describes the internal architecture of Meadow, its component relationships, and how data flows through the system during CRUD operations.
|
|
4
|
+
|
|
5
|
+
## Module Hierarchy
|
|
6
|
+
|
|
7
|
+
Meadow sits between your application and the database, orchestrating schema management, query generation, behavior execution, and data marshalling.
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
graph TB
|
|
11
|
+
Fable["Fable<br/>(Configuration, Logging, DI)"]
|
|
12
|
+
Meadow["Meadow<br/>(Data Broker)"]
|
|
13
|
+
Schema["Schema<br/>(Column Types, JSON Schema,<br/>Default Object, Authorizer)"]
|
|
14
|
+
FoxHound["FoxHound<br/>(Query DSL)"]
|
|
15
|
+
Behaviors["Behaviors<br/>(Create, Read, Reads,<br/>Update, Delete, Undelete, Count)"]
|
|
16
|
+
Provider["Provider<br/>(Database Adapter)"]
|
|
17
|
+
Database["Database<br/>(MySQL, MSSQL, PostgreSQL,<br/>SQLite, MongoDB, RocksDB, etc.)"]
|
|
18
|
+
|
|
19
|
+
Fable --> Meadow
|
|
20
|
+
Meadow --> Schema
|
|
21
|
+
Meadow --> FoxHound
|
|
22
|
+
Meadow --> Behaviors
|
|
23
|
+
Meadow --> Provider
|
|
24
|
+
Provider --> Database
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## CRUD Behavior Flow
|
|
28
|
+
|
|
29
|
+
Every CRUD operation follows the same general flow: a query object enters a behavior module, which orchestrates provider calls, marshals results, and returns data through the callback.
|
|
30
|
+
|
|
31
|
+
```mermaid
|
|
32
|
+
flowchart TD
|
|
33
|
+
A["Application calls<br/>meadow.doCreate / doRead / doReads /<br/>doUpdate / doDelete / doUndelete / doCount"]
|
|
34
|
+
B["Behavior Module<br/>(async waterfall)"]
|
|
35
|
+
C["Provider.Create / Read /<br/>Update / Delete / Undelete / Count"]
|
|
36
|
+
D["Database Result"]
|
|
37
|
+
E["Marshal Record<br/>(marshalRecordFromSourceToObject)"]
|
|
38
|
+
F["Callback<br/>(pError, pQuery, pRecord)"]
|
|
39
|
+
|
|
40
|
+
A --> B
|
|
41
|
+
B --> C
|
|
42
|
+
C --> D
|
|
43
|
+
D --> E
|
|
44
|
+
E --> F
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Create Waterfall
|
|
48
|
+
|
|
49
|
+
The create behavior is the most involved operation, performing multiple steps in an async waterfall to ensure GUID uniqueness and return the complete created record.
|
|
50
|
+
|
|
51
|
+
```mermaid
|
|
52
|
+
flowchart TD
|
|
53
|
+
A["Step 0: GUID Uniqueness Check<br/>If GUID is provided and >= 5 chars,<br/>query for existing record with same GUID"]
|
|
54
|
+
B{"GUID<br/>already exists?"}
|
|
55
|
+
C["Error: Record with GUID already exists"]
|
|
56
|
+
D["Step 1: Insert Record<br/>Merge default object with submitted record,<br/>set IDUser, call Provider.Create"]
|
|
57
|
+
E{"Insert<br/>succeeded?"}
|
|
58
|
+
F["Error: Creation failed"]
|
|
59
|
+
G["Step 2: Read Back<br/>Query by new auto-increment ID,<br/>call Provider.Read"]
|
|
60
|
+
H["Step 3: Marshal<br/>marshalRecordFromSourceToObject<br/>returns plain JavaScript object"]
|
|
61
|
+
I["Callback with<br/>created record"]
|
|
62
|
+
|
|
63
|
+
A --> B
|
|
64
|
+
B -->|Yes| C
|
|
65
|
+
B -->|No| D
|
|
66
|
+
D --> E
|
|
67
|
+
E -->|No| F
|
|
68
|
+
E -->|Yes| G
|
|
69
|
+
G --> H
|
|
70
|
+
H --> I
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Provider Architecture
|
|
74
|
+
|
|
75
|
+
Every provider implements the same interface. Meadow ships with providers for multiple database engines, plus a `None` provider for testing and a `MeadowEndpoints` provider for proxying to remote REST APIs.
|
|
76
|
+
|
|
77
|
+
```mermaid
|
|
78
|
+
classDiagram
|
|
79
|
+
class ProviderInterface {
|
|
80
|
+
+Create(pQuery, fCallback)
|
|
81
|
+
+Read(pQuery, fCallback)
|
|
82
|
+
+Update(pQuery, fCallback)
|
|
83
|
+
+Delete(pQuery, fCallback)
|
|
84
|
+
+Undelete(pQuery, fCallback)
|
|
85
|
+
+Count(pQuery, fCallback)
|
|
86
|
+
+marshalRecordFromSourceToObject(pObject, pRecord)
|
|
87
|
+
+setSchema(pScope, pSchema, pDefaultIdentifier, pDefaultGUIdentifier)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
class MySQL {
|
|
91
|
+
+Create()
|
|
92
|
+
+Read()
|
|
93
|
+
+Update()
|
|
94
|
+
+Delete()
|
|
95
|
+
+Undelete()
|
|
96
|
+
+Count()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class MSSQL {
|
|
100
|
+
+Create()
|
|
101
|
+
+Read()
|
|
102
|
+
+Update()
|
|
103
|
+
+Delete()
|
|
104
|
+
+Undelete()
|
|
105
|
+
+Count()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class PostgreSQL {
|
|
109
|
+
+Create()
|
|
110
|
+
+Read()
|
|
111
|
+
+Update()
|
|
112
|
+
+Delete()
|
|
113
|
+
+Undelete()
|
|
114
|
+
+Count()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class SQLite {
|
|
118
|
+
+Create()
|
|
119
|
+
+Read()
|
|
120
|
+
+Update()
|
|
121
|
+
+Delete()
|
|
122
|
+
+Undelete()
|
|
123
|
+
+Count()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
class MongoDB {
|
|
127
|
+
+Create()
|
|
128
|
+
+Read()
|
|
129
|
+
+Update()
|
|
130
|
+
+Delete()
|
|
131
|
+
+Undelete()
|
|
132
|
+
+Count()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
class RocksDB {
|
|
136
|
+
+Create()
|
|
137
|
+
+Read()
|
|
138
|
+
+Update()
|
|
139
|
+
+Delete()
|
|
140
|
+
+Undelete()
|
|
141
|
+
+Count()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
class ALASQL {
|
|
145
|
+
+Create()
|
|
146
|
+
+Read()
|
|
147
|
+
+Update()
|
|
148
|
+
+Delete()
|
|
149
|
+
+Undelete()
|
|
150
|
+
+Count()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
class MeadowEndpoints {
|
|
154
|
+
+Create()
|
|
155
|
+
+Read()
|
|
156
|
+
+Update()
|
|
157
|
+
+Delete()
|
|
158
|
+
+Count()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
class None {
|
|
162
|
+
+Create()
|
|
163
|
+
+Read()
|
|
164
|
+
+Update()
|
|
165
|
+
+Delete()
|
|
166
|
+
+Undelete()
|
|
167
|
+
+Count()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
ProviderInterface <|-- MySQL
|
|
171
|
+
ProviderInterface <|-- MSSQL
|
|
172
|
+
ProviderInterface <|-- PostgreSQL
|
|
173
|
+
ProviderInterface <|-- SQLite
|
|
174
|
+
ProviderInterface <|-- MongoDB
|
|
175
|
+
ProviderInterface <|-- RocksDB
|
|
176
|
+
ProviderInterface <|-- ALASQL
|
|
177
|
+
ProviderInterface <|-- MeadowEndpoints
|
|
178
|
+
ProviderInterface <|-- None
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Schema System
|
|
182
|
+
|
|
183
|
+
The schema drives query generation, validation, record initialization, and access control. A single schema definition feeds multiple subsystems.
|
|
184
|
+
|
|
185
|
+
```mermaid
|
|
186
|
+
graph LR
|
|
187
|
+
Schema["Meadow Schema"]
|
|
188
|
+
ColumnSchema["Column Schema<br/>(Column, Type, Size)"]
|
|
189
|
+
JsonSchema["JSON Schema<br/>(Validation Rules)"]
|
|
190
|
+
DefaultObject["Default Object<br/>(Initial Values)"]
|
|
191
|
+
Authorizer["Authorizer<br/>(Role Permissions)"]
|
|
192
|
+
|
|
193
|
+
QueryGen["Query Generation<br/>(FoxHound builds SQL<br/>from column definitions)"]
|
|
194
|
+
Validation["Validation<br/>(is-my-json-valid<br/>checks objects)"]
|
|
195
|
+
RecordInit["Record Initialization<br/>(merge defaults into<br/>new records)"]
|
|
196
|
+
AccessControl["Access Control<br/>(role-based CRUD<br/>permissions)"]
|
|
197
|
+
|
|
198
|
+
Schema --> ColumnSchema
|
|
199
|
+
Schema --> JsonSchema
|
|
200
|
+
Schema --> DefaultObject
|
|
201
|
+
Schema --> Authorizer
|
|
202
|
+
|
|
203
|
+
ColumnSchema --> QueryGen
|
|
204
|
+
JsonSchema --> Validation
|
|
205
|
+
DefaultObject --> RecordInit
|
|
206
|
+
Authorizer --> AccessControl
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Query Lifecycle
|
|
210
|
+
|
|
211
|
+
This diagram shows the full sequence of a CRUD operation from the application through to the database and back.
|
|
212
|
+
|
|
213
|
+
```mermaid
|
|
214
|
+
sequenceDiagram
|
|
215
|
+
participant App as Application
|
|
216
|
+
participant Meadow as Meadow
|
|
217
|
+
participant Behavior as Behavior Module
|
|
218
|
+
participant FH as FoxHound
|
|
219
|
+
participant Provider as Provider
|
|
220
|
+
participant DB as Database
|
|
221
|
+
|
|
222
|
+
App->>Meadow: meadow.query (clone FoxHound)
|
|
223
|
+
Meadow-->>App: Independent query clone
|
|
224
|
+
App->>App: addFilter / addRecord / setCap
|
|
225
|
+
App->>Meadow: meadow.doCreate / doRead / etc.
|
|
226
|
+
Meadow->>Behavior: Invoke behavior (async waterfall)
|
|
227
|
+
Behavior->>FH: setDialect / buildQuery
|
|
228
|
+
FH-->>Behavior: Generated SQL + parameters
|
|
229
|
+
Behavior->>Provider: Provider.Create / Read / etc.
|
|
230
|
+
Provider->>DB: Execute query
|
|
231
|
+
DB-->>Provider: Result rows
|
|
232
|
+
Provider-->>Behavior: Result on query object
|
|
233
|
+
Behavior->>Behavior: Marshal records (source to POJO)
|
|
234
|
+
Behavior-->>App: Callback (pError, pQuery, pRecord)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Key Architectural Concepts
|
|
238
|
+
|
|
239
|
+
### Factory Pattern
|
|
240
|
+
|
|
241
|
+
Meadow uses a factory pattern for instantiation. When you `require('meadow')`, the module returns a constructor object. Calling `.new()` without a Fable instance returns a bare constructor. Calling `.new(pFable, pScope)` with a valid Fable instance returns a fully initialized Meadow DAL:
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
var libMeadow = require('meadow');
|
|
245
|
+
|
|
246
|
+
// Returns a constructor (no Fable passed)
|
|
247
|
+
var tmpConstructor = libMeadow.new();
|
|
248
|
+
|
|
249
|
+
// Returns a fully initialized Meadow instance
|
|
250
|
+
var tmpBookDAL = libMeadow.new(_Fable, 'Book');
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
This same pattern is used throughout the Retold ecosystem in providers, schemas, and raw query objects.
|
|
254
|
+
|
|
255
|
+
### Behavior Modules
|
|
256
|
+
|
|
257
|
+
Each CRUD operation is encapsulated in its own behavior module under `source/behaviors/`. Behaviors use `async/waterfall` to sequence multi-step operations:
|
|
258
|
+
|
|
259
|
+
- **Meadow-Create.js** -- GUID check, insert, read back, marshal (4 steps)
|
|
260
|
+
- **Meadow-Read.js** -- read, marshal (2 steps)
|
|
261
|
+
- **Meadow-Reads.js** -- read, marshal each record, profile timing (2 steps)
|
|
262
|
+
- **Meadow-Update.js** -- validate, update, read back, marshal (4 steps)
|
|
263
|
+
- **Meadow-Delete.js** -- delete or soft-delete (1 step)
|
|
264
|
+
- **Meadow-Undelete.js** -- restore soft-deleted record (1 step)
|
|
265
|
+
- **Meadow-Count.js** -- count, validate result, profile timing (2 steps)
|
|
266
|
+
|
|
267
|
+
Each behavior receives the Meadow instance, a query object, and a callback. Errors at any waterfall step short-circuit to the final callback.
|
|
268
|
+
|
|
269
|
+
### Provider Interface
|
|
270
|
+
|
|
271
|
+
Every provider must implement six methods plus a marshaller:
|
|
272
|
+
|
|
273
|
+
| Method | Purpose |
|
|
274
|
+
|--------|---------|
|
|
275
|
+
| `Create(pQuery, fCallback)` | Insert a new record |
|
|
276
|
+
| `Read(pQuery, fCallback)` | Read one or more records |
|
|
277
|
+
| `Update(pQuery, fCallback)` | Update an existing record |
|
|
278
|
+
| `Delete(pQuery, fCallback)` | Delete (or soft-delete) a record |
|
|
279
|
+
| `Undelete(pQuery, fCallback)` | Restore a soft-deleted record |
|
|
280
|
+
| `Count(pQuery, fCallback)` | Count matching records |
|
|
281
|
+
| `marshalRecordFromSourceToObject(pObject, pRecord)` | Copy database row data into a JavaScript object |
|
|
282
|
+
|
|
283
|
+
Providers that need schema information also implement `setSchema(pScope, pSchema, pDefaultIdentifier, pDefaultGUIdentifier)`, which Meadow calls automatically when the schema or scope changes.
|
|
284
|
+
|
|
285
|
+
### Query Cloning
|
|
286
|
+
|
|
287
|
+
Every access to `meadow.query` returns an independent clone of the internal FoxHound query object. This guarantees that configuring one query never leaks state into another:
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
var tmpQuery1 = tmpBookDAL.query.addFilter('Author', 'Asimov');
|
|
291
|
+
var tmpQuery2 = tmpBookDAL.query.addFilter('Author', 'Herbert');
|
|
292
|
+
// tmpQuery1 and tmpQuery2 are completely independent
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The clone copies filters, caps, begin offsets, data elements, and sort configuration. The schema reference is set on each clone so that FoxHound has access to column type information for query generation.
|
|
296
|
+
|
|
297
|
+
### Auto-Stamping
|
|
298
|
+
|
|
299
|
+
When the schema includes special column types, Meadow automatically populates them during create, update, and delete operations:
|
|
300
|
+
|
|
301
|
+
| Column Type | Stamped During | Value |
|
|
302
|
+
|------------|----------------|-------|
|
|
303
|
+
| `CreateDate` | Create | Current timestamp (`NOW()`) |
|
|
304
|
+
| `CreateIDUser` | Create | Current user ID |
|
|
305
|
+
| `UpdateDate` | Update | Current timestamp (`NOW()`) |
|
|
306
|
+
| `UpdateIDUser` | Update | Current user ID |
|
|
307
|
+
| `DeleteDate` | Delete | Current timestamp (`NOW()`) |
|
|
308
|
+
| `DeleteIDUser` | Delete | Current user ID |
|
|
309
|
+
| `AutoIdentity` | Create | Auto-increment value from database |
|
|
310
|
+
| `AutoGUID` | Create | Generated UUID |
|
|
311
|
+
|
|
312
|
+
User identity comes from `meadow.setIDUser(n)` or from `pQuery.query.IDUser` on a per-query basis. Auto-stamping can be disabled per query with `disableAutoDateStamp` and `disableAutoUserStamp`.
|
|
313
|
+
|
|
314
|
+
### Soft Delete Filtering
|
|
315
|
+
|
|
316
|
+
When a schema contains a column with type `'Deleted'`, Meadow and FoxHound automatically add `WHERE Deleted = 0` to all read queries. This filters out logically deleted records without any additional code. To see deleted records, set `setDisableDeleteTracking(true)` on the query. See the [Soft Deletes](soft-deletes.md) documentation for details.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Audit Tracking
|
|
2
|
+
|
|
3
|
+
Meadow provides automatic audit tracking through special schema column types. When these columns are present in your schema, Meadow stamps them with timestamps and user identity during create, update, and delete operations -- without any manual intervention.
|
|
4
|
+
|
|
5
|
+
## Schema Types for Audit Tracking
|
|
6
|
+
|
|
7
|
+
The following schema column types enable automatic audit tracking:
|
|
8
|
+
|
|
9
|
+
| Column Type | Purpose | Stamped During | Value |
|
|
10
|
+
|------------|---------|----------------|-------|
|
|
11
|
+
| `CreateDate` | Records when a row was created | `doCreate` | Current timestamp (`NOW()`) |
|
|
12
|
+
| `CreateIDUser` | Records who created the row | `doCreate` | Current user ID |
|
|
13
|
+
| `UpdateDate` | Records when a row was last modified | `doUpdate` | Current timestamp (`NOW()`) |
|
|
14
|
+
| `UpdateIDUser` | Records who last modified the row | `doUpdate` | Current user ID |
|
|
15
|
+
| `DeleteDate` | Records when a row was soft-deleted | `doDelete` | Current timestamp (`NOW()`) |
|
|
16
|
+
| `DeleteIDUser` | Records who soft-deleted the row | `doDelete` | Current user ID |
|
|
17
|
+
|
|
18
|
+
### Schema Definition Example
|
|
19
|
+
|
|
20
|
+
A complete schema with all audit columns:
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
var tmpSchema =
|
|
24
|
+
[
|
|
25
|
+
{ Column: 'IDBook', Type: 'AutoIdentity' },
|
|
26
|
+
{ Column: 'GUIDBook', Type: 'AutoGUID' },
|
|
27
|
+
{ Column: 'Title', Type: 'String', Size: '255' },
|
|
28
|
+
{ Column: 'Author', Type: 'String', Size: '128' },
|
|
29
|
+
|
|
30
|
+
// Audit tracking columns
|
|
31
|
+
{ Column: 'CreateDate', Type: 'CreateDate' },
|
|
32
|
+
{ Column: 'CreatingIDUser', Type: 'CreateIDUser' },
|
|
33
|
+
{ Column: 'UpdateDate', Type: 'UpdateDate' },
|
|
34
|
+
{ Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
|
|
35
|
+
{ Column: 'DeleteDate', Type: 'DeleteDate' },
|
|
36
|
+
{ Column: 'DeletingIDUser', Type: 'DeleteIDUser' },
|
|
37
|
+
{ Column: 'Deleted', Type: 'Deleted' }
|
|
38
|
+
];
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You can name the columns whatever you like. The `Type` field determines the behavior, not the `Column` name. For example, `{ Column: 'ModifiedOn', Type: 'UpdateDate' }` works just as well.
|
|
42
|
+
|
|
43
|
+
## How Each Column Is Populated
|
|
44
|
+
|
|
45
|
+
### During Create (`doCreate`)
|
|
46
|
+
|
|
47
|
+
When a record is created, Meadow merges the submitted record with the default object and passes it to the provider. The provider (via FoxHound query generation) handles `CreateDate` and `CreateIDUser`:
|
|
48
|
+
|
|
49
|
+
- **CreateDate** -- FoxHound generates `NOW()` (or the database-specific equivalent) in the INSERT statement for this column.
|
|
50
|
+
- **CreateIDUser** -- The current user ID is written into the record before the INSERT. The user ID comes from `meadow.userIdentifier` (set via `setIDUser`) or from `pQuery.query.IDUser` if set on the query.
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
tmpBookDAL.setIDUser(42);
|
|
54
|
+
|
|
55
|
+
tmpBookDAL.doCreate(tmpBookDAL.query.addRecord({ Title: 'Dune' }),
|
|
56
|
+
function (pError, pCreateQuery, pReadQuery, pRecord)
|
|
57
|
+
{
|
|
58
|
+
// pRecord.CreateDate is set to the current timestamp
|
|
59
|
+
// pRecord.CreatingIDUser is 42
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### During Update (`doUpdate`)
|
|
64
|
+
|
|
65
|
+
The update behavior iterates through the schema looking for `UpdateDate` and `UpdateIDUser` column types. It sets these values to `false` in the record, which signals FoxHound to generate `NOW()` for dates and to use the current user ID:
|
|
66
|
+
|
|
67
|
+
- **UpdateDate** -- Set to `false` in the record before the UPDATE, which FoxHound interprets as "use `NOW()`".
|
|
68
|
+
- **UpdateIDUser** -- Set to `false` in the record, which FoxHound populates with the `IDUser` from the query.
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
tmpBookDAL.setIDUser(99);
|
|
72
|
+
|
|
73
|
+
tmpBookDAL.doUpdate(tmpBookDAL.query.addRecord({ IDBook: 1, Title: 'Dune (Revised)' }),
|
|
74
|
+
function (pError, pUpdateQuery, pReadQuery, pRecord)
|
|
75
|
+
{
|
|
76
|
+
// pRecord.UpdateDate is set to the current timestamp
|
|
77
|
+
// pRecord.UpdatingIDUser is 99
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### During Delete (`doDelete`)
|
|
82
|
+
|
|
83
|
+
When the schema includes a `Deleted` column (type `'Deleted'`), the delete operation is a soft delete -- an UPDATE that sets `Deleted = 1`. FoxHound also populates the `DeleteDate` and `DeleteIDUser` columns during this operation:
|
|
84
|
+
|
|
85
|
+
- **DeleteDate** -- Set to `NOW()` in the generated UPDATE statement.
|
|
86
|
+
- **DeleteIDUser** -- Set to the current user ID in the generated UPDATE statement.
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
tmpBookDAL.setIDUser(7);
|
|
90
|
+
|
|
91
|
+
tmpBookDAL.doDelete(tmpBookDAL.query.addFilter('IDBook', 1),
|
|
92
|
+
function (pError, pQuery, pResult)
|
|
93
|
+
{
|
|
94
|
+
// The record now has:
|
|
95
|
+
// Deleted = 1
|
|
96
|
+
// DeleteDate = current timestamp
|
|
97
|
+
// DeletingIDUser = 7
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Setting User Identity
|
|
102
|
+
|
|
103
|
+
### DAL-Level Identity with setIDUser
|
|
104
|
+
|
|
105
|
+
Set the user ID on the Meadow DAL instance. This value applies to all subsequent operations on that DAL:
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
tmpBookDAL.setIDUser(42);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The value is accessible through `meadow.userIdentifier`.
|
|
112
|
+
|
|
113
|
+
### Per-Query Identity
|
|
114
|
+
|
|
115
|
+
Override the user ID for a single operation by setting `IDUser` on the query object:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
var tmpQuery = tmpBookDAL.query
|
|
119
|
+
.addRecord({ Title: 'New Book' });
|
|
120
|
+
|
|
121
|
+
tmpQuery.query.IDUser = 99;
|
|
122
|
+
|
|
123
|
+
tmpBookDAL.doCreate(tmpQuery,
|
|
124
|
+
function (pError, pCreateQuery, pReadQuery, pRecord)
|
|
125
|
+
{
|
|
126
|
+
// CreatingIDUser is 99, not the DAL-level value
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The per-query `IDUser` takes precedence. The behavior first checks `pQuery.query.IDUser`, and only falls back to `meadow.userIdentifier` if the query-level value is not set.
|
|
131
|
+
|
|
132
|
+
### Identity Resolution Order
|
|
133
|
+
|
|
134
|
+
1. If `pQuery.query.IDUser` is already set (non-falsy), use it as-is.
|
|
135
|
+
2. If the query has `pQuery.userID` set as a valid non-negative integer, use that.
|
|
136
|
+
3. Otherwise, use `meadow.userIdentifier` (from `setIDUser`).
|
|
137
|
+
|
|
138
|
+
## Disabling Auto-Stamps
|
|
139
|
+
|
|
140
|
+
In some cases you may need to prevent Meadow from overwriting date or user columns -- for example, during data migrations or when synchronizing records from an external system.
|
|
141
|
+
|
|
142
|
+
### disableAutoDateStamp
|
|
143
|
+
|
|
144
|
+
Prevents the update behavior from overwriting `UpdateDate` columns:
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
var tmpQuery = tmpBookDAL.query
|
|
148
|
+
.addRecord(
|
|
149
|
+
{
|
|
150
|
+
IDBook: 1,
|
|
151
|
+
Title: 'Migrated Record',
|
|
152
|
+
UpdateDate: '2020-01-15 10:30:00'
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
tmpQuery.query.disableAutoDateStamp = true;
|
|
156
|
+
|
|
157
|
+
tmpBookDAL.doUpdate(tmpQuery,
|
|
158
|
+
function (pError, pUpdateQuery, pReadQuery, pRecord)
|
|
159
|
+
{
|
|
160
|
+
// UpdateDate retains the value '2020-01-15 10:30:00'
|
|
161
|
+
// instead of being overwritten with NOW()
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### disableAutoUserStamp
|
|
166
|
+
|
|
167
|
+
Prevents the update behavior from overwriting `UpdateIDUser` columns:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
var tmpQuery = tmpBookDAL.query
|
|
171
|
+
.addRecord(
|
|
172
|
+
{
|
|
173
|
+
IDBook: 1,
|
|
174
|
+
Title: 'Imported Record',
|
|
175
|
+
UpdatingIDUser: 500
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
tmpQuery.query.disableAutoUserStamp = true;
|
|
179
|
+
|
|
180
|
+
tmpBookDAL.doUpdate(tmpQuery,
|
|
181
|
+
function (pError, pUpdateQuery, pReadQuery, pRecord)
|
|
182
|
+
{
|
|
183
|
+
// UpdatingIDUser retains the value 500
|
|
184
|
+
// instead of being overwritten with the current user ID
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Combining Both Flags
|
|
189
|
+
|
|
190
|
+
You can disable both auto-stamps simultaneously:
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
var tmpQuery = tmpBookDAL.query
|
|
194
|
+
.addRecord(
|
|
195
|
+
{
|
|
196
|
+
IDBook: 1,
|
|
197
|
+
Title: 'Full Migration',
|
|
198
|
+
UpdateDate: '2019-06-01 08:00:00',
|
|
199
|
+
UpdatingIDUser: 200
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
tmpQuery.query.disableAutoDateStamp = true;
|
|
203
|
+
tmpQuery.query.disableAutoUserStamp = true;
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Use Cases
|
|
207
|
+
|
|
208
|
+
### Compliance and Audit Trails
|
|
209
|
+
|
|
210
|
+
Including all six audit columns in your schema creates a complete record of who did what and when:
|
|
211
|
+
|
|
212
|
+
- **Who created this record?** Check `CreatingIDUser` and `CreateDate`.
|
|
213
|
+
- **Who last modified it?** Check `UpdatingIDUser` and `UpdateDate`.
|
|
214
|
+
- **Who deleted it and when?** Check `DeletingIDUser` and `DeleteDate`.
|
|
215
|
+
|
|
216
|
+
This information is populated automatically with no additional application code.
|
|
217
|
+
|
|
218
|
+
### Change History
|
|
219
|
+
|
|
220
|
+
Combine audit columns with soft deletes for a complete change history. Since deleted records are preserved in the database (just flagged with `Deleted = 1`), you always have a trail of when records were removed and by whom.
|
|
221
|
+
|
|
222
|
+
For more granular change tracking, consider logging the full before/after state of records using Meadow behaviors in conjunction with your application logic.
|
|
223
|
+
|
|
224
|
+
### Data Migration
|
|
225
|
+
|
|
226
|
+
When importing historical data, use `disableAutoDateStamp` and `disableAutoUserStamp` to preserve the original timestamps and user identity from the source system instead of overwriting them with current values.
|