@vertexvis/api-client-node 0.20.9 → 0.21.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.
@@ -1,5 +1,24 @@
1
1
  import { CreateSceneItemRequest, SceneItem } from '../../index';
2
2
  import { BaseReq, Polling } from '../index';
3
+ export declare enum SceneItemErrorStatus {
4
+ NotFound = "404",
5
+ ServerError = "500"
6
+ }
7
+ export declare enum SceneItemErrorCode {
8
+ NotFound = "NotFound",
9
+ ServerError = "ServerError"
10
+ }
11
+ export declare enum SceneItemErrorSourcePointer {
12
+ Parent = "/body/data/attributes/parent",
13
+ SourcePart = "/body/data/relationships/source/data"
14
+ }
15
+ export declare enum SceneItemSystemMetadata {
16
+ IsMissingGeometry = "VERTEX_IS_MISSING_GEOMETRY",
17
+ MissingGeometrySetId = "VERTEX_MISSING_GEOMETRY_SET_ID",
18
+ MissingPartRevisionId = "VERTEX_MISSING_PART_REVISION_ID",
19
+ MissingSuppliedPartId = "VERTEX_MISSING_SUPPLIED_PART_ID",
20
+ MissingSuppliedPartRevisionId = "VERTEX_MISSING_SUPPLIED_PART_REVISION_ID"
21
+ }
3
22
  /**
4
23
  * Create scene item arguments.
5
24
  */
@@ -17,3 +36,5 @@ export interface CreateSceneItemReq extends BaseReq {
17
36
  * @param args - The {@link CreateSceneItemReq}.
18
37
  */
19
38
  export declare function createSceneItem({ client, createSceneItemReq, onMsg, polling, sceneId, verbose, }: CreateSceneItemReq): Promise<SceneItem>;
39
+ export declare function isPartNotFoundError(e: unknown): boolean;
40
+ export declare function isParentNotFoundError(e: unknown): boolean;
@@ -7,7 +7,30 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { isPollError, MaxAttempts, PollIntervalMs, pollQueuedJob, throwOnError, } from '../index';
10
+ import { defined, isApiError, isPollError, MaxAttempts, PollIntervalMs, pollQueuedJob, throwOnError, } from '../index';
11
+ export var SceneItemErrorStatus;
12
+ (function (SceneItemErrorStatus) {
13
+ SceneItemErrorStatus["NotFound"] = "404";
14
+ SceneItemErrorStatus["ServerError"] = "500";
15
+ })(SceneItemErrorStatus || (SceneItemErrorStatus = {}));
16
+ export var SceneItemErrorCode;
17
+ (function (SceneItemErrorCode) {
18
+ SceneItemErrorCode["NotFound"] = "NotFound";
19
+ SceneItemErrorCode["ServerError"] = "ServerError";
20
+ })(SceneItemErrorCode || (SceneItemErrorCode = {}));
21
+ export var SceneItemErrorSourcePointer;
22
+ (function (SceneItemErrorSourcePointer) {
23
+ SceneItemErrorSourcePointer["Parent"] = "/body/data/attributes/parent";
24
+ SceneItemErrorSourcePointer["SourcePart"] = "/body/data/relationships/source/data";
25
+ })(SceneItemErrorSourcePointer || (SceneItemErrorSourcePointer = {}));
26
+ export var SceneItemSystemMetadata;
27
+ (function (SceneItemSystemMetadata) {
28
+ SceneItemSystemMetadata["IsMissingGeometry"] = "VERTEX_IS_MISSING_GEOMETRY";
29
+ SceneItemSystemMetadata["MissingGeometrySetId"] = "VERTEX_MISSING_GEOMETRY_SET_ID";
30
+ SceneItemSystemMetadata["MissingPartRevisionId"] = "VERTEX_MISSING_PART_REVISION_ID";
31
+ SceneItemSystemMetadata["MissingSuppliedPartId"] = "VERTEX_MISSING_SUPPLIED_PART_ID";
32
+ SceneItemSystemMetadata["MissingSuppliedPartRevisionId"] = "VERTEX_MISSING_SUPPLIED_PART_REVISION_ID";
33
+ })(SceneItemSystemMetadata || (SceneItemSystemMetadata = {}));
11
34
  /**
12
35
  * Create a scene item.
13
36
  *
@@ -34,3 +57,17 @@ export function createSceneItem({ client, createSceneItemReq, onMsg = console.lo
34
57
  return pollRes.res;
35
58
  });
36
59
  }
60
+ export function isPartNotFoundError(e) {
61
+ return (defined(e) &&
62
+ isApiError(e) &&
63
+ e.code === SceneItemErrorCode.NotFound &&
64
+ e.source !== undefined &&
65
+ e.source.pointer === SceneItemErrorSourcePointer.SourcePart);
66
+ }
67
+ export function isParentNotFoundError(e) {
68
+ return (defined(e) &&
69
+ isApiError(e) &&
70
+ e.code === SceneItemErrorCode.NotFound &&
71
+ e.source !== undefined &&
72
+ e.source.pointer === SceneItemErrorSourcePointer.Parent);
73
+ }
@@ -1,4 +1,6 @@
1
1
  import { AxiosResponse } from 'axios';
2
+ import { Limit } from 'p-limit';
3
+ import { RelationshipData } from '../../index';
2
4
  import { ApiError, BatchOperation, CreateSceneItemRequest, CreateSceneItemRequestData, CreateSceneRequest, Failure, QueuedJob, Scene, SceneData, UpdateSceneRequestDataAttributes } from '../../index';
3
5
  import { BaseReq, DeleteReq, Polling, RenderImageReq, VertexClient } from '../index';
4
6
  export interface CreateSceneAndSceneItemsReq extends BaseReq {
@@ -17,35 +19,23 @@ export interface CreateSceneAndSceneItemsReq extends BaseReq {
17
19
  /** Whether or not to return queued scene items. */
18
20
  readonly returnQueued?: boolean;
19
21
  }
