@onyx.dev/onyx-database 0.3.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -1
- package/dist/gen/cli/generate.cjs +313 -30
- package/dist/gen/cli/generate.cjs.map +1 -1
- package/dist/index.cjs +329 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +92 -6
- package/dist/index.d.ts +92 -6
- package/dist/index.js +328 -37
- package/dist/index.js.map +1 -1
- package/dist/schema/cli/schema.cjs +496 -35
- package/dist/schema/cli/schema.cjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,22 @@ npm i @onyx.dev/onyx-database
|
|
|
42
42
|
|
|
43
43
|
The package is dual-module (ESM + CJS) and has **no runtime or peer dependencies**.
|
|
44
44
|
|
|
45
|
+
To use the bundled CLIs (`onyx-gen` and `onyx-schema`) globally:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install -g @onyx.dev/onyx-database
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
To install the CLI globally from this repo checkout (useful for local development and testing):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# run from the repo root
|
|
55
|
+
npm install
|
|
56
|
+
npm run build
|
|
57
|
+
npm uninstall -g @onyx.dev/onyx-database # optional: clear older global versions
|
|
58
|
+
npm install -g . # installs the built onyx-schema and onyx-gen
|
|
59
|
+
```
|
|
60
|
+
|
|
45
61
|
---
|
|
46
62
|
|
|
47
63
|
## Initialize the client
|
|
@@ -148,6 +164,9 @@ onyx-schema publish
|
|
|
148
164
|
# Overwrite ./onyx.schema.json with the remote schema
|
|
149
165
|
onyx-schema get
|
|
150
166
|
|
|
167
|
+
# Print the remote schema without writing a file
|
|
168
|
+
onyx-schema get --print
|
|
169
|
+
|
|
151
170
|
# Fetch only selected tables (prints to stdout; does not overwrite files)
|
|
152
171
|
onyx-schema get --tables=User,Profile
|
|
153
172
|
|
|
@@ -174,6 +193,10 @@ onyx-schema get --tables=User,Profile
|
|
|
174
193
|
|
|
175
194
|
# Validate a schema file without publishing
|
|
176
195
|
onyx-schema validate ./onyx.schema.json
|
|
196
|
+
|
|
197
|
+
# Diff local schema vs API
|
|
198
|
+
onyx-schema diff ./onyx.schema.json
|
|
199
|
+
# Prints YAML with added/removed/changed tables and attribute differences between the API schema and your local file.
|
|
177
200
|
```
|
|
178
201
|
|
|
179
202
|
When `--tables` is provided, the subset is printed to stdout instead of writing a
|
|
@@ -190,6 +213,16 @@ npm run schema:publish # validate then publish the local schema
|
|
|
190
213
|
The CLI reuses the same configuration resolution as `onyx.init()` (env vars,
|
|
191
214
|
project config, and home profile files).
|
|
192
215
|
|
|
216
|
+
Programmatic diffing is also available:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { onyx } from '@onyx.dev/onyx-database';
|
|
220
|
+
|
|
221
|
+
const db = onyx.init();
|
|
222
|
+
const diff = await db.diffSchema(localSchema); // SchemaUpsertRequest
|
|
223
|
+
console.log(diff.changedTables);
|
|
224
|
+
```
|
|
225
|
+
|
|
193
226
|
You can also emit to multiple paths in one run (comma-separated or by repeating `--out`):
|
|
194
227
|
|
|
195
228
|
```bash
|
|
@@ -322,7 +355,9 @@ Importable helpers for conditions and sort:
|
|
|
322
355
|
|
|
323
356
|
```ts
|
|
324
357
|
import {
|
|
325
|
-
eq, neq,
|
|
358
|
+
eq, neq, within, notWithin, // preferred aliases for IN/NOT IN
|
|
359
|
+
inOp, notIn,
|
|
360
|
+
between,
|
|
326
361
|
gt, gte, lt, lte,
|
|
327
362
|
like, notLike, contains, notContains,
|
|
328
363
|
startsWith, notStartsWith, matches, notMatches,
|
|
@@ -331,6 +366,41 @@ import {
|
|
|
331
366
|
} from '@onyx.dev/onyx-database';
|
|
332
367
|
```
|
|
333
368
|
|
|
369
|
+
- Prefer `within`/`notWithin` for inclusion checks (supports arrays, comma-separated strings, or inner queries).
|
|
370
|
+
- `inOp`/`notIn` remain available for backward compatibility and are exact aliases.
|
|
371
|
+
|
|
372
|
+
### Inner queries (IN/NOT IN with sub-selects)
|
|
373
|
+
|
|
374
|
+
You can pass another query builder to `within` or `notWithin` to create nested filters. The SDK serializes the inner query (including its table) before sending the request.
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
import { onyx, within, notWithin, eq, tables, Schema } from '@onyx.dev/onyx-database';
|
|
378
|
+
|
|
379
|
+
const db = onyx.init<Schema>();
|
|
380
|
+
|
|
381
|
+
// Users that HAVE the admin role
|
|
382
|
+
const usersWithAdmin = await db
|
|
383
|
+
.from(tables.User)
|
|
384
|
+
.where(
|
|
385
|
+
within(
|
|
386
|
+
'id',
|
|
387
|
+
db.select('userId').from(tables.UserRole).where(eq('roleId', 'role-admin')),
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
.list();
|
|
391
|
+
|
|
392
|
+
// Roles that DO NOT include a specific permission
|
|
393
|
+
const rolesMissingPermission = await db
|
|
394
|
+
.from(tables.Role)
|
|
395
|
+
.where(
|
|
396
|
+
notWithin(
|
|
397
|
+
'id',
|
|
398
|
+
db.from(tables.RolePermission).where(eq('permissionId', 'perm-manage-users')),
|
|
399
|
+
),
|
|
400
|
+
)
|
|
401
|
+
.list();
|
|
402
|
+
```
|
|
403
|
+
|
|
334
404
|
---
|
|
335
405
|
|
|
336
406
|
## Usage examples with `User`, `Role`, `Permission`
|
|
@@ -76,7 +76,7 @@ function readEnv(targetId) {
|
|
|
76
76
|
return res;
|
|
77
77
|
}
|
|
78
78
|
async function readProjectFile(databaseId) {
|
|
79
|
-
if (!isNode) return {};
|
|
79
|
+
if (!isNode) return { config: {} };
|
|
80
80
|
const fs = await nodeImport("node:fs/promises");
|
|
81
81
|
const path = await nodeImport("node:path");
|
|
82
82
|
const cwd = gProcess?.cwd?.() ?? ".";
|
|
@@ -85,7 +85,7 @@ async function readProjectFile(databaseId) {
|
|
|
85
85
|
const sanitized = txt.replace(/[\r\n]+/g, "");
|
|
86
86
|
const json = dropUndefined(JSON.parse(sanitized));
|
|
87
87
|
dbg("project file:", p, "\u2192", mask(json));
|
|
88
|
-
return json;
|
|
88
|
+
return { config: json, path: p };
|
|
89
89
|
};
|
|
90
90
|
if (databaseId) {
|
|
91
91
|
const specific = path.resolve(cwd, `onyx-database-${databaseId}.json`);
|
|
@@ -100,11 +100,11 @@ async function readProjectFile(databaseId) {
|
|
|
100
100
|
return await tryRead(fallback);
|
|
101
101
|
} catch {
|
|
102
102
|
dbg("project file not found:", fallback);
|
|
103
|
-
return {};
|
|
103
|
+
return { config: {} };
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
async function readHomeProfile(databaseId) {
|
|
107
|
-
if (!isNode) return {};
|
|
107
|
+
if (!isNode) return { config: {} };
|
|
108
108
|
const fs = await nodeImport("node:fs/promises");
|
|
109
109
|
const os = await nodeImport("node:os");
|
|
110
110
|
const path = await nodeImport("node:path");
|
|
@@ -124,7 +124,7 @@ async function readHomeProfile(databaseId) {
|
|
|
124
124
|
const sanitized = txt.replace(/[\r\n]+/g, "");
|
|
125
125
|
const json = dropUndefined(JSON.parse(sanitized));
|
|
126
126
|
dbg("home profile used:", p, "\u2192", mask(json));
|
|
127
|
-
return json;
|
|
127
|
+
return { config: json, path: p };
|
|
128
128
|
} catch (e) {
|
|
129
129
|
const msg = e instanceof Error ? e.message : String(e);
|
|
130
130
|
throw new OnyxConfigError(`Failed to read ${p}: ${msg}`);
|
|
@@ -143,7 +143,7 @@ async function readHomeProfile(databaseId) {
|
|
|
143
143
|
dbg("no home-root fallback:", defaultInHome);
|
|
144
144
|
if (!await fileExists(dir)) {
|
|
145
145
|
dbg("~/.onyx does not exist:", dir);
|
|
146
|
-
return {};
|
|
146
|
+
return { config: {} };
|
|
147
147
|
}
|
|
148
148
|
const files = await fs.readdir(dir).catch(() => []);
|
|
149
149
|
const matches = files.filter((f) => f.startsWith("onyx-database-") && f.endsWith(".json"));
|
|
@@ -157,10 +157,10 @@ async function readHomeProfile(databaseId) {
|
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
159
|
dbg("no usable home profiles found in", dir);
|
|
160
|
-
return {};
|
|
160
|
+
return { config: {} };
|
|
161
161
|
}
|
|
162
162
|
async function readConfigPath(p) {
|
|
163
|
-
if (!isNode) return {};
|
|
163
|
+
if (!isNode) return { config: {} };
|
|
164
164
|
const fs = await nodeImport("node:fs/promises");
|
|
165
165
|
const path = await nodeImport("node:path");
|
|
166
166
|
const cwd = gProcess?.cwd?.() ?? ".";
|
|
@@ -170,7 +170,7 @@ async function readConfigPath(p) {
|
|
|
170
170
|
const sanitized = txt.replace(/[\r\n]+/g, "");
|
|
171
171
|
const json = dropUndefined(JSON.parse(sanitized));
|
|
172
172
|
dbg("config path:", resolved, "\u2192", mask(json));
|
|
173
|
-
return json;
|
|
173
|
+
return { config: json, path: resolved };
|
|
174
174
|
} catch (e) {
|
|
175
175
|
const msg = e instanceof Error ? e.message : String(e);
|
|
176
176
|
throw new OnyxConfigError(`Failed to read ${resolved}: ${msg}`);
|
|
@@ -181,7 +181,8 @@ async function resolveConfig(input) {
|
|
|
181
181
|
const env = readEnv(input?.databaseId);
|
|
182
182
|
let cfgPath = {};
|
|
183
183
|
if (configPath) {
|
|
184
|
-
|
|
184
|
+
const cfgRes = await readConfigPath(configPath);
|
|
185
|
+
cfgPath = cfgRes.config;
|
|
185
186
|
}
|
|
186
187
|
const targetId = input?.databaseId ?? env.databaseId ?? cfgPath.databaseId;
|
|
187
188
|
let haveDbId = !!(input?.databaseId ?? env.databaseId ?? cfgPath.databaseId);
|
|
@@ -189,14 +190,16 @@ async function resolveConfig(input) {
|
|
|
189
190
|
let haveApiSecret = !!(input?.apiSecret ?? env.apiSecret ?? cfgPath.apiSecret);
|
|
190
191
|
let project = {};
|
|
191
192
|
if (!(haveDbId && haveApiKey && haveApiSecret)) {
|
|
192
|
-
|
|
193
|
+
const projRes = await readProjectFile(targetId);
|
|
194
|
+
project = projRes.config;
|
|
193
195
|
if (project.databaseId) haveDbId = true;
|
|
194
196
|
if (project.apiKey) haveApiKey = true;
|
|
195
197
|
if (project.apiSecret) haveApiSecret = true;
|
|
196
198
|
}
|
|
197
199
|
let home = {};
|
|
198
200
|
if (!(haveDbId && haveApiKey && haveApiSecret)) {
|
|
199
|
-
|
|
201
|
+
const homeRes = await readHomeProfile(targetId);
|
|
202
|
+
home = homeRes.config;
|
|
200
203
|
}
|
|
201
204
|
const merged = {
|
|
202
205
|
baseUrl: DEFAULT_BASE_URL,
|
|
@@ -931,6 +934,56 @@ var QueryResults = class extends Array {
|
|
|
931
934
|
}
|
|
932
935
|
};
|
|
933
936
|
|
|
937
|
+
// src/helpers/condition-normalizer.ts
|
|
938
|
+
function isQueryBuilderLike(value) {
|
|
939
|
+
return !!value && typeof value.toSerializableQueryObject === "function";
|
|
940
|
+
}
|
|
941
|
+
function normalizeCriteriaValue(value) {
|
|
942
|
+
if (Array.isArray(value)) {
|
|
943
|
+
let changed = false;
|
|
944
|
+
const normalized = value.map((item) => {
|
|
945
|
+
const result = normalizeCriteriaValue(item);
|
|
946
|
+
if (result.changed) changed = true;
|
|
947
|
+
return result.value;
|
|
948
|
+
});
|
|
949
|
+
if (!changed) {
|
|
950
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
951
|
+
if (normalized[i] !== value[i]) {
|
|
952
|
+
changed = true;
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return { value: changed ? normalized : value, changed };
|
|
958
|
+
}
|
|
959
|
+
if (isQueryBuilderLike(value)) {
|
|
960
|
+
return { value: value.toSerializableQueryObject(), changed: true };
|
|
961
|
+
}
|
|
962
|
+
return { value, changed: false };
|
|
963
|
+
}
|
|
964
|
+
function normalizeConditionInternal(condition) {
|
|
965
|
+
if (condition.conditionType === "SingleCondition") {
|
|
966
|
+
const { value, changed: changed2 } = normalizeCriteriaValue(condition.criteria.value);
|
|
967
|
+
if (!changed2) return condition;
|
|
968
|
+
return {
|
|
969
|
+
...condition,
|
|
970
|
+
criteria: { ...condition.criteria, value }
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
let changed = false;
|
|
974
|
+
const normalizedConditions = condition.conditions.map((child) => {
|
|
975
|
+
const normalized = normalizeConditionInternal(child);
|
|
976
|
+
if (normalized !== child) changed = true;
|
|
977
|
+
return normalized;
|
|
978
|
+
});
|
|
979
|
+
if (!changed) return condition;
|
|
980
|
+
return { ...condition, conditions: normalizedConditions };
|
|
981
|
+
}
|
|
982
|
+
function normalizeCondition(condition) {
|
|
983
|
+
if (!condition) return condition;
|
|
984
|
+
return normalizeConditionInternal(condition);
|
|
985
|
+
}
|
|
986
|
+
|
|
934
987
|
// src/builders/cascade-relationship-builder.ts
|
|
935
988
|
var CascadeRelationshipBuilder = class {
|
|
936
989
|
graphName;
|
|
@@ -1005,6 +1058,194 @@ var OnyxError = class extends Error {
|
|
|
1005
1058
|
}
|
|
1006
1059
|
};
|
|
1007
1060
|
|
|
1061
|
+
// src/helpers/schema-diff.ts
|
|
1062
|
+
function mapByName(items) {
|
|
1063
|
+
const map = /* @__PURE__ */ new Map();
|
|
1064
|
+
for (const item of items ?? []) {
|
|
1065
|
+
if (!item?.name) continue;
|
|
1066
|
+
map.set(item.name, item);
|
|
1067
|
+
}
|
|
1068
|
+
return map;
|
|
1069
|
+
}
|
|
1070
|
+
function normalizeEntities(schema) {
|
|
1071
|
+
if (Array.isArray(schema.entities)) {
|
|
1072
|
+
return schema.entities ?? [];
|
|
1073
|
+
}
|
|
1074
|
+
const tables = schema.tables;
|
|
1075
|
+
if (!Array.isArray(tables)) return [];
|
|
1076
|
+
return tables.map((table) => ({
|
|
1077
|
+
name: table.name,
|
|
1078
|
+
attributes: table.attributes ?? []
|
|
1079
|
+
}));
|
|
1080
|
+
}
|
|
1081
|
+
function normalizePartition(partition) {
|
|
1082
|
+
if (partition == null) return "";
|
|
1083
|
+
const trimmed = partition.trim();
|
|
1084
|
+
return trimmed;
|
|
1085
|
+
}
|
|
1086
|
+
function identifiersEqual(a, b) {
|
|
1087
|
+
if (!a && !b) return true;
|
|
1088
|
+
if (!a || !b) return false;
|
|
1089
|
+
return a.name === b.name && a.generator === b.generator && a.type === b.type;
|
|
1090
|
+
}
|
|
1091
|
+
function diffAttributes(apiAttrs, localAttrs) {
|
|
1092
|
+
const apiMap = mapByName(apiAttrs);
|
|
1093
|
+
const localMap = mapByName(localAttrs);
|
|
1094
|
+
const added = [];
|
|
1095
|
+
const removed = [];
|
|
1096
|
+
const changed = [];
|
|
1097
|
+
for (const [name, local] of localMap.entries()) {
|
|
1098
|
+
if (!apiMap.has(name)) {
|
|
1099
|
+
added.push(local);
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
const api = apiMap.get(name);
|
|
1103
|
+
const apiNull = Boolean(api.isNullable);
|
|
1104
|
+
const localNull = Boolean(local.isNullable);
|
|
1105
|
+
if (api.type !== local.type || apiNull !== localNull) {
|
|
1106
|
+
changed.push({
|
|
1107
|
+
name,
|
|
1108
|
+
from: { type: api.type, isNullable: apiNull },
|
|
1109
|
+
to: { type: local.type, isNullable: localNull }
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
for (const name of apiMap.keys()) {
|
|
1114
|
+
if (!localMap.has(name)) removed.push(name);
|
|
1115
|
+
}
|
|
1116
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
1117
|
+
removed.sort();
|
|
1118
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
1119
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
1120
|
+
return { added, removed, changed };
|
|
1121
|
+
}
|
|
1122
|
+
function diffIndexes(apiIndexes, localIndexes) {
|
|
1123
|
+
const apiMap = mapByName(apiIndexes);
|
|
1124
|
+
const localMap = mapByName(localIndexes);
|
|
1125
|
+
const added = [];
|
|
1126
|
+
const removed = [];
|
|
1127
|
+
const changed = [];
|
|
1128
|
+
for (const [name, local] of localMap.entries()) {
|
|
1129
|
+
if (!apiMap.has(name)) {
|
|
1130
|
+
added.push(local);
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1133
|
+
const api = apiMap.get(name);
|
|
1134
|
+
const apiType = api.type ?? "DEFAULT";
|
|
1135
|
+
const localType = local.type ?? "DEFAULT";
|
|
1136
|
+
const apiScore = api.minimumScore;
|
|
1137
|
+
const localScore = local.minimumScore;
|
|
1138
|
+
if (apiType !== localType || apiScore !== localScore) {
|
|
1139
|
+
changed.push({ name, from: api, to: local });
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
for (const name of apiMap.keys()) {
|
|
1143
|
+
if (!localMap.has(name)) removed.push(name);
|
|
1144
|
+
}
|
|
1145
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
1146
|
+
removed.sort();
|
|
1147
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
1148
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
1149
|
+
return { added, removed, changed };
|
|
1150
|
+
}
|
|
1151
|
+
function diffResolvers(apiResolvers, localResolvers) {
|
|
1152
|
+
const apiMap = mapByName(apiResolvers);
|
|
1153
|
+
const localMap = mapByName(localResolvers);
|
|
1154
|
+
const added = [];
|
|
1155
|
+
const removed = [];
|
|
1156
|
+
const changed = [];
|
|
1157
|
+
for (const [name, local] of localMap.entries()) {
|
|
1158
|
+
if (!apiMap.has(name)) {
|
|
1159
|
+
added.push(local);
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
const api = apiMap.get(name);
|
|
1163
|
+
if (api.resolver !== local.resolver) {
|
|
1164
|
+
changed.push({ name, from: api, to: local });
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
for (const name of apiMap.keys()) {
|
|
1168
|
+
if (!localMap.has(name)) removed.push(name);
|
|
1169
|
+
}
|
|
1170
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
1171
|
+
removed.sort();
|
|
1172
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
1173
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
1174
|
+
return { added, removed, changed };
|
|
1175
|
+
}
|
|
1176
|
+
function diffTriggers(apiTriggers, localTriggers) {
|
|
1177
|
+
const apiMap = mapByName(apiTriggers);
|
|
1178
|
+
const localMap = mapByName(localTriggers);
|
|
1179
|
+
const added = [];
|
|
1180
|
+
const removed = [];
|
|
1181
|
+
const changed = [];
|
|
1182
|
+
for (const [name, local] of localMap.entries()) {
|
|
1183
|
+
if (!apiMap.has(name)) {
|
|
1184
|
+
added.push(local);
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
const api = apiMap.get(name);
|
|
1188
|
+
if (api.event !== local.event || api.trigger !== local.trigger) {
|
|
1189
|
+
changed.push({ name, from: api, to: local });
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
for (const name of apiMap.keys()) {
|
|
1193
|
+
if (!localMap.has(name)) removed.push(name);
|
|
1194
|
+
}
|
|
1195
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
1196
|
+
removed.sort();
|
|
1197
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
1198
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
1199
|
+
return { added, removed, changed };
|
|
1200
|
+
}
|
|
1201
|
+
function computeSchemaDiff(apiSchema, localSchema) {
|
|
1202
|
+
const apiEntities = normalizeEntities(apiSchema);
|
|
1203
|
+
const localEntities = normalizeEntities(localSchema);
|
|
1204
|
+
const apiMap = mapByName(apiEntities);
|
|
1205
|
+
const localMap = mapByName(localEntities);
|
|
1206
|
+
const newTables = [];
|
|
1207
|
+
const removedTables = [];
|
|
1208
|
+
const changedTables = [];
|
|
1209
|
+
for (const [name, localEntity] of localMap.entries()) {
|
|
1210
|
+
if (!apiMap.has(name)) {
|
|
1211
|
+
newTables.push(name);
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
const apiEntity = apiMap.get(name);
|
|
1215
|
+
const tableDiff = { name };
|
|
1216
|
+
const partitionFrom = normalizePartition(apiEntity.partition);
|
|
1217
|
+
const partitionTo = normalizePartition(localEntity.partition);
|
|
1218
|
+
if (partitionFrom !== partitionTo) {
|
|
1219
|
+
tableDiff.partition = { from: partitionFrom || null, to: partitionTo || null };
|
|
1220
|
+
}
|
|
1221
|
+
if (!identifiersEqual(apiEntity.identifier, localEntity.identifier)) {
|
|
1222
|
+
tableDiff.identifier = {
|
|
1223
|
+
from: apiEntity.identifier ?? null,
|
|
1224
|
+
to: localEntity.identifier ?? null
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
const attrs = diffAttributes(apiEntity.attributes, localEntity.attributes);
|
|
1228
|
+
if (attrs) tableDiff.attributes = attrs;
|
|
1229
|
+
const indexes = diffIndexes(apiEntity.indexes, localEntity.indexes);
|
|
1230
|
+
if (indexes) tableDiff.indexes = indexes;
|
|
1231
|
+
const resolvers = diffResolvers(apiEntity.resolvers, localEntity.resolvers);
|
|
1232
|
+
if (resolvers) tableDiff.resolvers = resolvers;
|
|
1233
|
+
const triggers = diffTriggers(apiEntity.triggers, localEntity.triggers);
|
|
1234
|
+
if (triggers) tableDiff.triggers = triggers;
|
|
1235
|
+
const hasChange = tableDiff.partition || tableDiff.identifier || tableDiff.attributes || tableDiff.indexes || tableDiff.resolvers || tableDiff.triggers;
|
|
1236
|
+
if (hasChange) {
|
|
1237
|
+
changedTables.push(tableDiff);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
for (const name of apiMap.keys()) {
|
|
1241
|
+
if (!localMap.has(name)) removedTables.push(name);
|
|
1242
|
+
}
|
|
1243
|
+
newTables.sort();
|
|
1244
|
+
removedTables.sort();
|
|
1245
|
+
changedTables.sort((a, b) => a.name.localeCompare(b.name));
|
|
1246
|
+
return { newTables, removedTables, changedTables };
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1008
1249
|
// src/impl/onyx.ts
|
|
1009
1250
|
var DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
|
|
1010
1251
|
var cachedCfg = null;
|
|
@@ -1058,6 +1299,10 @@ function serializeDates(value) {
|
|
|
1058
1299
|
}
|
|
1059
1300
|
return value;
|
|
1060
1301
|
}
|
|
1302
|
+
function stripEntityText(input) {
|
|
1303
|
+
const { entityText, ...rest } = input;
|
|
1304
|
+
return rest;
|
|
1305
|
+
}
|
|
1061
1306
|
function normalizeSecretMetadata(input) {
|
|
1062
1307
|
return { ...input, updatedAt: new Date(input.updatedAt) };
|
|
1063
1308
|
}
|
|
@@ -1071,7 +1316,20 @@ function normalizeDate(value) {
|
|
|
1071
1316
|
return Number.isNaN(ts.getTime()) ? void 0 : ts;
|
|
1072
1317
|
}
|
|
1073
1318
|
function normalizeSchemaRevision(input, fallbackDatabaseId) {
|
|
1074
|
-
const {
|
|
1319
|
+
const {
|
|
1320
|
+
meta,
|
|
1321
|
+
createdAt,
|
|
1322
|
+
publishedAt,
|
|
1323
|
+
revisionId,
|
|
1324
|
+
entityText,
|
|
1325
|
+
databaseId,
|
|
1326
|
+
entities,
|
|
1327
|
+
revisionDescription,
|
|
1328
|
+
...rest
|
|
1329
|
+
} = input;
|
|
1330
|
+
const dbId = typeof databaseId === "string" ? databaseId : fallbackDatabaseId;
|
|
1331
|
+
const entityList = Array.isArray(entities) ? entities : [];
|
|
1332
|
+
const revisionDesc = typeof revisionDescription === "string" ? revisionDescription : void 0;
|
|
1075
1333
|
const mergedMeta = {
|
|
1076
1334
|
revisionId: meta?.revisionId ?? revisionId,
|
|
1077
1335
|
createdAt: normalizeDate(meta?.createdAt ?? createdAt),
|
|
@@ -1079,10 +1337,11 @@ function normalizeSchemaRevision(input, fallbackDatabaseId) {
|
|
|
1079
1337
|
};
|
|
1080
1338
|
const cleanedMeta = mergedMeta.revisionId || mergedMeta.createdAt || mergedMeta.publishedAt ? mergedMeta : void 0;
|
|
1081
1339
|
return {
|
|
1082
|
-
|
|
1083
|
-
|
|
1340
|
+
databaseId: dbId,
|
|
1341
|
+
revisionDescription: revisionDesc,
|
|
1342
|
+
entities: entityList,
|
|
1084
1343
|
meta: cleanedMeta,
|
|
1085
|
-
|
|
1344
|
+
...rest
|
|
1086
1345
|
};
|
|
1087
1346
|
}
|
|
1088
1347
|
var OnyxDatabaseImpl = class {
|
|
@@ -1194,7 +1453,8 @@ var OnyxDatabaseImpl = class {
|
|
|
1194
1453
|
const path = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(
|
|
1195
1454
|
table
|
|
1196
1455
|
)}/${encodeURIComponent(primaryKey)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1197
|
-
|
|
1456
|
+
await http.request("DELETE", path);
|
|
1457
|
+
return true;
|
|
1198
1458
|
}
|
|
1199
1459
|
async saveDocument(doc) {
|
|
1200
1460
|
const { http, databaseId } = await this.ensureClient();
|
|
@@ -1237,20 +1497,32 @@ var OnyxDatabaseImpl = class {
|
|
|
1237
1497
|
const res = await http.request("GET", path);
|
|
1238
1498
|
return Array.isArray(res) ? res.map((entry) => normalizeSchemaRevision(entry, databaseId)) : [];
|
|
1239
1499
|
}
|
|
1500
|
+
async diffSchema(localSchema) {
|
|
1501
|
+
const apiSchema = await this.getSchema();
|
|
1502
|
+
return computeSchemaDiff(apiSchema, localSchema);
|
|
1503
|
+
}
|
|
1240
1504
|
async updateSchema(schema, options) {
|
|
1241
1505
|
const { http, databaseId } = await this.ensureClient();
|
|
1242
1506
|
const params = new URLSearchParams();
|
|
1243
1507
|
if (options?.publish) params.append("publish", "true");
|
|
1244
1508
|
const path = `/schemas/${encodeURIComponent(databaseId)}${params.size ? `?${params.toString()}` : ""}`;
|
|
1245
|
-
const body = { ...schema, databaseId: schema.databaseId ?? databaseId };
|
|
1246
|
-
const res = await http.request(
|
|
1509
|
+
const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
|
|
1510
|
+
const res = await http.request(
|
|
1511
|
+
"PUT",
|
|
1512
|
+
path,
|
|
1513
|
+
serializeDates(body)
|
|
1514
|
+
);
|
|
1247
1515
|
return normalizeSchemaRevision(res, databaseId);
|
|
1248
1516
|
}
|
|
1249
1517
|
async validateSchema(schema) {
|
|
1250
1518
|
const { http, databaseId } = await this.ensureClient();
|
|
1251
1519
|
const path = `/schemas/${encodeURIComponent(databaseId)}/validate`;
|
|
1252
|
-
const body = { ...schema, databaseId: schema.databaseId ?? databaseId };
|
|
1253
|
-
const res = await http.request(
|
|
1520
|
+
const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
|
|
1521
|
+
const res = await http.request(
|
|
1522
|
+
"POST",
|
|
1523
|
+
path,
|
|
1524
|
+
serializeDates(body)
|
|
1525
|
+
);
|
|
1254
1526
|
const normalizedSchema = res.schema ? normalizeSchemaRevision(res.schema, databaseId) : void 0;
|
|
1255
1527
|
return {
|
|
1256
1528
|
...res,
|
|
@@ -1412,11 +1684,14 @@ var QueryBuilderImpl = class {
|
|
|
1412
1684
|
if (!this.table) throw new Error("Table is not defined. Call from(<table>) first.");
|
|
1413
1685
|
return this.table;
|
|
1414
1686
|
}
|
|
1687
|
+
serializableConditions() {
|
|
1688
|
+
return normalizeCondition(this.conditions);
|
|
1689
|
+
}
|
|
1415
1690
|
toSelectQuery() {
|
|
1416
1691
|
return {
|
|
1417
1692
|
type: "SelectQuery",
|
|
1418
1693
|
fields: this.fields,
|
|
1419
|
-
conditions: this.
|
|
1694
|
+
conditions: this.serializableConditions(),
|
|
1420
1695
|
sort: this.sort,
|
|
1421
1696
|
limit: this.limitValue,
|
|
1422
1697
|
distinct: this.distinctValue,
|
|
@@ -1425,6 +1700,21 @@ var QueryBuilderImpl = class {
|
|
|
1425
1700
|
resolvers: this.resolvers
|
|
1426
1701
|
};
|
|
1427
1702
|
}
|
|
1703
|
+
toUpdateQuery() {
|
|
1704
|
+
return {
|
|
1705
|
+
type: "UpdateQuery",
|
|
1706
|
+
conditions: this.serializableConditions(),
|
|
1707
|
+
updates: this.updates ?? {},
|
|
1708
|
+
sort: this.sort,
|
|
1709
|
+
limit: this.limitValue,
|
|
1710
|
+
partition: this.partitionValue ?? null
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
toSerializableQueryObject() {
|
|
1714
|
+
const table = this.ensureTable();
|
|
1715
|
+
const payload = this.mode === "update" ? this.toUpdateQuery() : this.toSelectQuery();
|
|
1716
|
+
return { ...payload, table };
|
|
1717
|
+
}
|
|
1428
1718
|
from(table) {
|
|
1429
1719
|
this.table = table;
|
|
1430
1720
|
return this;
|
|
@@ -1560,14 +1850,7 @@ var QueryBuilderImpl = class {
|
|
|
1560
1850
|
async update() {
|
|
1561
1851
|
if (this.mode !== "update") throw new Error("Call setUpdates(...) before update().");
|
|
1562
1852
|
const table = this.ensureTable();
|
|
1563
|
-
const update =
|
|
1564
|
-
type: "UpdateQuery",
|
|
1565
|
-
conditions: this.conditions,
|
|
1566
|
-
updates: this.updates ?? {},
|
|
1567
|
-
sort: this.sort,
|
|
1568
|
-
limit: this.limitValue,
|
|
1569
|
-
partition: this.partitionValue ?? null
|
|
1570
|
-
};
|
|
1853
|
+
const update = this.toUpdateQuery();
|
|
1571
1854
|
return this.db._update(table, update, this.partitionValue);
|
|
1572
1855
|
}
|
|
1573
1856
|
onItemAdded(listener) {
|