@saschabrunnerch/arcgis-maps-sdk-js-ai-context 0.0.1 → 0.1.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.
Files changed (50) hide show
  1. package/README.md +163 -201
  2. package/bin/cli.js +157 -173
  3. package/contexts/4.34/{claude → skills}/arcgis-3d-advanced/SKILL.md +586 -586
  4. package/contexts/4.34/{claude → skills}/arcgis-advanced-layers/SKILL.md +431 -431
  5. package/contexts/4.34/{claude → skills}/arcgis-analysis-services/SKILL.md +607 -607
  6. package/contexts/4.34/{claude → skills}/arcgis-authentication/SKILL.md +301 -301
  7. package/contexts/4.34/{claude → skills}/arcgis-cim-symbols/SKILL.md +486 -486
  8. package/contexts/4.34/{claude → skills}/arcgis-coordinates-projection/SKILL.md +406 -406
  9. package/contexts/4.34/{claude → skills}/arcgis-core-maps/SKILL.md +739 -739
  10. package/contexts/4.34/{claude → skills}/arcgis-core-utilities/SKILL.md +732 -732
  11. package/contexts/4.34/{claude → skills}/arcgis-custom-rendering/SKILL.md +445 -445
  12. package/contexts/4.34/{claude → skills}/arcgis-editing-advanced/SKILL.md +702 -702
  13. package/contexts/4.34/{claude → skills}/arcgis-feature-effects/SKILL.md +393 -393
  14. package/contexts/4.34/{claude → skills}/arcgis-geometry-operations/SKILL.md +489 -489
  15. package/contexts/4.34/{claude → skills}/arcgis-imagery/SKILL.md +307 -307
  16. package/contexts/4.34/{claude → skills}/arcgis-interaction/SKILL.md +572 -572
  17. package/contexts/4.34/{claude → skills}/arcgis-knowledge-graphs/SKILL.md +582 -582
  18. package/contexts/4.34/{claude → skills}/arcgis-layers/SKILL.md +601 -601
  19. package/contexts/4.34/{claude → skills}/arcgis-map-tools/SKILL.md +668 -668
  20. package/contexts/4.34/{claude → skills}/arcgis-media-layers/SKILL.md +290 -290
  21. package/contexts/4.34/{claude → skills}/arcgis-portal-content/SKILL.md +679 -679
  22. package/contexts/4.34/{claude → skills}/arcgis-scene-effects/SKILL.md +512 -512
  23. package/contexts/4.34/{claude → skills}/arcgis-smart-mapping/SKILL.md +686 -686
  24. package/contexts/4.34/skills/arcgis-starter-app/SKILL.md +273 -0
  25. package/contexts/4.34/skills/arcgis-starter-app-extended/SKILL.md +649 -0
  26. package/contexts/4.34/{claude → skills}/arcgis-tables-forms/SKILL.md +877 -877
  27. package/contexts/4.34/{claude → skills}/arcgis-time-animation/SKILL.md +722 -722
  28. package/contexts/4.34/{claude → skills}/arcgis-utility-networks/SKILL.md +301 -301
  29. package/contexts/4.34/{claude → skills}/arcgis-visualization/SKILL.md +580 -580
  30. package/contexts/4.34/{claude → skills}/arcgis-widgets-ui/SKILL.md +574 -574
  31. package/lib/installer.js +294 -379
  32. package/package.json +45 -45
  33. package/contexts/4.34/copilot/arcgis-3d.instructions.md +0 -267
  34. package/contexts/4.34/copilot/arcgis-analysis.instructions.md +0 -294
  35. package/contexts/4.34/copilot/arcgis-arcade.instructions.md +0 -234
  36. package/contexts/4.34/copilot/arcgis-authentication.instructions.md +0 -187
  37. package/contexts/4.34/copilot/arcgis-cim-symbols.instructions.md +0 -177
  38. package/contexts/4.34/copilot/arcgis-core-maps.instructions.md +0 -246
  39. package/contexts/4.34/copilot/arcgis-core-utilities.instructions.md +0 -247
  40. package/contexts/4.34/copilot/arcgis-editing.instructions.md +0 -262
  41. package/contexts/4.34/copilot/arcgis-geometry.instructions.md +0 -225
  42. package/contexts/4.34/copilot/arcgis-layers.instructions.md +0 -278
  43. package/contexts/4.34/copilot/arcgis-popup-templates.instructions.md +0 -266
  44. package/contexts/4.34/copilot/arcgis-portal-advanced.instructions.md +0 -275
  45. package/contexts/4.34/copilot/arcgis-smart-mapping.instructions.md +0 -184
  46. package/contexts/4.34/copilot/arcgis-time-animation.instructions.md +0 -112
  47. package/contexts/4.34/copilot/arcgis-visualization.instructions.md +0 -321
  48. package/contexts/4.34/copilot/arcgis-widgets-ui.instructions.md +0 -277
  49. /package/contexts/4.34/{claude → skills}/arcgis-arcade/SKILL.md +0 -0
  50. /package/contexts/4.34/{claude → skills}/arcgis-popup-templates/SKILL.md +0 -0