20
- export interface CreateSceneAndSceneItemsReqEXPERIMENTAL extends BaseReq {
21
- /** A list of {@link CreateSceneItemRequest}. */
22
- readonly createSceneItemReqs: Array<Array<CreateSceneItemRequest>>;
23
- /** Function returning a {@link CreateSceneRequest}. */
24
- readonly createSceneReq: () => CreateSceneRequest;
25
- /** Whether or not to fail if any scene item fails initial validation. */
26
- readonly failFast?: boolean;
27
- /** How many requests to run in parallel. */
28
- readonly parallelism: number;
29
- /** {@link Polling} */
30
- readonly polling?: Polling;
31
- /** Callback with total number of requests and number complete. */
32
- onProgress?: (complete: number, total: number) => void;
33
- /** Whether or not to return queued scene items. */
34
- readonly returnQueued?: boolean;
35
- }
36
22
  export interface CreateSceneAndSceneItemsRes {
37
- readonly errors: QueuedSceneItem[];
38
- readonly scene: Scene;
39
- /** Only populated if `returnQueued` is true in request. */
40
- readonly queued: QueuedSceneItem[];
41
- }
42
- export interface CreateSceneAndSceneItemsResEXPERIMENTAL {
43
23
  readonly errors: QueuedBatchOps[];
44
- readonly scene: Scene;
24
+ readonly scene?: Scene;
45
25
  readonly sceneItemErrors: SceneItemError[];
46
26
  /** Only populated if `returnQueued` is true in request. */
47
27
  readonly queued: QueuedBatchOps[];
48
28
  }
