@onyx.dev/onyx-database 1.0.0 → 1.0.3
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 +134 -2
- package/dist/aggregates-BGXzij4U.d.cts +1424 -0
- package/dist/aggregates-BGXzij4U.d.ts +1424 -0
- package/dist/edge.cjs +1954 -0
- package/dist/edge.cjs.map +1 -0
- package/dist/edge.d.cts +9 -0
- package/dist/edge.d.ts +9 -0
- package/dist/edge.js +1911 -0
- package/dist/edge.js.map +1 -0
- package/dist/gen/cli/generate.cjs +303 -56
- package/dist/gen/cli/generate.cjs.map +1 -1
- package/dist/index.cjs +303 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1325
- package/dist/index.d.ts +3 -1325
- package/dist/index.js +303 -55
- package/dist/index.js.map +1 -1
- package/dist/schema/cli/schema.cjs +473 -267
- package/dist/schema/cli/schema.cjs.map +1 -1
- package/package.json +22 -5
|
@@ -78,7 +78,7 @@ function readEnv(targetId) {
|
|
|
78
78
|
return res;
|
|
79
79
|
}
|
|
80
80
|
async function readProjectFile(databaseId) {
|
|
81
|
-
if (!isNode) return {};
|
|
81
|
+
if (!isNode) return { config: {} };
|
|
82
82
|
const fs = await nodeImport("node:fs/promises");
|
|
83
83
|
const path2 = await nodeImport("node:path");
|
|
84
84
|
const cwd = gProcess?.cwd?.() ?? ".";
|
|
@@ -87,7 +87,7 @@ async function readProjectFile(databaseId) {
|
|
|
87
87
|
const sanitized = txt.replace(/[\r\n]+/g, "");
|
|
88
88
|
const json = dropUndefined(JSON.parse(sanitized));
|
|
89
89
|
dbg("project file:", p, "\u2192", mask(json));
|
|
90
|
-
return json;
|
|
90
|
+
return { config: json, path: p };
|
|
91
91
|
};
|
|
92
92
|
if (databaseId) {
|
|
93
93
|
const specific = path2.resolve(cwd, `onyx-database-${databaseId}.json`);
|
|
@@ -102,11 +102,11 @@ async function readProjectFile(databaseId) {
|
|
|
102
102
|
return await tryRead(fallback);
|
|
103
103
|
} catch {
|
|
104
104
|
dbg("project file not found:", fallback);
|
|
105
|
-
return {};
|
|
105
|
+
return { config: {} };
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
async function readHomeProfile(databaseId) {
|
|
109
|
-
if (!isNode) return {};
|
|
109
|
+
if (!isNode) return { config: {} };
|
|
110
110
|
const fs = await nodeImport("node:fs/promises");
|
|
111
111
|
const os = await nodeImport("node:os");
|
|
112
112
|
const path2 = await nodeImport("node:path");
|
|
@@ -126,7 +126,7 @@ async function readHomeProfile(databaseId) {
|
|
|
126
126
|
const sanitized = txt.replace(/[\r\n]+/g, "");
|
|
127
127
|
const json = dropUndefined(JSON.parse(sanitized));
|
|
128
128
|
dbg("home profile used:", p, "\u2192", mask(json));
|
|
129
|
-
return json;
|
|
129
|
+
return { config: json, path: p };
|
|
130
130
|
} catch (e) {
|
|
131
131
|
const msg = e instanceof Error ? e.message : String(e);
|
|
132
132
|
throw new OnyxConfigError(`Failed to read ${p}: ${msg}`);
|
|
@@ -145,7 +145,7 @@ async function readHomeProfile(databaseId) {
|
|
|
145
145
|
dbg("no home-root fallback:", defaultInHome);
|
|
146
146
|
if (!await fileExists(dir)) {
|
|
147
147
|
dbg("~/.onyx does not exist:", dir);
|
|
148
|
-
return {};
|
|
148
|
+
return { config: {} };
|
|
149
149
|
}
|
|
150
150
|
const files = await fs.readdir(dir).catch(() => []);
|
|
151
151
|
const matches = files.filter((f) => f.startsWith("onyx-database-") && f.endsWith(".json"));
|
|
@@ -159,10 +159,10 @@ async function readHomeProfile(databaseId) {
|
|
|
159
159
|
);
|
|
160
160
|
}
|
|
161
161
|
dbg("no usable home profiles found in", dir);
|
|
162
|
-
return {};
|
|
162
|
+
return { config: {} };
|
|
163
163
|
}
|
|
164
164
|
async function readConfigPath(p) {
|
|
165
|
-
if (!isNode) return {};
|
|
165
|
+
if (!isNode) return { config: {} };
|
|
166
166
|
const fs = await nodeImport("node:fs/promises");
|
|
167
167
|
const path2 = await nodeImport("node:path");
|
|
168
168
|
const cwd = gProcess?.cwd?.() ?? ".";
|
|
@@ -172,7 +172,7 @@ async function readConfigPath(p) {
|
|
|
172
172
|
const sanitized = txt.replace(/[\r\n]+/g, "");
|
|
173
173
|
const json = dropUndefined(JSON.parse(sanitized));
|
|
174
174
|
dbg("config path:", resolved, "\u2192", mask(json));
|
|
175
|
-
return json;
|
|
175
|
+
return { config: json, path: resolved };
|
|
176
176
|
} catch (e) {
|
|
177
177
|
const msg = e instanceof Error ? e.message : String(e);
|
|
178
178
|
throw new OnyxConfigError(`Failed to read ${resolved}: ${msg}`);
|
|
@@ -183,7 +183,8 @@ async function resolveConfig(input) {
|
|
|
183
183
|
const env = readEnv(input?.databaseId);
|
|
184
184
|
let cfgPath = {};
|
|
185
185
|
if (configPath) {
|
|
186
|
-
|
|
186
|
+
const cfgRes = await readConfigPath(configPath);
|
|
187
|
+
cfgPath = cfgRes.config;
|
|
187
188
|
}
|
|
188
189
|
const targetId = input?.databaseId ?? env.databaseId ?? cfgPath.databaseId;
|
|
189
190
|
let haveDbId = !!(input?.databaseId ?? env.databaseId ?? cfgPath.databaseId);
|
|
@@ -191,14 +192,16 @@ async function resolveConfig(input) {
|
|
|
191
192
|
let haveApiSecret = !!(input?.apiSecret ?? env.apiSecret ?? cfgPath.apiSecret);
|
|
192
193
|
let project = {};
|
|
193
194
|
if (!(haveDbId && haveApiKey && haveApiSecret)) {
|
|
194
|
-
|
|
195
|
+
const projRes = await readProjectFile(targetId);
|
|
196
|
+
project = projRes.config;
|
|
195
197
|
if (project.databaseId) haveDbId = true;
|
|
196
198
|
if (project.apiKey) haveApiKey = true;
|
|
197
199
|
if (project.apiSecret) haveApiSecret = true;
|
|
198
200
|
}
|
|
199
201
|
let home = {};
|
|
200
202
|
if (!(haveDbId && haveApiKey && haveApiSecret)) {
|
|
201
|
-
|
|
203
|
+
const homeRes = await readHomeProfile(targetId);
|
|
204
|
+
home = homeRes.config;
|
|
202
205
|
}
|
|
203
206
|
const merged = {
|
|
204
207
|
baseUrl: DEFAULT_BASE_URL,
|
|
@@ -217,6 +220,10 @@ async function resolveConfig(input) {
|
|
|
217
220
|
const fetchImpl = merged.fetch ?? (typeof gfetch === "function" ? (u, i) => gfetch(u, i) : async () => {
|
|
218
221
|
throw new OnyxConfigError("No fetch available; provide OnyxConfig.fetch");
|
|
219
222
|
});
|
|
223
|
+
const retryConfig = input?.retry ?? env.retry ?? cfgPath.retry ?? project.retry ?? home.retry ?? {};
|
|
224
|
+
const retryEnabled = retryConfig.enabled ?? true;
|
|
225
|
+
const maxRetries = retryConfig.maxRetries ?? 3;
|
|
226
|
+
const retryInitialDelayMs = retryConfig.initialDelayMs ?? 300;
|
|
220
227
|
const missing = [];
|
|
221
228
|
if (!databaseId) missing.push("databaseId");
|
|
222
229
|
if (!apiKey) missing.push("apiKey");
|
|
@@ -239,7 +246,16 @@ async function resolveConfig(input) {
|
|
|
239
246
|
`Missing required config: ${missing.join(", ")}. Sources: ${sources.join(", ")}`
|
|
240
247
|
);
|
|
241
248
|
}
|
|
242
|
-
const resolved = {
|
|
249
|
+
const resolved = {
|
|
250
|
+
baseUrl,
|
|
251
|
+
databaseId,
|
|
252
|
+
apiKey,
|
|
253
|
+
apiSecret,
|
|
254
|
+
fetch: fetchImpl,
|
|
255
|
+
retryEnabled,
|
|
256
|
+
maxRetries,
|
|
257
|
+
retryInitialDelayMs
|
|
258
|
+
};
|
|
243
259
|
const source = {
|
|
244
260
|
databaseId: input?.databaseId ? "explicit config" : env.databaseId ? "env" : cfgPath.databaseId ? "env ONYX_CONFIG_PATH" : project.databaseId ? "project file" : home.databaseId ? "home profile" : "unknown",
|
|
245
261
|
apiKey: input?.apiKey ? "explicit config" : env.apiKey ? "env" : cfgPath.apiKey ? "env ONYX_CONFIG_PATH" : project.apiKey ? "project file" : home.apiKey ? "home profile" : "unknown",
|
|
@@ -249,6 +265,27 @@ async function resolveConfig(input) {
|
|
|
249
265
|
dbg("resolved:", mask(resolved));
|
|
250
266
|
return resolved;
|
|
251
267
|
}
|
|
268
|
+
async function resolveConfigWithSource(input) {
|
|
269
|
+
const configPathEnv = gProcess?.env?.ONYX_CONFIG_PATH;
|
|
270
|
+
const env = readEnv(input?.databaseId);
|
|
271
|
+
const cfgPathRes = configPathEnv ? await readConfigPath(configPathEnv) : { config: {} };
|
|
272
|
+
const cfgPath = cfgPathRes.config;
|
|
273
|
+
const projectRes = await readProjectFile(env.databaseId ?? cfgPath.databaseId);
|
|
274
|
+
const project = projectRes.config;
|
|
275
|
+
const homeRes = await readHomeProfile(env.databaseId ?? cfgPath.databaseId);
|
|
276
|
+
const home = homeRes.config;
|
|
277
|
+
const base = await resolveConfig(input);
|
|
278
|
+
const sources = {
|
|
279
|
+
baseUrl: input?.baseUrl ? "explicit config" : env.baseUrl ? "env" : cfgPath.baseUrl ? "env ONYX_CONFIG_PATH" : project.baseUrl ? "project file" : home.baseUrl ? "home profile" : "default",
|
|
280
|
+
databaseId: input?.databaseId ? "explicit config" : env.databaseId ? "env" : cfgPath.databaseId ? "env ONYX_CONFIG_PATH" : project.databaseId ? "project file" : home.databaseId ? "home profile" : "unknown",
|
|
281
|
+
apiKey: input?.apiKey ? "explicit config" : env.apiKey ? "env" : cfgPath.apiKey ? "env ONYX_CONFIG_PATH" : project.apiKey ? "project file" : home.apiKey ? "home profile" : "unknown",
|
|
282
|
+
apiSecret: input?.apiSecret ? "explicit config" : env.apiSecret ? "env" : cfgPath.apiSecret ? "env ONYX_CONFIG_PATH" : project.apiSecret ? "project file" : home.apiSecret ? "home profile" : "unknown",
|
|
283
|
+
configPath: cfgPathRes.path,
|
|
284
|
+
projectFile: projectRes.path,
|
|
285
|
+
homeProfile: homeRes.path
|
|
286
|
+
};
|
|
287
|
+
return { ...base, sources };
|
|
288
|
+
}
|
|
252
289
|
function mask(obj) {
|
|
253
290
|
if (!obj) return obj;
|
|
254
291
|
const clone = { ...obj };
|
|
@@ -296,7 +333,7 @@ function parseJsonAllowNaN(txt) {
|
|
|
296
333
|
return JSON.parse(fixed);
|
|
297
334
|
}
|
|
298
335
|
}
|
|
299
|
-
var HttpClient = class {
|
|
336
|
+
var HttpClient = class _HttpClient {
|
|
300
337
|
baseUrl;
|
|
301
338
|
apiKey;
|
|
302
339
|
apiSecret;
|
|
@@ -304,6 +341,25 @@ var HttpClient = class {
|
|
|
304
341
|
defaults;
|
|
305
342
|
requestLoggingEnabled;
|
|
306
343
|
responseLoggingEnabled;
|
|
344
|
+
retryEnabled;
|
|
345
|
+
maxRetries;
|
|
346
|
+
retryInitialDelayMs;
|
|
347
|
+
shouldRetry;
|
|
348
|
+
static parseRetryAfter(header) {
|
|
349
|
+
if (!header) return null;
|
|
350
|
+
const trimmed = header.trim();
|
|
351
|
+
if (trimmed === "") return null;
|
|
352
|
+
const seconds = Number(trimmed);
|
|
353
|
+
if (Number.isFinite(seconds)) {
|
|
354
|
+
return Math.max(0, seconds * 1e3);
|
|
355
|
+
}
|
|
356
|
+
const dateMs = Date.parse(trimmed);
|
|
357
|
+
if (!Number.isNaN(dateMs)) {
|
|
358
|
+
const now = Date.now();
|
|
359
|
+
return Math.max(0, dateMs - now);
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
307
363
|
constructor(opts) {
|
|
308
364
|
if (!opts.baseUrl || opts.baseUrl.trim() === "") {
|
|
309
365
|
throw new OnyxConfigError("baseUrl is required");
|
|
@@ -328,6 +384,10 @@ var HttpClient = class {
|
|
|
328
384
|
const envDebug = globalThis.process?.env?.ONYX_DEBUG === "true";
|
|
329
385
|
this.requestLoggingEnabled = !!opts.requestLoggingEnabled || envDebug;
|
|
330
386
|
this.responseLoggingEnabled = !!opts.responseLoggingEnabled || envDebug;
|
|
387
|
+
this.retryEnabled = opts.retryEnabled ?? true;
|
|
388
|
+
this.maxRetries = Math.max(0, opts.maxRetries ?? 2);
|
|
389
|
+
this.retryInitialDelayMs = Math.max(0, opts.retryInitialDelayMs ?? 100);
|
|
390
|
+
this.shouldRetry = (method, path2) => method === "GET" || path2.startsWith("/query/");
|
|
331
391
|
}
|
|
332
392
|
headers(extra) {
|
|
333
393
|
const extras = { ...extra ?? {} };
|
|
@@ -368,9 +428,8 @@ var HttpClient = class {
|
|
|
368
428
|
headers,
|
|
369
429
|
body: payload
|
|
370
430
|
};
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
const maxAttempts = canRetry ? 3 : 1;
|
|
431
|
+
const canRetry = this.retryEnabled && this.shouldRetry(method, path2);
|
|
432
|
+
const maxAttempts = canRetry ? this.maxRetries + 1 : 1;
|
|
374
433
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
375
434
|
try {
|
|
376
435
|
const res = await this.fetchImpl(url, init);
|
|
@@ -388,7 +447,10 @@ var HttpClient = class {
|
|
|
388
447
|
if (!res.ok) {
|
|
389
448
|
const msg = typeof data === "object" && data !== null && "error" in data && typeof data.error?.message === "string" ? String(data.error.message) : `${res.status} ${res.statusText}`;
|
|
390
449
|
if (canRetry && res.status >= 500 && attempt + 1 < maxAttempts) {
|
|
391
|
-
|
|
450
|
+
const serverRetry = _HttpClient.parseRetryAfter(res.headers.get("retry-after"));
|
|
451
|
+
const backoff = this.retryInitialDelayMs * 2 ** attempt;
|
|
452
|
+
const delay = serverRetry ?? backoff;
|
|
453
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
392
454
|
continue;
|
|
393
455
|
}
|
|
394
456
|
throw new OnyxHttpError(msg, res.status, res.statusText, data, raw);
|
|
@@ -397,7 +459,8 @@ var HttpClient = class {
|
|
|
397
459
|
} catch (err) {
|
|
398
460
|
const retryable = canRetry && (!(err instanceof OnyxHttpError) || err.status >= 500);
|
|
399
461
|
if (attempt + 1 < maxAttempts && retryable) {
|
|
400
|
-
|
|
462
|
+
const delay = this.retryInitialDelayMs * 2 ** attempt;
|
|
463
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
401
464
|
continue;
|
|
402
465
|
}
|
|
403
466
|
throw err;
|
|
@@ -958,31 +1021,299 @@ var CascadeRelationshipBuilder = class {
|
|
|
958
1021
|
}
|
|
959
1022
|
};
|
|
960
1023
|
|
|
961
|
-
// src/
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1024
|
+
// src/helpers/schema-diff.ts
|
|
1025
|
+
function mapByName(items) {
|
|
1026
|
+
const map = /* @__PURE__ */ new Map();
|
|
1027
|
+
for (const item of items ?? []) {
|
|
1028
|
+
if (!item?.name) continue;
|
|
1029
|
+
map.set(item.name, item);
|
|
966
1030
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
return
|
|
1031
|
+
return map;
|
|
1032
|
+
}
|
|
1033
|
+
function normalizeEntities(schema) {
|
|
1034
|
+
if (Array.isArray(schema.entities)) {
|
|
1035
|
+
return schema.entities ?? [];
|
|
1036
|
+
}
|
|
1037
|
+
const tables = schema.tables;
|
|
1038
|
+
if (!Array.isArray(tables)) return [];
|
|
1039
|
+
return tables.map((table) => ({
|
|
1040
|
+
name: table.name,
|
|
1041
|
+
attributes: table.attributes ?? []
|
|
1042
|
+
}));
|
|
1043
|
+
}
|
|
1044
|
+
function normalizePartition(partition) {
|
|
1045
|
+
if (partition == null) return "";
|
|
1046
|
+
const trimmed = partition.trim();
|
|
1047
|
+
return trimmed;
|
|
1048
|
+
}
|
|
1049
|
+
function identifiersEqual(a, b) {
|
|
1050
|
+
if (!a && !b) return true;
|
|
1051
|
+
if (!a || !b) return false;
|
|
1052
|
+
return a.name === b.name && a.generator === b.generator && a.type === b.type;
|
|
1053
|
+
}
|
|
1054
|
+
function diffAttributes(apiAttrs, localAttrs) {
|
|
1055
|
+
const apiMap = mapByName(apiAttrs);
|
|
1056
|
+
const localMap = mapByName(localAttrs);
|
|
1057
|
+
const added = [];
|
|
1058
|
+
const removed = [];
|
|
1059
|
+
const changed = [];
|
|
1060
|
+
for (const [name, local] of localMap.entries()) {
|
|
1061
|
+
if (!apiMap.has(name)) {
|
|
1062
|
+
added.push(local);
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
const api = apiMap.get(name);
|
|
1066
|
+
const apiNull = Boolean(api.isNullable);
|
|
1067
|
+
const localNull = Boolean(local.isNullable);
|
|
1068
|
+
if (api.type !== local.type || apiNull !== localNull) {
|
|
1069
|
+
changed.push({
|
|
1070
|
+
name,
|
|
1071
|
+
from: { type: api.type, isNullable: apiNull },
|
|
1072
|
+
to: { type: local.type, isNullable: localNull }
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
for (const name of apiMap.keys()) {
|
|
1077
|
+
if (!localMap.has(name)) removed.push(name);
|
|
1078
|
+
}
|
|
1079
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
1080
|
+
removed.sort();
|
|
1081
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
1082
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
1083
|
+
return { added, removed, changed };
|
|
1084
|
+
}
|
|
1085
|
+
function diffIndexes(apiIndexes, localIndexes) {
|
|
1086
|
+
const apiMap = mapByName(apiIndexes);
|
|
1087
|
+
const localMap = mapByName(localIndexes);
|
|
1088
|
+
const added = [];
|
|
1089
|
+
const removed = [];
|
|
1090
|
+
const changed = [];
|
|
1091
|
+
for (const [name, local] of localMap.entries()) {
|
|
1092
|
+
if (!apiMap.has(name)) {
|
|
1093
|
+
added.push(local);
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
const api = apiMap.get(name);
|
|
1097
|
+
const apiType = api.type ?? "DEFAULT";
|
|
1098
|
+
const localType = local.type ?? "DEFAULT";
|
|
1099
|
+
const apiScore = api.minimumScore;
|
|
1100
|
+
const localScore = local.minimumScore;
|
|
1101
|
+
if (apiType !== localType || apiScore !== localScore) {
|
|
1102
|
+
changed.push({ name, from: api, to: local });
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
for (const name of apiMap.keys()) {
|
|
1106
|
+
if (!localMap.has(name)) removed.push(name);
|
|
1107
|
+
}
|
|
1108
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
1109
|
+
removed.sort();
|
|
1110
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
1111
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
1112
|
+
return { added, removed, changed };
|
|
1113
|
+
}
|
|
1114
|
+
function diffResolvers(apiResolvers, localResolvers) {
|
|
1115
|
+
const apiMap = mapByName(apiResolvers);
|
|
1116
|
+
const localMap = mapByName(localResolvers);
|
|
1117
|
+
const added = [];
|
|
1118
|
+
const removed = [];
|
|
1119
|
+
const changed = [];
|
|
1120
|
+
for (const [name, local] of localMap.entries()) {
|
|
1121
|
+
if (!apiMap.has(name)) {
|
|
1122
|
+
added.push(local);
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
const api = apiMap.get(name);
|
|
1126
|
+
if (api.resolver !== local.resolver) {
|
|
1127
|
+
changed.push({ name, from: api, to: local });
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
for (const name of apiMap.keys()) {
|
|
1131
|
+
if (!localMap.has(name)) removed.push(name);
|
|
1132
|
+
}
|
|
1133
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
1134
|
+
removed.sort();
|
|
1135
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
1136
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
1137
|
+
return { added, removed, changed };
|
|
1138
|
+
}
|
|
1139
|
+
function diffTriggers(apiTriggers, localTriggers) {
|
|
1140
|
+
const apiMap = mapByName(apiTriggers);
|
|
1141
|
+
const localMap = mapByName(localTriggers);
|
|
1142
|
+
const added = [];
|
|
1143
|
+
const removed = [];
|
|
1144
|
+
const changed = [];
|
|
1145
|
+
for (const [name, local] of localMap.entries()) {
|
|
1146
|
+
if (!apiMap.has(name)) {
|
|
1147
|
+
added.push(local);
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
const api = apiMap.get(name);
|
|
1151
|
+
if (api.event !== local.event || api.trigger !== local.trigger) {
|
|
1152
|
+
changed.push({ name, from: api, to: local });
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
for (const name of apiMap.keys()) {
|
|
1156
|
+
if (!localMap.has(name)) removed.push(name);
|
|
1157
|
+
}
|
|
1158
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
1159
|
+
removed.sort();
|
|
1160
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
1161
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
1162
|
+
return { added, removed, changed };
|
|
1163
|
+
}
|
|
1164
|
+
function computeSchemaDiff(apiSchema, localSchema) {
|
|
1165
|
+
const apiEntities = normalizeEntities(apiSchema);
|
|
1166
|
+
const localEntities = normalizeEntities(localSchema);
|
|
1167
|
+
const apiMap = mapByName(apiEntities);
|
|
1168
|
+
const localMap = mapByName(localEntities);
|
|
1169
|
+
const newTables = [];
|
|
1170
|
+
const removedTables = [];
|
|
1171
|
+
const changedTables = [];
|
|
1172
|
+
for (const [name, localEntity] of localMap.entries()) {
|
|
1173
|
+
if (!apiMap.has(name)) {
|
|
1174
|
+
newTables.push(name);
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
const apiEntity = apiMap.get(name);
|
|
1178
|
+
const tableDiff = { name };
|
|
1179
|
+
const partitionFrom = normalizePartition(apiEntity.partition);
|
|
1180
|
+
const partitionTo = normalizePartition(localEntity.partition);
|
|
1181
|
+
if (partitionFrom !== partitionTo) {
|
|
1182
|
+
tableDiff.partition = { from: partitionFrom || null, to: partitionTo || null };
|
|
1183
|
+
}
|
|
1184
|
+
if (!identifiersEqual(apiEntity.identifier, localEntity.identifier)) {
|
|
1185
|
+
tableDiff.identifier = {
|
|
1186
|
+
from: apiEntity.identifier ?? null,
|
|
1187
|
+
to: localEntity.identifier ?? null
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
const attrs = diffAttributes(apiEntity.attributes, localEntity.attributes);
|
|
1191
|
+
if (attrs) tableDiff.attributes = attrs;
|
|
1192
|
+
const indexes = diffIndexes(apiEntity.indexes, localEntity.indexes);
|
|
1193
|
+
if (indexes) tableDiff.indexes = indexes;
|
|
1194
|
+
const resolvers = diffResolvers(apiEntity.resolvers, localEntity.resolvers);
|
|
1195
|
+
if (resolvers) tableDiff.resolvers = resolvers;
|
|
1196
|
+
const triggers = diffTriggers(apiEntity.triggers, localEntity.triggers);
|
|
1197
|
+
if (triggers) tableDiff.triggers = triggers;
|
|
1198
|
+
const hasChange = tableDiff.partition || tableDiff.identifier || tableDiff.attributes || tableDiff.indexes || tableDiff.resolvers || tableDiff.triggers;
|
|
1199
|
+
if (hasChange) {
|
|
1200
|
+
changedTables.push(tableDiff);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
for (const name of apiMap.keys()) {
|
|
1204
|
+
if (!localMap.has(name)) removedTables.push(name);
|
|
1205
|
+
}
|
|
1206
|
+
newTables.sort();
|
|
1207
|
+
removedTables.sort();
|
|
1208
|
+
changedTables.sort((a, b) => a.name.localeCompare(b.name));
|
|
1209
|
+
return { newTables, removedTables, changedTables };
|
|
1210
|
+
}
|
|
1211
|
+
function isScalar(value) {
|
|
1212
|
+
return value == null || value instanceof Date || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
1213
|
+
}
|
|
1214
|
+
function formatScalar(value) {
|
|
1215
|
+
if (value === null || value === void 0) return "null";
|
|
1216
|
+
if (value instanceof Date) return JSON.stringify(value.toISOString());
|
|
1217
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
1218
|
+
return String(value);
|
|
1219
|
+
}
|
|
1220
|
+
function isRecord(value) {
|
|
1221
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
1222
|
+
}
|
|
1223
|
+
function toYamlLines(value, indent = 0) {
|
|
1224
|
+
const pad = " ".repeat(indent);
|
|
1225
|
+
if (Array.isArray(value)) {
|
|
1226
|
+
if (!value.length) return [`${pad}[]`];
|
|
1227
|
+
const lines = [];
|
|
1228
|
+
for (const item of value) {
|
|
1229
|
+
if (isScalar(item)) {
|
|
1230
|
+
lines.push(`${pad}- ${formatScalar(item)}`);
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
const nested = toYamlLines(item, indent + 2);
|
|
1234
|
+
const [first, ...rest] = nested;
|
|
1235
|
+
lines.push(`${pad}- ${first ? first.trimStart() : "{}"}`);
|
|
1236
|
+
for (const line of rest) lines.push(line);
|
|
1237
|
+
}
|
|
1238
|
+
return lines;
|
|
1239
|
+
}
|
|
1240
|
+
if (isRecord(value)) {
|
|
1241
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0);
|
|
1242
|
+
if (!entries.length) return [`${pad}{}`];
|
|
1243
|
+
const lines = [];
|
|
1244
|
+
for (const [key, val] of entries) {
|
|
1245
|
+
if (Array.isArray(val) && val.length === 0) {
|
|
1246
|
+
lines.push(`${pad}${key}: []`);
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
if (isScalar(val)) {
|
|
1250
|
+
lines.push(`${pad}${key}: ${formatScalar(val)}`);
|
|
1251
|
+
} else {
|
|
1252
|
+
lines.push(`${pad}${key}:`);
|
|
1253
|
+
lines.push(...toYamlLines(val, indent + 2));
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return lines;
|
|
1257
|
+
}
|
|
1258
|
+
return [`${pad}${formatScalar(value)}`];
|
|
1259
|
+
}
|
|
1260
|
+
function pruneTableDiff(table) {
|
|
1261
|
+
const pruned = { name: table.name };
|
|
1262
|
+
if (table.partition && table.partition.from !== table.partition.to) {
|
|
1263
|
+
pruned.partition = table.partition;
|
|
1264
|
+
}
|
|
1265
|
+
if (table.identifier && !identifiersEqual(table.identifier.from ?? void 0, table.identifier.to ?? void 0)) {
|
|
1266
|
+
pruned.identifier = table.identifier;
|
|
1267
|
+
}
|
|
1268
|
+
if (table.attributes) {
|
|
1269
|
+
const { added = [], removed = [], changed = [] } = table.attributes;
|
|
1270
|
+
if (added.length || removed.length || changed.length) {
|
|
1271
|
+
pruned.attributes = { added, removed, changed };
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
if (table.indexes) {
|
|
1275
|
+
const { added = [], removed = [], changed = [] } = table.indexes;
|
|
1276
|
+
if (added.length || removed.length || changed.length) {
|
|
1277
|
+
pruned.indexes = { added, removed, changed };
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
if (table.resolvers) {
|
|
1281
|
+
const { added = [], removed = [], changed = [] } = table.resolvers;
|
|
1282
|
+
if (added.length || removed.length || changed.length) {
|
|
1283
|
+
pruned.resolvers = { added, removed, changed };
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (table.triggers) {
|
|
1287
|
+
const { added = [], removed = [], changed = [] } = table.triggers;
|
|
1288
|
+
if (added.length || removed.length || changed.length) {
|
|
1289
|
+
pruned.triggers = { added, removed, changed };
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
const hasChange = pruned.partition || pruned.identifier || pruned.attributes && (pruned.attributes.added.length || pruned.attributes.removed.length || pruned.attributes.changed.length) || pruned.indexes && (pruned.indexes.added.length || pruned.indexes.removed.length || pruned.indexes.changed.length) || pruned.resolvers && (pruned.resolvers.added.length || pruned.resolvers.removed.length || pruned.resolvers.changed.length) || pruned.triggers && (pruned.triggers.added.length || pruned.triggers.removed.length || pruned.triggers.changed.length);
|
|
1293
|
+
return hasChange ? pruned : null;
|
|
982
1294
|
}
|
|
983
|
-
function
|
|
984
|
-
|
|
1295
|
+
function formatSchemaDiff(diff, filePath) {
|
|
1296
|
+
const hasChanges = diff.newTables.length || diff.removedTables.length || diff.changedTables.length;
|
|
1297
|
+
if (!hasChanges) {
|
|
1298
|
+
return `No differences found between API schema and ${filePath ?? "local schema"}.
|
|
1299
|
+
`;
|
|
1300
|
+
}
|
|
1301
|
+
const header = filePath ? `# Diff between API schema and ${filePath}
|
|
1302
|
+
` : "# Schema diff";
|
|
1303
|
+
const prunedTables = diff.changedTables.map(pruneTableDiff).filter((t) => Boolean(t));
|
|
1304
|
+
const yamlObject = {
|
|
1305
|
+
newTables: [...diff.newTables],
|
|
1306
|
+
removedTables: [...diff.removedTables],
|
|
1307
|
+
changedTables: prunedTables
|
|
1308
|
+
};
|
|
1309
|
+
const bodyLines = toYamlLines(yamlObject);
|
|
1310
|
+
return `${header}
|
|
1311
|
+
${bodyLines.join("\n")}
|
|
1312
|
+
`;
|
|
985
1313
|
}
|
|
1314
|
+
|
|
1315
|
+
// src/impl/onyx-core.ts
|
|
1316
|
+
var DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
|
|
986
1317
|
function toSingleCondition(criteria) {
|
|
987
1318
|
return { conditionType: "SingleCondition", criteria };
|
|
988
1319
|
}
|
|
@@ -1036,7 +1367,20 @@ function normalizeDate(value) {
|
|
|
1036
1367
|
return Number.isNaN(ts.getTime()) ? void 0 : ts;
|
|
1037
1368
|
}
|
|
1038
1369
|
function normalizeSchemaRevision(input, fallbackDatabaseId) {
|
|
1039
|
-
const {
|
|
1370
|
+
const {
|
|
1371
|
+
meta,
|
|
1372
|
+
createdAt,
|
|
1373
|
+
publishedAt,
|
|
1374
|
+
revisionId,
|
|
1375
|
+
entityText,
|
|
1376
|
+
databaseId,
|
|
1377
|
+
entities,
|
|
1378
|
+
revisionDescription,
|
|
1379
|
+
...rest
|
|
1380
|
+
} = input;
|
|
1381
|
+
const dbId = typeof databaseId === "string" ? databaseId : fallbackDatabaseId;
|
|
1382
|
+
const entityList = Array.isArray(entities) ? entities : [];
|
|
1383
|
+
const revisionDesc = typeof revisionDescription === "string" ? revisionDescription : void 0;
|
|
1040
1384
|
const mergedMeta = {
|
|
1041
1385
|
revisionId: meta?.revisionId ?? revisionId,
|
|
1042
1386
|
createdAt: normalizeDate(meta?.createdAt ?? createdAt),
|
|
@@ -1044,10 +1388,11 @@ function normalizeSchemaRevision(input, fallbackDatabaseId) {
|
|
|
1044
1388
|
};
|
|
1045
1389
|
const cleanedMeta = mergedMeta.revisionId || mergedMeta.createdAt || mergedMeta.publishedAt ? mergedMeta : void 0;
|
|
1046
1390
|
return {
|
|
1047
|
-
|
|
1048
|
-
|
|
1391
|
+
databaseId: dbId,
|
|
1392
|
+
revisionDescription: revisionDesc,
|
|
1393
|
+
entities: entityList,
|
|
1049
1394
|
meta: cleanedMeta,
|
|
1050
|
-
|
|
1395
|
+
...rest
|
|
1051
1396
|
};
|
|
1052
1397
|
}
|
|
1053
1398
|
var OnyxDatabaseImpl = class {
|
|
@@ -1058,7 +1403,7 @@ var OnyxDatabaseImpl = class {
|
|
|
1058
1403
|
requestLoggingEnabled;
|
|
1059
1404
|
responseLoggingEnabled;
|
|
1060
1405
|
defaultPartition;
|
|
1061
|
-
constructor(config) {
|
|
1406
|
+
constructor(config, resolveConfigWithCache) {
|
|
1062
1407
|
this.requestLoggingEnabled = !!config?.requestLoggingEnabled;
|
|
1063
1408
|
this.responseLoggingEnabled = !!config?.responseLoggingEnabled;
|
|
1064
1409
|
this.defaultPartition = config?.partition;
|
|
@@ -1075,7 +1420,10 @@ var OnyxDatabaseImpl = class {
|
|
|
1075
1420
|
apiSecret: this.resolved.apiSecret,
|
|
1076
1421
|
fetchImpl: this.resolved.fetch,
|
|
1077
1422
|
requestLoggingEnabled: this.requestLoggingEnabled,
|
|
1078
|
-
responseLoggingEnabled: this.responseLoggingEnabled
|
|
1423
|
+
responseLoggingEnabled: this.responseLoggingEnabled,
|
|
1424
|
+
retryEnabled: this.resolved.retryEnabled,
|
|
1425
|
+
maxRetries: this.resolved.maxRetries,
|
|
1426
|
+
retryInitialDelayMs: this.resolved.retryInitialDelayMs
|
|
1079
1427
|
});
|
|
1080
1428
|
}
|
|
1081
1429
|
return {
|
|
@@ -1159,7 +1507,8 @@ var OnyxDatabaseImpl = class {
|
|
|
1159
1507
|
const path2 = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(
|
|
1160
1508
|
table
|
|
1161
1509
|
)}/${encodeURIComponent(primaryKey)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1162
|
-
|
|
1510
|
+
await http.request("DELETE", path2);
|
|
1511
|
+
return true;
|
|
1163
1512
|
}
|
|
1164
1513
|
async saveDocument(doc) {
|
|
1165
1514
|
const { http, databaseId } = await this.ensureClient();
|
|
@@ -1202,6 +1551,10 @@ var OnyxDatabaseImpl = class {
|
|
|
1202
1551
|
const res = await http.request("GET", path2);
|
|
1203
1552
|
return Array.isArray(res) ? res.map((entry) => normalizeSchemaRevision(entry, databaseId)) : [];
|
|
1204
1553
|
}
|
|
1554
|
+
async diffSchema(localSchema) {
|
|
1555
|
+
const apiSchema = await this.getSchema();
|
|
1556
|
+
return computeSchemaDiff(apiSchema, localSchema);
|
|
1557
|
+
}
|
|
1205
1558
|
async updateSchema(schema, options) {
|
|
1206
1559
|
const { http, databaseId } = await this.ensureClient();
|
|
1207
1560
|
const params = new URLSearchParams();
|
|
@@ -1535,7 +1888,6 @@ var QueryBuilderImpl = class {
|
|
|
1535
1888
|
}
|
|
1536
1889
|
async firstOrNull() {
|
|
1537
1890
|
if (this.mode !== "select") throw new Error("Cannot call firstOrNull() in update mode.");
|
|
1538
|
-
if (!this.conditions) throw new OnyxError("firstOrNull() requires a where() clause.");
|
|
1539
1891
|
this.limitValue = 1;
|
|
1540
1892
|
const pg = await this.page();
|
|
1541
1893
|
return Array.isArray(pg.records) && pg.records.length > 0 ? pg.records[0] : null;
|
|
@@ -1627,219 +1979,33 @@ var CascadeBuilderImpl = class {
|
|
|
1627
1979
|
return this.db.delete(table, primaryKey, opts);
|
|
1628
1980
|
}
|
|
1629
1981
|
};
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
// schema/cli/diff.ts
|
|
1638
|
-
function mapByName(items) {
|
|
1639
|
-
const map = /* @__PURE__ */ new Map();
|
|
1640
|
-
for (const item of items ?? []) {
|
|
1641
|
-
if (!item?.name) continue;
|
|
1642
|
-
map.set(item.name, item);
|
|
1643
|
-
}
|
|
1644
|
-
return map;
|
|
1645
|
-
}
|
|
1646
|
-
function normalizeEntities(schema) {
|
|
1647
|
-
if (Array.isArray(schema.entities)) {
|
|
1648
|
-
return schema.entities ?? [];
|
|
1649
|
-
}
|
|
1650
|
-
const tables = schema.tables;
|
|
1651
|
-
if (!Array.isArray(tables)) return [];
|
|
1652
|
-
return tables.map((table) => ({
|
|
1653
|
-
name: table.name,
|
|
1654
|
-
attributes: table.attributes ?? []
|
|
1655
|
-
}));
|
|
1656
|
-
}
|
|
1657
|
-
function formatIdentifier(id) {
|
|
1658
|
-
if (!id) return "";
|
|
1659
|
-
const parts = [id.name ? `name=${id.name}` : null, id.generator ? `generator=${id.generator}` : null, id.type ? `type=${id.type}` : null].filter(Boolean);
|
|
1660
|
-
return parts.join(", ");
|
|
1661
|
-
}
|
|
1662
|
-
function describeIdentifierChange(api, local) {
|
|
1663
|
-
if (!api && !local) return null;
|
|
1664
|
-
if (!api && local) return "identifier removed";
|
|
1665
|
-
if (api && !local) return "identifier added";
|
|
1666
|
-
if (!api || !local) return null;
|
|
1667
|
-
if (api.name === local.name && api.generator === local.generator && api.type === local.type) {
|
|
1668
|
-
return null;
|
|
1669
|
-
}
|
|
1670
|
-
return `identifier: ${formatIdentifier(api) || "none"} -> ${formatIdentifier(local) || "none"}`;
|
|
1671
|
-
}
|
|
1672
|
-
function describePartitionChange(api, local) {
|
|
1673
|
-
const norm = (v) => v == null || v === "" ? "" : v;
|
|
1674
|
-
if (norm(api) === norm(local)) return null;
|
|
1675
|
-
return `partition: ${norm(api) || "none"} -> ${norm(local) || "none"}`;
|
|
1676
|
-
}
|
|
1677
|
-
function describeAttribute(attr) {
|
|
1678
|
-
const nullable = attr.isNullable ? "true" : "false";
|
|
1679
|
-
return `${attr.name} (${attr.type ?? "unknown"}, nullable: ${nullable})`;
|
|
1680
|
-
}
|
|
1681
|
-
function describeAttributeChange(api, local) {
|
|
1682
|
-
const diffs = [];
|
|
1683
|
-
if (api.type !== local.type) {
|
|
1684
|
-
diffs.push(`type ${api.type ?? "unknown"} -> ${local.type ?? "unknown"}`);
|
|
1685
|
-
}
|
|
1686
|
-
const apiNull = Boolean(api.isNullable);
|
|
1687
|
-
const localNull = Boolean(local.isNullable);
|
|
1688
|
-
if (apiNull !== localNull) {
|
|
1689
|
-
diffs.push(`nullable ${apiNull} -> ${localNull}`);
|
|
1690
|
-
}
|
|
1691
|
-
if (!diffs.length) return null;
|
|
1692
|
-
return `${local.name}: ${diffs.join("; ")}`;
|
|
1693
|
-
}
|
|
1694
|
-
function describeIndex(idx) {
|
|
1695
|
-
const parts = [`type: ${idx.type ?? "DEFAULT"}`];
|
|
1696
|
-
if (idx.minimumScore != null) parts.push(`minScore: ${idx.minimumScore}`);
|
|
1697
|
-
return `${idx.name} (${parts.join(", ")})`;
|
|
1698
|
-
}
|
|
1699
|
-
function describeIndexChange(api, local) {
|
|
1700
|
-
const diffs = [];
|
|
1701
|
-
if (api.type !== local.type) {
|
|
1702
|
-
diffs.push(`type ${api.type ?? "DEFAULT"} -> ${local.type ?? "DEFAULT"}`);
|
|
1703
|
-
}
|
|
1704
|
-
if (api.minimumScore !== local.minimumScore) {
|
|
1705
|
-
diffs.push(`minScore ${api.minimumScore ?? "none"} -> ${local.minimumScore ?? "none"}`);
|
|
1706
|
-
}
|
|
1707
|
-
if (!diffs.length) return null;
|
|
1708
|
-
return `${local.name}: ${diffs.join("; ")}`;
|
|
1709
|
-
}
|
|
1710
|
-
function describeResolverChange(api, local) {
|
|
1711
|
-
if (api.resolver === local.resolver) return null;
|
|
1712
|
-
return `${local.name}: resolver changed`;
|
|
1713
|
-
}
|
|
1714
|
-
function describeTrigger(trigger) {
|
|
1715
|
-
return `${trigger.name} (${trigger.event})`;
|
|
1716
|
-
}
|
|
1717
|
-
function describeTriggerChange(api, local) {
|
|
1718
|
-
const diffs = [];
|
|
1719
|
-
if (api.event !== local.event) {
|
|
1720
|
-
diffs.push(`event ${api.event ?? "none"} -> ${local.event ?? "none"}`);
|
|
1721
|
-
}
|
|
1722
|
-
if (api.trigger !== local.trigger) {
|
|
1723
|
-
diffs.push("trigger changed");
|
|
1724
|
-
}
|
|
1725
|
-
if (!diffs.length) return null;
|
|
1726
|
-
return `${local.name}: ${diffs.join("; ")}`;
|
|
1727
|
-
}
|
|
1728
|
-
function diffCollections(apiItems, localItems, describeAdd, describeChange) {
|
|
1729
|
-
const apiMap = mapByName(apiItems);
|
|
1730
|
-
const localMap = mapByName(localItems);
|
|
1731
|
-
const added = [];
|
|
1732
|
-
const removed = [];
|
|
1733
|
-
const changed = [];
|
|
1734
|
-
for (const [name, local] of localMap.entries()) {
|
|
1735
|
-
if (!apiMap.has(name)) {
|
|
1736
|
-
added.push(describeAdd(local));
|
|
1737
|
-
continue;
|
|
1738
|
-
}
|
|
1739
|
-
if (describeChange) {
|
|
1740
|
-
const detail = describeChange(apiMap.get(name), local);
|
|
1741
|
-
if (detail) changed.push(detail);
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
for (const name of apiMap.keys()) {
|
|
1745
|
-
if (!localMap.has(name)) removed.push(name);
|
|
1746
|
-
}
|
|
1747
|
-
return { added, removed, changed };
|
|
1748
|
-
}
|
|
1749
|
-
function computeSchemaDiff(apiSchema, localSchema) {
|
|
1750
|
-
const apiEntities = normalizeEntities(apiSchema);
|
|
1751
|
-
const localEntities = normalizeEntities(localSchema);
|
|
1752
|
-
const apiMap = mapByName(apiEntities);
|
|
1753
|
-
const localMap = mapByName(localEntities);
|
|
1754
|
-
const newTables = [];
|
|
1755
|
-
const removedTables = [];
|
|
1756
|
-
const changedTables = [];
|
|
1757
|
-
for (const [name, localEntity] of localMap.entries()) {
|
|
1758
|
-
if (!apiMap.has(name)) {
|
|
1759
|
-
newTables.push(name);
|
|
1760
|
-
continue;
|
|
1761
|
-
}
|
|
1762
|
-
const apiEntity = apiMap.get(name);
|
|
1763
|
-
const details = [];
|
|
1764
|
-
const partitionChange = describePartitionChange(apiEntity.partition, localEntity.partition);
|
|
1765
|
-
if (partitionChange) details.push(partitionChange);
|
|
1766
|
-
const idChange = describeIdentifierChange(apiEntity.identifier, localEntity.identifier);
|
|
1767
|
-
if (idChange) details.push(idChange);
|
|
1768
|
-
const attrDiff = diffCollections(apiEntity.attributes, localEntity.attributes, (attr) => `+ ${describeAttribute(attr)}`, describeAttributeChange);
|
|
1769
|
-
if (attrDiff.added.length || attrDiff.removed.length || attrDiff.changed.length) {
|
|
1770
|
-
details.push("attributes:");
|
|
1771
|
-
for (const a of attrDiff.added) details.push(` ${a}`);
|
|
1772
|
-
for (const c of attrDiff.changed) details.push(` ~ ${c}`);
|
|
1773
|
-
for (const r of attrDiff.removed) details.push(` - ${r}`);
|
|
1774
|
-
}
|
|
1775
|
-
const idxDiff = diffCollections(apiEntity.indexes, localEntity.indexes, (idx) => `+ ${describeIndex(idx)}`, describeIndexChange);
|
|
1776
|
-
if (idxDiff.added.length || idxDiff.removed.length || idxDiff.changed.length) {
|
|
1777
|
-
details.push("indexes:");
|
|
1778
|
-
for (const a of idxDiff.added) details.push(` ${a}`);
|
|
1779
|
-
for (const c of idxDiff.changed) details.push(` ~ ${c}`);
|
|
1780
|
-
for (const r of idxDiff.removed) details.push(` - ${r}`);
|
|
1781
|
-
}
|
|
1782
|
-
const resolverDiff = diffCollections(apiEntity.resolvers, localEntity.resolvers, (resolver) => `+ ${resolver.name}`, describeResolverChange);
|
|
1783
|
-
if (resolverDiff.added.length || resolverDiff.removed.length || resolverDiff.changed.length) {
|
|
1784
|
-
details.push("resolvers:");
|
|
1785
|
-
for (const a of resolverDiff.added) details.push(` ${a}`);
|
|
1786
|
-
for (const c of resolverDiff.changed) details.push(` ~ ${c}`);
|
|
1787
|
-
for (const r of resolverDiff.removed) details.push(` - ${r}`);
|
|
1788
|
-
}
|
|
1789
|
-
const triggerDiff = diffCollections(apiEntity.triggers, localEntity.triggers, (trigger) => `+ ${describeTrigger(trigger)}`, describeTriggerChange);
|
|
1790
|
-
if (triggerDiff.added.length || triggerDiff.removed.length || triggerDiff.changed.length) {
|
|
1791
|
-
details.push("triggers:");
|
|
1792
|
-
for (const a of triggerDiff.added) details.push(` ${a}`);
|
|
1793
|
-
for (const c of triggerDiff.changed) details.push(` ~ ${c}`);
|
|
1794
|
-
for (const r of triggerDiff.removed) details.push(` - ${r}`);
|
|
1982
|
+
function createOnyxFacade(resolveConfig2) {
|
|
1983
|
+
let cachedCfg = null;
|
|
1984
|
+
function resolveConfigWithCache(config) {
|
|
1985
|
+
const ttl = config?.ttl ?? DEFAULT_CACHE_TTL;
|
|
1986
|
+
const now = Date.now();
|
|
1987
|
+
if (cachedCfg && cachedCfg.expires > now) {
|
|
1988
|
+
return cachedCfg.promise;
|
|
1795
1989
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
for (const name of apiMap.keys()) {
|
|
1801
|
-
if (!localMap.has(name)) removedTables.push(name);
|
|
1802
|
-
}
|
|
1803
|
-
newTables.sort();
|
|
1804
|
-
removedTables.sort();
|
|
1805
|
-
changedTables.sort((a, b) => a.name.localeCompare(b.name));
|
|
1806
|
-
return { newTables, removedTables, changedTables };
|
|
1807
|
-
}
|
|
1808
|
-
function formatSchemaDiff(diff, filePath) {
|
|
1809
|
-
const lines = [];
|
|
1810
|
-
const hasChanges = diff.newTables.length || diff.removedTables.length || diff.changedTables.length;
|
|
1811
|
-
if (!hasChanges) {
|
|
1812
|
-
return `No differences found between API schema and ${filePath ?? "local file"}.
|
|
1813
|
-
`;
|
|
1990
|
+
const { ttl: _ttl, requestLoggingEnabled: _reqLog, responseLoggingEnabled: _resLog, ...rest } = config ?? {};
|
|
1991
|
+
const promise = resolveConfig2(rest);
|
|
1992
|
+
cachedCfg = { promise, expires: now + ttl };
|
|
1993
|
+
return promise;
|
|
1814
1994
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
if (diff.newTables.length) {
|
|
1818
|
-
for (const t of diff.newTables) lines.push(` ${t}`);
|
|
1819
|
-
} else {
|
|
1820
|
-
lines.push(" (none)");
|
|
1821
|
-
}
|
|
1822
|
-
lines.push("Removed Tables:");
|
|
1823
|
-
if (diff.removedTables.length) {
|
|
1824
|
-
for (const t of diff.removedTables) lines.push(` ${t}`);
|
|
1825
|
-
} else {
|
|
1826
|
-
lines.push(" (none)");
|
|
1827
|
-
}
|
|
1828
|
-
lines.push("Changes:");
|
|
1829
|
-
if (diff.changedTables.length) {
|
|
1830
|
-
for (const table of diff.changedTables) {
|
|
1831
|
-
lines.push(` ${table.name}`);
|
|
1832
|
-
for (const detail of table.details) {
|
|
1833
|
-
lines.push(` ${detail}`);
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
} else {
|
|
1837
|
-
lines.push(" (none)");
|
|
1995
|
+
function clearCacheConfig() {
|
|
1996
|
+
cachedCfg = null;
|
|
1838
1997
|
}
|
|
1839
|
-
return
|
|
1840
|
-
|
|
1998
|
+
return {
|
|
1999
|
+
init(config) {
|
|
2000
|
+
return new OnyxDatabaseImpl(config, resolveConfigWithCache);
|
|
2001
|
+
},
|
|
2002
|
+
clearCacheConfig
|
|
2003
|
+
};
|
|
1841
2004
|
}
|
|
1842
2005
|
|
|
2006
|
+
// src/impl/onyx.ts
|
|
2007
|
+
var onyx = createOnyxFacade((config) => resolveConfig(config));
|
|
2008
|
+
|
|
1843
2009
|
// schema/cli/schema.ts
|
|
1844
2010
|
var DEFAULT_SCHEMA_PATH = "./onyx.schema.json";
|
|
1845
2011
|
function printHelp() {
|
|
@@ -1850,10 +2016,12 @@ Usage:
|
|
|
1850
2016
|
onyx-schema get [file] [--tables tableA,tableB]
|
|
1851
2017
|
onyx-schema validate [file]
|
|
1852
2018
|
onyx-schema diff [file]
|
|
2019
|
+
onyx-schema info
|
|
1853
2020
|
|
|
1854
2021
|
Options:
|
|
1855
2022
|
[file] Path to schema JSON (default: ./onyx.schema.json)
|
|
1856
2023
|
--tables <list> Comma-separated list of tables to fetch (for get)
|
|
2024
|
+
--print Print the fetched schema to stdout instead of writing a file
|
|
1857
2025
|
-h, --help Show this help message
|
|
1858
2026
|
`);
|
|
1859
2027
|
}
|
|
@@ -1865,7 +2033,7 @@ function parseTables(value) {
|
|
|
1865
2033
|
function parseArgs(argv) {
|
|
1866
2034
|
const cmd = (argv[2] ?? "").toLowerCase();
|
|
1867
2035
|
let command = "help";
|
|
1868
|
-
if (cmd === "publish" || cmd === "get" || cmd === "validate" || cmd === "diff") {
|
|
2036
|
+
if (cmd === "publish" || cmd === "get" || cmd === "validate" || cmd === "diff" || cmd === "info") {
|
|
1869
2037
|
command = cmd;
|
|
1870
2038
|
}
|
|
1871
2039
|
let idx = 3;
|
|
@@ -1875,6 +2043,7 @@ function parseArgs(argv) {
|
|
|
1875
2043
|
idx++;
|
|
1876
2044
|
}
|
|
1877
2045
|
let tables;
|
|
2046
|
+
let print = false;
|
|
1878
2047
|
for (; idx < argv.length; idx++) {
|
|
1879
2048
|
const arg = argv[idx];
|
|
1880
2049
|
switch (arg) {
|
|
@@ -1882,6 +2051,9 @@ function parseArgs(argv) {
|
|
|
1882
2051
|
tables = parseTables(argv[idx + 1]);
|
|
1883
2052
|
idx++;
|
|
1884
2053
|
break;
|
|
2054
|
+
case "--print":
|
|
2055
|
+
print = true;
|
|
2056
|
+
break;
|
|
1885
2057
|
case "-h":
|
|
1886
2058
|
case "--help":
|
|
1887
2059
|
command = "help";
|
|
@@ -1896,7 +2068,7 @@ function parseArgs(argv) {
|
|
|
1896
2068
|
}
|
|
1897
2069
|
}
|
|
1898
2070
|
}
|
|
1899
|
-
return { command, filePath, tables };
|
|
2071
|
+
return { command, filePath, tables, print };
|
|
1900
2072
|
}
|
|
1901
2073
|
async function readFileJson(filePath) {
|
|
1902
2074
|
const fs = await import('fs/promises');
|
|
@@ -1911,10 +2083,10 @@ async function writeFileJson(filePath, data) {
|
|
|
1911
2083
|
`;
|
|
1912
2084
|
await fs.writeFile(resolved, serialized, "utf8");
|
|
1913
2085
|
}
|
|
1914
|
-
async function fetchSchema(filePath, tables) {
|
|
2086
|
+
async function fetchSchema(filePath, tables, print) {
|
|
1915
2087
|
const db = onyx.init();
|
|
1916
2088
|
const schema = await db.getSchema({ tables });
|
|
1917
|
-
if (tables?.length) {
|
|
2089
|
+
if (tables?.length || print) {
|
|
1918
2090
|
process__default.default.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
1919
2091
|
`);
|
|
1920
2092
|
return;
|
|
@@ -1952,14 +2124,45 @@ ${formatSchemaErrors(result.errors)}`);
|
|
|
1952
2124
|
}
|
|
1953
2125
|
async function diffSchema(filePath) {
|
|
1954
2126
|
const db = onyx.init();
|
|
1955
|
-
const
|
|
1956
|
-
|
|
1957
|
-
readFileJson(filePath)
|
|
1958
|
-
]);
|
|
1959
|
-
const diff = computeSchemaDiff(apiSchema, localSchema);
|
|
2127
|
+
const localSchema = await readFileJson(filePath);
|
|
2128
|
+
const diff = await db.diffSchema(localSchema);
|
|
1960
2129
|
const output = formatSchemaDiff(diff, filePath);
|
|
1961
2130
|
process__default.default.stdout.write(output);
|
|
1962
2131
|
}
|
|
2132
|
+
function maskValue(value) {
|
|
2133
|
+
if (!value) return "";
|
|
2134
|
+
if (value.length <= 4) return value;
|
|
2135
|
+
return `${value.slice(0, 2)}...${value.slice(-2)}`;
|
|
2136
|
+
}
|
|
2137
|
+
function formatSource(source) {
|
|
2138
|
+
if (!source) return "unknown";
|
|
2139
|
+
if (source.includes("env")) return "env";
|
|
2140
|
+
if (source.includes("file") || source.includes("profile") || source.includes("config")) return "file";
|
|
2141
|
+
if (source === "explicit config") return "explicit";
|
|
2142
|
+
return source;
|
|
2143
|
+
}
|
|
2144
|
+
async function printInfo() {
|
|
2145
|
+
const resolved = await resolveConfigWithSource();
|
|
2146
|
+
const db = onyx.init();
|
|
2147
|
+
const cfgFile = resolved.sources.configPath ?? resolved.sources.projectFile ?? resolved.sources.homeProfile ?? "(none)";
|
|
2148
|
+
let connection = "ok";
|
|
2149
|
+
try {
|
|
2150
|
+
await db.getSchema({ tables: [] });
|
|
2151
|
+
} catch (err) {
|
|
2152
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2153
|
+
connection = msg;
|
|
2154
|
+
}
|
|
2155
|
+
const lines = [
|
|
2156
|
+
`Database ID: ${resolved.databaseId} (source: ${formatSource(resolved.sources.databaseId)})`,
|
|
2157
|
+
`Base URL : ${resolved.baseUrl} (source: ${formatSource(resolved.sources.baseUrl)})`,
|
|
2158
|
+
`API Key : ${maskValue(resolved.apiKey)} (source: ${formatSource(resolved.sources.apiKey)})`,
|
|
2159
|
+
`API Secret : ${maskValue(resolved.apiSecret)} (source: ${formatSource(resolved.sources.apiSecret)})`,
|
|
2160
|
+
`Config file: ${cfgFile}`,
|
|
2161
|
+
`Connection : ${connection}`
|
|
2162
|
+
];
|
|
2163
|
+
process__default.default.stdout.write(`${lines.join("\n")}
|
|
2164
|
+
`);
|
|
2165
|
+
}
|
|
1963
2166
|
(async () => {
|
|
1964
2167
|
try {
|
|
1965
2168
|
const parsed = parseArgs(process__default.default.argv);
|
|
@@ -1968,7 +2171,7 @@ async function diffSchema(filePath) {
|
|
|
1968
2171
|
await publishSchema(parsed.filePath);
|
|
1969
2172
|
break;
|
|
1970
2173
|
case "get":
|
|
1971
|
-
await fetchSchema(parsed.filePath, parsed.tables);
|
|
2174
|
+
await fetchSchema(parsed.filePath, parsed.tables, parsed.print);
|
|
1972
2175
|
break;
|
|
1973
2176
|
case "validate":
|
|
1974
2177
|
await validateSchema(parsed.filePath);
|
|
@@ -1976,6 +2179,9 @@ async function diffSchema(filePath) {
|
|
|
1976
2179
|
case "diff":
|
|
1977
2180
|
await diffSchema(parsed.filePath);
|
|
1978
2181
|
break;
|
|
2182
|
+
case "info":
|
|
2183
|
+
await printInfo();
|
|
2184
|
+
break;
|
|
1979
2185
|
default:
|
|
1980
2186
|
printHelp();
|
|
1981
2187
|
return;
|