@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.
Files changed (163) hide show
  1. package/dist/featuremap.json +27 -12
  2. package/dist/features/config/helper/customOptions.js +9 -0
  3. package/dist/features/config/helper/isFeatureEnabled.d.ts +1 -1
  4. package/dist/features/config/helper/isFeatureEnabled.js +2 -0
  5. package/dist/features/meta/index.d.ts +1 -0
  6. package/dist/features/meta/index.js +5 -0
  7. package/dist/features/meta/services/registerMetaContext.d.ts +29 -0
  8. package/dist/features/meta/services/registerMetaContext.js +208 -0
  9. package/dist/features/permissions/types/Permissions.d.ts +24 -1
  10. package/dist/features/permissions/types/Permissions.js +34 -0
  11. package/dist/features/ruleset/applyRuleSet.d.ts +41 -0
  12. package/dist/features/ruleset/applyRuleSet.js +81 -0
  13. package/dist/features/schema/helper/defaultHandler.js +13 -0
  14. package/dist/features/schema/helper/migrationMode.d.ts +26 -0
  15. package/dist/features/schema/helper/migrationMode.js +48 -0
  16. package/dist/features/schema/index.d.ts +1 -0
  17. package/dist/features/schema/index.js +3 -1
  18. package/dist/features/schema/services/beforeSaveHook.js +21 -0
  19. package/dist/features/translations/services/initTranslation.js +1 -1
  20. package/dist/hooks/Chat_Message.d.ts +1 -0
  21. package/dist/hooks/Chat_Message.js +179 -0
  22. package/dist/hooks/Meta_Config.js +19 -10
  23. package/dist/hooks/Meta_Context.d.ts +6 -0
  24. package/dist/hooks/Meta_Context.js +16 -0
  25. package/dist/hooks/Meta_Entry.js +20 -10
  26. package/dist/hooks/Service_Equipment.d.ts +1 -0
  27. package/dist/hooks/Service_Equipment.js +38 -0
  28. package/dist/hooks/Service_FormConfig.d.ts +1 -0
  29. package/dist/hooks/Service_FormConfig.js +17 -0
  30. package/dist/hooks/Service_Project.d.ts +1 -0
  31. package/dist/hooks/Service_Project.js +17 -0
  32. package/dist/hooks/Service_Schedule.d.ts +1 -0
  33. package/dist/hooks/Service_Schedule.js +24 -0
  34. package/dist/hooks/Service_Schedule_Execution.d.ts +1 -0
  35. package/dist/hooks/Service_Schedule_Execution.js +17 -0
  36. package/dist/hooks/Service_Schedule_Template.d.ts +1 -0
  37. package/dist/hooks/Service_Schedule_Template.js +17 -0
  38. package/dist/hooks/Service_Status.d.ts +1 -0
  39. package/dist/hooks/Service_Status.js +32 -0
  40. package/dist/hooks/Service_Tag.d.ts +1 -0
  41. package/dist/hooks/Service_Tag.js +168 -0
  42. package/dist/hooks/Service_Ticket.d.ts +1 -0
  43. package/dist/hooks/Service_Ticket.js +433 -0
  44. package/dist/hooks/Service_TriggerState.d.ts +1 -0
  45. package/dist/hooks/Service_TriggerState.js +17 -0
  46. package/dist/hooks/_ChangeLog.js +27 -6
  47. package/dist/i18n/de-DE/chat.json +18 -0
  48. package/dist/i18n/de-DE/openservice.json +14 -0
  49. package/dist/i18n/en-US/chat.json +18 -0
  50. package/dist/i18n/en-US/openservice.json +14 -0
  51. package/dist/scripts/backfill-meta-contexts.d.ts +1 -0
  52. package/dist/scripts/backfill-meta-contexts.js +100 -0
  53. package/dist/scripts/migration/lib/acl.d.ts +52 -0
  54. package/dist/scripts/migration/lib/acl.js +79 -0
  55. package/dist/scripts/migration/lib/articleMeta.d.ts +47 -0
  56. package/dist/scripts/migration/lib/articleMeta.js +132 -0
  57. package/dist/scripts/migration/lib/env.d.ts +56 -0
  58. package/dist/scripts/migration/lib/env.js +106 -0
  59. package/dist/scripts/migration/lib/latest.d.ts +47 -0
  60. package/dist/scripts/migration/lib/latest.js +115 -0
  61. package/dist/scripts/migration/lib/map.d.ts +53 -0
  62. package/dist/scripts/migration/lib/map.js +113 -0
  63. package/dist/scripts/migration/lib/material.d.ts +47 -0
  64. package/dist/scripts/migration/lib/material.js +51 -0
  65. package/dist/scripts/migration/lib/message.d.ts +78 -0
  66. package/dist/scripts/migration/lib/message.js +115 -0
  67. package/dist/scripts/migration/lib/pass.d.ts +67 -0
  68. package/dist/scripts/migration/lib/pass.js +89 -0
  69. package/dist/scripts/migration/lib/ptr.d.ts +32 -0
  70. package/dist/scripts/migration/lib/ptr.js +38 -0
  71. package/dist/scripts/migration/lib/report.d.ts +41 -0
  72. package/dist/scripts/migration/lib/report.js +78 -0
  73. package/dist/scripts/migration/lib/rest.d.ts +47 -0
  74. package/dist/scripts/migration/lib/rest.js +68 -0
  75. package/dist/scripts/migration/lib/ticket.d.ts +72 -0
  76. package/dist/scripts/migration/lib/ticket.js +192 -0
  77. package/dist/scripts/migration/passes/01-statuses.d.ts +12 -0
  78. package/dist/scripts/migration/passes/01-statuses.js +122 -0
  79. package/dist/scripts/migration/passes/02-tags.d.ts +13 -0
  80. package/dist/scripts/migration/passes/02-tags.js +150 -0
  81. package/dist/scripts/migration/passes/03-equipment.d.ts +14 -0
  82. package/dist/scripts/migration/passes/03-equipment.js +176 -0
  83. package/dist/scripts/migration/passes/04-projects.d.ts +27 -0
  84. package/dist/scripts/migration/passes/04-projects.js +241 -0
  85. package/dist/scripts/migration/passes/05-articles.d.ts +25 -0
  86. package/dist/scripts/migration/passes/05-articles.js +203 -0
  87. package/dist/scripts/migration/passes/06-formconfigs.d.ts +17 -0
  88. package/dist/scripts/migration/passes/06-formconfigs.js +166 -0
  89. package/dist/scripts/migration/passes/07-meta-configs.d.ts +46 -0
  90. package/dist/scripts/migration/passes/07-meta-configs.js +185 -0
  91. package/dist/scripts/migration/passes/08-tickets.d.ts +27 -0
  92. package/dist/scripts/migration/passes/08-tickets.js +353 -0
  93. package/dist/scripts/migration/passes/09-ticket-meta.d.ts +12 -0
  94. package/dist/scripts/migration/passes/09-ticket-meta.js +136 -0
  95. package/dist/scripts/migration/passes/10-chat.d.ts +17 -0
  96. package/dist/scripts/migration/passes/10-chat.js +230 -0
  97. package/dist/scripts/migration/passes/11-assets.d.ts +17 -0
  98. package/dist/scripts/migration/passes/11-assets.js +93 -0
  99. package/dist/scripts/migration/passes/12-history.d.ts +24 -0
  100. package/dist/scripts/migration/passes/12-history.js +332 -0
  101. package/dist/scripts/migration/run.d.ts +1 -0
  102. package/dist/scripts/migration/run.js +148 -0
  103. package/dist/scripts/migration/verify.d.ts +1 -0
  104. package/dist/scripts/migration/verify.js +620 -0
  105. package/dist/scripts/seed-v1-testdata.d.ts +41 -0
  106. package/dist/scripts/seed-v1-testdata.js +543 -0
  107. package/dist/types/BDE_Article.d.ts +3 -0
  108. package/dist/types/BDE_Article.js +6 -0
  109. package/dist/types/Chat_Message.d.ts +49 -0
  110. package/dist/types/Chat_Message.js +87 -0
  111. package/dist/types/MES_RessourceOverview.d.ts +38 -0
  112. package/dist/types/MES_RessourceOverview.js +63 -0
  113. package/dist/types/Meta_Context.d.ts +43 -0
  114. package/dist/types/Meta_Context.js +75 -0
  115. package/dist/types/Service_Equipment.d.ts +33 -0
  116. package/dist/types/Service_Equipment.js +57 -0
  117. package/dist/types/Service_FormConfig.d.ts +33 -0
  118. package/dist/types/Service_FormConfig.js +57 -0
  119. package/dist/types/Service_MeterReading.d.ts +35 -0
  120. package/dist/types/Service_MeterReading.js +57 -0
  121. package/dist/types/Service_Project.d.ts +53 -0
  122. package/dist/types/Service_Project.js +93 -0
  123. package/dist/types/Service_Schedule.d.ts +66 -0
  124. package/dist/types/Service_Schedule.js +117 -0
  125. package/dist/types/Service_Schedule_Execution.d.ts +38 -0
  126. package/dist/types/Service_Schedule_Execution.js +63 -0
  127. package/dist/types/Service_Schedule_Template.d.ts +36 -0
  128. package/dist/types/Service_Schedule_Template.js +60 -0
  129. package/dist/types/Service_Status.d.ts +30 -0
  130. package/dist/types/Service_Status.js +51 -0
  131. package/dist/types/Service_Tag.d.ts +30 -0
  132. package/dist/types/Service_Tag.js +51 -0
  133. package/dist/types/Service_Ticket.d.ts +92 -0
  134. package/dist/types/Service_Ticket.js +165 -0
  135. package/dist/types/Service_TicketCounter.d.ts +18 -0
  136. package/dist/types/Service_TicketCounter.js +27 -0
  137. package/dist/types/Service_TriggerState.d.ts +31 -0
  138. package/dist/types/Service_TriggerState.js +51 -0
  139. package/dist/types/_Session.d.ts +0 -1
  140. package/dist/types/_User.d.ts +2 -6
  141. package/dist/types/custom/MetaField.d.ts +9 -0
  142. package/dist/types/custom/TicketMaterial.d.ts +32 -0
  143. package/dist/types/custom/TicketMaterial.js +2 -0
  144. package/dist/types/index.d.ts +30 -0
  145. package/dist/types/index.js +33 -3
  146. package/package.json +6 -3
  147. package/schema/BDE_Article.json +3 -0
  148. package/schema/Changelog.json +3 -6
  149. package/schema/Chat_Message.json +88 -0
  150. package/schema/Meta_Context.json +78 -0
  151. package/schema/Service_Equipment.json +55 -0
  152. package/schema/Service_FormConfig.json +66 -0
  153. package/schema/Service_MeterReading.json +54 -0
  154. package/schema/Service_MigrationMap.json +55 -0
  155. package/schema/Service_Project.json +77 -0
  156. package/schema/Service_Schedule.json +101 -0
  157. package/schema/Service_Schedule_Execution.json +58 -0
  158. package/schema/Service_Schedule_Template.json +73 -0
  159. package/schema/Service_Status.json +50 -0
  160. package/schema/Service_Tag.json +50 -0
  161. package/schema/Service_Ticket.json +114 -0
  162. package/schema/Service_TicketCounter.json +38 -0
  163. 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>>;