29
+ export interface CreateSceneItemBatchReq extends CreateSceneItemsReq {
30
+ /** {@link Polling} */
31
+ readonly polling?: Polling;
32
+ }
33
+ export interface CreateSceneItemBatchRes {
34
+ batchOps: QueuedBatchOps[];
35
+ batchErrors: QueuedBatchOps[];
36
+ itemErrors: SceneItemError[];
37
+ itemResults: SceneItemResult[];
38
+ }
49
39
  export interface CreateSceneItemsReq extends Base {
50
40
  /** A list of {@link CreateSceneItemRequest}. */
51
41
  readonly createSceneItemReqs: CreateSceneItemRequest[];
@@ -53,14 +43,10 @@ export interface CreateSceneItemsReq extends Base {
53
43
  readonly failFast: boolean;
54
44
  /** Callback with total number of requests and number complete. */
55
45
  readonly onProgress?: (complete: number, total: number) => void;
56
- /** How many requests to run in parallel. */
57
- readonly parallelism: number;
46
+ /** Limit for requests to run in parallel. */
47
+ readonly limit: Limit;
58
48
  }
59
49
  export interface CreateSceneItemsRes {
60
- readonly leaves: number;
61
- readonly queuedSceneItems: QueuedSceneItem[];
62
- }
63
- export interface CreateSceneItemsResEXPERIMENTAL {
64
50
  readonly chunks: number;
65
51
  readonly queuedBatchOps: QueuedBatchOps[];
66
52
  }
@@ -71,6 +57,11 @@ export interface QueuedBatchOps {
71
57
  export interface SceneItemError {
72
58
  readonly req: CreateSceneItemRequestData;
73
59
  readonly res?: ApiError;
60
+ placeholderItem?: RelationshipData;
61
+ }
62
+ export interface SceneItemResult {
63
+ readonly req: CreateSceneItemRequestData;
64
+ readonly res: RelationshipData | ApiError;
74
65
  }
75
66
  /**
76
67
  * Poll scene ready arguments.
@@ -104,15 +95,7 @@ export declare function createSceneAndSceneItems({ client, createSceneItemReqs,
104
95
  /**
105
96
  * Create scene items within a scene.
106
97
  */
107
- export declare function createSceneItems({ client, createSceneItemReqs, failFast, onProgress, parallelism, sceneId, }: CreateSceneItemsReq): Promise<CreateSceneItemsRes>;
108
- /**
109
- * Create a scene with scene items using experimental strategy.
110
- */
111
- export declare function createSceneAndSceneItemsEXPERIMENTAL({ client, createSceneItemReqs, createSceneReq, failFast, onMsg, onProgress, parallelism, polling, returnQueued, verbose, }: CreateSceneAndSceneItemsReqEXPERIMENTAL): Promise<CreateSceneAndSceneItemsResEXPERIMENTAL>;
112
- /**
113
- * Create scene items within a scene.
114
- */
115
- export declare function createSceneItemsEXPERIMENTAL({ client, createSceneItemReqs, failFast, parallelism, sceneId, }: CreateSceneItemsReq): Promise<CreateSceneItemsResEXPERIMENTAL>;
98
+ export declare function createSceneItems({ client, createSceneItemReqs, failFast, limit, sceneId, }: CreateSceneItemsReq): Promise<CreateSceneItemsRes>;
116
99
  /**
117
100
  * Delete all scenes.
118
101
  *
@@ -10,239 +10,280 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import pLimit from 'p-limit';
11
11
  import { hrtime } from 'process';
12
12
  import { BatchOperationOpEnum, BatchOperationRefTypeEnum, CameraFitTypeEnum, SceneRelationshipDataTypeEnum, UpdateSceneRequestDataAttributesStateEnum, } from '../../index';
13
- import { defined, getPage, hasVertexError, isQueuedJob, MaxAttempts, partition, PollIntervalMs, tryStream, } from '../index';
14
- import { arrayChunked, delay, isApiError, toAccept } from '../utils';
13
+ import { defined, getPage, hasVertexError, isPartNotFoundError, isQueuedJob, MaxAttempts, partition, PollIntervalMs, SceneItemSystemMetadata, tryStream, } from '../index';
14
+ import { arrayChunked, delay, formatTime, isApiError, isSceneItemRelationship, toAccept, } from '../utils';
15
15
  import { isBatch, isPollError, pollQueuedJob, throwOnError, } from './queued-jobs';
16
- const PollPercentage = 10;
16
+ const defaultPolling = { intervalMs: 200, maxAttempts: 4500 }; // 15 minute timeout for batch completions
17
+ const sceneReadyPolling = { intervalMs: 1000, maxAttempts: 3600 }; // one hour timeout for scene state ready
17
18
  /**
18
19
  * Create a scene with scene items.
19
20
  */
20
- export function createSceneAndSceneItems({ client, createSceneItemReqs, createSceneReq, failFast = false, onMsg = console.log, onProgress, parallelism, polling = { intervalMs: PollIntervalMs, maxAttempts: MaxAttempts }, returnQueued = false, verbose, }) {
21
- return __awaiter(this, void 0, void 0, function* () {
22
- const scene = (yield client.scenes.createScene({ createSceneRequest: createSceneReq() })).data;
23
- const sceneId = scene.data.id;
24
- const createRes = yield createSceneItems({
25
- client,
26
- createSceneItemReqs,
27
- failFast,
28
- onProgress,
29
- parallelism,
30
- sceneId,
31
- });
32
- const { a: queuedItems, b: errors } = partition(createRes.queuedSceneItems, (i) => isQueuedJob(i.res));
33
- const queued = returnQueued ? createRes.queuedSceneItems : [];
34
- if (queuedItems.length === 0 || errors.length === createRes.leaves) {
35
- return { errors, queued, scene };
36
- }
37
- if (verbose)
38
- onMsg(`Polling for completed scene-items...`);
39
- const limit = pLimit(Math.min(parallelism, 20));
40
- const cnt = queuedItems.length;
41
- const step = Math.floor(cnt / ((PollPercentage / 100) * cnt));
42
- const qis = [];
43
- // Poll for percentage of items starting at end of `queuedItems` array
44
- for (let i = 0; i < cnt; i += step)
45
- qis.push(queuedItems[cnt - i - 1]);
46
- function poll({ req, res }) {
47
- return __awaiter(this, void 0, void 0, function* () {
48
- const r = yield pollQueuedJob({
49
- id: res.data.id,
50
- getQueuedJob: (id, cancelToken) => client.sceneItems.getQueuedSceneItem({ id }, { cancelToken }),
51
- allow404: true,
52
- limit,
53
- polling,
54
- });
55
- if (isPollError(r.res)) {
56
- failFast ? throwOnError(r) : errors.push({ req, res: r.res });
57
- }
58
- });
59
- }
60
- yield Promise.all(qis.map((is) => limit(poll, is)));
61
- if (verbose)
62
- onMsg(`Committing scene and polling until ready...`);
63
- yield updateScene({
64
- attributes: { state: UpdateSceneRequestDataAttributesStateEnum.Commit },
65
- client,
66
- sceneId,
67
- });
68
- yield pollSceneReady({ client, id: sceneId, onMsg, polling, verbose });
69
- if (verbose)
70
- onMsg(`Fitting scene's camera to scene-items...`);
71
- const updated = (yield updateScene({
72
- attributes: { camera: { type: CameraFitTypeEnum.FitVisibleSceneItems } },
73
- client,
74
- sceneId,
75
- })).scene;
76
- return { errors, queued, scene: updated };
77
- });
78
- }
79
- /**
80
- * Create scene items within a scene.
81
- */
82
- export function createSceneItems({ client, createSceneItemReqs, failFast, onProgress, parallelism, sceneId, }) {
83
- return __awaiter(this, void 0, void 0, function* () {
84
- const limit = pLimit(parallelism);
85
- let complete = 0;
86
- let leaves = 0;
87
- const queuedSceneItems = yield Promise.all(createSceneItemReqs.map((r) => limit((req) => __awaiter(this, void 0, void 0, function* () {
88
- var _a;
89
- let res;
90
- try {
91
- if (defined(req.data.attributes.source) ||
92
- defined(req.data.relationships.source)) {
93
- leaves++;
94
- }
95
- res = (yield client.sceneItems.createSceneItem({
96
- id: sceneId,
97
- createSceneItemRequest: req,
98
- })).data;
99
- }
100
- catch (error) {
101
- if (!failFast && hasVertexError(error)) {
102
- res = (_a = error.vertexError) === null || _a === void 0 ? void 0 : _a.res;
103
- }
104
- else
105
- throw error;
106
- }
107
- if (onProgress != null) {
108
- complete += 1;
109
- onProgress(complete, createSceneItemReqs.length);
110
- }
111
- return { req, res };
112
- }), r)));
113
- return { leaves, queuedSceneItems };
114
- });
115
- }
116
- /**
117
- * Create a scene with scene items using experimental strategy.
118
- */
119
- export function createSceneAndSceneItemsEXPERIMENTAL({ client, createSceneItemReqs, createSceneReq, failFast = false, onMsg = console.log, onProgress, parallelism, polling = { intervalMs: PollIntervalMs, maxAttempts: MaxAttempts }, returnQueued = false, verbose, }) {
21
+ export function createSceneAndSceneItems({ client, createSceneItemReqs, createSceneReq, failFast = false, onMsg = console.log, onProgress, parallelism, polling = defaultPolling, returnQueued = false, verbose, }) {
120
22
  return __awaiter(this, void 0, void 0, function* () {
23
+ const limit = pLimit(Math.min(parallelism, 100));
121
24
  const startTime = hrtime.bigint();
122
25
  if (verbose)
123
26
  onMsg(`Creating scene...`);
124
27
  const scene = (yield client.scenes.createScene({ createSceneRequest: createSceneReq() })).data;
125
28
  const sceneId = scene.data.id;
29
+ if (verbose)
30
+ onMsg(`Scene ID: ${sceneId}`);
126
31
  if (verbose)
127
32
  onMsg(`Creating scene items...`);
128
33
  let itemCount = 0;
129
- let batchQueuedOps = [];
130
- let batchErrors = [];
131
- let sceneItemErrors = [];
132
- for (let depth = 0; depth < createSceneItemReqs.length; depth++) {
133
- const createItemReqs = createSceneItemReqs[depth];
34
+ let createFailed = false;
35
+ let sceneResult;
36
+ const reqMap = new Map();
37
+ // create parent map and set ordinals based on request order
38
+ createSceneItemReqs.forEach((req) => {
39
+ var _a, _b, _c;
40
+ const reqParent = (_c = (_a = req.data.attributes.parent) !== null && _a !== void 0 ? _a : (_b = req.data.relationships.parent) === null || _b === void 0 ? void 0 : _b.data.id) !== null && _c !== void 0 ? _c : '';
41
+ if (!reqMap.has(reqParent)) {
42
+ reqMap.set(reqParent, []);
43
+ }
44
+ const siblings = reqMap.get(reqParent);
45
+ if (req.data.attributes.ordinal == null) {
46
+ req.data.attributes.ordinal = siblings === null || siblings === void 0 ? void 0 : siblings.length;
47
+ }
48
+ siblings === null || siblings === void 0 ? void 0 : siblings.push(req);
49
+ });
50
+ // sort all scene item requests into depth sorted array of arrays
51
+ const depthSortedItems = [];
52
+ // fetch list of scene items with no parent (root items)
53
+ let nextChildren = reqMap.get('') || [];
54
+ reqMap.delete('');
55
+ while (nextChildren.length > 0) {
56
+ depthSortedItems.push(nextChildren);
57
+ nextChildren = nextChildren.flatMap((req) => {
58
+ if (req.data.attributes.suppliedId &&
59
+ reqMap.has(req.data.attributes.suppliedId)) {
60
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
61
+ const children = reqMap.get(req.data.attributes.suppliedId);
62
+ reqMap.delete(req.data.attributes.suppliedId);
63
+ return children;
64
+ }
65
+ else {
66
+ return [];
67
+ }
68
+ });
69
+ }
70
+ let resultQueuedOps = [];
71
+ let resultBatchErrors = [];
72
+ let resultItemErrors = [];
73
+ // if we had any scene item requests with invalid parents,
74
+ // add error entries indicating so.
75
+ reqMap.forEach((children) => {
76
+ children.forEach((childItem) => {
77
+ resultItemErrors.push({
78
+ req: childItem.data,
79
+ res: {
80
+ status: '404',
81
+ code: 'NotFound',
82
+ title: 'The requested resource was not found.',
83
+ source: { pointer: '/body/data/attributes/parent' },
84
+ },
85
+ });
86
+ });
87
+ });
88
+ let depth = 0;
89
+ for (depth; depth < depthSortedItems.length; depth++) {
90
+ const createItemReqs = depthSortedItems[depth];
134
91
  itemCount += createItemReqs.length;
135
92
  if (verbose)
136
93
  onMsg(`Creating ${createItemReqs.length} scene items at depth ${depth}...`);
137
94
  // Await is used intentionally to defer loop iteration
138
95
  // until all scene items have been created at each depth.
96
+ const { batchOps: queuedBatchOps, batchErrors: queuedBatchErrors, itemErrors: batchItemErrors, } =
139
97
  // eslint-disable-next-line no-await-in-loop
140
- const createRes = yield createSceneItemsEXPERIMENTAL({
98
+ yield createSceneItemBatch({
141
99
  client,
142
100
  createSceneItemReqs: createItemReqs,
143
101
  failFast,
144
102
  onProgress,
145
- parallelism,
103
+ limit,
146
104
  sceneId,
105
+ polling,
147
106
  });
148
- const { a: queuedOps, b: errors } = partition(createRes.queuedBatchOps, (i) => isQueuedJob(i.res));
149
- batchQueuedOps = batchQueuedOps.concat(queuedOps);
150
- if (errors.length) {
151
- batchErrors = batchErrors.concat(errors);
152
- if (verbose)
153
- onMsg(`WARNING: ${errors.length} scene item batch errors at depth ${depth}.`);
154
- }
155
- // Nothing succeeded, return early as something is likely wrong
156
- if (queuedOps.length === 0 || errors.length === createRes.chunks) {
157
- return {
158
- errors,
159
- queued: returnQueued ? createRes.queuedBatchOps : [],
160
- scene,
161
- sceneItemErrors: [],
162
- };
163
- }
164
- const limit = pLimit(Math.min(parallelism, 20));
165
- function poll({ ops, res, }) {
166
- return __awaiter(this, void 0, void 0, function* () {
167
- const r = yield pollQueuedJob({
168
- id: res.data.id,
169
- getQueuedJob: (id, cancelToken) => client.batches.getQueuedBatch({ id }, { cancelToken }),
170
- allow404: true,
171
- limit,
172
- polling,
173
- });
174
- if (isPollError(r.res)) {
175
- failFast ? throwOnError(r) : errors.push({ ops, res: r.res });
176
- }
177
- return r;
178
- });
179
- }
180
- // eslint-disable-next-line no-await-in-loop
181
- const batchRes = yield Promise.all(queuedOps.map((is) => limit(poll, is)));
182
- const batchItemErrors = batchRes
183
- .flatMap((b, i) => isBatch(b.res)
184
- ? b.res['vertexvis/batch:results'].map((r, j) => isApiError(r)
185
- ? { req: queuedOps[i].ops[j].data, res: r }
186
- : undefined)
187
- : [])
188
- .filter(defined);
107
+ resultQueuedOps = resultQueuedOps.concat(queuedBatchOps);
108
+ resultBatchErrors = resultBatchErrors.concat(queuedBatchErrors);
189
109
  if (batchItemErrors.length) {
190
- sceneItemErrors = sceneItemErrors.concat(batchItemErrors);
191
110
  if (verbose)
192
111
  onMsg(`WARNING: ${batchItemErrors.length} scene item creation errors at depth ${depth}.`);
112
+ resultItemErrors = resultItemErrors.concat(batchItemErrors);
113
+ if (failFast) {
114
+ createFailed = true;
115
+ break;
116
+ }
117
+ else {
118
+ // evaluate item errors and generate retry list
119
+ const retryErrors = batchItemErrors.filter((v) => isPartNotFoundError(v.res));
120
+ const retries = retryErrors.map((itemError) => {
121
+ var _a, _b, _c, _d, _e, _f;
122
+ const item = itemError.req;
123
+ return {
124
+ data: {
125
+ type: 'scene-item',
126
+ attributes: Object.assign(Object.assign({}, item.attributes), { metadata: Object.assign(Object.assign({}, item.attributes.metadata), { [SceneItemSystemMetadata.IsMissingGeometry]: toMetadataOrUndefined('1'), [SceneItemSystemMetadata.MissingGeometrySetId]: toMetadataOrUndefined((_a = item.relationships.source) === null || _a === void 0 ? void 0 : _a.data.id, ((_b = item.relationships.source) === null || _b === void 0 ? void 0 : _b.data.type) === 'geometry-set'), [SceneItemSystemMetadata.MissingPartRevisionId]: toMetadataOrUndefined((_c = item.relationships.source) === null || _c === void 0 ? void 0 : _c.data.id, ((_d = item.relationships.source) === null || _d === void 0 ? void 0 : _d.data.type) === 'part-revision'), [SceneItemSystemMetadata.MissingSuppliedPartId]: toMetadataOrUndefined((_e = item.attributes.source) === null || _e === void 0 ? void 0 : _e.suppliedPartId), [SceneItemSystemMetadata.MissingSuppliedPartRevisionId]: toMetadataOrUndefined((_f = item.attributes.source) === null || _f === void 0 ? void 0 : _f.suppliedRevisionId) }), source: undefined }),
127
+ relationships: Object.assign(Object.assign({}, item.relationships), { source: undefined }),
128
+ },
129
+ };
130
+ });
131
+ if (retries.length > 0) {
132
+ onMsg(`Creating ${retries.length} placeholder scene items at depth ${depth}.`);
133
+ // wait for placeholders to be created
134
+ const { itemResults: placeholderItemResults } =
135
+ // eslint-disable-next-line no-await-in-loop
136
+ yield createSceneItemBatch({
137
+ client,
138
+ createSceneItemReqs: retries,
139
+ failFast,
140
+ onProgress,
141
+ limit,
142
+ sceneId,
143
+ polling,
144
+ });
145
+ // attach placeholder references to item errors
146
+ placeholderItemResults.forEach((resultItem, i) => {
147
+ if (isSceneItemRelationship(resultItem.res)) {
148
+ retryErrors[i].placeholderItem = resultItem.res;
149
+ }
150
+ });
151
+ }
152
+ }
193
153
  }
194
154
  }
195
- if (verbose) {
196
- onMsg(`Scene item creation complete for ${itemCount} scene items with max depth of ${createSceneItemReqs.length - 1}.`);
197
- if (batchErrors.length) {
198
- onMsg(` Batch errors: ${batchErrors.length}`);
155
+ if (createFailed) {
156
+ if (verbose) {
157
+ onMsg(`Scene item creation failed in ${formatTime(Number(hrtime.bigint() - startTime) / 1000000000)} at depth ${depth}.`);
199
158
  }
200
- if (sceneItemErrors.length) {
201
- onMsg(` Scene item errors: ${sceneItemErrors.length}`);
159
+ }
160
+ else {
161
+ if (verbose) {
162
+ onMsg(`Scene item creation completed in ${formatTime(Number(hrtime.bigint() - startTime) / 1000000000)} for ${itemCount} scene items with max depth of ${depthSortedItems.length - 1}.`);
163
+ if (resultBatchErrors.length) {
164
+ onMsg(`Batch errors: ${resultBatchErrors.length}`);
165
+ }
166
+ if (resultItemErrors.length) {
167
+ onMsg(`Scene item errors: ${resultItemErrors.length}`);
168
+ }
202
169
  }
170
+ if (verbose)
171
+ onMsg(`Committing scene and polling until ready...`);
172
+ yield updateScene({
173
+ attributes: { state: UpdateSceneRequestDataAttributesStateEnum.Commit },
174
+ client,
175
+ sceneId,
176
+ });
177
+ yield pollSceneReady({
178
+ client,
179
+ id: sceneId,
180
+ onMsg,
181
+ polling: sceneReadyPolling,
182
+ verbose,
183
+ });
184
+ if (verbose)
185
+ onMsg(`Fitting scene's camera to scene items...`);
186
+ sceneResult = (yield updateScene({
187
+ attributes: {
188
+ camera: { type: CameraFitTypeEnum.FitVisibleSceneItems },
189
+ },
190
+ client,
191
+ sceneId,
192
+ })).scene;
203
193
  }
204
- if (verbose)
205
- onMsg(`Committing scene and polling until ready...`);
206
- yield updateScene({
207
- attributes: { state: UpdateSceneRequestDataAttributesStateEnum.Commit },
208
- client,
209
- sceneId,
210
- });
211
- yield pollSceneReady({ client, id: sceneId, onMsg, polling, verbose });
212
- if (verbose)
213
- onMsg(`Fitting scene's camera to scene items...`);
214
- const sceneResult = (yield updateScene({
215
- attributes: {
216
- camera: { type: CameraFitTypeEnum.FitVisibleSceneItems },
217
- },
218
- client,
219
- sceneId,
220
- })).scene;
221
194
  if (verbose) {
222
- const formatTime = (seconds) => {
223
- const h = Math.floor(seconds / 3600);
224
- const m = Math.floor((seconds % 3600) / 60);
225
- const s = Math.round(seconds % 60);
226
- return [h, m > 9 ? m : h ? '0' + m : m || '0', s > 9 ? s : '0' + s]
227
- .filter(Boolean)
228
- .join(':');
229
- };
230
195
  onMsg(`Scene creation completed in ${formatTime(Number(hrtime.bigint() - startTime) / 1000000000)}.`);
231
196
  }
232
197
  return {
233
- errors: batchErrors,
234
- queued: batchQueuedOps,
198
+ errors: resultBatchErrors,
199
+ queued: returnQueued ? resultQueuedOps : [],
235
200
  scene: sceneResult,
236
- sceneItemErrors,
201
+ sceneItemErrors: resultItemErrors,
237
202
  };
238
203
  });
239
204
  }
205
+ /**
206
+ * Helper function for building a metadata string object.
207
+ *
208
+ * @param value Value to convert to metadata object
209
+ * @param condition Setting to `false` will cause result to be `undefined`
210
+ * @returns Instance of a `MetadataValue` object
211
+ */
212
+ function toMetadataOrUndefined(value, condition = true) {
213
+ return condition && defined(value)
214
+ ? {
215
+ type: 'string',
216
+ value,
217
+ }
218
+ : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
219
+ undefined;
220
+ }
221
+ /**
222
+ * This async function takes a long list of create scene item data and handles
223
+ * batch based scene item creation. Batch operation results and errors are
224
+ * returned to the caller.
225
+ */
226
+ const createSceneItemBatch = ({ client, createSceneItemReqs: createItemReqs, failFast, onProgress, limit, sceneId, polling = { intervalMs: PollIntervalMs, maxAttempts: MaxAttempts }, }) => __awaiter(void 0, void 0, void 0, function* () {
227
+ let batchErrors = [];
228
+ let itemErrors = [];
229
+ let itemResults = [];
230
+ const createRes = yield createSceneItems({
231
+ client,
232
+ createSceneItemReqs: createItemReqs,
233
+ failFast,
234
+ onProgress,
235
+ limit,
236
+ sceneId,
237
+ });
238
+ const { a: batchOps, b: errors } = partition(createRes.queuedBatchOps, (i) => isQueuedJob(i.res));
239
+ if (errors.length) {
240
+ batchErrors = batchErrors.concat(errors);
241
+ }
242
+ // Nothing succeeded, return early as something is likely wrong
243
+ if (batchOps.length === 0 || errors.length === createRes.chunks) {
244
+ return { batchOps, batchErrors, itemErrors, itemResults };
245
+ }
246
+ else {
247
+ function poll({ ops, res, }) {
248
+ return __awaiter(this, void 0, void 0, function* () {
249
+ const r = yield pollQueuedJob({
250
+ id: res.data.id,
251
+ getQueuedJob: (id, cancelToken) => client.batches.getQueuedBatch({ id }, { cancelToken }),
252
+ allow404: true,
253
+ limit,
254
+ polling,
255
+ });
256
+ if (isPollError(r.res)) {
257
+ failFast ? throwOnError(r) : errors.push({ ops, res: r.res });
258
+ }
259
+ return r;
260
+ });
261
+ }
262
+ // eslint-disable-next-line no-await-in-loop
263
+ const batchRes = yield Promise.all(batchOps.map((is) => limit(poll, is)));
264
+ itemResults = batchRes.flatMap((b, i) => isBatch(b.res)
265
+ ? b.res['vertexvis/batch:results'].map((r, j) => {
266
+ return { req: batchOps[i].ops[j].data, res: r };
267
+ })
268
+ : []);
269
+ itemErrors = itemErrors.concat(itemResults.filter((resultItem) => isApiError(resultItem.res)));
270
+ }
271
+ // if the full batch failed add batch item error for each item
272
+ errors.forEach((error) => {
273
+ console.log(error);
274
+ error.ops.forEach((op) => {
275
+ // `error.res` guaranteed to be non-null due to `isApiError()` condition above
276
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
277
+ itemErrors.push({ req: op.data, res: error.res });
278
+ });
279
+ });
280
+ return { batchOps, batchErrors, itemErrors, itemResults };
281
+ });
240
282
  /**
241
283
  * Create scene items within a scene.
242
284
  */
243
- export function createSceneItemsEXPERIMENTAL({ client, createSceneItemReqs, failFast, parallelism, sceneId, }) {
285
+ export function createSceneItems({ client, createSceneItemReqs, failFast, limit, sceneId, }) {
244
286
  return __awaiter(this, void 0, void 0, function* () {
245
- const limit = pLimit(parallelism);
246
287
  const batchSize = 500;
247
288
  const opChunks = arrayChunked(createSceneItemReqs.map((req) => ({
248
289
  data: req.data,
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { AxiosError, AxiosResponse, Method } from 'axios';
3
3
  import { ParsedUrlQuery } from 'querystring';
4
- import { ApiError, Failure, ImageType, Matrix4, Oauth2Api, OAuth2Token, QueuedJob } from '../index';
4
+ import { ApiError, Failure, ImageType, Matrix4, Oauth2Api, OAuth2Token, QueuedJob, RelationshipData } from '../index';
5
5
  export interface Cursors {
6
6
  readonly next?: string;
7
7
  readonly self?: string;
@@ -136,6 +136,7 @@ export declare function is4x4Identity(transform: number[][]): boolean;
136
136
  */
137
137
  export declare function isEncoded(s: string): boolean;
138
138
  export declare function isApiError(error: unknown): error is ApiError;
139
+ export declare function isSceneItemRelationship(data: unknown): data is RelationshipData;
139
140
  export declare function isFailure(obj: unknown): obj is Failure;
140
141
  export declare function isQueuedJob(obj: unknown): obj is QueuedJob;
141
142
  export declare function hasVertexError(error: unknown): error is VertexError;
@@ -225,3 +226,9 @@ export declare function tryStream<T>(fn: () => Promise<T>): Promise<T>;
225
226
  * @returns `true` if webhook signature is valid and body is safe to parse.
226
227
  */
227
228
  export declare function isWebhookValid(body: string, secret: string, signature: string): boolean;
229
+ /**
230
+ * Formats a number in seconds as a time formatted string.
231
+ * @param seconds number of seconds to be formatted
232
+ * @returns `string` in the form of HH:MM:SS based on input
233
+ */
234
+ export declare function formatTime(seconds: number): string;