json-fs-query 3.2.0 → 3.3.0
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 +257 -15
- package/browser/jsonFsQuery.js +2253 -293
- package/browser/jsonFsQuery.min.js +1 -1
- package/dist/data/introspect/alter.d.ts +12 -0
- package/dist/data/introspect/alter.js +304 -0
- package/dist/data/introspect/alter.min.js +1 -0
- package/dist/data/introspect/create.d.ts +10 -0
- package/dist/data/introspect/create.js +2 -1
- package/dist/data/introspect/create.min.js +1 -1
- package/dist/data/introspect/drop.js +2 -0
- package/dist/data/introspect/drop.min.js +1 -1
- package/dist/data/introspect/formatDatabase.d.ts +17 -0
- package/dist/data/introspect/formatDatabase.js +56 -0
- package/dist/data/introspect/formatDatabase.min.js +1 -0
- package/dist/data/introspect/index.d.ts +9 -0
- package/dist/data/introspect/index.js +18 -5
- package/dist/data/introspect/index.min.js +1 -1
- package/dist/data/introspect/packDatabase.d.ts +27 -0
- package/dist/data/introspect/packDatabase.js +113 -0
- package/dist/data/introspect/packDatabase.min.js +1 -0
- package/dist/data/queries/databaseStatements.d.ts +31 -0
- package/dist/data/queries/databaseStatements.js +97 -0
- package/dist/data/queries/databaseStatements.min.js +1 -0
- package/dist/data/queries/deleteEntity.js +6 -1
- package/dist/data/queries/deleteEntity.min.js +1 -1
- package/dist/data/queries/insertEntity.d.ts +8 -0
- package/dist/data/queries/insertEntity.js +338 -33
- package/dist/data/queries/insertEntity.min.js +1 -1
- package/dist/data/queries/query.d.ts +2 -1
- package/dist/data/queries/query.js +5 -2
- package/dist/data/queries/query.min.js +1 -1
- package/dist/data/queries/selectFields.d.ts +5 -4
- package/dist/data/queries/selectFields.js +25 -11
- package/dist/data/queries/selectFields.min.js +1 -1
- package/dist/data/queries/updateEntity.js +39 -5
- package/dist/data/queries/updateEntity.min.js +1 -1
- package/dist/data/storage/databases.d.ts +32 -0
- package/dist/data/storage/databases.js +93 -0
- package/dist/data/storage/databases.min.js +1 -0
- package/dist/data/storage/fileExists.d.ts +2 -1
- package/dist/data/storage/fileExists.js +9 -2
- package/dist/data/storage/fileExists.min.js +1 -1
- package/dist/data/storage/index.d.ts +5 -2
- package/dist/data/storage/index.js +64 -1
- package/dist/data/storage/index.min.js +1 -1
- package/dist/data/storage/listFiles.d.ts +8 -0
- package/dist/data/storage/listFiles.js +52 -0
- package/dist/data/storage/listFiles.min.js +1 -0
- package/dist/data/storage/makeDirectory.d.ts +2 -1
- package/dist/data/storage/makeDirectory.js +8 -2
- package/dist/data/storage/makeDirectory.min.js +1 -1
- package/dist/data/storage/readJson.d.ts +9 -1
- package/dist/data/storage/readJson.js +20 -7
- package/dist/data/storage/readJson.min.js +1 -1
- package/dist/data/storage/removeDirectory.d.ts +3 -1
- package/dist/data/storage/removeDirectory.js +7 -1
- package/dist/data/storage/removeDirectory.min.js +1 -1
- package/dist/data/storage/removeFile.d.ts +2 -1
- package/dist/data/storage/removeFile.js +10 -1
- package/dist/data/storage/removeFile.min.js +1 -1
- package/dist/data/storage/storagePath.js +1 -1
- package/dist/data/storage/storagePath.min.js +1 -1
- package/dist/data/storage/writeJson.d.ts +12 -3
- package/dist/data/storage/writeJson.js +21 -7
- package/dist/data/storage/writeJson.min.js +1 -1
- package/dist/data/storage/writeSet.d.ts +64 -0
- package/dist/data/storage/writeSet.js +306 -0
- package/dist/data/storage/writeSet.min.js +1 -0
- package/dist/data/utilities/config.d.ts +37 -0
- package/dist/data/utilities/config.js +77 -5
- package/dist/data/utilities/config.min.js +1 -1
- package/dist/data/utilities/databaseLock.d.ts +5 -0
- package/dist/data/utilities/databaseLock.js +15 -3
- package/dist/data/utilities/databaseLock.min.js +1 -1
- package/dist/data/utilities/getConditionIndexedList.js +3 -4
- package/dist/data/utilities/getConditionIndexedList.min.js +1 -1
- package/dist/data/utilities/indexBuffer.d.ts +35 -0
- package/dist/data/utilities/indexBuffer.js +139 -0
- package/dist/data/utilities/indexBuffer.min.js +1 -0
- package/dist/data/utilities/joinBases.js +2 -10
- package/dist/data/utilities/joinBases.min.js +1 -1
- package/dist/data/utilities/knownFields.d.ts +26 -0
- package/dist/data/utilities/knownFields.js +128 -0
- package/dist/data/utilities/knownFields.min.js +1 -0
- package/dist/data/utilities/packedTable.d.ts +34 -0
- package/dist/data/utilities/packedTable.js +60 -0
- package/dist/data/utilities/packedTable.min.js +1 -0
- package/dist/data/utilities/parsers/findInsertValues.d.ts +3 -3
- package/dist/data/utilities/parsers/findInsertValues.js +28 -12
- package/dist/data/utilities/parsers/findInsertValues.min.js +1 -1
- package/dist/data/utilities/parsers/maskStrings.js +11 -2
- package/dist/data/utilities/parsers/maskStrings.min.js +1 -1
- package/dist/data/utilities/parsers/parseAlter.d.ts +33 -0
- package/dist/data/utilities/parsers/parseAlter.js +84 -0
- package/dist/data/utilities/parsers/parseAlter.min.js +1 -0
- package/dist/data/utilities/parsers/parseCreate.d.ts +14 -0
- package/dist/data/utilities/parsers/parseCreate.js +13 -5
- package/dist/data/utilities/parsers/parseCreate.min.js +1 -1
- package/dist/data/utilities/parsers/parseDatabaseStatement.d.ts +17 -0
- package/dist/data/utilities/parsers/parseDatabaseStatement.js +82 -0
- package/dist/data/utilities/parsers/parseDatabaseStatement.min.js +1 -0
- package/dist/data/utilities/parsers/parseQuery.d.ts +3 -0
- package/dist/data/utilities/parsers/parseQuery.js +24 -0
- package/dist/data/utilities/parsers/parseQuery.min.js +1 -1
- package/dist/data/utilities/parsers/startsQuery.js +1 -1
- package/dist/data/utilities/parsers/startsQuery.min.js +1 -1
- package/dist/data/utilities/retrieveFiles.d.ts +15 -0
- package/dist/data/utilities/retrieveFiles.js +48 -0
- package/dist/data/utilities/retrieveFiles.min.js +1 -0
- package/dist/data/utilities/retrieveRecord.d.ts +6 -0
- package/dist/data/utilities/retrieveRecord.js +24 -1
- package/dist/data/utilities/retrieveRecord.min.js +1 -1
- package/dist/data/utilities/retrieveRecords.d.ts +22 -0
- package/dist/data/utilities/retrieveRecords.js +60 -10
- package/dist/data/utilities/retrieveRecords.min.js +1 -1
- package/dist/data/utilities/runQuery.js +27 -2
- package/dist/data/utilities/runQuery.min.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +24 -0
- package/dist/index.min.js +1 -1
- package/package.json +7 -3
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { fieldDefinition } from './definitions/field';
|
|
2
|
+
import { ParsedAlter } from '../utilities/parsers/parseAlter';
|
|
3
|
+
/**
|
|
4
|
+
* Change one field of an existing record (see ParsedAlter): add, drop or rename it, or change whether it's optional or
|
|
5
|
+
* nullable and its default. The public version runs as one statement (see withWriteSet): a change that fails part way
|
|
6
|
+
* changes nothing. Node only.
|
|
7
|
+
* @param entity
|
|
8
|
+
* @param change
|
|
9
|
+
* @returns The record's new field definitions (as describe gives them).
|
|
10
|
+
*/
|
|
11
|
+
export declare const alter: (entity: string, change: ParsedAlter) => Promise<fieldDefinition[]>;
|
|
12
|
+
//# sourceMappingURL=alter.d.ts.map
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.alter = void 0;
|
|
7
|
+
require("core-js/modules/es.array.includes.js");
|
|
8
|
+
require("core-js/modules/es.json.stringify.js");
|
|
9
|
+
require("core-js/modules/esnext.iterator.constructor.js");
|
|
10
|
+
require("core-js/modules/esnext.iterator.filter.js");
|
|
11
|
+
require("core-js/modules/esnext.iterator.find.js");
|
|
12
|
+
require("core-js/modules/esnext.iterator.map.js");
|
|
13
|
+
require("core-js/modules/esnext.iterator.some.js");
|
|
14
|
+
var _field = require("./definitions/field");
|
|
15
|
+
var _create = require("./create");
|
|
16
|
+
var _parseCreate = require("../utilities/parsers/parseCreate");
|
|
17
|
+
var _retrieveRecord = require("../utilities/retrieveRecord");
|
|
18
|
+
var _retrieveFiles = require("../utilities/retrieveFiles");
|
|
19
|
+
var _retrieveRecords = require("../utilities/retrieveRecords");
|
|
20
|
+
var _knownFields = require("../utilities/knownFields");
|
|
21
|
+
var _packedTable = require("../utilities/packedTable");
|
|
22
|
+
var _requireNode = require("../utilities/requireNode");
|
|
23
|
+
var _showRecords = require("../queries/showRecords");
|
|
24
|
+
var _updateEntity = require("../queries/updateEntity");
|
|
25
|
+
var _typeCheck = require("./policies/typeCheck");
|
|
26
|
+
var _storage = require("../storage");
|
|
27
|
+
/**
|
|
28
|
+
* Every row of a record, with the entry file it's in.
|
|
29
|
+
* @param record
|
|
30
|
+
*/
|
|
31
|
+
const rowsOf = async record => {
|
|
32
|
+
const rows = await (0, _retrieveFiles.retrieveFiles)(record.entries.map(file => `${record.path}/${file}`), _retrieveRecords.retrieveEntry);
|
|
33
|
+
return rows.map((data, position) => ({
|
|
34
|
+
fileName: record.entries[position],
|
|
35
|
+
data
|
|
36
|
+
}));
|
|
37
|
+
};
|
|
38
|
+
const has = (row, name) => Object.prototype.hasOwnProperty.call(row, name);
|
|
39
|
+
/** The keys of a record that use a field. */
|
|
40
|
+
const keysUsing = (record, name) => record.keys.filter(key => key.fields.includes(name));
|
|
41
|
+
const describeKey = key => `${key.type} key (${key.fields.join(', ')})`;
|
|
42
|
+
/**
|
|
43
|
+
* Every record's foreign keys that reference `<entity>.<name>`, with the record they're on.
|
|
44
|
+
* @param entity
|
|
45
|
+
* @param name
|
|
46
|
+
*/
|
|
47
|
+
const referencesTo = async (entity, name) => {
|
|
48
|
+
const found = [];
|
|
49
|
+
for (const other of await (0, _showRecords.showRecords)()) {
|
|
50
|
+
const record = await (0, _retrieveRecord.retrieveRecord)(other);
|
|
51
|
+
for (const key of record.keys) {
|
|
52
|
+
if (key.type === 'foreign' && (key.references || []).some(reference => reference.fields.includes(`${entity}.${name}`))) {
|
|
53
|
+
found.push({
|
|
54
|
+
entity: other,
|
|
55
|
+
record,
|
|
56
|
+
key
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return found;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Write the record's new definition, after checking it as create checks a new one (defaults fit their types, keys name
|
|
65
|
+
* defined fields, primary key fields aren't nullable).
|
|
66
|
+
* @param entity
|
|
67
|
+
* @param record
|
|
68
|
+
*/
|
|
69
|
+
const writeRecord = async (entity, record) => {
|
|
70
|
+
(0, _create.assertValidDefinition)(entity, record);
|
|
71
|
+
await (0, _storage.writeJson)(`${entity}.json`, record);
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Give `rows` a value for `name` (or, with undefined, remove it). A field no key uses is written straight into each
|
|
75
|
+
* entry file. For a field a key uses, the rows go through update, as `update <entity> set <name> = ...` would change
|
|
76
|
+
* them, so the key's index moves with them and its checks (uniqueness, foreign keys) apply; that needs the record's
|
|
77
|
+
* new definition written first, and a primary key to find each row by.
|
|
78
|
+
* @param entity
|
|
79
|
+
* @param record - The record's new definition.
|
|
80
|
+
* @param rows
|
|
81
|
+
* @param name
|
|
82
|
+
* @param value - The value, or a function giving each row's; undefined removes the field.
|
|
83
|
+
*/
|
|
84
|
+
const setField = async (entity, record, rows, name, value) => {
|
|
85
|
+
if (!rows.length) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await (0, _packedTable.discardPacked)(record.path);
|
|
89
|
+
if (keysUsing(record, name).length && typeof value !== 'function') {
|
|
90
|
+
if (!record.keys.some(key => key.type === 'primary')) {
|
|
91
|
+
throw new Error(`Can't change "${name}" on ${entity}'s rows: a key uses it, and rows are found by the primary key, which ${entity} doesn't have`);
|
|
92
|
+
}
|
|
93
|
+
// update reads null in an optional field that isn't nullable as removing it (see updateEntity)
|
|
94
|
+
await (0, _updateEntity.updateEntity)(entity, {
|
|
95
|
+
[name]: typeof value === 'undefined' ? null : value
|
|
96
|
+
}, {
|
|
97
|
+
[entity]: rows.map(row => row.data)
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (const row of rows) {
|
|
102
|
+
const changed = Object.assign({}, row.data);
|
|
103
|
+
const next = typeof value === 'function' ? value(row) : value;
|
|
104
|
+
if (typeof next === 'undefined') {
|
|
105
|
+
delete changed[name];
|
|
106
|
+
} else {
|
|
107
|
+
changed[name] = next;
|
|
108
|
+
}
|
|
109
|
+
await (0, _storage.writeJson)(`${record.path}/${row.fileName}`, changed);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Add a field. Existing rows get its default, or a new id for an `auto` field; with neither, it must be optional (the
|
|
114
|
+
* rows would otherwise be missing a required field). A row that already holds a value under the name (written before
|
|
115
|
+
* the field was defined) keeps it, if it fits the type.
|
|
116
|
+
*/
|
|
117
|
+
const addField = async (entity, record, added) => {
|
|
118
|
+
if (record.definition.some(field => field.name === added.name)) {
|
|
119
|
+
throw new Error(`Field "${added.name}" already exists on ${entity}`);
|
|
120
|
+
}
|
|
121
|
+
const updated = {
|
|
122
|
+
...record,
|
|
123
|
+
definition: [...record.definition, added]
|
|
124
|
+
};
|
|
125
|
+
const rows = await rowsOf(record);
|
|
126
|
+
for (const row of rows.filter(row => has(row.data, added.name))) {
|
|
127
|
+
try {
|
|
128
|
+
(0, _typeCheck.typeCheck)(added.type, row.data[added.name], added.nullable);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw new Error(`Can't add "${added.name}" to ${entity}: row ${row.fileName} already holds a value for it that doesn't fit (${error.message})`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const missing = rows.filter(row => !has(row.data, added.name));
|
|
134
|
+
const hasDefault = has(added, 'default');
|
|
135
|
+
if (missing.length && !added.optional && !hasDefault && !added.autoGenerate) {
|
|
136
|
+
throw new Error(`Can't add required field "${added.name}" to ${entity}: its ${missing.length} row(s) have no value for it (give it a default, or make it optional)`);
|
|
137
|
+
}
|
|
138
|
+
await writeRecord(entity, updated);
|
|
139
|
+
if (added.autoGenerate) {
|
|
140
|
+
await setField(entity, updated, missing, added.name, () => crypto.randomUUID());
|
|
141
|
+
} else if (hasDefault) {
|
|
142
|
+
await setField(entity, updated, missing, added.name, added['default']);
|
|
143
|
+
}
|
|
144
|
+
return updated;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Drop a field, removing it from every row. Refused while a key uses it, or another record's foreign key references it
|
|
148
|
+
* (drop or change those first).
|
|
149
|
+
*/
|
|
150
|
+
const dropField = async (entity, record, name) => {
|
|
151
|
+
(0, _knownFields.assertFieldsDefined)(entity, record, [name], 'alter');
|
|
152
|
+
const key = keysUsing(record, name)[0];
|
|
153
|
+
if (key) {
|
|
154
|
+
throw new Error(`Can't drop "${name}" from ${entity}: its ${describeKey(key)} uses it`);
|
|
155
|
+
}
|
|
156
|
+
const reference = (await referencesTo(entity, name))[0];
|
|
157
|
+
if (reference) {
|
|
158
|
+
throw new Error(`Can't drop "${name}" from ${entity}: ${reference.entity}'s foreign key "${reference.key.fields[0]}" references it`);
|
|
159
|
+
}
|
|
160
|
+
const updated = {
|
|
161
|
+
...record,
|
|
162
|
+
definition: record.definition.filter(field => field.name !== name)
|
|
163
|
+
};
|
|
164
|
+
await writeRecord(entity, updated);
|
|
165
|
+
await setField(entity, updated, (await rowsOf(record)).filter(row => has(row.data, name)), name, undefined);
|
|
166
|
+
return updated;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Rename a field: in the definition, in every row, in the record's keys (moving an index file named after the field to
|
|
170
|
+
* its new name), and in every foreign key that references it.
|
|
171
|
+
*/
|
|
172
|
+
const renameField = async (entity, record, name, to) => {
|
|
173
|
+
(0, _knownFields.assertFieldsDefined)(entity, record, [name], 'alter');
|
|
174
|
+
if (record.definition.some(field => field.name === to)) {
|
|
175
|
+
throw new Error(`Can't rename "${name}" on ${entity} to "${to}": a field of that name already exists`);
|
|
176
|
+
}
|
|
177
|
+
const references = await referencesTo(entity, name);
|
|
178
|
+
const keys = record.keys.map(key => {
|
|
179
|
+
if (!key.fields.includes(name)) {
|
|
180
|
+
return key;
|
|
181
|
+
}
|
|
182
|
+
const fields = key.fields.map(field => field === name ? to : field);
|
|
183
|
+
// An index file create named after the fields moves to the new name; a name given some other way stays
|
|
184
|
+
const lookup = key.lookup && key.lookup === (0, _parseCreate.keyLookup)(key.type, key.fields) ? (0, _parseCreate.keyLookup)(key.type, fields) : key.lookup;
|
|
185
|
+
return {
|
|
186
|
+
...key,
|
|
187
|
+
fields,
|
|
188
|
+
lookup
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
const renameReferences = key => key.type !== 'foreign' ? key : {
|
|
192
|
+
...key,
|
|
193
|
+
references: (key.references || []).map(reference => ({
|
|
194
|
+
...reference,
|
|
195
|
+
fields: reference.fields.map(field => field === `${entity}.${name}` ? `${entity}.${to}` : field)
|
|
196
|
+
}))
|
|
197
|
+
};
|
|
198
|
+
const updated = {
|
|
199
|
+
...record,
|
|
200
|
+
definition: record.definition.map(field => field.name === name ? {
|
|
201
|
+
...field,
|
|
202
|
+
name: to
|
|
203
|
+
} : field),
|
|
204
|
+
keys: keys.map(renameReferences)
|
|
205
|
+
};
|
|
206
|
+
for (const [position, key] of record.keys.entries()) {
|
|
207
|
+
const moved = keys[position].lookup;
|
|
208
|
+
if (key.lookup && moved !== key.lookup && (0, _storage.fileExists)(`__indexes/${record.path}/${key.lookup}`)) {
|
|
209
|
+
await (0, _storage.writeJson)(`__indexes/${record.path}/${moved}`, await (0, _storage.readJson)(`__indexes/${record.path}/${key.lookup}`));
|
|
210
|
+
await (0, _storage.removeFile)(`__indexes/${record.path}/${key.lookup}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
await writeRecord(entity, updated);
|
|
214
|
+
for (const reference of references.filter(reference => reference.entity !== entity)) {
|
|
215
|
+
await (0, _storage.writeJson)(`${reference.entity}.json`, {
|
|
216
|
+
...reference.record,
|
|
217
|
+
keys: reference.record.keys.map(renameReferences)
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
// Each row's value moves to the new name (the indexes hold values, not names, so they only moved file)
|
|
221
|
+
const rows = (await rowsOf(record)).filter(row => has(row.data, name));
|
|
222
|
+
if (rows.length) {
|
|
223
|
+
await (0, _packedTable.discardPacked)(record.path);
|
|
224
|
+
}
|
|
225
|
+
for (const row of rows) {
|
|
226
|
+
const moved = Object.assign({}, row.data);
|
|
227
|
+
moved[to] = moved[name];
|
|
228
|
+
delete moved[name];
|
|
229
|
+
await (0, _storage.writeJson)(`${record.path}/${row.fileName}`, moved);
|
|
230
|
+
}
|
|
231
|
+
return updated;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Change a field's definition: whether it's optional or nullable, and its default (its type, and whether it's auto,
|
|
235
|
+
* stay). The statement gives the whole new definition, as MySQL's MODIFY does: what it leaves out is cleared, so a
|
|
236
|
+
* default it doesn't repeat is removed, as `optional` or `null` left out make the field required or not nullable. Rows that would no longer fit are filled with the new default, or the change is refused: a field made
|
|
237
|
+
* required needs a value in every row, and one made not nullable can't hold null (an optional one then has no value,
|
|
238
|
+
* as null means in an optional field).
|
|
239
|
+
*/
|
|
240
|
+
const modifyField = async (entity, record, changed) => {
|
|
241
|
+
(0, _knownFields.assertFieldsDefined)(entity, record, [changed.name], 'alter');
|
|
242
|
+
const current = record.definition.find(field => field.name === changed.name);
|
|
243
|
+
if (current.type !== changed.type) {
|
|
244
|
+
throw new Error(`Can't change the type of "${changed.name}" on ${entity} (${current.type} to ${changed.type}): only optional, null and default can change`);
|
|
245
|
+
}
|
|
246
|
+
if (Boolean(current.autoGenerate) !== Boolean(changed.autoGenerate)) {
|
|
247
|
+
throw new Error(`Can't change whether "${changed.name}" on ${entity} is auto: only optional, null and default can change`);
|
|
248
|
+
}
|
|
249
|
+
const updated = {
|
|
250
|
+
...record,
|
|
251
|
+
definition: record.definition.map(field => field.name === changed.name ? changed : field)
|
|
252
|
+
};
|
|
253
|
+
const rows = await rowsOf(record);
|
|
254
|
+
const hasDefault = has(changed, 'default');
|
|
255
|
+
const holdsNull = changed.nullable ? [] : rows.filter(row => row.data[changed.name] === null);
|
|
256
|
+
const missing = changed.optional ? [] : rows.filter(row => !has(row.data, changed.name));
|
|
257
|
+
const unfillable = changed.optional ? [] : holdsNull;
|
|
258
|
+
if ((missing.length || unfillable.length) && !hasDefault) {
|
|
259
|
+
throw missing.length ? new Error(`Can't make "${changed.name}" required on ${entity}: ${missing.length} row(s) have no value for it (give it a default to fill them in)`) : new Error(`Can't make "${changed.name}" not null on ${entity}: ${unfillable.length} row(s) hold null (give it a default to fill them in, or make it optional)`);
|
|
260
|
+
}
|
|
261
|
+
await writeRecord(entity, updated);
|
|
262
|
+
if (hasDefault) {
|
|
263
|
+
await setField(entity, updated, [...missing, ...unfillable], changed.name, changed['default']);
|
|
264
|
+
}
|
|
265
|
+
if (changed.optional && holdsNull.length) {
|
|
266
|
+
await setField(entity, updated, holdsNull, changed.name, undefined);
|
|
267
|
+
}
|
|
268
|
+
return updated;
|
|
269
|
+
};
|
|
270
|
+
/**
|
|
271
|
+
* Change one field of an existing record (see ParsedAlter): add, drop or rename it, or change whether it's optional or
|
|
272
|
+
* nullable and its default. The public version runs as one statement (see withWriteSet): a change that fails part way
|
|
273
|
+
* changes nothing. Node only.
|
|
274
|
+
* @param entity
|
|
275
|
+
* @param change
|
|
276
|
+
* @returns The record's new field definitions (as describe gives them).
|
|
277
|
+
*/
|
|
278
|
+
const alter = async (entity, change) => {
|
|
279
|
+
(0, _requireNode.requireNode)('alter');
|
|
280
|
+
if (entity === '__RECORDS') {
|
|
281
|
+
throw new Error('__RECORDS is reserved for the list of records');
|
|
282
|
+
}
|
|
283
|
+
// The same names the query language allows: a name becomes a key in every row and part of index file paths, so
|
|
284
|
+
// one like `../x` from a direct call must not get that far
|
|
285
|
+
const names = [entity, ...('field' in change ? [change.field?.name] : []), ...('name' in change ? [change.name] : []), ...('to' in change ? [change.to] : [])];
|
|
286
|
+
const invalid = names.findIndex(name => typeof name !== 'string' || !_parseCreate.nameMatch.test(name));
|
|
287
|
+
if (invalid !== -1) {
|
|
288
|
+
throw new Error(`Invalid name ${JSON.stringify(names[invalid]) ?? 'undefined'} in alter: names use letters, digits, _ and - only`);
|
|
289
|
+
}
|
|
290
|
+
const record = await (0, _retrieveRecord.retrieveRecord)(entity);
|
|
291
|
+
switch (change.action) {
|
|
292
|
+
case 'add':
|
|
293
|
+
return (await addField(entity, record, (0, _field.field)(change.field))).definition;
|
|
294
|
+
case 'drop':
|
|
295
|
+
return (await dropField(entity, record, change.name)).definition;
|
|
296
|
+
case 'rename':
|
|
297
|
+
return (await renameField(entity, record, change.name, change.to)).definition;
|
|
298
|
+
case 'modify':
|
|
299
|
+
return (await modifyField(entity, record, (0, _field.field)(change.field))).definition;
|
|
300
|
+
default:
|
|
301
|
+
throw new Error(`Unknown alter action: ${change.action}`);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
exports.alter = alter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.alter=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js");var _field=require("./definitions/field"),_create=require("./create"),_parseCreate=require("../utilities/parsers/parseCreate"),_retrieveRecord=require("../utilities/retrieveRecord"),_retrieveFiles=require("../utilities/retrieveFiles"),_retrieveRecords=require("../utilities/retrieveRecords"),_knownFields=require("../utilities/knownFields"),_packedTable=require("../utilities/packedTable"),_requireNode=require("../utilities/requireNode"),_showRecords=require("../queries/showRecords"),_updateEntity=require("../queries/updateEntity"),_typeCheck=require("./policies/typeCheck"),_storage=require("../storage");const rowsOf=async e=>(await(0,_retrieveFiles.retrieveFiles)(e.entries.map((t=>`${e.path}/${t}`)),_retrieveRecords.retrieveEntry)).map(((t,r)=>({fileName:e.entries[r],data:t}))),has=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),keysUsing=(e,t)=>e.keys.filter((e=>e.fields.includes(t))),describeKey=e=>`${e.type} key (${e.fields.join(", ")})`,referencesTo=async(e,t)=>{const r=[];for(const i of await(0,_showRecords.showRecords)()){const a=await(0,_retrieveRecord.retrieveRecord)(i);for(const o of a.keys)"foreign"===o.type&&(o.references||[]).some((r=>r.fields.includes(`${e}.${t}`)))&&r.push({entity:i,record:a,key:o})}return r},writeRecord=async(e,t)=>{(0,_create.assertValidDefinition)(e,t),await(0,_storage.writeJson)(`${e}.json`,t)},setField=async(e,t,r,i,a)=>{if(r.length)if(await(0,_packedTable.discardPacked)(t.path),keysUsing(t,i).length&&"function"!=typeof a){if(!t.keys.some((e=>"primary"===e.type)))throw new Error(`Can't change "${i}" on ${e}'s rows: a key uses it, and rows are found by the primary key, which ${e} doesn't have`);await(0,_updateEntity.updateEntity)(e,{[i]:void 0===a?null:a},{[e]:r.map((e=>e.data))})}else for(const e of r){const r=Object.assign({},e.data),o="function"==typeof a?a(e):a;void 0===o?delete r[i]:r[i]=o,await(0,_storage.writeJson)(`${t.path}/${e.fileName}`,r)}},addField=async(e,t,r)=>{if(t.definition.some((e=>e.name===r.name)))throw new Error(`Field "${r.name}" already exists on ${e}`);const i={...t,definition:[...t.definition,r]},a=await rowsOf(t);for(const t of a.filter((e=>has(e.data,r.name))))try{(0,_typeCheck.typeCheck)(r.type,t.data[r.name],r.nullable)}catch(i){throw new Error(`Can't add "${r.name}" to ${e}: row ${t.fileName} already holds a value for it that doesn't fit (${i.message})`)}const o=a.filter((e=>!has(e.data,r.name))),n=has(r,"default");if(o.length&&!r.optional&&!n&&!r.autoGenerate)throw new Error(`Can't add required field "${r.name}" to ${e}: its ${o.length} row(s) have no value for it (give it a default, or make it optional)`);return await writeRecord(e,i),r.autoGenerate?await setField(e,i,o,r.name,(()=>crypto.randomUUID())):n&&await setField(e,i,o,r.name,r.default),i},dropField=async(e,t,r)=>{(0,_knownFields.assertFieldsDefined)(e,t,[r],"alter");const i=keysUsing(t,r)[0];if(i)throw new Error(`Can't drop "${r}" from ${e}: its ${describeKey(i)} uses it`);const a=(await referencesTo(e,r))[0];if(a)throw new Error(`Can't drop "${r}" from ${e}: ${a.entity}'s foreign key "${a.key.fields[0]}" references it`);const o={...t,definition:t.definition.filter((e=>e.name!==r))};return await writeRecord(e,o),await setField(e,o,(await rowsOf(t)).filter((e=>has(e.data,r))),r,void 0),o},renameField=async(e,t,r,i)=>{if((0,_knownFields.assertFieldsDefined)(e,t,[r],"alter"),t.definition.some((e=>e.name===i)))throw new Error(`Can't rename "${r}" on ${e} to "${i}": a field of that name already exists`);const a=await referencesTo(e,r),o=t.keys.map((e=>{if(!e.fields.includes(r))return e;const t=e.fields.map((e=>e===r?i:e)),a=e.lookup&&e.lookup===(0,_parseCreate.keyLookup)(e.type,e.fields)?(0,_parseCreate.keyLookup)(e.type,t):e.lookup;return{...e,fields:t,lookup:a}})),n=t=>"foreign"!==t.type?t:{...t,references:(t.references||[]).map((t=>({...t,fields:t.fields.map((t=>t===`${e}.${r}`?`${e}.${i}`:t))})))},s={...t,definition:t.definition.map((e=>e.name===r?{...e,name:i}:e)),keys:o.map(n)};for(const[e,r]of t.keys.entries()){const i=o[e].lookup;r.lookup&&i!==r.lookup&&(0,_storage.fileExists)(`__indexes/${t.path}/${r.lookup}`)&&(await(0,_storage.writeJson)(`__indexes/${t.path}/${i}`,await(0,_storage.readJson)(`__indexes/${t.path}/${r.lookup}`)),await(0,_storage.removeFile)(`__indexes/${t.path}/${r.lookup}`))}await writeRecord(e,s);for(const t of a.filter((t=>t.entity!==e)))await(0,_storage.writeJson)(`${t.entity}.json`,{...t.record,keys:t.record.keys.map(n)});const d=(await rowsOf(t)).filter((e=>has(e.data,r)));d.length&&await(0,_packedTable.discardPacked)(t.path);for(const e of d){const a=Object.assign({},e.data);a[i]=a[r],delete a[r],await(0,_storage.writeJson)(`${t.path}/${e.fileName}`,a)}return s},modifyField=async(e,t,r)=>{(0,_knownFields.assertFieldsDefined)(e,t,[r.name],"alter");const i=t.definition.find((e=>e.name===r.name));if(i.type!==r.type)throw new Error(`Can't change the type of "${r.name}" on ${e} (${i.type} to ${r.type}): only optional, null and default can change`);if(Boolean(i.autoGenerate)!==Boolean(r.autoGenerate))throw new Error(`Can't change whether "${r.name}" on ${e} is auto: only optional, null and default can change`);const a={...t,definition:t.definition.map((e=>e.name===r.name?r:e))},o=await rowsOf(t),n=has(r,"default"),s=r.nullable?[]:o.filter((e=>null===e.data[r.name])),d=r.optional?[]:o.filter((e=>!has(e.data,r.name))),l=r.optional?[]:s;if((d.length||l.length)&&!n)throw d.length?new Error(`Can't make "${r.name}" required on ${e}: ${d.length} row(s) have no value for it (give it a default to fill them in)`):new Error(`Can't make "${r.name}" not null on ${e}: ${l.length} row(s) hold null (give it a default to fill them in, or make it optional)`);return await writeRecord(e,a),n&&await setField(e,a,[...d,...l],r.name,r.default),r.optional&&s.length&&await setField(e,a,s,r.name,void 0),a},alter=async(e,t)=>{if((0,_requireNode.requireNode)("alter"),"__RECORDS"===e)throw new Error("__RECORDS is reserved for the list of records");const r=[e,..."field"in t?[t.field?.name]:[],..."name"in t?[t.name]:[],..."to"in t?[t.to]:[]],i=r.findIndex((e=>"string"!=typeof e||!_parseCreate.nameMatch.test(e)));if(-1!==i)throw new Error(`Invalid name ${JSON.stringify(r[i])??"undefined"} in alter: names use letters, digits, _ and - only`);const a=await(0,_retrieveRecord.retrieveRecord)(e);switch(t.action){case"add":return(await addField(e,a,(0,_field.field)(t.field))).definition;case"drop":return(await dropField(e,a,t.name)).definition;case"rename":return(await renameField(e,a,t.name,t.to)).definition;case"modify":return(await modifyField(e,a,(0,_field.field)(t.field))).definition;default:throw new Error(`Unknown alter action: ${t.action}`)}};exports.alter=alter;
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { recordDefinition } from './definitions/record';
|
|
2
2
|
import { keyProperties } from './definitions/key';
|
|
3
3
|
import { fieldProperties } from './definitions/field';
|
|
4
|
+
/**
|
|
5
|
+
* Reject a definition that could never be written correctly, before anything is created: a
|
|
6
|
+
* default that fails its field's type check (otherwise every insert leaving that field out would
|
|
7
|
+
* fail instead), including a null default on a field that isn't nullable, and a nullable field in
|
|
8
|
+
* the primary key (an entry's file name comes from its primary key value). Also a key with no fields,
|
|
9
|
+
* or with a field that isn't in the definition.
|
|
10
|
+
* @param recordName
|
|
11
|
+
* @param recordContent
|
|
12
|
+
*/
|
|
13
|
+
export declare const assertValidDefinition: (recordName: string, recordContent: recordDefinition) => void;
|
|
4
14
|
/**
|
|
5
15
|
* Create a new record. Throws if the definition is invalid (see assertValidDefinition).
|
|
6
16
|
* @param recordName
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.create = void 0;
|
|
6
|
+
exports.create = exports.assertValidDefinition = void 0;
|
|
7
7
|
require("core-js/modules/es.array.includes.js");
|
|
8
8
|
require("core-js/modules/esnext.iterator.constructor.js");
|
|
9
9
|
require("core-js/modules/esnext.iterator.filter.js");
|
|
@@ -73,6 +73,7 @@ const assertValidDefinition = (recordName, recordContent) => {
|
|
|
73
73
|
* @param definition
|
|
74
74
|
* @param keys
|
|
75
75
|
*/
|
|
76
|
+
exports.assertValidDefinition = assertValidDefinition;
|
|
76
77
|
const create = async (recordName, definition = [], keys = []) => {
|
|
77
78
|
(0, _requireNode.requireNode)('create()');
|
|
78
79
|
if (recordName === '__RECORDS') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.create=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.some.js");var _record=require("./definitions/record"),_registerRecord=require("../utilities/registerRecord"),_storage=require("../storage"),_requireNode=require("../utilities/requireNode"),_typeCheck=require("./policies/typeCheck");const knownKeyProperties=["type","fields","lookup","references"],assertKnownKeyProperties=(e,r)=>{for(const o of r)for(const r of Object.keys(o))if(!knownKeyProperties.includes(r))throw new Error(`Unknown property "${r}" on ${o.type??"index"} key of ${e}; must be ${knownKeyProperties.join(", ")}`)},assertValidDefinition=(e,r)=>{for(const o of r.definition)if(Object.prototype.hasOwnProperty.call(o,"default"))try{(0,_typeCheck.typeCheck)(o.type,o.default,o.nullable)}catch(r){throw new Error(`Invalid default for field "${o.name}" of ${e}: ${r.message}`)}for(const o of r.keys){if(o.fields.length<1)throw new Error(`The ${o.type} key of ${e} must have at least one field`);for(const t of o.fields)if(!r.definition.some((e=>e.name===t)))throw new Error(`The ${o.type} key of ${e} names field "${t}", which isn't in the definition`)}const o=r.keys.find((e=>"primary"===e.type));for(const t of o?o.fields:[])if(r.definition.some((e=>e.name===t&&e.nullable)))throw new Error(`Primary key field "${t}" of ${e} can't be nullable`)}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.create=exports.assertValidDefinition=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.some.js");var _record=require("./definitions/record"),_registerRecord=require("../utilities/registerRecord"),_storage=require("../storage"),_requireNode=require("../utilities/requireNode"),_typeCheck=require("./policies/typeCheck");const knownKeyProperties=["type","fields","lookup","references"],assertKnownKeyProperties=(e,r)=>{for(const o of r)for(const r of Object.keys(o))if(!knownKeyProperties.includes(r))throw new Error(`Unknown property "${r}" on ${o.type??"index"} key of ${e}; must be ${knownKeyProperties.join(", ")}`)},assertValidDefinition=(e,r)=>{for(const o of r.definition)if(Object.prototype.hasOwnProperty.call(o,"default"))try{(0,_typeCheck.typeCheck)(o.type,o.default,o.nullable)}catch(r){throw new Error(`Invalid default for field "${o.name}" of ${e}: ${r.message}`)}for(const o of r.keys){if(o.fields.length<1)throw new Error(`The ${o.type} key of ${e} must have at least one field`);for(const t of o.fields)if(!r.definition.some((e=>e.name===t)))throw new Error(`The ${o.type} key of ${e} names field "${t}", which isn't in the definition`)}const o=r.keys.find((e=>"primary"===e.type));for(const t of o?o.fields:[])if(r.definition.some((e=>e.name===t&&e.nullable)))throw new Error(`Primary key field "${t}" of ${e} can't be nullable`)};exports.assertValidDefinition=assertValidDefinition;const create=async(e,r=[],o=[])=>{if((0,_requireNode.requireNode)("create()"),"__RECORDS"===e)return null;if((0,_storage.fileExists)(`${e}.json`))return null;assertKnownKeyProperties(e,o);const t=(0,_record.record)({path:e,definition:r,keys:o});assertValidDefinition(e,t),await(0,_storage.makeDirectory)(),await(0,_storage.writeJson)(`${e}.json`,t),await(0,_storage.makeDirectory)(e);const i=t.keys.filter((e=>e.lookup));if(i.length){await(0,_storage.makeDirectory)(`__indexes/${e}`);for(const r of i)await(0,_storage.writeJson)(`__indexes/${e}/${r.lookup}`,[])}return await(0,_registerRecord.registerRecord)(e),t};exports.create=create;
|
|
@@ -12,6 +12,7 @@ var _retrieveFile = require("../utilities/retrieveFile");
|
|
|
12
12
|
var _deregisterRecord = require("../utilities/deregisterRecord");
|
|
13
13
|
var _requireNode = require("../utilities/requireNode");
|
|
14
14
|
var _storage = require("../storage");
|
|
15
|
+
var _packedTable = require("../utilities/packedTable");
|
|
15
16
|
const drop = async record => {
|
|
16
17
|
(0, _requireNode.requireNode)('drop()');
|
|
17
18
|
if (record === '__RECORDS') {
|
|
@@ -23,6 +24,7 @@ const drop = async record => {
|
|
|
23
24
|
}
|
|
24
25
|
await (0, _storage.removeDirectory)(recordFile.path);
|
|
25
26
|
await (0, _storage.removeDirectory)(`__indexes/${recordFile.path}`);
|
|
27
|
+
await (0, _packedTable.discardPacked)(recordFile.path);
|
|
26
28
|
await (0, _storage.removeFile)(`${record}.json`);
|
|
27
29
|
await (0, _deregisterRecord.deregisterRecord)(record);
|
|
28
30
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";require("core-js/modules/esnext.async-iterator.constructor.js"),require("core-js/modules/esnext.async-iterator.drop.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.drop.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.drop=void 0;var _retrieveFile=require("../utilities/retrieveFile"),_deregisterRecord=require("../utilities/deregisterRecord"),_requireNode=require("../utilities/requireNode"),_storage=require("../storage");const drop=async e=>{if((0,_requireNode.requireNode)("drop()"),"__RECORDS"===e)return null;const r=await(0,_retrieveFile.retrieveFile)(`${e}.json`);for(const e of r.entries)await(0,_storage.removeFile)(`${r.path}/${e}`);await(0,_storage.removeDirectory)(r.path),await(0,_storage.removeDirectory)(`__indexes/${r.path}`),await(0,_storage.removeFile)(`${e}.json`),await(0,_deregisterRecord.deregisterRecord)(e)};exports.drop=drop;
|
|
1
|
+
"use strict";require("core-js/modules/esnext.async-iterator.constructor.js"),require("core-js/modules/esnext.async-iterator.drop.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.drop.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.drop=void 0;var _retrieveFile=require("../utilities/retrieveFile"),_deregisterRecord=require("../utilities/deregisterRecord"),_requireNode=require("../utilities/requireNode"),_storage=require("../storage"),_packedTable=require("../utilities/packedTable");const drop=async e=>{if((0,_requireNode.requireNode)("drop()"),"__RECORDS"===e)return null;const r=await(0,_retrieveFile.retrieveFile)(`${e}.json`);for(const e of r.entries)await(0,_storage.removeFile)(`${r.path}/${e}`);await(0,_storage.removeDirectory)(r.path),await(0,_storage.removeDirectory)(`__indexes/${r.path}`),await(0,_packedTable.discardPacked)(r.path),await(0,_storage.removeFile)(`${e}.json`),await(0,_deregisterRecord.deregisterRecord)(e)};exports.drop=drop;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type formatResult = {
|
|
2
|
+
files: number;
|
|
3
|
+
rewritten: number;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Rewrite every JSON file of the configured database (records, entries, indexes, `__RECORDS.json`) in one format:
|
|
7
|
+
* 'compact' or 'pretty' (see configure). Files already in that format are left alone, so running it twice changes
|
|
8
|
+
* nothing the second time. Writing in one format never stops the other from being read; this is for making a whole
|
|
9
|
+
* database match, for example after changing `format` on a database written before.
|
|
10
|
+
*
|
|
11
|
+
* Every file is read and checked before any is written: one that isn't valid JSON fails the whole call with its path,
|
|
12
|
+
* and nothing is changed (the public version runs as one statement, see withWriteSet). Node only.
|
|
13
|
+
* @param format - Defaults to the configured format.
|
|
14
|
+
* @throws {Error} When format is neither 'compact' nor 'pretty', when a file isn't valid JSON, or in a browser.
|
|
15
|
+
*/
|
|
16
|
+
export declare const formatDatabase: (format?: "compact" | "pretty") => Promise<formatResult>;
|
|
17
|
+
//# sourceMappingURL=formatDatabase.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.formatDatabase = void 0;
|
|
7
|
+
require("core-js/modules/esnext.iterator.constructor.js");
|
|
8
|
+
require("core-js/modules/esnext.iterator.filter.js");
|
|
9
|
+
var _storage = require("../storage");
|
|
10
|
+
var _config = require("../utilities/config");
|
|
11
|
+
var _requireNode = require("../utilities/requireNode");
|
|
12
|
+
/**
|
|
13
|
+
* Rewrite every JSON file of the configured database (records, entries, indexes, `__RECORDS.json`) in one format:
|
|
14
|
+
* 'compact' or 'pretty' (see configure). Files already in that format are left alone, so running it twice changes
|
|
15
|
+
* nothing the second time. Writing in one format never stops the other from being read; this is for making a whole
|
|
16
|
+
* database match, for example after changing `format` on a database written before.
|
|
17
|
+
*
|
|
18
|
+
* Every file is read and checked before any is written: one that isn't valid JSON fails the whole call with its path,
|
|
19
|
+
* and nothing is changed (the public version runs as one statement, see withWriteSet). Node only.
|
|
20
|
+
* @param format - Defaults to the configured format.
|
|
21
|
+
* @throws {Error} When format is neither 'compact' nor 'pretty', when a file isn't valid JSON, or in a browser.
|
|
22
|
+
*/
|
|
23
|
+
const formatDatabase = async (format = (0, _config.getSetting)('format', 'compact')) => {
|
|
24
|
+
(0, _requireNode.requireNode)('formatDatabase');
|
|
25
|
+
if (format !== 'compact' && format !== 'pretty') {
|
|
26
|
+
throw new Error(`Unknown format "${format}": use 'compact' or 'pretty'`);
|
|
27
|
+
}
|
|
28
|
+
const paths = (await (0, _storage.listFiles)()).filter(path => path.endsWith('.json'));
|
|
29
|
+
const changes = [];
|
|
30
|
+
for (const path of paths) {
|
|
31
|
+
const text = await (0, _storage.readText)(path);
|
|
32
|
+
let content;
|
|
33
|
+
try {
|
|
34
|
+
content = JSON.parse(text);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new Error(`Could not format ${path}: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
if ((0, _storage.jsonText)(content, format) !== text) {
|
|
39
|
+
changes.push({
|
|
40
|
+
path,
|
|
41
|
+
content
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const {
|
|
46
|
+
path,
|
|
47
|
+
content
|
|
48
|
+
} of changes) {
|
|
49
|
+
await (0, _storage.writeJson)(path, content, format);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
files: paths.length,
|
|
53
|
+
rewritten: changes.length
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
exports.formatDatabase = formatDatabase;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.formatDatabase=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js");var _storage=require("../storage"),_config=require("../utilities/config"),_requireNode=require("../utilities/requireNode");const formatDatabase=async(e=(0,_config.getSetting)("format","compact"))=>{if((0,_requireNode.requireNode)("formatDatabase"),"compact"!==e&&"pretty"!==e)throw new Error(`Unknown format "${e}": use 'compact' or 'pretty'`);const t=(await(0,_storage.listFiles)()).filter((e=>e.endsWith(".json"))),r=[];for(const o of t){const t=await(0,_storage.readText)(o);let a;try{a=JSON.parse(t)}catch(e){throw new Error(`Could not format ${o}: ${e.message}`)}(0,_storage.jsonText)(a,e)!==t&&r.push({path:o,content:a})}for(const{path:t,content:o}of r)await(0,_storage.writeJson)(t,o,e);return{files:t.length,rewritten:r.length}};exports.formatDatabase=formatDatabase;
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { create as createRecord } from './create';
|
|
2
2
|
import { drop as dropRecord } from './drop';
|
|
3
3
|
import { describe as describeRecord } from './describe';
|
|
4
|
+
import { formatDatabase as formatAll } from './formatDatabase';
|
|
5
|
+
import { alter as alterRecord } from './alter';
|
|
6
|
+
import { checkPacked as checkAllPacked, packDatabase as packAll } from './packDatabase';
|
|
4
7
|
export declare const create: (...args: Parameters<typeof createRecord>) => ReturnType<typeof createRecord>;
|
|
5
8
|
export declare const drop: (...args: Parameters<typeof dropRecord>) => ReturnType<typeof dropRecord>;
|
|
9
|
+
export declare const alter: (...args: Parameters<typeof alterRecord>) => ReturnType<typeof alterRecord>;
|
|
6
10
|
export declare const describe: (...args: Parameters<typeof describeRecord>) => ReturnType<typeof describeRecord>;
|
|
11
|
+
export declare const formatDatabase: (...args: Parameters<typeof formatAll>) => ReturnType<typeof formatAll>;
|
|
12
|
+
export type { formatResult } from './formatDatabase';
|
|
13
|
+
export declare const packDatabase: () => ReturnType<typeof packAll>;
|
|
14
|
+
export declare const checkPacked: () => ReturnType<typeof checkAllPacked>;
|
|
15
|
+
export type { packResult, packCheck } from './packDatabase';
|
|
7
16
|
export { fieldProperties, fieldDefinition, fieldName } from './definitions/field';
|
|
8
17
|
export { keyProperties, keyDefinition, indexType, indexLocation } from './definitions/key';
|
|
9
18
|
export { referenceProperties, reference } from './definitions/keyReference';
|
|
@@ -7,15 +7,28 @@ require("core-js/modules/esnext.iterator.drop.js");
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", {
|
|
8
8
|
value: true
|
|
9
9
|
});
|
|
10
|
-
exports.drop = exports.describe = exports.create = void 0;
|
|
10
|
+
exports.packDatabase = exports.formatDatabase = exports.drop = exports.describe = exports.create = exports.checkPacked = exports.alter = void 0;
|
|
11
11
|
var _create = require("./create");
|
|
12
12
|
var _drop = require("./drop");
|
|
13
13
|
var _describe = require("./describe");
|
|
14
|
+
var _formatDatabase = require("./formatDatabase");
|
|
15
|
+
var _alter = require("./alter");
|
|
16
|
+
var _packDatabase = require("./packDatabase");
|
|
14
17
|
var _databaseLock = require("../utilities/databaseLock");
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
var _storage = require("../storage");
|
|
19
|
+
// The public versions take the database lock (see withDatabaseLock), and create, drop, alter, formatDatabase and
|
|
20
|
+
// packDatabase change storage all at once or not at all (see withWriteSet); code inside the package imports the files directly
|
|
21
|
+
const create = (...args) => (0, _databaseLock.withDatabaseLock)(() => (0, _storage.withWriteSet)(() => (0, _create.create)(...args)));
|
|
17
22
|
exports.create = create;
|
|
18
|
-
const drop = (...args) => (0, _databaseLock.withDatabaseLock)(() => (0, _drop.drop)(...args));
|
|
23
|
+
const drop = (...args) => (0, _databaseLock.withDatabaseLock)(() => (0, _storage.withWriteSet)(() => (0, _drop.drop)(...args)));
|
|
19
24
|
exports.drop = drop;
|
|
25
|
+
const alter = (...args) => (0, _databaseLock.withDatabaseLock)(() => (0, _storage.withWriteSet)(() => (0, _alter.alter)(...args)));
|
|
26
|
+
exports.alter = alter;
|
|
20
27
|
const describe = (...args) => (0, _databaseLock.withDatabaseLock)(() => (0, _describe.describe)(...args));
|
|
21
|
-
exports.describe = describe;
|
|
28
|
+
exports.describe = describe;
|
|
29
|
+
const formatDatabase = (...args) => (0, _databaseLock.withDatabaseLock)(() => (0, _storage.withWriteSet)(() => (0, _formatDatabase.formatDatabase)(...args)));
|
|
30
|
+
exports.formatDatabase = formatDatabase;
|
|
31
|
+
const packDatabase = () => (0, _databaseLock.withDatabaseLock)(() => (0, _storage.withWriteSet)(() => (0, _packDatabase.packDatabase)()));
|
|
32
|
+
exports.packDatabase = packDatabase;
|
|
33
|
+
const checkPacked = () => (0, _databaseLock.withDatabaseLock)(() => (0, _packDatabase.checkPacked)());
|
|
34
|
+
exports.checkPacked = checkPacked;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";require("core-js/modules/esnext.async-iterator.constructor.js"),require("core-js/modules/esnext.async-iterator.drop.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.drop.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.drop=exports.describe=exports.create=void 0;var _create=require("./create"),_drop=require("./drop"),_describe=require("./describe"),_databaseLock=require("../utilities/databaseLock");const create=(...e)=>(0,_databaseLock.withDatabaseLock)((()=>(0,_create.create)(...e)));exports.create=create;const drop=(...e)=>(0,_databaseLock.withDatabaseLock)((()=>(0,_drop.drop)(...e)));exports.drop=drop;const describe=(...e)=>(0,_databaseLock.withDatabaseLock)((()=>(0,_describe.describe)(...e)));exports.describe=describe;
|
|
1
|
+
"use strict";require("core-js/modules/esnext.async-iterator.constructor.js"),require("core-js/modules/esnext.async-iterator.drop.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.drop.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.packDatabase=exports.formatDatabase=exports.drop=exports.describe=exports.create=exports.checkPacked=exports.alter=void 0;var _create=require("./create"),_drop=require("./drop"),_describe=require("./describe"),_formatDatabase=require("./formatDatabase"),_alter=require("./alter"),_packDatabase=require("./packDatabase"),_databaseLock=require("../utilities/databaseLock"),_storage=require("../storage");const create=(...e)=>(0,_databaseLock.withDatabaseLock)((()=>(0,_storage.withWriteSet)((()=>(0,_create.create)(...e)))));exports.create=create;const drop=(...e)=>(0,_databaseLock.withDatabaseLock)((()=>(0,_storage.withWriteSet)((()=>(0,_drop.drop)(...e)))));exports.drop=drop;const alter=(...e)=>(0,_databaseLock.withDatabaseLock)((()=>(0,_storage.withWriteSet)((()=>(0,_alter.alter)(...e)))));exports.alter=alter;const describe=(...e)=>(0,_databaseLock.withDatabaseLock)((()=>(0,_describe.describe)(...e)));exports.describe=describe;const formatDatabase=(...e)=>(0,_databaseLock.withDatabaseLock)((()=>(0,_storage.withWriteSet)((()=>(0,_formatDatabase.formatDatabase)(...e)))));exports.formatDatabase=formatDatabase;const packDatabase=()=>(0,_databaseLock.withDatabaseLock)((()=>(0,_storage.withWriteSet)((()=>(0,_packDatabase.packDatabase)()))));exports.packDatabase=packDatabase;const checkPacked=()=>(0,_databaseLock.withDatabaseLock)((()=>(0,_packDatabase.checkPacked)()));exports.checkPacked=checkPacked;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type packResult = {
|
|
2
|
+
records: number;
|
|
3
|
+
rows: number;
|
|
4
|
+
};
|
|
5
|
+
export type packCheck = {
|
|
6
|
+
stale: string[];
|
|
7
|
+
orphaned: string[];
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Write every record of the configured database into one packed file (`__packed/<record>.json`, compact JSON): its
|
|
11
|
+
* rows in the record's order. A browser then reads a record in one request instead of one per row when it needs more
|
|
12
|
+
* rows than one round of requests (see packedReads in configure); a static site runs this as a build step, after its data changes. The entry files
|
|
13
|
+
* stay the copy that's edited and reviewed; any insert, update or delete through json-fs-query removes the record's
|
|
14
|
+
* packed file, and reads only use one that matches its record, so a packed file is never read in place of a change
|
|
15
|
+
* made through json-fs-query. Packed files of records that no longer exist are removed.
|
|
16
|
+
*
|
|
17
|
+
* Runs as one statement (the public version, see withWriteSet): a row that can't be read fails it and nothing is
|
|
18
|
+
* written. Node only.
|
|
19
|
+
*/
|
|
20
|
+
export declare const packDatabase: () => Promise<packResult>;
|
|
21
|
+
/**
|
|
22
|
+
* Which records' packed files don't match their entry files (missing, or packed before a change made by
|
|
23
|
+
* hand), and which packed files belong to no record, without changing anything: for a build or test to check that
|
|
24
|
+
* packDatabase has run since the data last changed. Node only.
|
|
25
|
+
*/
|
|
26
|
+
export declare const checkPacked: () => Promise<packCheck>;
|
|
27
|
+
//# sourceMappingURL=packDatabase.d.ts.map
|