@openinc/parse-server-opendash 4.1.1 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/featuremap.json +27 -12
- package/dist/features/config/helper/customOptions.js +9 -0
- package/dist/features/config/helper/isFeatureEnabled.d.ts +1 -1
- package/dist/features/config/helper/isFeatureEnabled.js +2 -0
- package/dist/features/meta/index.d.ts +1 -0
- package/dist/features/meta/index.js +5 -0
- package/dist/features/meta/services/registerMetaContext.d.ts +29 -0
- package/dist/features/meta/services/registerMetaContext.js +208 -0
- package/dist/features/permissions/types/Permissions.d.ts +24 -1
- package/dist/features/permissions/types/Permissions.js +34 -0
- package/dist/features/ruleset/applyRuleSet.d.ts +41 -0
- package/dist/features/ruleset/applyRuleSet.js +81 -0
- package/dist/features/schema/helper/defaultHandler.js +13 -0
- package/dist/features/schema/helper/migrationMode.d.ts +26 -0
- package/dist/features/schema/helper/migrationMode.js +48 -0
- package/dist/features/schema/index.d.ts +1 -0
- package/dist/features/schema/index.js +3 -1
- package/dist/features/schema/services/beforeSaveHook.js +21 -0
- package/dist/features/translations/services/initTranslation.js +1 -1
- package/dist/hooks/Chat_Message.d.ts +1 -0
- package/dist/hooks/Chat_Message.js +179 -0
- package/dist/hooks/Meta_Config.js +19 -10
- package/dist/hooks/Meta_Context.d.ts +6 -0
- package/dist/hooks/Meta_Context.js +16 -0
- package/dist/hooks/Meta_Entry.js +20 -10
- package/dist/hooks/Service_Equipment.d.ts +1 -0
- package/dist/hooks/Service_Equipment.js +38 -0
- package/dist/hooks/Service_FormConfig.d.ts +1 -0
- package/dist/hooks/Service_FormConfig.js +17 -0
- package/dist/hooks/Service_Project.d.ts +1 -0
- package/dist/hooks/Service_Project.js +17 -0
- package/dist/hooks/Service_Schedule.d.ts +1 -0
- package/dist/hooks/Service_Schedule.js +24 -0
- package/dist/hooks/Service_Schedule_Execution.d.ts +1 -0
- package/dist/hooks/Service_Schedule_Execution.js +17 -0
- package/dist/hooks/Service_Schedule_Template.d.ts +1 -0
- package/dist/hooks/Service_Schedule_Template.js +17 -0
- package/dist/hooks/Service_Status.d.ts +1 -0
- package/dist/hooks/Service_Status.js +32 -0
- package/dist/hooks/Service_Tag.d.ts +1 -0
- package/dist/hooks/Service_Tag.js +168 -0
- package/dist/hooks/Service_Ticket.d.ts +1 -0
- package/dist/hooks/Service_Ticket.js +433 -0
- package/dist/hooks/Service_TriggerState.d.ts +1 -0
- package/dist/hooks/Service_TriggerState.js +17 -0
- package/dist/hooks/_ChangeLog.js +27 -6
- package/dist/i18n/de-DE/chat.json +18 -0
- package/dist/i18n/de-DE/openservice.json +14 -0
- package/dist/i18n/en-US/chat.json +18 -0
- package/dist/i18n/en-US/openservice.json +14 -0
- package/dist/scripts/backfill-meta-contexts.d.ts +1 -0
- package/dist/scripts/backfill-meta-contexts.js +100 -0
- package/dist/scripts/migration/lib/acl.d.ts +52 -0
- package/dist/scripts/migration/lib/acl.js +79 -0
- package/dist/scripts/migration/lib/articleMeta.d.ts +47 -0
- package/dist/scripts/migration/lib/articleMeta.js +132 -0
- package/dist/scripts/migration/lib/env.d.ts +56 -0
- package/dist/scripts/migration/lib/env.js +106 -0
- package/dist/scripts/migration/lib/latest.d.ts +47 -0
- package/dist/scripts/migration/lib/latest.js +115 -0
- package/dist/scripts/migration/lib/map.d.ts +53 -0
- package/dist/scripts/migration/lib/map.js +113 -0
- package/dist/scripts/migration/lib/material.d.ts +47 -0
- package/dist/scripts/migration/lib/material.js +51 -0
- package/dist/scripts/migration/lib/message.d.ts +78 -0
- package/dist/scripts/migration/lib/message.js +115 -0
- package/dist/scripts/migration/lib/pass.d.ts +67 -0
- package/dist/scripts/migration/lib/pass.js +89 -0
- package/dist/scripts/migration/lib/ptr.d.ts +32 -0
- package/dist/scripts/migration/lib/ptr.js +38 -0
- package/dist/scripts/migration/lib/report.d.ts +41 -0
- package/dist/scripts/migration/lib/report.js +78 -0
- package/dist/scripts/migration/lib/rest.d.ts +47 -0
- package/dist/scripts/migration/lib/rest.js +68 -0
- package/dist/scripts/migration/lib/ticket.d.ts +72 -0
- package/dist/scripts/migration/lib/ticket.js +192 -0
- package/dist/scripts/migration/passes/01-statuses.d.ts +12 -0
- package/dist/scripts/migration/passes/01-statuses.js +122 -0
- package/dist/scripts/migration/passes/02-tags.d.ts +13 -0
- package/dist/scripts/migration/passes/02-tags.js +150 -0
- package/dist/scripts/migration/passes/03-equipment.d.ts +14 -0
- package/dist/scripts/migration/passes/03-equipment.js +176 -0
- package/dist/scripts/migration/passes/04-projects.d.ts +27 -0
- package/dist/scripts/migration/passes/04-projects.js +241 -0
- package/dist/scripts/migration/passes/05-articles.d.ts +25 -0
- package/dist/scripts/migration/passes/05-articles.js +203 -0
- package/dist/scripts/migration/passes/06-formconfigs.d.ts +17 -0
- package/dist/scripts/migration/passes/06-formconfigs.js +166 -0
- package/dist/scripts/migration/passes/07-meta-configs.d.ts +46 -0
- package/dist/scripts/migration/passes/07-meta-configs.js +185 -0
- package/dist/scripts/migration/passes/08-tickets.d.ts +27 -0
- package/dist/scripts/migration/passes/08-tickets.js +353 -0
- package/dist/scripts/migration/passes/09-ticket-meta.d.ts +12 -0
- package/dist/scripts/migration/passes/09-ticket-meta.js +136 -0
- package/dist/scripts/migration/passes/10-chat.d.ts +17 -0
- package/dist/scripts/migration/passes/10-chat.js +230 -0
- package/dist/scripts/migration/passes/11-assets.d.ts +17 -0
- package/dist/scripts/migration/passes/11-assets.js +93 -0
- package/dist/scripts/migration/passes/12-history.d.ts +24 -0
- package/dist/scripts/migration/passes/12-history.js +332 -0
- package/dist/scripts/migration/run.d.ts +1 -0
- package/dist/scripts/migration/run.js +148 -0
- package/dist/scripts/migration/verify.d.ts +1 -0
- package/dist/scripts/migration/verify.js +620 -0
- package/dist/scripts/seed-v1-testdata.d.ts +41 -0
- package/dist/scripts/seed-v1-testdata.js +543 -0
- package/dist/types/BDE_Article.d.ts +3 -0
- package/dist/types/BDE_Article.js +6 -0
- package/dist/types/Chat_Message.d.ts +49 -0
- package/dist/types/Chat_Message.js +87 -0
- package/dist/types/MES_RessourceOverview.d.ts +38 -0
- package/dist/types/MES_RessourceOverview.js +63 -0
- package/dist/types/Meta_Context.d.ts +43 -0
- package/dist/types/Meta_Context.js +75 -0
- package/dist/types/Service_Equipment.d.ts +33 -0
- package/dist/types/Service_Equipment.js +57 -0
- package/dist/types/Service_FormConfig.d.ts +33 -0
- package/dist/types/Service_FormConfig.js +57 -0
- package/dist/types/Service_MeterReading.d.ts +35 -0
- package/dist/types/Service_MeterReading.js +57 -0
- package/dist/types/Service_Project.d.ts +53 -0
- package/dist/types/Service_Project.js +93 -0
- package/dist/types/Service_Schedule.d.ts +66 -0
- package/dist/types/Service_Schedule.js +117 -0
- package/dist/types/Service_Schedule_Execution.d.ts +38 -0
- package/dist/types/Service_Schedule_Execution.js +63 -0
- package/dist/types/Service_Schedule_Template.d.ts +36 -0
- package/dist/types/Service_Schedule_Template.js +60 -0
- package/dist/types/Service_Status.d.ts +30 -0
- package/dist/types/Service_Status.js +51 -0
- package/dist/types/Service_Tag.d.ts +30 -0
- package/dist/types/Service_Tag.js +51 -0
- package/dist/types/Service_Ticket.d.ts +92 -0
- package/dist/types/Service_Ticket.js +165 -0
- package/dist/types/Service_TicketCounter.d.ts +18 -0
- package/dist/types/Service_TicketCounter.js +27 -0
- package/dist/types/Service_TriggerState.d.ts +31 -0
- package/dist/types/Service_TriggerState.js +51 -0
- package/dist/types/_Session.d.ts +0 -1
- package/dist/types/_User.d.ts +2 -6
- package/dist/types/custom/MetaField.d.ts +9 -0
- package/dist/types/custom/TicketMaterial.d.ts +32 -0
- package/dist/types/custom/TicketMaterial.js +2 -0
- package/dist/types/index.d.ts +30 -0
- package/dist/types/index.js +33 -3
- package/package.json +6 -3
- package/schema/BDE_Article.json +3 -0
- package/schema/Changelog.json +3 -6
- package/schema/Chat_Message.json +88 -0
- package/schema/Meta_Context.json +78 -0
- package/schema/Service_Equipment.json +55 -0
- package/schema/Service_FormConfig.json +66 -0
- package/schema/Service_MeterReading.json +54 -0
- package/schema/Service_MigrationMap.json +55 -0
- package/schema/Service_Project.json +77 -0
- package/schema/Service_Schedule.json +101 -0
- package/schema/Service_Schedule_Execution.json +58 -0
- package/schema/Service_Schedule_Template.json +73 -0
- package/schema/Service_Status.json +50 -0
- package/schema/Service_Tag.json +50 -0
- package/schema/Service_Ticket.json +114 -0
- package/schema/Service_TicketCounter.json +38 -0
- package/schema/Service_TriggerState.json +50 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type MigrationConfig } from "./env.js";
|
|
2
|
+
import type { MigrationMap } from "./map.js";
|
|
3
|
+
import type { PassReport, Report } from "./report.js";
|
|
4
|
+
/** Everything a pass needs, handed to it by the orchestrator. */
|
|
5
|
+
export interface PassContext {
|
|
6
|
+
config: MigrationConfig;
|
|
7
|
+
map: MigrationMap;
|
|
8
|
+
report: Report;
|
|
9
|
+
entry: PassReport;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A migration pass. `id` doubles as the ordering key and the `--only` selector;
|
|
13
|
+
* `needs` names the pass ids whose output this one reads, which the orchestrator
|
|
14
|
+
* enforces rather than trusting the operator to invoke things in order.
|
|
15
|
+
*/
|
|
16
|
+
export interface Pass {
|
|
17
|
+
id: string;
|
|
18
|
+
title: string;
|
|
19
|
+
needs: string[];
|
|
20
|
+
run(ctx: PassContext): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Page through a query, yielding batches.
|
|
24
|
+
*
|
|
25
|
+
* Paging rule for every read in the migration: **stop only on an empty page.**
|
|
26
|
+
*
|
|
27
|
+
* A short page must NOT be read as "no more data". Parse Server silently clamps a
|
|
28
|
+
* requested `limit` down to its configured `maxLimit`
|
|
29
|
+
* (`parse-server/lib/Routers/ClassesRouter.js`: `options.limit = maxLimit`) with no
|
|
30
|
+
* error, so a `--batch` above that ceiling makes every page come back short — and a
|
|
31
|
+
* `page.length < batchSize` break would end the loop after the first page and
|
|
32
|
+
* migrate a fraction of the data while reporting a clean run.
|
|
33
|
+
*
|
|
34
|
+
* Stopping on empty costs one extra round trip per table and is correct whatever
|
|
35
|
+
* the server's limit config happens to be.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately *not* the v1 read pattern: `openinc-openservice-ticket-data.ts`
|
|
38
|
+
* issues roughly ten point queries per ticket, which at 10k tickets is 100k
|
|
39
|
+
* round-trips. Passes load each source table once in pages and reduce in memory.
|
|
40
|
+
*
|
|
41
|
+
* Ordered by `createdAt` so paging is stable and so passes that depend on insert
|
|
42
|
+
* order (tickets, whose numbers come from the backend counter) get it for free.
|
|
43
|
+
*/
|
|
44
|
+
export declare function pages<T extends Parse.Object>(build: () => Parse.Query<T>, batchSize: number): AsyncGenerator<T[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Count rows by paging, not with `Query.count()`.
|
|
47
|
+
*
|
|
48
|
+
* `count()` is **not** usable under maintenance auth: Parse's class-level
|
|
49
|
+
* permissions carry a separate `count` action, and that check is not bypassed for
|
|
50
|
+
* maintenance the way `find` is. Verified against this server — an identical query
|
|
51
|
+
* on a role-restricted class succeeds with `find()` and fails with
|
|
52
|
+
* `count()`: "Permission denied". Left as `count()`, every counting check in
|
|
53
|
+
* `verify.ts` would report a permissions error dressed up as a data problem.
|
|
54
|
+
*
|
|
55
|
+
* Costs one request per page, which at this volume is a handful.
|
|
56
|
+
*/
|
|
57
|
+
export declare function countAll<T extends Parse.Object>(build: () => Parse.Query<T>, batchSize: number): Promise<number>;
|
|
58
|
+
/** Collect every row of a query. Only for tables known to be small. */
|
|
59
|
+
export declare function all<T extends Parse.Object>(build: () => Parse.Query<T>, batchSize: number): Promise<T[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Scope a query to one tenant when `--tenant` was given.
|
|
62
|
+
*
|
|
63
|
+
* Passing the id as a bare pointer avoids fetching the Tenant object first.
|
|
64
|
+
*/
|
|
65
|
+
export declare function scopeToTenant<T extends Parse.Object>(query: Parse.Query<T>, tenantId: string | undefined): Parse.Query<T>;
|
|
66
|
+
/** The objectId behind a pointer-ish value, without fetching it. */
|
|
67
|
+
export declare function refId(value: unknown): string | undefined;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pages = pages;
|
|
4
|
+
exports.countAll = countAll;
|
|
5
|
+
exports.all = all;
|
|
6
|
+
exports.scopeToTenant = scopeToTenant;
|
|
7
|
+
exports.refId = refId;
|
|
8
|
+
const env_js_1 = require("./env.js");
|
|
9
|
+
/**
|
|
10
|
+
* Page through a query, yielding batches.
|
|
11
|
+
*
|
|
12
|
+
* Paging rule for every read in the migration: **stop only on an empty page.**
|
|
13
|
+
*
|
|
14
|
+
* A short page must NOT be read as "no more data". Parse Server silently clamps a
|
|
15
|
+
* requested `limit` down to its configured `maxLimit`
|
|
16
|
+
* (`parse-server/lib/Routers/ClassesRouter.js`: `options.limit = maxLimit`) with no
|
|
17
|
+
* error, so a `--batch` above that ceiling makes every page come back short — and a
|
|
18
|
+
* `page.length < batchSize` break would end the loop after the first page and
|
|
19
|
+
* migrate a fraction of the data while reporting a clean run.
|
|
20
|
+
*
|
|
21
|
+
* Stopping on empty costs one extra round trip per table and is correct whatever
|
|
22
|
+
* the server's limit config happens to be.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately *not* the v1 read pattern: `openinc-openservice-ticket-data.ts`
|
|
25
|
+
* issues roughly ten point queries per ticket, which at 10k tickets is 100k
|
|
26
|
+
* round-trips. Passes load each source table once in pages and reduce in memory.
|
|
27
|
+
*
|
|
28
|
+
* Ordered by `createdAt` so paging is stable and so passes that depend on insert
|
|
29
|
+
* order (tickets, whose numbers come from the backend counter) get it for free.
|
|
30
|
+
*/
|
|
31
|
+
async function* pages(build, batchSize) {
|
|
32
|
+
let skip = 0;
|
|
33
|
+
for (;;) {
|
|
34
|
+
const page = await build().limit(batchSize).skip(skip).ascending("createdAt").find(env_js_1.MK);
|
|
35
|
+
if (page.length === 0)
|
|
36
|
+
return;
|
|
37
|
+
yield page;
|
|
38
|
+
// Advance by what came back, not by what was asked for — see the note above.
|
|
39
|
+
skip += page.length;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Count rows by paging, not with `Query.count()`.
|
|
44
|
+
*
|
|
45
|
+
* `count()` is **not** usable under maintenance auth: Parse's class-level
|
|
46
|
+
* permissions carry a separate `count` action, and that check is not bypassed for
|
|
47
|
+
* maintenance the way `find` is. Verified against this server — an identical query
|
|
48
|
+
* on a role-restricted class succeeds with `find()` and fails with
|
|
49
|
+
* `count()`: "Permission denied". Left as `count()`, every counting check in
|
|
50
|
+
* `verify.ts` would report a permissions error dressed up as a data problem.
|
|
51
|
+
*
|
|
52
|
+
* Costs one request per page, which at this volume is a handful.
|
|
53
|
+
*/
|
|
54
|
+
async function countAll(build, batchSize) {
|
|
55
|
+
let total = 0;
|
|
56
|
+
for await (const page of pages(build, batchSize))
|
|
57
|
+
total += page.length;
|
|
58
|
+
return total;
|
|
59
|
+
}
|
|
60
|
+
/** Collect every row of a query. Only for tables known to be small. */
|
|
61
|
+
async function all(build, batchSize) {
|
|
62
|
+
const out = [];
|
|
63
|
+
for await (const page of pages(build, batchSize))
|
|
64
|
+
out.push(...page);
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Scope a query to one tenant when `--tenant` was given.
|
|
69
|
+
*
|
|
70
|
+
* Passing the id as a bare pointer avoids fetching the Tenant object first.
|
|
71
|
+
*/
|
|
72
|
+
function scopeToTenant(query, tenantId) {
|
|
73
|
+
if (!tenantId)
|
|
74
|
+
return query;
|
|
75
|
+
return query.equalTo("tenant", {
|
|
76
|
+
__type: "Pointer",
|
|
77
|
+
className: "OD3_Tenant",
|
|
78
|
+
objectId: tenantId,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** The objectId behind a pointer-ish value, without fetching it. */
|
|
82
|
+
function refId(value) {
|
|
83
|
+
if (!value)
|
|
84
|
+
return undefined;
|
|
85
|
+
if (typeof value === "string")
|
|
86
|
+
return value;
|
|
87
|
+
const obj = value;
|
|
88
|
+
return obj.id ?? obj.objectId;
|
|
89
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pointer construction without fetching.
|
|
3
|
+
*
|
|
4
|
+
* Every pass writes pointers whose targets it already knows the id of, so
|
|
5
|
+
* materialising the target object first would be pure round-trips. Parse accepts a
|
|
6
|
+
* bare pointer literal on save.
|
|
7
|
+
*/
|
|
8
|
+
export interface Ptr {
|
|
9
|
+
__type: "Pointer";
|
|
10
|
+
className: string;
|
|
11
|
+
objectId: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function ptr(className: string, objectId: string): Ptr;
|
|
14
|
+
export declare function maybePtr(className: string, objectId: string | undefined): Ptr | undefined;
|
|
15
|
+
export declare const tenantPtr: (id: string) => Ptr;
|
|
16
|
+
export declare const userPtr: (id: string) => Ptr;
|
|
17
|
+
export declare const rolePtr: (id: string) => Ptr;
|
|
18
|
+
/**
|
|
19
|
+
* The wire form of a Date, as Parse serialises it.
|
|
20
|
+
*
|
|
21
|
+
* Needed when building the `original` / `value` snapshots for reconstructed
|
|
22
|
+
* changelog entries: the changelog viewer's diff renderer keys off `__type`, so a
|
|
23
|
+
* raw JS Date in a snapshot would render as an opaque string instead of a date.
|
|
24
|
+
*/
|
|
25
|
+
export declare function dateVal(d: Date): {
|
|
26
|
+
__type: "Date";
|
|
27
|
+
iso: string;
|
|
28
|
+
};
|
|
29
|
+
/** JSON-stringified context array, matching how the chat and meta plugins store it. */
|
|
30
|
+
export declare function contextToString(parts: string[]): string;
|
|
31
|
+
/** The context every artefact attached to one ticket shares (chat, assets, ...). */
|
|
32
|
+
export declare function ticketContext(ticketId: string): string[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rolePtr = exports.userPtr = exports.tenantPtr = void 0;
|
|
4
|
+
exports.ptr = ptr;
|
|
5
|
+
exports.maybePtr = maybePtr;
|
|
6
|
+
exports.dateVal = dateVal;
|
|
7
|
+
exports.contextToString = contextToString;
|
|
8
|
+
exports.ticketContext = ticketContext;
|
|
9
|
+
function ptr(className, objectId) {
|
|
10
|
+
return { __type: "Pointer", className, objectId };
|
|
11
|
+
}
|
|
12
|
+
function maybePtr(className, objectId) {
|
|
13
|
+
return objectId ? ptr(className, objectId) : undefined;
|
|
14
|
+
}
|
|
15
|
+
const tenantPtr = (id) => ptr("OD3_Tenant", id);
|
|
16
|
+
exports.tenantPtr = tenantPtr;
|
|
17
|
+
const userPtr = (id) => ptr("_User", id);
|
|
18
|
+
exports.userPtr = userPtr;
|
|
19
|
+
const rolePtr = (id) => ptr("_Role", id);
|
|
20
|
+
exports.rolePtr = rolePtr;
|
|
21
|
+
/**
|
|
22
|
+
* The wire form of a Date, as Parse serialises it.
|
|
23
|
+
*
|
|
24
|
+
* Needed when building the `original` / `value` snapshots for reconstructed
|
|
25
|
+
* changelog entries: the changelog viewer's diff renderer keys off `__type`, so a
|
|
26
|
+
* raw JS Date in a snapshot would render as an opaque string instead of a date.
|
|
27
|
+
*/
|
|
28
|
+
function dateVal(d) {
|
|
29
|
+
return { __type: "Date", iso: d.toISOString() };
|
|
30
|
+
}
|
|
31
|
+
/** JSON-stringified context array, matching how the chat and meta plugins store it. */
|
|
32
|
+
function contextToString(parts) {
|
|
33
|
+
return JSON.stringify(parts);
|
|
34
|
+
}
|
|
35
|
+
/** The context every artefact attached to one ticket shares (chat, assets, ...). */
|
|
36
|
+
function ticketContext(ticketId) {
|
|
37
|
+
return ["service", "ticket", ticketId];
|
|
38
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-pass tally. Every pass reports what it did, what it skipped and why, so a
|
|
3
|
+
* run leaves an artefact you can diff against the next one rather than a wall of
|
|
4
|
+
* log lines.
|
|
5
|
+
*/
|
|
6
|
+
export interface PassReport {
|
|
7
|
+
pass: string;
|
|
8
|
+
created: number;
|
|
9
|
+
updated: number;
|
|
10
|
+
skipped: number;
|
|
11
|
+
/** Non-fatal oddities worth a human look — unparseable values, missing refs. */
|
|
12
|
+
warnings: string[];
|
|
13
|
+
/** Pass-level summary lines. Never suppressed — see `Report.info`. */
|
|
14
|
+
notes: string[];
|
|
15
|
+
/** Fatal per-row failures; the pass continues, the run is marked failed. */
|
|
16
|
+
errors: string[];
|
|
17
|
+
startedAt: string;
|
|
18
|
+
finishedAt?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare class Report {
|
|
21
|
+
private readonly passes;
|
|
22
|
+
private current?;
|
|
23
|
+
begin(pass: string): PassReport;
|
|
24
|
+
end(): void;
|
|
25
|
+
/**
|
|
26
|
+
* A pass-level summary line — counts, totals, what was skipped in bulk.
|
|
27
|
+
*
|
|
28
|
+
* Deliberately a separate channel from {@link warn}. Warnings are capped, because
|
|
29
|
+
* a per-row warning over 10k rows must not print 10k lines — but the summaries
|
|
30
|
+
* are emitted *after* the loop that produced those warnings, so sharing the cap
|
|
31
|
+
* would suppress exactly the lines the run report is read for.
|
|
32
|
+
*/
|
|
33
|
+
info(report: PassReport, message: string): void;
|
|
34
|
+
/** A capped sample of per-row warnings. */
|
|
35
|
+
warn(report: PassReport, message: string, cap?: number): void;
|
|
36
|
+
error(report: PassReport, message: string): void;
|
|
37
|
+
get failed(): boolean;
|
|
38
|
+
/** Machine-readable summary — write this next to the run for later comparison. */
|
|
39
|
+
toJSON(): PassReport[];
|
|
40
|
+
printSummary(runId: string, dryRun: boolean): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Report = void 0;
|
|
4
|
+
class Report {
|
|
5
|
+
passes = [];
|
|
6
|
+
current;
|
|
7
|
+
begin(pass) {
|
|
8
|
+
this.current = {
|
|
9
|
+
pass,
|
|
10
|
+
created: 0,
|
|
11
|
+
updated: 0,
|
|
12
|
+
skipped: 0,
|
|
13
|
+
warnings: [],
|
|
14
|
+
notes: [],
|
|
15
|
+
errors: [],
|
|
16
|
+
startedAt: new Date().toISOString(),
|
|
17
|
+
};
|
|
18
|
+
this.passes.push(this.current);
|
|
19
|
+
console.log(`\n── ${pass} ${"─".repeat(Math.max(0, 60 - pass.length))}`);
|
|
20
|
+
return this.current;
|
|
21
|
+
}
|
|
22
|
+
end() {
|
|
23
|
+
if (!this.current)
|
|
24
|
+
return;
|
|
25
|
+
this.current.finishedAt = new Date().toISOString();
|
|
26
|
+
const { created, updated, skipped, warnings, errors } = this.current;
|
|
27
|
+
console.log(` created ${created} · updated ${updated} · skipped ${skipped}` +
|
|
28
|
+
`${warnings.length ? ` · ${warnings.length} warning(s)` : ""}` +
|
|
29
|
+
`${errors.length ? ` · ${errors.length} ERROR(S)` : ""}`);
|
|
30
|
+
this.current = undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A pass-level summary line — counts, totals, what was skipped in bulk.
|
|
34
|
+
*
|
|
35
|
+
* Deliberately a separate channel from {@link warn}. Warnings are capped, because
|
|
36
|
+
* a per-row warning over 10k rows must not print 10k lines — but the summaries
|
|
37
|
+
* are emitted *after* the loop that produced those warnings, so sharing the cap
|
|
38
|
+
* would suppress exactly the lines the run report is read for.
|
|
39
|
+
*/
|
|
40
|
+
info(report, message) {
|
|
41
|
+
report.notes.push(message);
|
|
42
|
+
console.log(` · ${message}`);
|
|
43
|
+
}
|
|
44
|
+
/** A capped sample of per-row warnings. */
|
|
45
|
+
warn(report, message, cap = 25) {
|
|
46
|
+
if (report.warnings.length < cap) {
|
|
47
|
+
report.warnings.push(message);
|
|
48
|
+
console.warn(` ! ${message}`);
|
|
49
|
+
}
|
|
50
|
+
else if (report.warnings.length === cap) {
|
|
51
|
+
report.warnings.push(`… further warnings suppressed`);
|
|
52
|
+
console.warn(` ! further warnings suppressed`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
error(report, message) {
|
|
56
|
+
report.errors.push(message);
|
|
57
|
+
console.error(` ✗ ${message}`);
|
|
58
|
+
}
|
|
59
|
+
get failed() {
|
|
60
|
+
return this.passes.some((p) => p.errors.length > 0);
|
|
61
|
+
}
|
|
62
|
+
/** Machine-readable summary — write this next to the run for later comparison. */
|
|
63
|
+
toJSON() {
|
|
64
|
+
return this.passes;
|
|
65
|
+
}
|
|
66
|
+
printSummary(runId, dryRun) {
|
|
67
|
+
console.log(`\n${"═".repeat(64)}`);
|
|
68
|
+
console.log(`run ${runId}${dryRun ? " (DRY RUN — nothing written)" : ""}`);
|
|
69
|
+
for (const p of this.passes) {
|
|
70
|
+
const flag = p.errors.length ? "FAIL" : "ok ";
|
|
71
|
+
console.log(` ${flag} ${p.pass.padEnd(22)} ` +
|
|
72
|
+
`+${String(p.created).padStart(6)} ~${String(p.updated).padStart(6)} ` +
|
|
73
|
+
`skip ${String(p.skipped).padStart(6)}`);
|
|
74
|
+
}
|
|
75
|
+
console.log(`${"═".repeat(64)}\n`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.Report = Report;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { MigrationConfig } from "./env.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creating objects with historical timestamps.
|
|
4
|
+
*
|
|
5
|
+
* **Why this exists instead of `object.save()`.** The Parse JS SDK silently drops
|
|
6
|
+
* `createdAt` / `updatedAt`:
|
|
7
|
+
*
|
|
8
|
+
* ```js
|
|
9
|
+
* // ParseObject.js, in the set() path
|
|
10
|
+
* if (k === "createdAt" || k === "updatedAt") {
|
|
11
|
+
* // This property is read-only, but for legacy reasons we silently ignore it
|
|
12
|
+
* continue;
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* So `object.set("createdAt", …)` never reaches `dirtyKeys()` and never reaches the
|
|
17
|
+
* wire — the save succeeds and the server stamps `now`. No error anywhere. Since
|
|
18
|
+
* the whole point of running under the maintenance key is to preserve the original
|
|
19
|
+
* timestamps, every create has to bypass the SDK and post the REST body itself.
|
|
20
|
+
*
|
|
21
|
+
* Queries and updates stay on the SDK: reads do not care, and Parse ignores
|
|
22
|
+
* `createdAt` on update regardless.
|
|
23
|
+
*/
|
|
24
|
+
interface CreateResult {
|
|
25
|
+
objectId: string;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* POST a new object with explicit timestamps, returning its new objectId.
|
|
30
|
+
*
|
|
31
|
+
* `object.toJSON()` already produces exactly the REST body shape — dates and
|
|
32
|
+
* pointers encoded, ACL included — so the typed Parse subclasses stay usable for
|
|
33
|
+
* building the object even though the SDK cannot save it for us.
|
|
34
|
+
*
|
|
35
|
+
* The trigger context travels in `X-Parse-Cloud-Context` (see
|
|
36
|
+
* `parse-server/lib/middlewares.js`), which is how `skipChangelog` still reaches
|
|
37
|
+
* the changelog hook on this path.
|
|
38
|
+
*/
|
|
39
|
+
export declare function restCreate(config: MigrationConfig, className: string, object: Parse.Object, createdAt: Date | undefined, updatedAt?: Date): Promise<CreateResult>;
|
|
40
|
+
/**
|
|
41
|
+
* The same create, from a plain REST body rather than a Parse.Object.
|
|
42
|
+
*
|
|
43
|
+
* Used by the v1 test-data seeder, which is clearer to read as literal payloads
|
|
44
|
+
* than as a pile of `.set()` calls.
|
|
45
|
+
*/
|
|
46
|
+
export declare function restCreateRaw(config: MigrationConfig, className: string, payload: Record<string, unknown>, createdAt: Date | undefined, updatedAt?: Date): Promise<CreateResult>;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.restCreate = restCreate;
|
|
4
|
+
exports.restCreateRaw = restCreateRaw;
|
|
5
|
+
function dateVal(d) {
|
|
6
|
+
return { __type: "Date", iso: d.toISOString() };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* POST a new object with explicit timestamps, returning its new objectId.
|
|
10
|
+
*
|
|
11
|
+
* `object.toJSON()` already produces exactly the REST body shape — dates and
|
|
12
|
+
* pointers encoded, ACL included — so the typed Parse subclasses stay usable for
|
|
13
|
+
* building the object even though the SDK cannot save it for us.
|
|
14
|
+
*
|
|
15
|
+
* The trigger context travels in `X-Parse-Cloud-Context` (see
|
|
16
|
+
* `parse-server/lib/middlewares.js`), which is how `skipChangelog` still reaches
|
|
17
|
+
* the changelog hook on this path.
|
|
18
|
+
*/
|
|
19
|
+
async function restCreate(config, className, object, createdAt, updatedAt) {
|
|
20
|
+
const body = { ...object.toJSON() };
|
|
21
|
+
// toJSON() carries these on an existing object; a create must not send them.
|
|
22
|
+
delete body.objectId;
|
|
23
|
+
delete body.className;
|
|
24
|
+
return restCreateRaw(config, className, body, createdAt, updatedAt);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The same create, from a plain REST body rather than a Parse.Object.
|
|
28
|
+
*
|
|
29
|
+
* Used by the v1 test-data seeder, which is clearer to read as literal payloads
|
|
30
|
+
* than as a pile of `.set()` calls.
|
|
31
|
+
*/
|
|
32
|
+
async function restCreateRaw(config, className, payload, createdAt, updatedAt) {
|
|
33
|
+
const body = { ...payload };
|
|
34
|
+
if (createdAt) {
|
|
35
|
+
body.createdAt = dateVal(createdAt);
|
|
36
|
+
// Parse rejects an updatedAt earlier than createdAt, and defaults it to
|
|
37
|
+
// createdAt when omitted.
|
|
38
|
+
const resolved = updatedAt && updatedAt.getTime() >= createdAt.getTime()
|
|
39
|
+
? updatedAt
|
|
40
|
+
: createdAt;
|
|
41
|
+
body.updatedAt = dateVal(resolved);
|
|
42
|
+
}
|
|
43
|
+
const response = await fetch(`${config.parseUrl}/classes/${encodeURIComponent(className)}`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: {
|
|
46
|
+
"X-Parse-Application-Id": config.appId,
|
|
47
|
+
"X-Parse-Maintenance-Key": config.maintenanceKey,
|
|
48
|
+
"X-Parse-Cloud-Context": JSON.stringify({ skipChangelog: true }),
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
},
|
|
51
|
+
body: JSON.stringify(body),
|
|
52
|
+
});
|
|
53
|
+
const text = await response.text();
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new Error(`POST /classes/${className} → ${response.status}: ${text.slice(0, 400)}`);
|
|
56
|
+
}
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(text);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new Error(`POST /classes/${className} returned unparseable body: ${text.slice(0, 200)}`);
|
|
63
|
+
}
|
|
64
|
+
if (!parsed.objectId) {
|
|
65
|
+
throw new Error(`POST /classes/${className} returned no objectId: ${text.slice(0, 200)}`);
|
|
66
|
+
}
|
|
67
|
+
return { objectId: parsed.objectId, createdAt: parsed.createdAt ?? "" };
|
|
68
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { MigrationConfig } from "./env.js";
|
|
2
|
+
/**
|
|
3
|
+
* Hands out `Ticket.number` the way the backend hook would.
|
|
4
|
+
*
|
|
5
|
+
* Per-project when the ticket has one (`Project.ticketCounter`), otherwise
|
|
6
|
+
* per-tenant (`OD3_Service_TicketCounter.value`).
|
|
7
|
+
*
|
|
8
|
+
* Counters are seeded from their **current** stored values and flushed once at the
|
|
9
|
+
* end, rather than incremented per ticket. That is both far fewer round trips and
|
|
10
|
+
* safe for a re-run: an already-migrated ticket keeps the number it has and
|
|
11
|
+
* consumes nothing, so a resumed run continues the sequence instead of restarting
|
|
12
|
+
* it. Safe precisely because the migration is single-threaded — the hook needs
|
|
13
|
+
* atomic `$inc` because concurrent clients race; here nothing else is writing.
|
|
14
|
+
*/
|
|
15
|
+
export declare class TicketNumberer {
|
|
16
|
+
private readonly config;
|
|
17
|
+
private readonly projectCounters;
|
|
18
|
+
private readonly tenantCounters;
|
|
19
|
+
private readonly tenantCounterIds;
|
|
20
|
+
private readonly dirtyProjects;
|
|
21
|
+
private readonly dirtyTenants;
|
|
22
|
+
constructor(config: MigrationConfig);
|
|
23
|
+
/** Read the current counter values so numbering continues rather than restarts. */
|
|
24
|
+
load(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* The next number for a ticket, or undefined when there is no scope to count in
|
|
27
|
+
* (no project and no tenant) — matching the hook, which leaves those unnumbered.
|
|
28
|
+
*/
|
|
29
|
+
next(projectId: string | undefined, tenantId: string | undefined): number | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Persist the counters.
|
|
32
|
+
*
|
|
33
|
+
* Must run even if the pass had per-ticket errors: leaving a counter behind the
|
|
34
|
+
* numbers already handed out would make the *next* run reuse them, producing
|
|
35
|
+
* duplicates that `verify.ts` would then flag.
|
|
36
|
+
*/
|
|
37
|
+
flush(): Promise<{
|
|
38
|
+
projects: number;
|
|
39
|
+
tenants: number;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
/** Role id → role name, needed because Parse ACLs grant by name, not by id. */
|
|
43
|
+
export declare function loadRoleNames(config: MigrationConfig): Promise<Map<string, string>>;
|
|
44
|
+
/**
|
|
45
|
+
* The ACL a v2 ticket should carry.
|
|
46
|
+
*
|
|
47
|
+
* `migrationAcl` covers what `defaultAclHandler` would have granted (od-admin and
|
|
48
|
+
* the tenant roles); this adds what `setTicketACL` would have granted on top —
|
|
49
|
+
* creator and assignees, read and write.
|
|
50
|
+
*
|
|
51
|
+
* Note the hook cannot actually do the assignee half today: it reads the arrays via
|
|
52
|
+
* a helper that takes `.id` off each entry, but the entity adapter stores
|
|
53
|
+
* array-of-ref fields as plain id **strings**, so it always resolves to nothing.
|
|
54
|
+
* Here we use the ids directly, so migrated tickets end up with the grants the hook
|
|
55
|
+
* was intended to give. That is a deliberate divergence, not an oversight.
|
|
56
|
+
*/
|
|
57
|
+
export declare function ticketAcl(options: {
|
|
58
|
+
tenantId: string;
|
|
59
|
+
creatorId?: string;
|
|
60
|
+
assignedUserIds: string[];
|
|
61
|
+
assignedRoleIds: string[];
|
|
62
|
+
roleNames: Map<string, string>;
|
|
63
|
+
}): Parse.ACL;
|
|
64
|
+
/**
|
|
65
|
+
* The ACLs of the already-migrated v2 tickets, keyed by ticket id.
|
|
66
|
+
*
|
|
67
|
+
* Pass 12 needs these twice: the reconstructed changelog entry must carry the ACL
|
|
68
|
+
* of the ticket it describes (normally `beforeSave(OD3_Changelog)` mirrors it from
|
|
69
|
+
* the snapshot, but that trigger is suppressed too), and the snapshot itself should
|
|
70
|
+
* show the v2 ACL rather than the v1 one.
|
|
71
|
+
*/
|
|
72
|
+
export declare function loadTicketAcls(config: MigrationConfig): Promise<Map<string, Parse.ACL>>;
|