@@ -1,732 +1,732 @@
1
- ---
2
- name: arcgis-core-utilities
3
- description: Core utilities including Accessor pattern, Collection, reactiveUtils, promiseUtils, and workers. Use for reactive programming, property watching, async operations, and background processing.
4
- ---
5
-
6
- # ArcGIS Core Utilities
7
-
8
- Use this skill for core infrastructure patterns like Accessor, Collection, reactive utilities, and workers.
9
-
10
- ## Accessor Pattern
11
-
12
- The Accessor class is the foundation for all ArcGIS classes, providing property watching and computed properties.
13
-
14
- ### Creating Custom Accessor Classes
15
-
16
- ```javascript
17
- import Accessor from "@arcgis/core/core/Accessor.js";
18
- import { subclass, property } from "@arcgis/core/core/accessorSupport/decorators.js";
19
-
20
- @subclass("myapp.CustomClass")
21
- class CustomClass extends Accessor {
22
- @property()
23
- name = "default";
24
-
25
- @property()
26
- value = 0;
27
-
28
- // Computed property
29
- @property({
30
- readOnly: true,
31
- dependsOn: ["name", "value"]
32
- })
33
- get summary() {
34
- return `${this.name}: ${this.value}`;
35
- }
36
- }
37
-
38
- const instance = new CustomClass({ name: "Test", value: 42 });
39
- console.log(instance.summary); // "Test: 42"
40
- ```
41
-
42
- ### Property Decorators
43
-
44
- ```javascript
45
- @subclass("myapp.MyClass")
46
- class MyClass extends Accessor {
47
- // Basic property
48
- @property()
49
- title = "";
50
-
51
- // Property with type casting
52
- @property({ type: Number })
53
- count = 0;
54
-
55
- // Read-only computed property
56
- @property({ readOnly: true })
57
- get displayName() {
58
- return this.title.toUpperCase();
59
- }
60
-
61
- // Property with custom setter
62
- @property()
63
- get status() {
64
- return this._status;
65
- }
66
- set status(value) {
67
- this._status = value?.toLowerCase() || "unknown";
68
- }
69
-
70
- // Property that depends on others
71
- @property({
72
- readOnly: true,
73
- dependsOn: ["count", "title"]
74
- })
75
- get info() {
76
- return `${this.title} (${this.count})`;
77
- }
78
- }
79
- ```
80
-
81
- ### Property Types
82
-
83
- ```javascript
84
- @property({ type: String })
85
- name = "";
86
-
87
- @property({ type: Number })
88
- count = 0;
89
-
90
- @property({ type: Boolean })
91
- enabled = true;
92
-
93
- @property({ type: Date })
94
- createdAt = new Date();
95
-
96
- @property({ type: [Number] }) // Array of numbers
97
- values = [];
98
-
99
- @property({ type: SomeClass }) // Custom class
100
- child = null;
101
- ```
102
-
103
- ## Collection
104
-
105
- Collection is an array-like class with change notifications.
106
-
107
- ### Basic Usage
108
-
109
- ```javascript
110
- import Collection from "@arcgis/core/core/Collection.js";
111
-
112
- const collection = new Collection([
113
- { id: 1, name: "Item 1" },
114
- { id: 2, name: "Item 2" }
115
- ]);
116
-
117
- // Add items
118
- collection.add({ id: 3, name: "Item 3" });
119
- collection.addMany([
120
- { id: 4, name: "Item 4" },
121
- { id: 5, name: "Item 5" }
122
- ]);
123
-
124
- // Remove items
125
- collection.remove(item);
126
- collection.removeAt(0);
127
- collection.removeMany([item1, item2]);
128
- collection.removeAll();
129
-
130
- // Access items
131
- const first = collection.getItemAt(0);
132
- const all = collection.toArray();
133
- const length = collection.length;
134
- ```
135
-
136
- ### Collection Methods
137
-
138
- ```javascript
139
- // Find items
140
- const found = collection.find(item => item.id === 3);
141
- const index = collection.findIndex(item => item.name === "Item 2");
142
- const filtered = collection.filter(item => item.value > 10);
143
-
144
- // Transform
145
- const mapped = collection.map(item => item.name);
146
- const reduced = collection.reduce((acc, item) => acc + item.value, 0);
147
-
148
- // Check
149
- const hasItem = collection.includes(item);
150
- const exists = collection.some(item => item.active);
151
- const allActive = collection.every(item => item.active);
152
-
153
- // Sort and reorder
154
- collection.sort((a, b) => a.name.localeCompare(b.name));
155
- collection.reverse();
156
- collection.reorder(item, 0); // Move item to index 0
157
-
158
- // Iterate
159
- collection.forEach(item => console.log(item.name));
160
- for (const item of collection) {
161
- console.log(item);
162
- }
163
- ```
164
-
165
- ### Collection Events
166
-
167
- ```javascript
168
- // Watch for changes
169
- collection.on("change", (event) => {
170
- console.log("Added:", event.added);
171
- console.log("Removed:", event.removed);
172
- console.log("Moved:", event.moved);
173
- });
174
-
175
- // Watch length
176
- collection.watch("length", (newLength, oldLength) => {
177
- console.log(`Length changed from ${oldLength} to ${newLength}`);
178
- });
179
- ```
180
-
181
- ### Typed Collections
182
-
183
- ```javascript
184
- // Collection of specific type
185
- import Graphic from "@arcgis/core/Graphic.js";
186
-
187
- const graphics = new Collection();
188
- graphics.addMany([
189
- new Graphic({ geometry: point1 }),
190
- new Graphic({ geometry: point2 })
191
- ]);
192
-
193
- // Map's layers is a Collection
194
- map.layers.add(featureLayer);
195
- map.layers.reorder(featureLayer, 0);
196
- ```
197
-
198
- ## reactiveUtils
199
-
200
- Modern reactive utilities for watching properties and state changes.
201
-
202
- ### watch()
203
-
204
- Watch a property for changes.
205
-
206
- ```javascript
207
- import reactiveUtils from "@arcgis/core/core/reactiveUtils.js";
208
-
209
- // Watch single property
210
- const handle = reactiveUtils.watch(
211
- () => view.scale,
212
- (scale) => {
213
- console.log("Scale changed to:", scale);
214
- }
215
- );
216
-
217
- // With options
218
- reactiveUtils.watch(
219
- () => view.extent,
220
- (extent) => console.log("Extent:", extent),
221
- { initial: true } // Call immediately with current value
222
- );
223
-
224
- // Stop watching
225
- handle.remove();
226
- ```
227
-
228
- ### when()
229
-
230
- Wait for a condition to become true.
231
-
232
- ```javascript
233
- // Wait for view to be ready
234
- await reactiveUtils.when(
235
- () => view.ready,
236
- () => console.log("View is ready!")
237
- );
238
-
239
- // Wait for layer to load
240
- await reactiveUtils.when(
241
- () => layer.loaded
242
- );
243
- console.log("Layer loaded");
244
-
245
- // With timeout (throws if not met)
246
- await reactiveUtils.when(
247
- () => view.stationary,
248
- { timeout: 5000 }
249
- );
250
- ```
251
-
252
- ### once()
253
-
254
- Watch for a value change only once.
255
-
256
- ```javascript
257
- // Execute once when condition is met
258
- await reactiveUtils.once(
259
- () => view.ready
260
- );
261
- console.log("View became ready");
262
-
263
- // Once with callback
264
- reactiveUtils.once(
265
- () => layer.visible,
266
- (visible) => console.log("Layer visibility changed to:", visible)
267
- );
268
- ```
269
-
270
- ### whenOnce()
271
-
272
- Deprecated - use when() or once() instead.
273
-
274
- ### on()
275
-
276
- Listen to events reactively.
277
-
278
- ```javascript
279
- // Listen to click events
280
- const handle = reactiveUtils.on(
281
- () => view,
282
- "click",
283
- (event) => {
284
- console.log("Clicked at:", event.mapPoint);
285
- }
286
- );
287
-
288
- // Listen to layer view events
289
- reactiveUtils.on(
290
- () => view.whenLayerView(layer),
291
- "highlight",
292
- (event) => console.log("Highlight changed")
293
- );
294
- ```
295
-
296
- ### Multiple Properties
297
-
298
- ```javascript
299
- // Watch multiple properties
300
- reactiveUtils.watch(
301
- () => [view.center, view.zoom],
302
- ([center, zoom]) => {
303
- console.log(`Center: ${center.x}, ${center.y}, Zoom: ${zoom}`);
304
- }
305
- );
306
-
307
- // Computed expression
308
- reactiveUtils.watch(
309
- () => view.scale < 50000,
310
- (isZoomedIn) => {
311
- layer.visible = isZoomedIn;
312
- }
313
- );
314
- ```
315
-
316
- ### Sync Option
317
-
318
- ```javascript
319
- // Synchronous updates (use carefully)
320
- reactiveUtils.watch(
321
- () => view.extent,
322
- (extent) => updateUI(extent),
323
- { sync: true }
324
- );
325
- ```
326
-
327
- ## Handles
328
-
329
- Manage multiple watch handles for cleanup.
330
-
331
- ```javascript
332
- import Handles from "@arcgis/core/core/Handles.js";
333
-
334
- const handles = new Handles();
335
-
336
- // Add handles
337
- handles.add(
338
- reactiveUtils.watch(() => view.scale, (scale) => {})
339
- );
340
-
341
- handles.add(
342
- view.on("click", (e) => {}),
343
- "click-handlers" // Group name
344
- );
345
-
346
- handles.add([
347
- reactiveUtils.watch(() => view.center, () => {}),
348
- reactiveUtils.watch(() => view.zoom, () => {})
349
- ], "view-watchers");
350
-
351
- // Remove specific group
352
- handles.remove("click-handlers");
353
-
354
- // Remove all
355
- handles.removeAll();
356
-
357
- // Destroy (also removes all)
358
- handles.destroy();
359
- ```
360
-
361
- ### In Custom Classes
362
-
363
- ```javascript
364
- @subclass("myapp.MyWidget")
365
- class MyWidget extends Accessor {
366
- constructor(props) {
367
- super(props);
368
- this.handles = new Handles();
369
- }
370
-
371
- initialize() {
372
- this.handles.add(
373
- reactiveUtils.watch(
374
- () => this.view?.scale,
375
- (scale) => this.onScaleChange(scale)
376
- )
377
- );
378
- }
379
-
380
- destroy() {
381
- this.handles.destroy();
382
- super.destroy();
383
- }
384
- }
385
- ```
386
-
387
- ## promiseUtils
388
-
389
- Utilities for working with Promises.
390
-
391
- ```javascript
392
- import promiseUtils from "@arcgis/core/core/promiseUtils.js";
393
- ```
394
-
395
- ### create()
396
-
397
- Create an abortable promise.
398
-
399
- ```javascript
400
- const { promise, resolve, reject } = promiseUtils.create();
401
-
402
- // Later...
403
- resolve(result);
404
- // or
405
- reject(new Error("Failed"));
406
-
407
- // Wait for it
408
- const result = await promise;
409
- ```
410
-
411
- ### eachAlways()
412
-
413
- Wait for all promises, regardless of success/failure.
414
-
415
- ```javascript
416
- const results = await promiseUtils.eachAlways([
417
- fetch("/api/data1"),
418
- fetch("/api/data2"),
419
- fetch("/api/data3")
420
- ]);
421
-
422
- results.forEach((result, index) => {
423
- if (result.error) {
424
- console.error(`Request ${index} failed:`, result.error);
425
- } else {
426
- console.log(`Request ${index} succeeded:`, result.value);
427
- }
428
- });
429
- ```
430
-
431
- ### debounce()
432
-
433
- Debounce a function.
434
-
435
- ```javascript
436
- const debouncedSearch = promiseUtils.debounce(async (query) => {
437
- const results = await searchService.search(query);
438
- return results;
439
- });
440
-
441
- // Rapid calls will be debounced
442
- input.addEventListener("input", async (e) => {
443
- try {
444
- const results = await debouncedSearch(e.target.value);
445
- displayResults(results);
446
- } catch (e) {
447
- if (!promiseUtils.isAbortError(e)) {
448
- console.error(e);
449
- }
450
- }
451
- });
452
- ```
453
-
454
- ### throttle()
455
-
456
- Throttle a function.
457
-
458
- ```javascript
459
- const throttledUpdate = promiseUtils.throttle(async (extent) => {
460
- await updateDisplay(extent);
461
- }, 100); // Max once per 100ms
462
-
463
- view.watch("extent", throttledUpdate);
464
- ```
465
-
466
- ### isAbortError()
467
-
468
- Check if error is from aborted operation.
469
-
470
- ```javascript
471
- try {
472
- await someAsyncOperation();
473
- } catch (error) {
474
- if (promiseUtils.isAbortError(error)) {
475
- console.log("Operation was cancelled");
476
- } else {
477
- throw error;
478
- }
479
- }
480
- ```
481
-
482
- ### timeout()
483
-
484
- Add timeout to a promise.
485
-
486
- ```javascript
487
- try {
488
- const result = await promiseUtils.timeout(
489
- fetch("/api/slow-endpoint"),
490
- 5000 // 5 second timeout
491
- );
492
- } catch (e) {
493
- console.error("Request timed out or failed");
494
- }
495
- ```
496
-
497
- ## Workers
498
-
499
- Run heavy computations in background threads.
500
-
501
- ```javascript
502
- import workers from "@arcgis/core/core/workers.js";
503
- ```
504
-
505
- ### Open Worker Connection
506
-
507
- ```javascript
508
- const connection = await workers.open("path/to/worker.js");
509
-
510
- // Execute method on worker
511
- const result = await connection.invoke("methodName", { data: "params" });
512
-
513
- // Close when done
514
- connection.close();
515
- ```
516
-
517
- ### Worker Script Example
518
-
519
- ```javascript
520
- // worker.js
521
- define([], function() {
522
- return {
523
- methodName: function(params) {
524
- // Heavy computation here
525
- const result = processData(params.data);
526
- return result;
527
- },
528
-
529
- anotherMethod: function(params) {
530
- return params.value * 2;
531
- }
532
- };
533
- });
534
- ```
535
-
536
- ### Using Workers for Heavy Tasks
537
-
538
- ```javascript
539
- // Main script
540
- import workers from "@arcgis/core/core/workers.js";
541
-
542
- async function processLargeDataset(data) {
543
- const connection = await workers.open("./dataProcessor.js");
544
-
545
- try {
546
- const result = await connection.invoke("process", {
547
- data: data,
548
- options: { threshold: 100 }
549
- });
550
- return result;
551
- } finally {
552
- connection.close();
553
- }
554
- }
555
- ```
556
-
557
- ## Error Handling
558
-
559
- ```javascript
560
- import Error from "@arcgis/core/core/Error.js";
561
-
562
- // Create custom error
563
- const error = new Error("layer-load-error", "Failed to load layer", {
564
- layer: layer,
565
- originalError: e
566
- });
567
-
568
- // Check error type
569
- if (error.name === "layer-load-error") {
570
- console.log("Layer failed:", error.details.layer.title);
571
- }
572
- ```
573
-
574
- ## URL Utilities
575
-
576
- ```javascript
577
- import urlUtils from "@arcgis/core/core/urlUtils.js";
578
-
579
- // Add proxy rule
580
- urlUtils.addProxyRule({
581
- urlPrefix: "https://services.arcgis.com",
582
- proxyUrl: "/proxy"
583
- });
584
-
585
- // Get proxy URL
586
- const proxiedUrl = urlUtils.getProxyUrl("https://services.arcgis.com/data");
587
-
588
- // URL helpers
589
- const normalized = urlUtils.normalize("https://example.com//path//to//resource");
590
- ```
591
-
592
- ## Units and Quantities
593
-
594
- ```javascript
595
- import units from "@arcgis/core/core/units.js";
596
-
597
- // Convert units
598
- const meters = units.convertUnit(100, "feet", "meters");
599
- const sqKm = units.convertUnit(1000, "acres", "square-kilometers");
600
-
601
- // Get unit info
602
- const info = units.getUnitInfo("meters");
603
- console.log(info.abbreviation); // "m"
604
- console.log(info.pluralLabel); // "meters"
605
- ```
606
-
607
- ## Scheduling
608
-
609
- ```javascript
610
- import scheduling from "@arcgis/core/core/scheduling.js";
611
-
612
- // Schedule for next frame
613
- const handle = scheduling.addFrameTask({
614
- update: (event) => {
615
- // Called every frame
616
- console.log("Delta time:", event.deltaTime);
617
- console.log("Elapsed:", event.elapsedTime);
618
- }
619
- });
620
-
621
- // Remove task
622
- handle.remove();
623
-
624
- // One-time frame callback
625
- scheduling.schedule(() => {
626
- console.log("Executed on next frame");
627
- });
628
- ```
629
-
630
- ## Common Patterns
631
-
632
- ### Cleanup Pattern
633
-
634
- ```javascript
635
- class MyComponent {
636
- constructor(view) {
637
- this.view = view;
638
- this.handles = new Handles();
639
- this.setup();
640
- }
641
-
642
- setup() {
643
- this.handles.add([
644
- reactiveUtils.watch(
645
- () => this.view.scale,
646
- (scale) => this.onScaleChange(scale)
647
- ),
648
- reactiveUtils.watch(
649
- () => this.view.extent,
650
- (extent) => this.onExtentChange(extent)
651
- ),
652
- this.view.on("click", (e) => this.onClick(e))
653
- ]);
654
- }
655
-
656
- destroy() {
657
- this.handles.removeAll();
658
- }
659
- }
660
- ```
661
-
662
- ### Debounced Updates
663
-
664
- ```javascript
665
- const debouncedUpdate = promiseUtils.debounce(async () => {
666
- const extent = view.extent;
667
- const results = await layer.queryFeatures({
668
- geometry: extent,
669
- returnGeometry: true
670
- });
671
- updateDisplay(results);
672
- });
673
-
674
- reactiveUtils.watch(
675
- () => view.stationary && view.extent,
676
- debouncedUpdate
677
- );
678
- ```
679
-
680
- ### Conditional Watching
681
-
682
- ```javascript
683
- // Only react when view is stationary
684
- reactiveUtils.watch(
685
- () => view.stationary ? view.extent : null,
686
- (extent) => {
687
- if (extent) {
688
- updateForExtent(extent);
689
- }
690
- }
691
- );
692
- ```
693
-
694
- ## Common Pitfalls
695
-
696
- 1. **Memory Leaks**: Always remove handles when done
697
- ```javascript
698
- // Store handle reference
699
- const handle = reactiveUtils.watch(...);
700
- // Remove when no longer needed
701
- handle.remove();
702
- ```
703
-
704
- 2. **Initial Value**: Use `initial: true` to get current value immediately
705
- ```javascript
706
- reactiveUtils.watch(
707
- () => view.scale,
708
- (scale) => updateUI(scale),
709
- { initial: true } // Called immediately with current scale
710
- );
711
- ```
712
-
713
- 3. **Sync vs Async**: Default is async batching, use `sync: true` carefully
714
- ```javascript
715
- // Async (default) - batched, better performance
716
- reactiveUtils.watch(() => value, callback);
717
-
718
- // Sync - immediate, can cause performance issues
719
- reactiveUtils.watch(() => value, callback, { sync: true });
720
- ```
721
-
722
- 4. **Abort Errors**: Always check for abort errors in catch blocks
723
- ```javascript
724
- try {
725
- await debouncedFunction();
726
- } catch (e) {
727
- if (!promiseUtils.isAbortError(e)) {
728
- throw e;
729
- }
730
- }
731
- ```
732
-
1
+ ---
2
+ name: arcgis-core-utilities
3
+ description: Core utilities including Accessor pattern, Collection, reactiveUtils, promiseUtils, and workers. Use for reactive programming, property watching, async operations, and background processing.
4
+ ---
5
+
6
+ # ArcGIS Core Utilities
7
+
8
+ Use this skill for core infrastructure patterns like Accessor, Collection, reactive utilities, and workers.
9
+
10
+ ## Accessor Pattern
11
+
12
+ The Accessor class is the foundation for all ArcGIS classes, providing property watching and computed properties.
13
+
14
+ ### Creating Custom Accessor Classes
15
+
16
+ ```javascript
17
+ import Accessor from "@arcgis/core/core/Accessor.js";
18
+ import { subclass, property } from "@arcgis/core/core/accessorSupport/decorators.js";
19
+
20
+ @subclass("myapp.CustomClass")
21
+ class CustomClass extends Accessor {
22
+ @property()
23
+ name = "default";
24
+
25
+ @property()
26
+ value = 0;
27
+
28
+ // Computed property
29
+ @property({
30
+ readOnly: true,
31
+ dependsOn: ["name", "value"]
32
+ })
33
+ get summary() {
34
+ return `${this.name}: ${this.value}`;
35
+ }
36
+ }
37
+
38
+ const instance = new CustomClass({ name: "Test", value: 42 });
39
+ console.log(instance.summary); // "Test: 42"
40
+ ```
41
+
42
+ ### Property Decorators
43
+
44
+ ```javascript
45
+ @subclass("myapp.MyClass")
46
+ class MyClass extends Accessor {
47
+ // Basic property
48
+ @property()
49
+ title = "";
50
+
51
+ // Property with type casting
52
+ @property({ type: Number })
53
+ count = 0;
54
+
55
+ // Read-only computed property
56
+ @property({ readOnly: true })
57
+ get displayName() {
58
+ return this.title.toUpperCase();
59
+ }
60
+
61
+ // Property with custom setter
62
+ @property()
63
+ get status() {
64
+ return this._status;
65
+ }
66
+ set status(value) {
67
+ this._status = value?.toLowerCase() || "unknown";
68
+ }
69
+
70
+ // Property that depends on others
71
+ @property({
72
+ readOnly: true,
73
+ dependsOn: ["count", "title"]
74
+ })
75
+ get info() {
76
+ return `${this.title} (${this.count})`;
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### Property Types
82
+
83
+ ```javascript
84
+ @property({ type: String })
85
+ name = "";
86
+
87
+ @property({ type: Number })
88
+ count = 0;
89
+
90
+ @property({ type: Boolean })
91
+ enabled = true;
92
+
93
+ @property({ type: Date })
94
+ createdAt = new Date();
95
+
96
+ @property({ type: [Number] }) // Array of numbers
97
+ values = [];
98
+
99
+ @property({ type: SomeClass }) // Custom class
100
+ child = null;
101
+ ```
102
+
103
+ ## Collection
104
+
105
+ Collection is an array-like class with change notifications.
106
+
107
+ ### Basic Usage
108
+
109
+ ```javascript
110
+ import Collection from "@arcgis/core/core/Collection.js";
111
+
112
+ const collection = new Collection([
113
+ { id: 1, name: "Item 1" },
114
+ { id: 2, name: "Item 2" }
115
+ ]);
116
+
117
+ // Add items
118
+ collection.add({ id: 3, name: "Item 3" });
119
+ collection.addMany([
120
+ { id: 4, name: "Item 4" },
121
+ { id: 5, name: "Item 5" }
122
+ ]);
123
+
124
+ // Remove items
125
+ collection.remove(item);
126
+ collection.removeAt(0);
127
+ collection.removeMany([item1, item2]);
128
+ collection.removeAll();
129
+
130
+ // Access items
131
+ const first = collection.getItemAt(0);
132
+ const all = collection.toArray();
133
+ const length = collection.length;
134
+ ```
135
+
136
+ ### Collection Methods
137
+
138
+ ```javascript
139
+ // Find items
140
+ const found = collection.find(item => item.id === 3);
141
+ const index = collection.findIndex(item => item.name === "Item 2");
142
+ const filtered = collection.filter(item => item.value > 10);
143
+
144
+ // Transform
145
+ const mapped = collection.map(item => item.name);
146
+ const reduced = collection.reduce((acc, item) => acc + item.value, 0);
147
+
148
+ // Check
149
+ const hasItem = collection.includes(item);
150
+ const exists = collection.some(item => item.active);
151
+ const allActive = collection.every(item => item.active);
152
+
153
+ // Sort and reorder
154
+ collection.sort((a, b) => a.name.localeCompare(b.name));
155
+ collection.reverse();
156
+ collection.reorder(item, 0); // Move item to index 0
157
+
158
+ // Iterate
159
+ collection.forEach(item => console.log(item.name));
160
+ for (const item of collection) {
161
+ console.log(item);
162
+ }
163
+ ```
164
+
165
+ ### Collection Events
166
+
167
+ ```javascript
168
+ // Watch for changes
169
+ collection.on("change", (event) => {
170
+ console.log("Added:", event.added);
171
+ console.log("Removed:", event.removed);
172
+ console.log("Moved:", event.moved);
173
+ });
174
+
175
+ // Watch length
176
+ collection.watch("length", (newLength, oldLength) => {
177
+ console.log(`Length changed from ${oldLength} to ${newLength}`);
178
+ });
179
+ ```
180
+
181
+ ### Typed Collections
182
+
183
+ ```javascript
184
+ // Collection of specific type
185
+ import Graphic from "@arcgis/core/Graphic.js";
186
+
187
+ const graphics = new Collection();
188
+ graphics.addMany([
189
+ new Graphic({ geometry: point1 }),
190
+ new Graphic({ geometry: point2 })
191
+ ]);
192
+
193
+ // Map's layers is a Collection
194
+ map.layers.add(featureLayer);
195
+ map.layers.reorder(featureLayer, 0);
196
+ ```
197
+
198
+ ## reactiveUtils
199
+
200
+ Modern reactive utilities for watching properties and state changes.
201
+
202
+ ### watch()
203
+
204
+ Watch a property for changes.
205
+
206
+ ```javascript
207
+ import reactiveUtils from "@arcgis/core/core/reactiveUtils.js";
208
+
209
+ // Watch single property
210
+ const handle = reactiveUtils.watch(
211
+ () => view.scale,
212
+ (scale) => {
213
+ console.log("Scale changed to:", scale);
214
+ }
215
+ );
216
+
217
+ // With options
218
+ reactiveUtils.watch(
219
+ () => view.extent,
220
+ (extent) => console.log("Extent:", extent),
221
+ { initial: true } // Call immediately with current value
222
+ );
223
+
224
+ // Stop watching
225
+ handle.remove();
226
+ ```
227
+
228
+ ### when()
229
+
230
+ Wait for a condition to become true.
231
+
232
+ ```javascript
233
+ // Wait for view to be ready
234
+ await reactiveUtils.when(
235
+ () => view.ready,
236
+ () => console.log("View is ready!")
237
+ );
238
+
239
+ // Wait for layer to load
240
+ await reactiveUtils.when(
241
+ () => layer.loaded
242
+ );
243
+ console.log("Layer loaded");
244
+
245
+ // With timeout (throws if not met)
246
+ await reactiveUtils.when(
247
+ () => view.stationary,
248
+ { timeout: 5000 }
249
+ );
250
+ ```
251
+
252
+ ### once()
253
+
254
+ Watch for a value change only once.
255
+
256
+ ```javascript
257
+ // Execute once when condition is met
258
+ await reactiveUtils.once(
259
+ () => view.ready
260
+ );
261
+ console.log("View became ready");
262
+
263
+ // Once with callback
264
+ reactiveUtils.once(
265
+ () => layer.visible,
266
+ (visible) => console.log("Layer visibility changed to:", visible)
267
+ );
268
+ ```
269
+
270
+ ### whenOnce()
271
+
272
+ Deprecated - use when() or once() instead.
273
+
274
+ ### on()
275
+
276
+ Listen to events reactively.
277
+
278
+ ```javascript
279
+ // Listen to click events
280
+ const handle = reactiveUtils.on(
281
+ () => view,
282
+ "click",
283
+ (event) => {
284
+ console.log("Clicked at:", event.mapPoint);
285
+ }
286
+ );
287
+
288
+ // Listen to layer view events
289
+ reactiveUtils.on(
290
+ () => view.whenLayerView(layer),
291
+ "highlight",
292
+ (event) => console.log("Highlight changed")
293
+ );
294
+ ```
295
+
296
+ ### Multiple Properties
297
+
298
+ ```javascript
299
+ // Watch multiple properties
300
+ reactiveUtils.watch(
301
+ () => [view.center, view.zoom],
302
+ ([center, zoom]) => {
303
+ console.log(`Center: ${center.x}, ${center.y}, Zoom: ${zoom}`);
304
+ }
305
+ );
306
+
307
+ // Computed expression
308
+ reactiveUtils.watch(
309
+ () => view.scale < 50000,
310
+ (isZoomedIn) => {
311
+ layer.visible = isZoomedIn;
312
+ }
313
+ );
314
+ ```
315
+
316
+ ### Sync Option
317
+
318
+ ```javascript
319
+ // Synchronous updates (use carefully)
320
+ reactiveUtils.watch(
321
+ () => view.extent,
322
+ (extent) => updateUI(extent),
323
+ { sync: true }
324
+ );
325
+ ```
326
+
327
+ ## Handles
328
+
329
+ Manage multiple watch handles for cleanup.
330
+
331
+ ```javascript
332
+ import Handles from "@arcgis/core/core/Handles.js";
333
+
334
+ const handles = new Handles();
335
+
336
+ // Add handles
337
+ handles.add(
338
+ reactiveUtils.watch(() => view.scale, (scale) => {})
339
+ );
340
+
341
+ handles.add(
342
+ view.on("click", (e) => {}),
343
+ "click-handlers" // Group name
344
+ );
345
+
346
+ handles.add([
347
+ reactiveUtils.watch(() => view.center, () => {}),
348
+ reactiveUtils.watch(() => view.zoom, () => {})
349
+ ], "view-watchers");
350
+
351
+ // Remove specific group
352
+ handles.remove("click-handlers");
353
+
354
+ // Remove all
355
+ handles.removeAll();
356
+
357
+ // Destroy (also removes all)
358
+ handles.destroy();
359
+ ```
360
+
361
+ ### In Custom Classes
362
+
363
+ ```javascript
364
+ @subclass("myapp.MyWidget")
365
+ class MyWidget extends Accessor {
366
+ constructor(props) {
367
+ super(props);
368
+ this.handles = new Handles();
369
+ }
370
+
371
+ initialize() {
372
+ this.handles.add(
373
+ reactiveUtils.watch(
374
+ () => this.view?.scale,
375
+ (scale) => this.onScaleChange(scale)
376
+ )
377
+ );
378
+ }
379
+
380
+ destroy() {
381
+ this.handles.destroy();
382
+ super.destroy();
383
+ }
384
+ }
385
+ ```
386
+
387
+ ## promiseUtils
388
+
389
+ Utilities for working with Promises.
390
+
391
+ ```javascript
392
+ import promiseUtils from "@arcgis/core/core/promiseUtils.js";
393
+ ```
394
+
395
+ ### create()
396
+
397
+ Create an abortable promise.
398
+
399
+ ```javascript
400
+ const { promise, resolve, reject } = promiseUtils.create();
401
+
402
+ // Later...
403
+ resolve(result);
404
+ // or
405
+ reject(new Error("Failed"));
406
+
407
+ // Wait for it
408
+ const result = await promise;
409
+ ```
410
+
411
+ ### eachAlways()
412
+
413
+ Wait for all promises, regardless of success/failure.
414
+
415
+ ```javascript
416
+ const results = await promiseUtils.eachAlways([
417
+ fetch("/api/data1"),
418
+ fetch("/api/data2"),
419
+ fetch("/api/data3")
420
+ ]);
421
+
422
+ results.forEach((result, index) => {
423
+ if (result.error) {
424
+ console.error(`Request ${index} failed:`, result.error);
425
+ } else {
426
+ console.log(`Request ${index} succeeded:`, result.value);
427
+ }
428
+ });
429
+ ```
430
+
431
+ ### debounce()
432
+
433
+ Debounce a function.
434
+
435
+ ```javascript
436
+ const debouncedSearch = promiseUtils.debounce(async (query) => {
437
+ const results = await searchService.search(query);
438
+ return results;
439
+ });
440
+
441
+ // Rapid calls will be debounced
442
+ input.addEventListener("input", async (e) => {
443
+ try {
444
+ const results = await debouncedSearch(e.target.value);
445
+ displayResults(results);
446
+ } catch (e) {
447
+ if (!promiseUtils.isAbortError(e)) {
448
+ console.error(e);
449
+ }
450
+ }
451
+ });
452
+ ```
453
+
454
+ ### throttle()
455
+
456
+ Throttle a function.
457
+
458
+ ```javascript
459
+ const throttledUpdate = promiseUtils.throttle(async (extent) => {
460
+ await updateDisplay(extent);
461
+ }, 100); // Max once per 100ms
462
+
463
+ view.watch("extent", throttledUpdate);
464
+ ```
465
+
466
+ ### isAbortError()
467
+
468
+ Check if error is from aborted operation.
469
+
470
+ ```javascript
471
+ try {
472
+ await someAsyncOperation();
473
+ } catch (error) {
474
+ if (promiseUtils.isAbortError(error)) {
475
+ console.log("Operation was cancelled");
476
+ } else {
477
+ throw error;
478
+ }
479
+ }
480
+ ```
481
+
482
+ ### timeout()
483
+
484
+ Add timeout to a promise.
485
+
486
+ ```javascript
487
+ try {
488
+ const result = await promiseUtils.timeout(
489
+ fetch("/api/slow-endpoint"),
490
+ 5000 // 5 second timeout
491
+ );
492
+ } catch (e) {
493
+ console.error("Request timed out or failed");
494
+ }
495
+ ```
496
+
497
+ ## Workers
498
+
499
+ Run heavy computations in background threads.
500
+
501
+ ```javascript
502
+ import workers from "@arcgis/core/core/workers.js";
503
+ ```
504
+
505
+ ### Open Worker Connection
506
+
507
+ ```javascript
508
+ const connection = await workers.open("path/to/worker.js");
509
+
510
+ // Execute method on worker
511
+ const result = await connection.invoke("methodName", { data: "params" });
512
+
513
+ // Close when done
514
+ connection.close();
515
+ ```
516
+
517
+ ### Worker Script Example
518
+
519
+ ```javascript
520
+ // worker.js
521
+ define([], function() {
522
+ return {
523
+ methodName: function(params) {
524
+ // Heavy computation here
525
+ const result = processData(params.data);
526
+ return result;
527
+ },
528
+
529
+ anotherMethod: function(params) {
530
+ return params.value * 2;
531
+ }
532
+ };
533
+ });
534
+ ```
535
+
536
+ ### Using Workers for Heavy Tasks
537
+
538
+ ```javascript
539
+ // Main script
540
+ import workers from "@arcgis/core/core/workers.js";
541
+
542
+ async function processLargeDataset(data) {
543
+ const connection = await workers.open("./dataProcessor.js");
544
+
545
+ try {
546
+ const result = await connection.invoke("process", {
547
+ data: data,
548
+ options: { threshold: 100 }
549
+ });
550
+ return result;
551
+ } finally {
552
+ connection.close();
553
+ }
554
+ }
555
+ ```
556
+
557
+ ## Error Handling
558
+
559
+ ```javascript
560
+ import Error from "@arcgis/core/core/Error.js";
561
+
562
+ // Create custom error
563
+ const error = new Error("layer-load-error", "Failed to load layer", {
564
+ layer: layer,
565
+ originalError: e
566
+ });
567
+
568
+ // Check error type
569
+ if (error.name === "layer-load-error") {
570
+ console.log("Layer failed:", error.details.layer.title);
571
+ }
572
+ ```
573
+
574
+ ## URL Utilities
575
+
576
+ ```javascript
577
+ import urlUtils from "@arcgis/core/core/urlUtils.js";
578
+
579
+ // Add proxy rule
580
+ urlUtils.addProxyRule({
581
+ urlPrefix: "https://services.arcgis.com",
582
+ proxyUrl: "/proxy"
583
+ });
584
+
585
+ // Get proxy URL
586
+ const proxiedUrl = urlUtils.getProxyUrl("https://services.arcgis.com/data");
587
+
588
+ // URL helpers
589
+ const normalized = urlUtils.normalize("https://example.com//path//to//resource");
590
+ ```
591
+
592
+ ## Units and Quantities
593
+
594
+ ```javascript
595
+ import units from "@arcgis/core/core/units.js";
596
+
597
+ // Convert units
598
+ const meters = units.convertUnit(100, "feet", "meters");
599
+ const sqKm = units.convertUnit(1000, "acres", "square-kilometers");
600
+
601
+ // Get unit info
602
+ const info = units.getUnitInfo("meters");
603
+ console.log(info.abbreviation); // "m"
604
+ console.log(info.pluralLabel); // "meters"
605
+ ```
606
+
607
+ ## Scheduling
608
+
609
+ ```javascript
610
+ import scheduling from "@arcgis/core/core/scheduling.js";
611
+
612
+ // Schedule for next frame
613
+ const handle = scheduling.addFrameTask({
614
+ update: (event) => {
615
+ // Called every frame
616
+ console.log("Delta time:", event.deltaTime);
617
+ console.log("Elapsed:", event.elapsedTime);
618
+ }
619
+ });
620
+
621
+ // Remove task
622
+ handle.remove();
623
+
624
+ // One-time frame callback
625
+ scheduling.schedule(() => {
626
+ console.log("Executed on next frame");
627
+ });
628
+ ```
629
+
630
+ ## Common Patterns
631
+
632
+ ### Cleanup Pattern
633
+
634
+ ```javascript
635
+ class MyComponent {
636
+ constructor(view) {
637
+ this.view = view;
638
+ this.handles = new Handles();
639
+ this.setup();
640
+ }
641
+
642
+ setup() {
643
+ this.handles.add([
644
+ reactiveUtils.watch(
645
+ () => this.view.scale,
646
+ (scale) => this.onScaleChange(scale)
647
+ ),
648
+ reactiveUtils.watch(
649
+ () => this.view.extent,
650
+ (extent) => this.onExtentChange(extent)
651
+ ),
652
+ this.view.on("click", (e) => this.onClick(e))
653
+ ]);
654
+ }
655
+
656
+ destroy() {
657
+ this.handles.removeAll();
658
+ }
659
+ }
660
+ ```
661
+
662
+ ### Debounced Updates
663
+
664
+ ```javascript
665
+ const debouncedUpdate = promiseUtils.debounce(async () => {
666
+ const extent = view.extent;
667
+ const results = await layer.queryFeatures({
668
+ geometry: extent,
669
+ returnGeometry: true
670
+ });
671
+ updateDisplay(results);
672
+ });
673
+
674
+ reactiveUtils.watch(
675
+ () => view.stationary && view.extent,
676
+ debouncedUpdate
677
+ );
678
+ ```
679
+
680
+ ### Conditional Watching
681
+
682
+ ```javascript
683
+ // Only react when view is stationary
684
+ reactiveUtils.watch(
685
+ () => view.stationary ? view.extent : null,
686
+ (extent) => {
687
+ if (extent) {
688
+ updateForExtent(extent);
689
+ }
690
+ }
691
+ );
692
+ ```
693
+
694
+ ## Common Pitfalls
695
+
696
+ 1. **Memory Leaks**: Always remove handles when done
697
+ ```javascript
698
+ // Store handle reference
699
+ const handle = reactiveUtils.watch(...);
700
+ // Remove when no longer needed
701
+ handle.remove();
702
+ ```
703
+
704
+ 2. **Initial Value**: Use `initial: true` to get current value immediately
705
+ ```javascript
706
+ reactiveUtils.watch(
707
+ () => view.scale,
708
+ (scale) => updateUI(scale),
709
+ { initial: true } // Called immediately with current scale
710
+ );
711
+ ```
712
+
713
+ 3. **Sync vs Async**: Default is async batching, use `sync: true` carefully
714
+ ```javascript
715
+ // Async (default) - batched, better performance
716
+ reactiveUtils.watch(() => value, callback);
717
+
718
+ // Sync - immediate, can cause performance issues
719
+ reactiveUtils.watch(() => value, callback, { sync: true });
720
+ ```
721
+
722
+ 4. **Abort Errors**: Always check for abort errors in catch blocks
723
+ ```javascript
724
+ try {
725
+ await debouncedFunction();
726
+ } catch (e) {
727
+ if (!promiseUtils.isAbortError(e)) {
728
+ throw e;
729
+ }
730
+ }
731
+ ```
732
+