@rebasepro/server 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/rebase-server.js +4 -2
- package/dist/{GCSStorageController-CLIJXwGS.js → GCSStorageController-BSiP1c-f.js} +57 -29
- package/dist/GCSStorageController-BSiP1c-f.js.map +1 -0
- package/dist/{S3StorageController-Dcuf8lMA.js → S3StorageController-CAwFRgjV.js} +19 -7
- package/dist/S3StorageController-CAwFRgjV.js.map +1 -0
- package/dist/api/ast-schema-editor.d.ts +127 -1
- package/dist/api/errors.d.ts +9 -0
- package/dist/api/live-schema-routes.d.ts +52 -8
- package/dist/api/logs-routes.d.ts +39 -1
- package/dist/api/openapi-generator.d.ts +17 -0
- package/dist/api/rest/api-generator.d.ts +158 -42
- package/dist/api/rest/auth-collection-writes.d.ts +85 -0
- package/dist/api/rest/field-access-query.d.ts +6 -2
- package/dist/api/rest/idempotency.d.ts +7 -1
- package/dist/api/rest/nested-write-access.d.ts +46 -0
- package/dist/api/rest/write-validation.d.ts +34 -2
- package/dist/api/types.d.ts +17 -1
- package/dist/{ast-schema-editor-CslO8Oje.js → ast-schema-editor-CWqS_sLJ.js} +411 -13
- package/dist/ast-schema-editor-CWqS_sLJ.js.map +1 -0
- package/dist/auth/access.d.ts +105 -0
- package/dist/auth/adapter-middleware.d.ts +2 -1
- package/dist/auth/address-ownership.d.ts +68 -0
- package/dist/auth/admin-roles-route.d.ts +4 -2
- package/dist/auth/admin-roles.d.ts +17 -20
- package/dist/auth/admin-user-ops.d.ts +35 -2
- package/dist/auth/admin-users-route.d.ts +1 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +56 -55
- package/dist/auth/api-keys/api-key-routes.d.ts +41 -11
- package/dist/auth/api-keys/api-key-store.d.ts +31 -8
- package/dist/auth/api-keys/api-key-types.d.ts +14 -16
- package/dist/auth/api-keys/http-operation.d.ts +19 -0
- package/dist/auth/api-keys/index.d.ts +11 -11
- package/dist/auth/api-keys/key-grant.d.ts +41 -0
- package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
- package/dist/auth/auth-hooks.d.ts +50 -7
- package/dist/auth/builtin-auth-adapter.d.ts +8 -0
- package/dist/auth/captcha.d.ts +5 -0
- package/dist/auth/cookie-utils.d.ts +7 -0
- package/dist/auth/deliverable-address.d.ts +6 -0
- package/dist/auth/email-change-routes.d.ts +41 -0
- package/dist/auth/expired-token-sweep.d.ts +67 -0
- package/dist/auth/impersonation.d.ts +110 -0
- package/dist/auth/index.d.ts +4 -2
- package/dist/auth/interfaces.d.ts +146 -65
- package/dist/auth/jwt.d.ts +66 -3
- package/dist/auth/magic-link-routes.d.ts +2 -6
- package/dist/auth/mfa-routes.d.ts +2 -9
- package/dist/auth/middleware.d.ts +17 -5
- package/dist/auth/oauth-signin-policy.d.ts +25 -8
- package/dist/auth/otp-routes.d.ts +2 -6
- package/dist/auth/passwordless-signup.d.ts +27 -0
- package/dist/auth/platform-token.d.ts +122 -0
- package/dist/auth/rate-limiter.d.ts +72 -1
- package/dist/auth/routes.d.ts +45 -0
- package/dist/auth/scope-routes.d.ts +22 -0
- package/dist/auth/session-routes.d.ts +18 -6
- package/dist/auth/token-revocation.d.ts +53 -1
- package/dist/auth/verify-credential.d.ts +28 -0
- package/dist/{auth-CCDpk2rn.js → auth-DMLngxn_.js} +2712 -711
- package/dist/auth-DMLngxn_.js.map +1 -0
- package/dist/backend-DTAOsLQc.js +30 -0
- package/dist/backend-DTAOsLQc.js.map +1 -0
- package/dist/backup/backup-common.d.ts +29 -0
- package/dist/backup/backup-routes.d.ts +24 -4
- package/dist/backup/backup-schedule.d.ts +33 -0
- package/dist/backup/backup-storage.d.ts +14 -0
- package/dist/backup/index.d.ts +2 -0
- package/dist/backup-CN0s50D2.js +444 -0
- package/dist/backup-CN0s50D2.js.map +1 -0
- package/dist/boot/bundle.d.ts +19 -0
- package/dist/boot/driver.d.ts +10 -0
- package/dist/boot/env.d.ts +51 -6
- package/dist/boot/fetch-bundle.d.ts +18 -1
- package/dist/boot/rls-audit-option.d.ts +26 -0
- package/dist/boot/security-headers.d.ts +26 -0
- package/dist/boot/sources.d.ts +1 -0
- package/dist/boot/static-routing.d.ts +56 -0
- package/dist/collection_patch-BRu-BvDv.js +472 -0
- package/dist/collection_patch-BRu-BvDv.js.map +1 -0
- package/dist/{contract-routes-eLxV0le1.js → contract-routes-fz8i4pxs.js} +17 -4
- package/dist/contract-routes-fz8i4pxs.js.map +1 -0
- package/dist/cron/cron-routes.d.ts +7 -2
- package/dist/cron/cron-scheduler.d.ts +146 -21
- package/dist/cron/cron-store.d.ts +76 -8
- package/dist/cron/index.d.ts +1 -1
- package/dist/{cron-loader-CQjvjpEw.js → cron-loader-CwaANlOG.js} +4 -4
- package/dist/cron-loader-CwaANlOG.js.map +1 -0
- package/dist/cron-routes-Bc-SB0Se.js +96 -0
- package/dist/cron-routes-Bc-SB0Se.js.map +1 -0
- package/dist/{cron-scheduler-COPQxlEq.js → cron-scheduler-CYQgco86.js} +427 -83
- package/dist/cron-scheduler-CYQgco86.js.map +1 -0
- package/dist/{cron-store-BYGZFNWk.js → cron-store-D2Q9-Aco.js} +139 -23
- package/dist/cron-store-D2Q9-Aco.js.map +1 -0
- package/dist/{ddl-bootstrap-CfNvxMuK.js → ddl-bootstrap-BaqMSa4Y.js} +3 -26
- package/dist/ddl-bootstrap-BaqMSa4Y.js.map +1 -0
- package/dist/email/index.d.ts +2 -2
- package/dist/email/link-base.d.ts +5 -4
- package/dist/email/smtp-email-service.d.ts +13 -1
- package/dist/email/templates.d.ts +31 -0
- package/dist/email/types.d.ts +29 -2
- package/dist/env.d.ts +25 -7
- package/dist/{errors-DWsX4yTd.js → errors-D6_y86c5.js} +102 -8
- package/dist/errors-D6_y86c5.js.map +1 -0
- package/dist/{function-loader-xnbDAPfa.js → function-loader-D7o5Epjj.js} +2 -2
- package/dist/{function-loader-xnbDAPfa.js.map → function-loader-D7o5Epjj.js.map} +1 -1
- package/dist/{function-routes-Chet4-lB.js → function-routes-CaNG4waN.js} +24 -12
- package/dist/function-routes-CaNG4waN.js.map +1 -0
- package/dist/functions/context.d.ts +17 -6
- package/dist/functions/guards.d.ts +22 -5
- package/dist/functions/index.d.ts +2 -2
- package/dist/functions/index.js +90 -36
- package/dist/functions/index.js.map +1 -1
- package/dist/{history-recorder-BQmB0P_j.js → history-recorder-Nr8zLvoU.js} +9 -7
- package/dist/history-recorder-Nr8zLvoU.js.map +1 -0
- package/dist/{history-store-CetkrBBD.js → history-store-rcAm_xFR.js} +2 -2
- package/dist/{history-store-CetkrBBD.js.map → history-store-rcAm_xFR.js.map} +1 -1
- package/dist/index.d.ts +14 -4
- package/dist/index.es.js +5733 -1551
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +5 -2
- package/dist/init/health.d.ts +17 -2
- package/dist/init/shutdown.d.ts +18 -3
- package/dist/init.d.ts +54 -0
- package/dist/jobs/index.d.ts +2 -2
- package/dist/jobs/job-queue.d.ts +23 -2
- package/dist/jobs/job-store.d.ts +37 -5
- package/dist/jobs/types.d.ts +8 -6
- package/dist/{jobs-Bjr8DZAi.js → jobs-DqYNfquG.js} +306 -167
- package/dist/jobs-DqYNfquG.js.map +1 -0
- package/dist/{jwt-C4OW-DNq.js → jwt-R6bSPMjk.js} +114 -38
- package/dist/{jwt-C4OW-DNq.js.map → jwt-R6bSPMjk.js.map} +1 -1
- package/dist/{keys-Qfc4XieN.js → keys-GAVZqbqx.js} +18 -17
- package/dist/{keys-Qfc4XieN.js.map → keys-GAVZqbqx.js.map} +1 -1
- package/dist/{logger-DO2PZc4i.js → logger-D-S-hO5e.js} +26 -3
- package/dist/logger-D-S-hO5e.js.map +1 -0
- package/dist/{logs-routes-3EEzPjhl.js → logs-routes-DAdv37GI.js} +54 -11
- package/dist/logs-routes-DAdv37GI.js.map +1 -0
- package/dist/mcp/consent-page.d.ts +1 -1
- package/dist/mcp/mcp-routes.d.ts +45 -2
- package/dist/mcp/mcp-tools.d.ts +22 -10
- package/dist/mcp/oauth-metadata.d.ts +21 -16
- package/dist/mcp/oauth-routes.d.ts +34 -1
- package/dist/mcp/oauth-store.d.ts +29 -13
- package/dist/metrics/history-recorder.d.ts +1 -1
- package/dist/{openapi-generator-D8uFz-LW.js → openapi-generator-DAq_XVDu.js} +135 -22
- package/dist/openapi-generator-DAq_XVDu.js.map +1 -0
- package/dist/{proxy-Czngl3p9.js → proxy-qRlqeUmO.js} +2 -2
- package/dist/{proxy-Czngl3p9.js.map → proxy-qRlqeUmO.js.map} +1 -1
- package/dist/{query-parser-BleZmY18.js → query-parser-BgiKJKvc.js} +41 -82
- package/dist/query-parser-BgiKJKvc.js.map +1 -0
- package/dist/{request-timeout-C_4C2BeR.js → request-timeout-DgH7j8qO.js} +3 -3
- package/dist/{request-timeout-C_4C2BeR.js.map → request-timeout-DgH7j8qO.js.map} +1 -1
- package/dist/rls-audit/index.d.ts +4 -0
- package/dist/schema-edit/apply-schema-change.d.ts +63 -3
- package/dist/schema-edit/project-root.d.ts +3 -2
- package/dist/schema-edit/remote-source.d.ts +9 -4
- package/dist/{schema-editor-routes-DdLihzp0.js → schema-editor-routes-oIyuWl3L.js} +12 -7
- package/dist/schema-editor-routes-oIyuWl3L.js.map +1 -0
- package/dist/serve-spa.d.ts +58 -0
- package/dist/services/routed-realtime-service.d.ts +11 -0
- package/dist/soft-delete-params-BWPilMPF.js +59 -0
- package/dist/soft-delete-params-BWPilMPF.js.map +1 -0
- package/dist/{src-Br6ARbs6.js → src-CatHFUym.js} +439 -45
- package/dist/src-CatHFUym.js.map +1 -0
- package/dist/{src-1vL-I1Po.js → src-I3aG1PcY.js} +371 -81
- package/dist/src-I3aG1PcY.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +13 -1
- package/dist/storage/LocalStorageController.d.ts +2 -0
- package/dist/storage/S3StorageController.d.ts +2 -0
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/keys.d.ts +12 -0
- package/dist/storage/property-limits.d.ts +41 -6
- package/dist/storage/rendition-cache.d.ts +11 -1
- package/dist/storage/request-keys.d.ts +82 -0
- package/dist/storage/requested-object.d.ts +74 -0
- package/dist/storage/routes.d.ts +36 -18
- package/dist/storage/tus-handler.d.ts +30 -5
- package/dist/storage/types.d.ts +36 -1
- package/dist/types-BfKcm9do.js.map +1 -1
- package/dist/utils/logger.d.ts +12 -0
- package/package.json +9 -9
- package/dist/GCSStorageController-CLIJXwGS.js.map +0 -1
- package/dist/S3StorageController-Dcuf8lMA.js.map +0 -1
- package/dist/admin-roles-vYdp_Pil.js +0 -36
- package/dist/admin-roles-vYdp_Pil.js.map +0 -1
- package/dist/admin_block-DxKLmdiv.js +0 -206
- package/dist/admin_block-DxKLmdiv.js.map +0 -1
- package/dist/ast-schema-editor-CslO8Oje.js.map +0 -1
- package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
- package/dist/auth-CCDpk2rn.js.map +0 -1
- package/dist/backup-DzI9jLwc.js +0 -192
- package/dist/backup-DzI9jLwc.js.map +0 -1
- package/dist/contract-routes-eLxV0le1.js.map +0 -1
- package/dist/cron-loader-CQjvjpEw.js.map +0 -1
- package/dist/cron-routes-B7CRGfiq.js +0 -72
- package/dist/cron-routes-B7CRGfiq.js.map +0 -1
- package/dist/cron-scheduler-COPQxlEq.js.map +0 -1
- package/dist/cron-store-BYGZFNWk.js.map +0 -1
- package/dist/ddl-bootstrap-CfNvxMuK.js.map +0 -1
- package/dist/errors-DWsX4yTd.js.map +0 -1
- package/dist/function-routes-Chet4-lB.js.map +0 -1
- package/dist/history-recorder-BQmB0P_j.js.map +0 -1
- package/dist/jobs-Bjr8DZAi.js.map +0 -1
- package/dist/logger-DO2PZc4i.js.map +0 -1
- package/dist/logs-routes-3EEzPjhl.js.map +0 -1
- package/dist/openapi-generator-D8uFz-LW.js.map +0 -1
- package/dist/query-parser-BleZmY18.js.map +0 -1
- package/dist/schema-editor-routes-DdLihzp0.js.map +0 -1
- package/dist/src-1vL-I1Po.js.map +0 -1
- package/dist/src-Br6ARbs6.js.map +0 -1
|
@@ -2,8 +2,8 @@ import { createRequire as __rebaseCreateRequire } from "module";
|
|
|
2
2
|
import __rebaseProcess from "process";
|
|
3
3
|
globalThis.process ??= __rebaseProcess;
|
|
4
4
|
__rebaseCreateRequire(import.meta.url);
|
|
5
|
-
import "./src-
|
|
6
|
-
import {
|
|
5
|
+
import "./src-CatHFUym.js";
|
|
6
|
+
import { a as nestCollectionPatchPaths, l as nestAdminCollectionKeys, n as collectionPatchProblems, o as runtimeOnlyKeyIn, u as nestAdminPropertyKeys } from "./collection_patch-BRu-BvDv.js";
|
|
7
7
|
import * as fs$1 from "fs";
|
|
8
8
|
import * as path$1 from "path";
|
|
9
9
|
import { IndentationText, Node, Project, SyntaxKind } from "ts-morph";
|
|
@@ -130,6 +130,26 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
130
130
|
if (!resolved.startsWith(this.collectionsDir + path$1.sep) && resolved !== this.collectionsDir) throw new Error("Path traversal detected: resolved path is outside the collections directory.");
|
|
131
131
|
return resolved;
|
|
132
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Bring the in-memory project back in line with the disk.
|
|
135
|
+
*
|
|
136
|
+
* The source-only editor builds ONE of these at boot and serves every
|
|
137
|
+
* request with it, and ts-morph reads a file once and then trusts its
|
|
138
|
+
* copy. `project.save()` writes that copy back whole, so anything that
|
|
139
|
+
* reached the disk in between — the live editor's committed change (it
|
|
140
|
+
* builds a fresh editor per request), or the developer's own edit in the
|
|
141
|
+
* IDE — was overwritten by the boot-time copy plus this edit. In `rebase
|
|
142
|
+
* dev` the tsx-watch restart usually closed the window; outside watch mode
|
|
143
|
+
* the loss was permanent.
|
|
144
|
+
*
|
|
145
|
+
* Called at the top of every public method, so each edit applies to what
|
|
146
|
+
* is on disk now.
|
|
147
|
+
*/
|
|
148
|
+
syncWithDisk() {
|
|
149
|
+
for (const file of this.project.getSourceFiles()) if (!fs$1.existsSync(file.getFilePath())) this.project.removeSourceFile(file);
|
|
150
|
+
else file.refreshFromFileSystemSync();
|
|
151
|
+
if (fs$1.existsSync(this.collectionsDir)) this.project.addSourceFilesAtPaths(`${this.collectionsDir}/**/*.ts`);
|
|
152
|
+
}
|
|
133
153
|
getCollectionFile(collectionId) {
|
|
134
154
|
const safeId = this.sanitizeCollectionId(collectionId);
|
|
135
155
|
const filePath = this.safePath(`${safeId}.ts`);
|
|
@@ -212,6 +232,39 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
212
232
|
static isPlainObject(value) {
|
|
213
233
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof RawExpression);
|
|
214
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* The field that identifies each element of an array of objects, so an
|
|
237
|
+
* old element can be found again in the new array.
|
|
238
|
+
*
|
|
239
|
+
* Arrays travel whole (see `collection_patch.ts`), and an element's
|
|
240
|
+
* position is not its identity — adding an entity action at the front
|
|
241
|
+
* shifts every other one. `key`, `id`, `relationName`, `name`: the first
|
|
242
|
+
* one every new element carries, distinct, wins.
|
|
243
|
+
*/
|
|
244
|
+
static identityField(items) {
|
|
245
|
+
for (const field of [
|
|
246
|
+
"key",
|
|
247
|
+
"id",
|
|
248
|
+
"relationName",
|
|
249
|
+
"name",
|
|
250
|
+
"slug"
|
|
251
|
+
]) {
|
|
252
|
+
const values = items.map((item) => AstSchemaEditor.isPlainObject(item) ? item[field] : void 0);
|
|
253
|
+
if (values.every((value) => typeof value === "string" || typeof value === "number") && new Set(values).size === values.length) return field;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/** The old array's object elements, keyed by `field`'s literal value. */
|
|
257
|
+
oldElementsBy(oldArray, field) {
|
|
258
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
259
|
+
const array = oldArray.asKind(SyntaxKind.ArrayLiteralExpression);
|
|
260
|
+
for (const element of array?.getElements() ?? []) {
|
|
261
|
+
const object = element.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
262
|
+
const value = object ? this.initializerOf(object, field) : void 0;
|
|
263
|
+
const literal = value?.asKind(SyntaxKind.StringLiteral)?.getLiteralValue() ?? value?.asKind(SyntaxKind.NoSubstitutionTemplateLiteral)?.getLiteralValue() ?? value?.asKind(SyntaxKind.NumericLiteral)?.getText();
|
|
264
|
+
if (object && literal !== void 0) byKey.set(String(literal), object);
|
|
265
|
+
}
|
|
266
|
+
return byKey;
|
|
267
|
+
}
|
|
215
268
|
convertJsonToAstString(obj, indentLevel = 0, oldAstNode) {
|
|
216
269
|
const indentStr = " ";
|
|
217
270
|
const indent = indentStr.repeat(indentLevel);
|
|
@@ -222,14 +275,17 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
222
275
|
if (typeof obj === "number" || typeof obj === "boolean") return String(obj);
|
|
223
276
|
if (Array.isArray(obj)) {
|
|
224
277
|
if (obj.length === 0) return "[]";
|
|
225
|
-
|
|
278
|
+
const field = oldAstNode ? AstSchemaEditor.identityField(obj) : void 0;
|
|
279
|
+
const old = oldAstNode && field ? this.oldElementsBy(oldAstNode, field) : void 0;
|
|
280
|
+
return `[\n${innerIndent}${obj.map((item) => this.convertJsonToAstString(item, indentLevel + 1, old && field && AstSchemaEditor.isPlainObject(item) ? old.get(String(item[field])) : void 0)).join(`,\n${innerIndent}`)}\n${indent}]`;
|
|
226
281
|
}
|
|
227
282
|
if (typeof obj === "object") {
|
|
228
283
|
const record = obj;
|
|
229
284
|
const keys = Object.keys(record);
|
|
285
|
+
const oldObject = oldAstNode?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
230
286
|
const preservedProps = [];
|
|
231
|
-
if (
|
|
232
|
-
const oldProps =
|
|
287
|
+
if (oldObject) {
|
|
288
|
+
const oldProps = oldObject.getProperties();
|
|
233
289
|
for (const oldProp of oldProps) if (oldProp.isKind(SyntaxKind.PropertyAssignment)) {
|
|
234
290
|
let name = oldProp.getNameNode().getText();
|
|
235
291
|
if (name.startsWith("\"") && name.endsWith("\"")) name = name.slice(1, -1);
|
|
@@ -247,9 +303,9 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
247
303
|
return `{\n${innerIndent}${[...keys.map((key) => {
|
|
248
304
|
const keyStr = AstSchemaEditor.quoteKey(key);
|
|
249
305
|
let childAstNode;
|
|
250
|
-
if (
|
|
251
|
-
const oldProp = this.findProperty(
|
|
252
|
-
if (oldProp && oldProp.isKind(SyntaxKind.PropertyAssignment)) childAstNode = oldProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);
|
|
306
|
+
if (oldObject && (AstSchemaEditor.isPlainObject(record[key]) || Array.isArray(record[key]))) {
|
|
307
|
+
const oldProp = this.findProperty(oldObject, key);
|
|
308
|
+
if (oldProp && oldProp.isKind(SyntaxKind.PropertyAssignment)) childAstNode = oldProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression) ?? oldProp.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);
|
|
253
309
|
}
|
|
254
310
|
return `${keyStr}: ${this.convertJsonToAstString(record[key], indentLevel + 1, childAstNode)}`;
|
|
255
311
|
}), ...preservedProps].join(`,\n${innerIndent}`)}\n${indent}}`;
|
|
@@ -280,7 +336,31 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
280
336
|
});
|
|
281
337
|
}
|
|
282
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Drop whatever a refused edit left in memory.
|
|
341
|
+
*
|
|
342
|
+
* One editor serves every request, and `project.save()` writes every file
|
|
343
|
+
* with unsaved changes. So an edit refused halfway through — a relation with
|
|
344
|
+
* no target, found after the name was already rewritten and an import
|
|
345
|
+
* added — reached the disk with the next save of some other collection, and
|
|
346
|
+
* a file refused as it was being created appeared anyway.
|
|
347
|
+
*/
|
|
348
|
+
forgetUnsaved(collectionId) {
|
|
349
|
+
const file = this.project.getSourceFile(path$1.resolve(this.collectionsDir, `${collectionId}.ts`));
|
|
350
|
+
if (!file || file.isSaved()) return;
|
|
351
|
+
if (fs$1.existsSync(file.getFilePath())) file.refreshFromFileSystemSync();
|
|
352
|
+
else this.project.removeSourceFile(file);
|
|
353
|
+
}
|
|
283
354
|
async saveProperty(collectionId, propertyKey, propertyConfig) {
|
|
355
|
+
this.syncWithDisk();
|
|
356
|
+
try {
|
|
357
|
+
await this.writeProperty(collectionId, propertyKey, propertyConfig);
|
|
358
|
+
} catch (err) {
|
|
359
|
+
this.forgetUnsaved(collectionId);
|
|
360
|
+
throw err;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async writeProperty(collectionId, propertyKey, propertyConfig) {
|
|
284
364
|
const collectionObj = this.requireCollectionObject(collectionId);
|
|
285
365
|
const nestedConfig = nestAdminPropertyKeys(propertyConfig);
|
|
286
366
|
let propertiesProp = collectionObj.getProperty("properties");
|
|
@@ -293,7 +373,8 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
293
373
|
const existingProp = this.findProperty(propsObj, propertyKey);
|
|
294
374
|
let oldPropAstNode;
|
|
295
375
|
if (existingProp && existingProp.isKind(SyntaxKind.PropertyAssignment)) oldPropAstNode = existingProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);
|
|
296
|
-
const
|
|
376
|
+
const withTargets = this.relationTargetsAsSource(collectionId, this.getCollectionFile(collectionId), { [propertyKey]: nestedConfig }, propsObj)[propertyKey];
|
|
377
|
+
const newInitializer = this.convertJsonToAstString(withTargets, 2, oldPropAstNode);
|
|
297
378
|
if (existingProp) {
|
|
298
379
|
if (existingProp.isKind(SyntaxKind.PropertyAssignment)) existingProp.setInitializer(newInitializer);
|
|
299
380
|
} else propsObj.addPropertyAssignment({
|
|
@@ -306,6 +387,7 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
306
387
|
}
|
|
307
388
|
}
|
|
308
389
|
async deleteProperty(collectionId, propertyKey) {
|
|
390
|
+
this.syncWithDisk();
|
|
309
391
|
const propertiesProp = this.requireCollectionObject(collectionId).getProperty("properties");
|
|
310
392
|
if (propertiesProp) {
|
|
311
393
|
const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);
|
|
@@ -332,6 +414,191 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
332
414
|
* default, which in the scaffold is `access: "public"`.
|
|
333
415
|
*/
|
|
334
416
|
async saveCollection(collectionId, collectionData, options = {}) {
|
|
417
|
+
this.syncWithDisk();
|
|
418
|
+
const runtimeOnly = runtimeOnlyKeyIn(collectionData);
|
|
419
|
+
if (runtimeOnly) throw new Error(`Refusing to write "${collectionId}": it holds "${runtimeOnly}", which the runtime adds to a collection it has loaded and a collection file must not hold. Save what changed instead.`);
|
|
420
|
+
try {
|
|
421
|
+
await this.writeCollection(collectionId, collectionData, options);
|
|
422
|
+
} catch (err) {
|
|
423
|
+
this.forgetUnsaved(collectionId);
|
|
424
|
+
throw err;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Write what changed about a collection, and nothing else.
|
|
429
|
+
*
|
|
430
|
+
* The patch is the difference between the collection the panel loaded and
|
|
431
|
+
* the one it is saving (`diffCollections` in `@rebasepro/types`), so it
|
|
432
|
+
* names only keys the person changed. Each is written where it is; every
|
|
433
|
+
* other byte of the file — imports, comments, formatting, handlers,
|
|
434
|
+
* properties shared from another module — is left as it was.
|
|
435
|
+
*
|
|
436
|
+
* A key whose value in the file is code — `status: statusProperty`,
|
|
437
|
+
* `enum: LOCALE_ENUM`, a spread — is not overwritten with the JSON the
|
|
438
|
+
* panel saw of it, which is what the whole-collection save did. The edit
|
|
439
|
+
* is refused, naming the code, so it can be made where that code lives.
|
|
440
|
+
*
|
|
441
|
+
* All or nothing: a refused operation leaves the file as it was, including
|
|
442
|
+
* the operations before it.
|
|
443
|
+
*/
|
|
444
|
+
async applyPatch(collectionId, patch) {
|
|
445
|
+
this.syncWithDisk();
|
|
446
|
+
const problems = collectionPatchProblems(patch);
|
|
447
|
+
if (problems.length > 0) throw new Error(`Refusing to edit "${collectionId}": ${problems.join(" ")}`);
|
|
448
|
+
try {
|
|
449
|
+
await this.writePatch(collectionId, nestCollectionPatchPaths(patch));
|
|
450
|
+
} catch (err) {
|
|
451
|
+
this.forgetUnsaved(collectionId);
|
|
452
|
+
throw err;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
async writePatch(collectionId, patch) {
|
|
456
|
+
this.requireCollectionObject(collectionId);
|
|
457
|
+
const file = this.getCollectionFile(collectionId);
|
|
458
|
+
for (const op of patch) this.applyPatchOp(collectionId, file, this.requireCollectionObject(collectionId), op);
|
|
459
|
+
await this.project.save();
|
|
460
|
+
}
|
|
461
|
+
applyPatchOp(collectionId, file, collectionObj, op) {
|
|
462
|
+
const { path: keys } = op;
|
|
463
|
+
const dotted = (end) => keys.slice(0, end).join(".");
|
|
464
|
+
let target = collectionObj;
|
|
465
|
+
for (let depth = 0; depth < keys.length - 1; depth++) {
|
|
466
|
+
const key = keys[depth];
|
|
467
|
+
const member = this.findMember(target, key);
|
|
468
|
+
if (!member) {
|
|
469
|
+
const spread = this.spreadIn(target);
|
|
470
|
+
if (spread) throw this.codeRefusal(collectionId, file, dotted(depth + 1), spread, "comes from");
|
|
471
|
+
if (op.op === "remove") return;
|
|
472
|
+
target = target.addPropertyAssignment({
|
|
473
|
+
name: AstSchemaEditor.quoteKey(key),
|
|
474
|
+
initializer: "{}"
|
|
475
|
+
}).getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const value = member.isKind(SyntaxKind.PropertyAssignment) ? AstSchemaEditor.unwrapExpression(member.getInitializer()) : void 0;
|
|
479
|
+
const next = value?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
480
|
+
if (!next) throw this.codeRefusal(collectionId, file, dotted(depth + 1), value ?? member, "is");
|
|
481
|
+
target = next;
|
|
482
|
+
}
|
|
483
|
+
const key = keys[keys.length - 1];
|
|
484
|
+
const member = this.findMember(target, key);
|
|
485
|
+
if (op.op === "remove") {
|
|
486
|
+
if (member) {
|
|
487
|
+
member.remove();
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const spread = this.spreadIn(target);
|
|
491
|
+
if (spread) throw this.codeRefusal(collectionId, file, dotted(keys.length), spread, "comes from");
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (keys.length === 1 && key === "relations") {
|
|
495
|
+
this.writeRelations(collectionId, file, collectionObj, op.value);
|
|
496
|
+
this.findMember(collectionObj, "relations")?.formatText();
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const old = member?.isKind(SyntaxKind.PropertyAssignment) ? member.getInitializer() : void 0;
|
|
500
|
+
if (member && (!old || !AstSchemaEditor.isLiteralValue(old))) throw this.codeRefusal(collectionId, file, dotted(keys.length), old ?? member, "is");
|
|
501
|
+
if (old?.isKind(SyntaxKind.ArrayLiteralExpression)) {
|
|
502
|
+
const code = old.getElements().find((element) => !AstSchemaEditor.isLiteralValue(element));
|
|
503
|
+
if (code) throw this.codeRefusal(collectionId, file, dotted(keys.length), code, "holds");
|
|
504
|
+
}
|
|
505
|
+
const value = this.patchValueAsSource(collectionId, file, keys, op.value, old);
|
|
506
|
+
const text = this.convertJsonToAstString(value, 0, old);
|
|
507
|
+
(member?.isKind(SyntaxKind.PropertyAssignment) ? member.setInitializer(text) : target.addPropertyAssignment({
|
|
508
|
+
name: AstSchemaEditor.quoteKey(key),
|
|
509
|
+
initializer: text
|
|
510
|
+
})).formatText();
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* A patched value with every relation target it carries turned back into
|
|
514
|
+
* source — `() => xCollection` plus its import — according to where the
|
|
515
|
+
* path lands: a relation's `target`, a relation, a property, or a map of
|
|
516
|
+
* properties.
|
|
517
|
+
*/
|
|
518
|
+
patchValueAsSource(collectionId, file, keys, value, old) {
|
|
519
|
+
let position = "collection";
|
|
520
|
+
for (const key of keys) switch (position) {
|
|
521
|
+
case "collection":
|
|
522
|
+
position = key === "properties" ? "properties" : "other";
|
|
523
|
+
break;
|
|
524
|
+
case "properties":
|
|
525
|
+
position = "property";
|
|
526
|
+
break;
|
|
527
|
+
case "property":
|
|
528
|
+
position = key === "properties" ? "properties" : key === "of" ? "property" : key === "oneOf" ? "oneOf" : key === "relation" ? "relation" : "other";
|
|
529
|
+
break;
|
|
530
|
+
case "oneOf":
|
|
531
|
+
position = key === "properties" ? "properties" : "other";
|
|
532
|
+
break;
|
|
533
|
+
case "relation":
|
|
534
|
+
if (key === "target" && typeof value === "string" && value.trim().length > 0) {
|
|
535
|
+
const trimmed = value.trim();
|
|
536
|
+
return new RawExpression(ARROW_TO_IDENTIFIER.test(trimmed) ? trimmed : this.targetThunk(file, trimmed));
|
|
537
|
+
}
|
|
538
|
+
position = "other";
|
|
539
|
+
break;
|
|
540
|
+
default: position = "other";
|
|
541
|
+
}
|
|
542
|
+
const oldObject = old?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
543
|
+
const where = keys.join(".");
|
|
544
|
+
if (position === "property") return this.propertyRelationTargetsAsSource(collectionId, file, value, oldObject, where);
|
|
545
|
+
if (position === "properties") return this.relationTargetsAsSource(collectionId, file, value, oldObject, where);
|
|
546
|
+
if (position === "relation" && AstSchemaEditor.isPlainObject(value)) {
|
|
547
|
+
const wrapped = this.propertyRelationTargetsAsSource(collectionId, file, {
|
|
548
|
+
type: "relation",
|
|
549
|
+
relation: value
|
|
550
|
+
}, oldObject ? void 0 : void 0, where);
|
|
551
|
+
const relation = AstSchemaEditor.isPlainObject(wrapped) ? wrapped.relation : value;
|
|
552
|
+
if (AstSchemaEditor.isPlainObject(relation) && !("target" in relation) && !(oldObject && this.findProperty(oldObject, "target"))) throw new Error(`Relation "${where}" on collection "${collectionId}" has no target collection. Pick one before saving.`);
|
|
553
|
+
return relation;
|
|
554
|
+
}
|
|
555
|
+
return value;
|
|
556
|
+
}
|
|
557
|
+
/** A member of an object literal by name: an assignment, a shorthand, a method. */
|
|
558
|
+
findMember(obj, name) {
|
|
559
|
+
return obj.getProperties().find((member) => {
|
|
560
|
+
if (member.isKind(SyntaxKind.SpreadAssignment)) return false;
|
|
561
|
+
const memberName = member.getName();
|
|
562
|
+
return memberName === name || memberName === JSON.stringify(name) || memberName === `'${name}'`;
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
spreadIn(obj) {
|
|
566
|
+
return obj.getProperties().find((member) => member.isKind(SyntaxKind.SpreadAssignment));
|
|
567
|
+
}
|
|
568
|
+
/** `x as const`, `x satisfies T`, `(x)` — the value inside. */
|
|
569
|
+
static unwrapExpression(node) {
|
|
570
|
+
if (!node) return void 0;
|
|
571
|
+
if (Node.isAsExpression(node) || Node.isSatisfiesExpression(node) || Node.isParenthesizedExpression(node)) return AstSchemaEditor.unwrapExpression(node.getExpression());
|
|
572
|
+
return node;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Whether a value in the file is data the panel's JSON fully describes —
|
|
576
|
+
* a literal — rather than code it saw only the result of.
|
|
577
|
+
*/
|
|
578
|
+
static isLiteralValue(node) {
|
|
579
|
+
const value = AstSchemaEditor.unwrapExpression(node);
|
|
580
|
+
if (!value) return false;
|
|
581
|
+
switch (value.getKind()) {
|
|
582
|
+
case SyntaxKind.ObjectLiteralExpression:
|
|
583
|
+
case SyntaxKind.ArrayLiteralExpression:
|
|
584
|
+
case SyntaxKind.StringLiteral:
|
|
585
|
+
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
586
|
+
case SyntaxKind.NumericLiteral:
|
|
587
|
+
case SyntaxKind.TrueKeyword:
|
|
588
|
+
case SyntaxKind.FalseKeyword:
|
|
589
|
+
case SyntaxKind.NullKeyword: return true;
|
|
590
|
+
case SyntaxKind.PrefixUnaryExpression: return value.asKindOrThrow(SyntaxKind.PrefixUnaryExpression).getOperand().isKind(SyntaxKind.NumericLiteral);
|
|
591
|
+
case SyntaxKind.Identifier: return value.getText() === "undefined";
|
|
592
|
+
default: return false;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/** "`properties.status` is `statusProperty` in posts.ts…" */
|
|
596
|
+
codeRefusal(collectionId, file, where, code, verb) {
|
|
597
|
+
const text = code.getText();
|
|
598
|
+
const shown = text.length > 60 ? `${text.slice(0, 57)}…` : text;
|
|
599
|
+
return /* @__PURE__ */ new Error(`\`${where}\` ${verb} \`${shown}\` in ${path$1.basename(file.getFilePath())}, which is code the editor cannot change without losing what it does. Change "${collectionId}" there instead.`);
|
|
600
|
+
}
|
|
601
|
+
async writeCollection(collectionId, collectionData, options) {
|
|
335
602
|
const partial = options.partial === true;
|
|
336
603
|
let file = this.getCollectionFile(collectionId);
|
|
337
604
|
const collectionObj = file ? this.getCollectionObject(collectionId) : null;
|
|
@@ -342,7 +609,14 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
342
609
|
const newFilePath = this.safePath(`${safeId}.ts`);
|
|
343
610
|
if (fs$1.existsSync(newFilePath)) throw new Error(`Refusing to overwrite ${newFilePath}: a file for "${collectionId}" already exists but could not be parsed.`);
|
|
344
611
|
const varName = `${AstSchemaEditor.collectionVarName(safeId)}Collection`;
|
|
345
|
-
file = this.project.createSourceFile(newFilePath, `import { CollectionConfig } from "@rebasepro/types";\n\nconst ${varName}: CollectionConfig =
|
|
612
|
+
file = this.project.createSourceFile(newFilePath, `import { CollectionConfig } from "@rebasepro/types";\n\nconst ${varName}: CollectionConfig = {};\n\nexport default ${varName};\n`);
|
|
613
|
+
const { relations, ...rest } = nestAdminKeysDeep(collectionData);
|
|
614
|
+
const data = "properties" in rest ? {
|
|
615
|
+
...rest,
|
|
616
|
+
properties: this.relationTargetsAsSource(collectionId, file, rest.properties, void 0)
|
|
617
|
+
} : rest;
|
|
618
|
+
file.getVariableDeclarationOrThrow(varName).setInitializer(this.convertJsonToAstString(data));
|
|
619
|
+
if (relations !== void 0) this.writeRelations(collectionId, file, this.requireCollectionObject(collectionId), relations);
|
|
346
620
|
} else {
|
|
347
621
|
if (!partial) {
|
|
348
622
|
if (!("securityRules" in collectionData) || collectionData.securityRules === void 0 || Array.isArray(collectionData.securityRules) && collectionData.securityRules.length === 0) {
|
|
@@ -360,6 +634,7 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
360
634
|
const prop = collectionObj.getProperty(key);
|
|
361
635
|
let oldAstNode;
|
|
362
636
|
if (prop && prop.isKind(SyntaxKind.PropertyAssignment)) oldAstNode = prop.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);
|
|
637
|
+
if (key === "properties") collectionData[key] = this.relationTargetsAsSource(collectionId, file, collectionData[key], oldAstNode);
|
|
363
638
|
if (partial && oldAstNode && AstSchemaEditor.isPlainObject(collectionData[key])) {
|
|
364
639
|
this.mergeIntoObjectLiteral(oldAstNode, collectionData[key], 2);
|
|
365
640
|
continue;
|
|
@@ -376,6 +651,66 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
376
651
|
await this.project.save();
|
|
377
652
|
}
|
|
378
653
|
/**
|
|
654
|
+
* Every property relation's `target`, as source.
|
|
655
|
+
*
|
|
656
|
+
* The collection-level `relations` array has {@link writeRelations}. A
|
|
657
|
+
* relation declared on a property went through `convertJsonToAstString`
|
|
658
|
+
* like any other value, so a target the property form had picked — a slug,
|
|
659
|
+
* which is all JSON can carry — was written as the string literal
|
|
660
|
+
* `target: "authors"`, and the next boot threw in `resolveRelation` for every
|
|
661
|
+
* collection in the directory. The same rule applies here as there: a slug
|
|
662
|
+
* becomes `() => xCollection` plus its import, an arrow returning one
|
|
663
|
+
* identifier is written through, and a relation sent without a target keeps
|
|
664
|
+
* the one already in the file — or is refused, when there is none.
|
|
665
|
+
*
|
|
666
|
+
* Walks into `map` properties, an array's `of` and a block's `oneOf`, which
|
|
667
|
+
* the registry resolves relations in too. `oldProperties` is the same
|
|
668
|
+
* properties object in the file, when there is one.
|
|
669
|
+
*/
|
|
670
|
+
relationTargetsAsSource(collectionId, file, properties, oldProperties, path = "properties") {
|
|
671
|
+
if (!AstSchemaEditor.isPlainObject(properties)) return {};
|
|
672
|
+
return Object.fromEntries(Object.entries(properties).map(([key, property]) => [key, this.propertyRelationTargetsAsSource(collectionId, file, property, oldProperties ? this.objectAt(oldProperties, key) : void 0, `${path}.${key}`)]));
|
|
673
|
+
}
|
|
674
|
+
propertyRelationTargetsAsSource(collectionId, file, property, oldProperty, path) {
|
|
675
|
+
if (!AstSchemaEditor.isPlainObject(property)) return property;
|
|
676
|
+
const next = { ...property };
|
|
677
|
+
if (property.type === "relation" && AstSchemaEditor.isPlainObject(property.relation)) {
|
|
678
|
+
const relation = { ...property.relation };
|
|
679
|
+
const rawTarget = relation.target;
|
|
680
|
+
if (typeof rawTarget === "string" && rawTarget.trim().length > 0) {
|
|
681
|
+
const trimmed = rawTarget.trim();
|
|
682
|
+
relation.target = new RawExpression(ARROW_TO_IDENTIFIER.test(trimmed) ? trimmed : this.targetThunk(file, trimmed));
|
|
683
|
+
} else {
|
|
684
|
+
const oldRelation = oldProperty ? this.objectAt(oldProperty, "relation") : void 0;
|
|
685
|
+
if (!oldRelation || !this.findProperty(oldRelation, "target")) throw new Error(`Relation property "${path}" on collection "${collectionId}" has no target collection. Pick one before saving.`);
|
|
686
|
+
delete relation.target;
|
|
687
|
+
}
|
|
688
|
+
next.relation = relation;
|
|
689
|
+
}
|
|
690
|
+
if (AstSchemaEditor.isPlainObject(property.properties)) next.properties = this.relationTargetsAsSource(collectionId, file, property.properties, oldProperty ? this.objectAt(oldProperty, "properties") : void 0, path);
|
|
691
|
+
if (Array.isArray(property.of)) {
|
|
692
|
+
const oldOf = oldProperty ? this.initializerOf(oldProperty, "of")?.asKind(SyntaxKind.ArrayLiteralExpression) : void 0;
|
|
693
|
+
next.of = property.of.map((item, index) => this.propertyRelationTargetsAsSource(collectionId, file, item, oldOf?.getElements()[index]?.asKind(SyntaxKind.ObjectLiteralExpression), `${path}.of[${index}]`));
|
|
694
|
+
} else if (AstSchemaEditor.isPlainObject(property.of)) next.of = this.propertyRelationTargetsAsSource(collectionId, file, property.of, oldProperty ? this.objectAt(oldProperty, "of") : void 0, `${path}.of`);
|
|
695
|
+
if (AstSchemaEditor.isPlainObject(property.oneOf) && AstSchemaEditor.isPlainObject(property.oneOf.properties)) {
|
|
696
|
+
const oldOneOf = oldProperty ? this.objectAt(oldProperty, "oneOf") : void 0;
|
|
697
|
+
next.oneOf = {
|
|
698
|
+
...property.oneOf,
|
|
699
|
+
properties: this.relationTargetsAsSource(collectionId, file, property.oneOf.properties, oldOneOf ? this.objectAt(oldOneOf, "properties") : void 0, `${path}.oneOf`)
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
return next;
|
|
703
|
+
}
|
|
704
|
+
/** The initializer of `obj[key]` in the file, when it is written as a property assignment. */
|
|
705
|
+
initializerOf(obj, key) {
|
|
706
|
+
const prop = this.findProperty(obj, key);
|
|
707
|
+
return prop && prop.isKind(SyntaxKind.PropertyAssignment) ? prop.getInitializer() : void 0;
|
|
708
|
+
}
|
|
709
|
+
/** `obj[key]` in the file, when it is an object literal. */
|
|
710
|
+
objectAt(obj, key) {
|
|
711
|
+
return this.initializerOf(obj, key)?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
379
714
|
* Write the collection-level `relations` array.
|
|
380
715
|
*
|
|
381
716
|
* Relations are the one key whose values are not all data: `target` is a
|
|
@@ -454,12 +789,75 @@ var AstSchemaEditor = class AstSchemaEditor {
|
|
|
454
789
|
if (expr && expr.getKind() === SyntaxKind.Identifier) return expr.getText();
|
|
455
790
|
for (const varDecl of file.getVariableDeclarations()) if (this.unwrapCollectionObject(varDecl.getInitializer())) return varDecl.getName();
|
|
456
791
|
}
|
|
792
|
+
/**
|
|
793
|
+
* Delete a collection's file, and take it out of `index.ts`.
|
|
794
|
+
*
|
|
795
|
+
* This used to unlink `<slug>.ts` and stop. Every scaffold's `index.ts`
|
|
796
|
+
* imports every collection, so the next boot failed for the whole project
|
|
797
|
+
* ("Cannot find module …/tags"), and a collection another one linked to
|
|
798
|
+
* took that one down with it. Now:
|
|
799
|
+
*
|
|
800
|
+
* - a file any other module imports is refused, naming the importers — a
|
|
801
|
+
* relation to it has to go first, and that is a change the person makes;
|
|
802
|
+
* - `index.ts`'s import of it is removed, and so is each place it is listed
|
|
803
|
+
* in an array; any other use there is refused, naming the line;
|
|
804
|
+
* - the file is deleted.
|
|
805
|
+
*
|
|
806
|
+
* Only the source. The table and its rows are not touched.
|
|
807
|
+
*
|
|
808
|
+
* Returns what changed, as absolute paths, so the caller can commit it.
|
|
809
|
+
*/
|
|
457
810
|
async deleteCollection(collectionId) {
|
|
458
|
-
|
|
459
|
-
|
|
811
|
+
this.syncWithDisk();
|
|
812
|
+
const target = this.getCollectionFile(collectionId);
|
|
813
|
+
if (!target) throw new Error(`Collection "${collectionId}" has no file at ${path$1.join(this.collectionsDir, `${collectionId}.ts`)}.`);
|
|
814
|
+
const targetPath = target.getFilePath();
|
|
815
|
+
const indexPath = path$1.join(this.collectionsDir, "index.ts");
|
|
816
|
+
const importers = this.project.getSourceFiles().filter((file) => file !== target && this.declarationsOf(file, targetPath).length > 0);
|
|
817
|
+
const others = importers.filter((file) => file.getFilePath() !== indexPath);
|
|
818
|
+
if (others.length > 0) throw new Error(`Cannot delete "${collectionId}": ${others.map((file) => path$1.basename(file.getFilePath())).join(", ")} ${others.length === 1 ? "imports" : "import"} it — a relation to it, most likely. Remove that first; deleting the file would stop every collection from loading.`);
|
|
819
|
+
const changed = [];
|
|
820
|
+
const index = importers.find((file) => file.getFilePath() === indexPath);
|
|
821
|
+
try {
|
|
822
|
+
if (index) {
|
|
823
|
+
for (const declaration of this.declarationsOf(index, targetPath)) {
|
|
824
|
+
const names = declaration.isKind(SyntaxKind.ImportDeclaration) ? [declaration.getDefaultImport()?.getText(), ...declaration.getNamedImports().map((named) => (named.getAliasNode() ?? named.getNameNode()).getText())].filter((name) => Boolean(name)) : [];
|
|
825
|
+
for (const name of names) {
|
|
826
|
+
const uses = index.getDescendantsOfKind(SyntaxKind.Identifier).filter((identifier) => identifier.getText() === name && !identifier.getFirstAncestorByKind(SyntaxKind.ImportDeclaration));
|
|
827
|
+
for (const use of uses) {
|
|
828
|
+
const array = use.getParentIfKind(SyntaxKind.ArrayLiteralExpression);
|
|
829
|
+
if (!array) throw new Error(`Cannot delete "${collectionId}": index.ts uses \`${name}\` at line ${use.getStartLineNumber()} in a way the editor cannot remove. Remove it there first.`);
|
|
830
|
+
array.removeElement(use);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
declaration.remove();
|
|
834
|
+
}
|
|
835
|
+
changed.push(index.getFilePath());
|
|
836
|
+
}
|
|
837
|
+
target.delete();
|
|
838
|
+
await this.project.save();
|
|
839
|
+
} catch (err) {
|
|
840
|
+
if (index && !index.isSaved()) index.refreshFromFileSystemSync();
|
|
841
|
+
this.syncWithDisk();
|
|
842
|
+
throw err;
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
deleted: targetPath,
|
|
846
|
+
changed
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
/** The import and export declarations in `file` whose relative specifier resolves to `targetPath`. */
|
|
850
|
+
declarationsOf(file, targetPath) {
|
|
851
|
+
const stem = (absolute) => absolute.replace(/\.(ts|tsx|js|mjs|cjs)$/, "");
|
|
852
|
+
const target = stem(targetPath);
|
|
853
|
+
return [...file.getImportDeclarations(), ...file.getExportDeclarations()].filter((declaration) => {
|
|
854
|
+
const specifier = declaration.getModuleSpecifierValue();
|
|
855
|
+
if (!specifier || !specifier.startsWith(".")) return false;
|
|
856
|
+
return stem(path$1.resolve(path$1.dirname(file.getFilePath()), specifier)) === target;
|
|
857
|
+
});
|
|
460
858
|
}
|
|
461
859
|
};
|
|
462
860
|
//#endregion
|
|
463
861
|
export { AstSchemaEditor };
|
|
464
862
|
|
|
465
|
-
//# sourceMappingURL=ast-schema-editor-
|
|
863
|
+
//# sourceMappingURL=ast-schema-editor-CWqS_sLJ.js.map
|