@aouda/client 0.1.9 → 0.1.11
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/dist/cli/index.cjs +241 -9
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +241 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/{client-CxP9Vnc8.d.cts → client-CT3GdrvA.d.cts} +111 -5
- package/dist/{client-CxP9Vnc8.d.ts → client-CT3GdrvA.d.ts} +111 -5
- package/dist/index.cjs +246 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +243 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -421,7 +421,7 @@ var import_node_url = require("url");
|
|
|
421
421
|
// package.json
|
|
422
422
|
var package_default = {
|
|
423
423
|
name: "@aouda/client",
|
|
424
|
-
version: "0.1.
|
|
424
|
+
version: "0.1.11",
|
|
425
425
|
description: "Official TypeScript/JavaScript client library for Aouda",
|
|
426
426
|
type: "module",
|
|
427
427
|
main: "./dist/index.cjs",
|
|
@@ -659,6 +659,7 @@ var AuthHandler = class {
|
|
|
659
659
|
this._tokenExpiry = null;
|
|
660
660
|
this._authenticated = false;
|
|
661
661
|
this._refreshPromise = null;
|
|
662
|
+
this._onAccessTokenRefreshed = null;
|
|
662
663
|
this._serverUrl = serverUrl.replace(/\/+$/, "");
|
|
663
664
|
this._timeout = timeout;
|
|
664
665
|
this._authBasePath = config.basePath;
|
|
@@ -908,10 +909,16 @@ var AuthHandler = class {
|
|
|
908
909
|
const thresholdSeconds = this._refreshThresholdMs / 1e3;
|
|
909
910
|
return this._tokenExpiry <= nowSeconds + thresholdSeconds;
|
|
910
911
|
}
|
|
912
|
+
setOnAccessTokenRefreshed(handler) {
|
|
913
|
+
this._onAccessTokenRefreshed = handler;
|
|
914
|
+
}
|
|
911
915
|
async _doRefresh() {
|
|
912
916
|
try {
|
|
913
917
|
const result = await this.callRefreshEndpoint();
|
|
914
918
|
this.handleSignInResult(result);
|
|
919
|
+
if (result.accessToken) {
|
|
920
|
+
this._onAccessTokenRefreshed?.(result.accessToken);
|
|
921
|
+
}
|
|
915
922
|
return true;
|
|
916
923
|
} catch {
|
|
917
924
|
return false;
|
|
@@ -1512,7 +1519,15 @@ var ERROR_CODE_MAP = {
|
|
|
1512
1519
|
AUTH_TOKEN_INVALID: AoudaAuthenticationError,
|
|
1513
1520
|
AUTH_TOKEN_REVOKED: AoudaAuthenticationError,
|
|
1514
1521
|
AUTH_API_KEY_INVALID: AoudaAuthenticationError,
|
|
1515
|
-
AUTH_REQUIRED: AoudaAuthenticationError
|
|
1522
|
+
AUTH_REQUIRED: AoudaAuthenticationError,
|
|
1523
|
+
NAMED_QUERY_NOT_FOUND: AoudaNotFoundError,
|
|
1524
|
+
NAMED_MUTATION_NOT_FOUND: AoudaNotFoundError,
|
|
1525
|
+
NAMED_QUERY_BATCH_EMPTY: AoudaValidationError,
|
|
1526
|
+
NAMED_QUERY_BATCH_TOO_LARGE: AoudaValidationError,
|
|
1527
|
+
NAMED_QUERY_BATCH_MUTATION: AoudaValidationError,
|
|
1528
|
+
NAMED_QUERY_BIND_FAILED: AoudaValidationError,
|
|
1529
|
+
NAMED_QUERY_PARAM_REQUIRED: AoudaValidationError,
|
|
1530
|
+
NAMED_MUTATION_BIND_FAILED: AoudaValidationError
|
|
1516
1531
|
};
|
|
1517
1532
|
function createComposedAbortController(...signals) {
|
|
1518
1533
|
const controller = new AbortController();
|
|
@@ -2405,6 +2420,8 @@ var TableSubscription = class {
|
|
|
2405
2420
|
this._started = false;
|
|
2406
2421
|
this._startPromise = null;
|
|
2407
2422
|
this._lastVersion = 0;
|
|
2423
|
+
this._pendingSnapshotRows = [];
|
|
2424
|
+
this._forceFreshSubscribe = false;
|
|
2408
2425
|
this.id = createStreamingId("sub");
|
|
2409
2426
|
this._transport = transport;
|
|
2410
2427
|
this._tableName = tableName;
|
|
@@ -2412,6 +2429,7 @@ var TableSubscription = class {
|
|
|
2412
2429
|
this._onSnapshot = options.onSnapshot;
|
|
2413
2430
|
this._onChange = options.onChange;
|
|
2414
2431
|
this._onError = options.onError;
|
|
2432
|
+
this._conflate = options.conflate;
|
|
2415
2433
|
this._reconnectHandlerKey = `${this.id}::reconnect`;
|
|
2416
2434
|
}
|
|
2417
2435
|
get lastVersion() {
|
|
@@ -2461,7 +2479,9 @@ var TableSubscription = class {
|
|
|
2461
2479
|
return;
|
|
2462
2480
|
}
|
|
2463
2481
|
try {
|
|
2464
|
-
|
|
2482
|
+
this._pendingSnapshotRows = [];
|
|
2483
|
+
const resumeFrom = this._forceFreshSubscribe || this._lastVersion <= 0 ? void 0 : this._lastVersion;
|
|
2484
|
+
this._forceFreshSubscribe = false;
|
|
2465
2485
|
await this._sendSubscribe(resumeFrom);
|
|
2466
2486
|
} catch (error) {
|
|
2467
2487
|
const wrapped = error instanceof Error ? error : new AoudaConnectionError("Subscription reconnect failed");
|
|
@@ -2481,6 +2501,9 @@ var TableSubscription = class {
|
|
|
2481
2501
|
if (resumeFrom !== void 0) {
|
|
2482
2502
|
message.resume_from = resumeFrom;
|
|
2483
2503
|
}
|
|
2504
|
+
if (this._conflate !== void 0) {
|
|
2505
|
+
message.conflate = this._conflate;
|
|
2506
|
+
}
|
|
2484
2507
|
await this._transport.send(message);
|
|
2485
2508
|
}
|
|
2486
2509
|
_handleMessage(message) {
|
|
@@ -2489,7 +2512,13 @@ var TableSubscription = class {
|
|
|
2489
2512
|
}
|
|
2490
2513
|
switch (message.type) {
|
|
2491
2514
|
case "snapshot":
|
|
2492
|
-
this.
|
|
2515
|
+
this._handleSnapshotPage(message);
|
|
2516
|
+
return;
|
|
2517
|
+
case "snapshot_complete":
|
|
2518
|
+
this._handleSnapshotComplete(message);
|
|
2519
|
+
return;
|
|
2520
|
+
case "gap":
|
|
2521
|
+
void this._handleGap(message);
|
|
2493
2522
|
return;
|
|
2494
2523
|
case "change":
|
|
2495
2524
|
this._handleChange(message);
|
|
@@ -2501,9 +2530,13 @@ var TableSubscription = class {
|
|
|
2501
2530
|
return;
|
|
2502
2531
|
}
|
|
2503
2532
|
}
|
|
2504
|
-
|
|
2533
|
+
_handleSnapshotPage(message) {
|
|
2534
|
+
this._pendingSnapshotRows.push(...message.rows);
|
|
2535
|
+
}
|
|
2536
|
+
_handleSnapshotComplete(message) {
|
|
2505
2537
|
this._lastVersion = message.version;
|
|
2506
|
-
const rows =
|
|
2538
|
+
const rows = this._pendingSnapshotRows;
|
|
2539
|
+
this._pendingSnapshotRows = [];
|
|
2507
2540
|
this._onSnapshot?.(rows, message.version);
|
|
2508
2541
|
this._queue.push({
|
|
2509
2542
|
type: "snapshot",
|
|
@@ -2511,6 +2544,16 @@ var TableSubscription = class {
|
|
|
2511
2544
|
version: message.version
|
|
2512
2545
|
});
|
|
2513
2546
|
}
|
|
2547
|
+
async _handleGap(message) {
|
|
2548
|
+
this._pendingSnapshotRows = [];
|
|
2549
|
+
this._lastVersion = message.last_seq;
|
|
2550
|
+
try {
|
|
2551
|
+
await this._sendSubscribe(message.last_seq);
|
|
2552
|
+
} catch (error) {
|
|
2553
|
+
const wrapped = error instanceof Error ? error : new AoudaConnectionError("Subscription gap resume failed");
|
|
2554
|
+
this._notifyError(wrapped);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2514
2557
|
_handleChange(message) {
|
|
2515
2558
|
this._lastVersion = message.version;
|
|
2516
2559
|
const event = {
|
|
@@ -2521,10 +2564,20 @@ var TableSubscription = class {
|
|
|
2521
2564
|
key: message.key,
|
|
2522
2565
|
version: message.version
|
|
2523
2566
|
};
|
|
2567
|
+
if (message.values_skipped !== void 0) {
|
|
2568
|
+
event.values_skipped = message.values_skipped;
|
|
2569
|
+
}
|
|
2524
2570
|
this._onChange?.(event);
|
|
2525
2571
|
this._queue.push(event);
|
|
2526
2572
|
}
|
|
2527
2573
|
_handleServerError(message) {
|
|
2574
|
+
if (message.code === "SLOW_CONSUMER") {
|
|
2575
|
+
this._pendingSnapshotRows = [];
|
|
2576
|
+
this._lastVersion = 0;
|
|
2577
|
+
this._forceFreshSubscribe = true;
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
this._pendingSnapshotRows = [];
|
|
2528
2581
|
const error = new AoudaConnectionError(
|
|
2529
2582
|
`Subscription error (${message.code}): ${message.message}`
|
|
2530
2583
|
);
|
|
@@ -3237,7 +3290,8 @@ var TableQuery = class _TableQuery {
|
|
|
3237
3290
|
{
|
|
3238
3291
|
onSnapshot: options.onSnapshot,
|
|
3239
3292
|
onChange: options.onChange,
|
|
3240
|
-
onError: options.onError
|
|
3293
|
+
onError: options.onError,
|
|
3294
|
+
conflate: options.conflate
|
|
3241
3295
|
},
|
|
3242
3296
|
mergedFilter
|
|
3243
3297
|
);
|
|
@@ -4932,6 +4986,147 @@ var MaterializedQueriesApi = class {
|
|
|
4932
4986
|
}
|
|
4933
4987
|
};
|
|
4934
4988
|
|
|
4989
|
+
// src/named-queries.ts
|
|
4990
|
+
var MAX_NAMED_QUERY_BATCH_SIZE = 32;
|
|
4991
|
+
function raiseDeprecationWarnings(sink, warnings) {
|
|
4992
|
+
if (warnings == null) return;
|
|
4993
|
+
for (const warning of warnings) {
|
|
4994
|
+
if (warning.code !== "NAMED_QUERY_DEPRECATED" && warning.code !== "NAMED_MUTATION_DEPRECATED") {
|
|
4995
|
+
continue;
|
|
4996
|
+
}
|
|
4997
|
+
const sunset = warning.sunsetAt != null ? ` sunsetAt=${warning.sunsetAt}` : "";
|
|
4998
|
+
const hash = warning.hash != null && warning.hash.length > 0 ? ` hash=${warning.hash}` : "";
|
|
4999
|
+
sink({
|
|
5000
|
+
code: warning.code,
|
|
5001
|
+
hash: warning.hash,
|
|
5002
|
+
sunsetAt: warning.sunsetAt,
|
|
5003
|
+
message: `${warning.code}:${hash}${sunset}`.trim()
|
|
5004
|
+
});
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
function emptyStats() {
|
|
5008
|
+
return {
|
|
5009
|
+
rowsScanned: 0,
|
|
5010
|
+
rowsReturned: 0,
|
|
5011
|
+
segmentsAccessed: 0,
|
|
5012
|
+
executionMs: 0
|
|
5013
|
+
};
|
|
5014
|
+
}
|
|
5015
|
+
var NamedQueriesApi = class {
|
|
5016
|
+
constructor(transport, database, onWarning) {
|
|
5017
|
+
this.transport = transport;
|
|
5018
|
+
this.database = database;
|
|
5019
|
+
this.onWarning = onWarning;
|
|
5020
|
+
}
|
|
5021
|
+
async execute(hash, args, options) {
|
|
5022
|
+
if (typeof hash !== "string" || hash.trim().length === 0) {
|
|
5023
|
+
throw new Error("Named query hash must be a non-empty string");
|
|
5024
|
+
}
|
|
5025
|
+
const prefix = databasePath2(this.database);
|
|
5026
|
+
const path4 = `${prefix}/named-queries/${encodeURIComponent(hash)}/query?format=columnar`;
|
|
5027
|
+
const response = await this.transport.post(
|
|
5028
|
+
path4,
|
|
5029
|
+
{ args: args ?? {} },
|
|
5030
|
+
{ signal: options?.signal }
|
|
5031
|
+
);
|
|
5032
|
+
const rows = columnarToRows(response);
|
|
5033
|
+
raiseDeprecationWarnings(this.onWarning, response.warnings);
|
|
5034
|
+
return {
|
|
5035
|
+
rows,
|
|
5036
|
+
stats: response.stats,
|
|
5037
|
+
warnings: response.warnings
|
|
5038
|
+
};
|
|
5039
|
+
}
|
|
5040
|
+
async batch(items, options) {
|
|
5041
|
+
if (items.length === 0) {
|
|
5042
|
+
throw new AoudaValidationError(
|
|
5043
|
+
"Named query batch requires a non-empty queries array.",
|
|
5044
|
+
"NAMED_QUERY_BATCH_EMPTY",
|
|
5045
|
+
400
|
|
5046
|
+
);
|
|
5047
|
+
}
|
|
5048
|
+
if (items.length > MAX_NAMED_QUERY_BATCH_SIZE) {
|
|
5049
|
+
throw new AoudaValidationError(
|
|
5050
|
+
`Named query batch exceeds ${MAX_NAMED_QUERY_BATCH_SIZE} elements.`,
|
|
5051
|
+
"NAMED_QUERY_BATCH_TOO_LARGE",
|
|
5052
|
+
400
|
|
5053
|
+
);
|
|
5054
|
+
}
|
|
5055
|
+
const prefix = databasePath2(this.database);
|
|
5056
|
+
const path4 = `${prefix}/named-queries/batch?format=columnar`;
|
|
5057
|
+
const envelope = await this.transport.post(
|
|
5058
|
+
path4,
|
|
5059
|
+
{ queries: items },
|
|
5060
|
+
{ signal: options?.signal }
|
|
5061
|
+
);
|
|
5062
|
+
return (envelope.results ?? []).map((slot) => {
|
|
5063
|
+
if (slot.code != null && slot.code.length > 0) {
|
|
5064
|
+
return {
|
|
5065
|
+
isError: true,
|
|
5066
|
+
code: slot.code,
|
|
5067
|
+
error: slot.error
|
|
5068
|
+
};
|
|
5069
|
+
}
|
|
5070
|
+
const columnar = {
|
|
5071
|
+
columns: slot.columns ?? [],
|
|
5072
|
+
types: slot.types ?? [],
|
|
5073
|
+
data: slot.data ?? [],
|
|
5074
|
+
rowCount: slot.rowCount ?? 0,
|
|
5075
|
+
stats: slot.stats ?? emptyStats(),
|
|
5076
|
+
warnings: slot.warnings
|
|
5077
|
+
};
|
|
5078
|
+
const result = {
|
|
5079
|
+
rows: columnarToRows(columnar),
|
|
5080
|
+
stats: columnar.stats,
|
|
5081
|
+
warnings: slot.warnings
|
|
5082
|
+
};
|
|
5083
|
+
raiseDeprecationWarnings(this.onWarning, slot.warnings);
|
|
5084
|
+
return { isError: false, result };
|
|
5085
|
+
});
|
|
5086
|
+
}
|
|
5087
|
+
};
|
|
5088
|
+
var NamedMutationsApi = class {
|
|
5089
|
+
constructor(transport, database, onWarning) {
|
|
5090
|
+
this.transport = transport;
|
|
5091
|
+
this.database = database;
|
|
5092
|
+
this.onWarning = onWarning;
|
|
5093
|
+
}
|
|
5094
|
+
async execute(hash, args) {
|
|
5095
|
+
if (typeof hash !== "string" || hash.trim().length === 0) {
|
|
5096
|
+
throw new Error("Named mutation hash must be a non-empty string");
|
|
5097
|
+
}
|
|
5098
|
+
const prefix = databasePath2(this.database);
|
|
5099
|
+
const path4 = `${prefix}/named-mutations/${encodeURIComponent(hash)}/execute`;
|
|
5100
|
+
const wire = await this.transport.post(path4, {
|
|
5101
|
+
args: args ?? {}
|
|
5102
|
+
});
|
|
5103
|
+
let op = "unknown";
|
|
5104
|
+
let rowsAffected = 0;
|
|
5105
|
+
if (wire.rowsInserted != null) {
|
|
5106
|
+
op = "insert";
|
|
5107
|
+
rowsAffected = wire.rowsInserted;
|
|
5108
|
+
} else if (wire.rowsUpdated != null) {
|
|
5109
|
+
op = "update";
|
|
5110
|
+
rowsAffected = wire.rowsUpdated;
|
|
5111
|
+
} else if (wire.rowsDeleted != null) {
|
|
5112
|
+
op = "delete";
|
|
5113
|
+
rowsAffected = wire.rowsDeleted;
|
|
5114
|
+
}
|
|
5115
|
+
raiseDeprecationWarnings(this.onWarning, wire.warnings);
|
|
5116
|
+
const returning = wire.rows == null ? void 0 : {
|
|
5117
|
+
rows: columnarToRows(wire.rows),
|
|
5118
|
+
stats: wire.rows.stats,
|
|
5119
|
+
warnings: wire.rows.warnings
|
|
5120
|
+
};
|
|
5121
|
+
return {
|
|
5122
|
+
op,
|
|
5123
|
+
rowsAffected,
|
|
5124
|
+
returning,
|
|
5125
|
+
warnings: wire.warnings
|
|
5126
|
+
};
|
|
5127
|
+
}
|
|
5128
|
+
};
|
|
5129
|
+
|
|
4935
5130
|
// src/streaming/websocket-transport.ts
|
|
4936
5131
|
var import_msgpack = require("@msgpack/msgpack");
|
|
4937
5132
|
var DEFAULT_PING_INTERVAL_MS = 2e4;
|
|
@@ -4956,6 +5151,7 @@ var WebSocketTransport = class {
|
|
|
4956
5151
|
// Resolved/rejected when the auth handshake completes.
|
|
4957
5152
|
this._authResolve = null;
|
|
4958
5153
|
this._authReject = null;
|
|
5154
|
+
this._handshakeComplete = false;
|
|
4959
5155
|
this._serverUrl = serverUrl;
|
|
4960
5156
|
this._database = database;
|
|
4961
5157
|
this._authHandler = options.authHandler;
|
|
@@ -4965,6 +5161,11 @@ var WebSocketTransport = class {
|
|
|
4965
5161
|
this._maxMissedHeartbeats = options.maxMissedHeartbeats ?? DEFAULT_MAX_MISSED_HEARTBEATS;
|
|
4966
5162
|
this._enableCompression = options.enableCompression ?? true;
|
|
4967
5163
|
this._wireMode = options.wireMode ?? "json";
|
|
5164
|
+
this._authHandler?.setOnAccessTokenRefreshed((token) => {
|
|
5165
|
+
if (this._handshakeComplete && this._ws?.readyState === WS_READY_STATE_OPEN) {
|
|
5166
|
+
void this.send({ type: "re_auth", token });
|
|
5167
|
+
}
|
|
5168
|
+
});
|
|
4968
5169
|
}
|
|
4969
5170
|
// ─── Public API ────────────────────────────────────────────────────────────
|
|
4970
5171
|
/** Latest version number from the most recent server `heartbeat` message. */
|
|
@@ -5137,6 +5338,7 @@ var WebSocketTransport = class {
|
|
|
5137
5338
|
const resolve4 = this._authResolve;
|
|
5138
5339
|
this._authResolve = null;
|
|
5139
5340
|
this._authReject = null;
|
|
5341
|
+
this._handshakeComplete = true;
|
|
5140
5342
|
if (resolve4) resolve4();
|
|
5141
5343
|
break;
|
|
5142
5344
|
}
|
|
@@ -5163,10 +5365,15 @@ var WebSocketTransport = class {
|
|
|
5163
5365
|
break;
|
|
5164
5366
|
}
|
|
5165
5367
|
const id = "id" in msg ? msg.id : void 0;
|
|
5166
|
-
if (id !== void 0 && id !== null) {
|
|
5368
|
+
if (id !== void 0 && id !== null && id !== "") {
|
|
5167
5369
|
this._handlers.get(id)?.(msg);
|
|
5168
5370
|
} else {
|
|
5169
5371
|
this._handlers.get("__global__")?.(msg);
|
|
5372
|
+
for (const [key, handler] of this._handlers) {
|
|
5373
|
+
if (key !== "__global__") {
|
|
5374
|
+
handler(msg);
|
|
5375
|
+
}
|
|
5376
|
+
}
|
|
5170
5377
|
}
|
|
5171
5378
|
}
|
|
5172
5379
|
// ─── Internal: reconnect ──────────────────────────────────────────────────
|
|
@@ -5694,6 +5901,19 @@ var AoudaClient = class {
|
|
|
5694
5901
|
this.transport,
|
|
5695
5902
|
this.database
|
|
5696
5903
|
);
|
|
5904
|
+
const onNamedArtifactWarning = options.onNamedArtifactWarning ?? ((warning) => {
|
|
5905
|
+
console.warn(warning.message);
|
|
5906
|
+
});
|
|
5907
|
+
this._namedQueries = new NamedQueriesApi(
|
|
5908
|
+
this.transport,
|
|
5909
|
+
this.database,
|
|
5910
|
+
onNamedArtifactWarning
|
|
5911
|
+
);
|
|
5912
|
+
this._namedMutations = new NamedMutationsApi(
|
|
5913
|
+
this.transport,
|
|
5914
|
+
this.database,
|
|
5915
|
+
onNamedArtifactWarning
|
|
5916
|
+
);
|
|
5697
5917
|
}
|
|
5698
5918
|
/**
|
|
5699
5919
|
* Connects to the Aouda server.
|
|
@@ -5836,6 +6056,18 @@ var AoudaClient = class {
|
|
|
5836
6056
|
get materializedQueries() {
|
|
5837
6057
|
return this._materializedQueries;
|
|
5838
6058
|
}
|
|
6059
|
+
/**
|
|
6060
|
+
* Hash-only named-query execute and batch. Names are codegen aliases (D-5).
|
|
6061
|
+
*/
|
|
6062
|
+
get namedQueries() {
|
|
6063
|
+
return this._namedQueries;
|
|
6064
|
+
}
|
|
6065
|
+
/**
|
|
6066
|
+
* Hash-only named-mutation execute. No batch.
|
|
6067
|
+
*/
|
|
6068
|
+
get namedMutations() {
|
|
6069
|
+
return this._namedMutations;
|
|
6070
|
+
}
|
|
5839
6071
|
/**
|
|
5840
6072
|
* Access auth operations (signUp, signIn, signOut, refresh, me, changePassword).
|
|
5841
6073
|
* @returns The auth API.
|
|
@@ -5984,7 +6216,7 @@ Usage:
|
|
|
5984
6216
|
npx @aouda/client schema <command> [options]
|
|
5985
6217
|
|
|
5986
6218
|
Commands:
|
|
5987
|
-
generate Fetch schema from Aouda server and output TypeScript types
|
|
6219
|
+
generate Fetch schema from Aouda server and output TypeScript types (tables + named query/mutation hashes)
|
|
5988
6220
|
schema Schema management (diff, apply, export, validate, history, seed)
|
|
5989
6221
|
diff Show migration plan (desired vs current)
|
|
5990
6222
|
apply Apply schema (use --allow-destructive for drops; --dry-run to preview)
|