cotomy 0.1.66 → 0.1.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,6 +20,13 @@ npm i cotomy
20
20
  Cotomy will continue to expand with more detailed usage instructions and code examples added to the README in the future.
21
21
  For the latest updates, please check the official documentation or repository regularly.
22
22
 
23
+ ### Entity API forms
24
+
25
+ `CotomyEntityApiForm` targets REST endpoints that identify records with a single surrogate key.
26
+ Attach `data-cotomy-entity-key="<id>"` to the form when editing an existing entity; omit the attribute (or leave it empty) to issue a `POST` to the base `action` URL.
27
+ On `201 Created`, the form reads the `Location` header and stores the generated key back into `data-cotomy-entity-key`, enabling subsequent `PUT` submissions.
28
+ Composite or natural keys are no longer supported—migrate any legacy markup that relied on `data-cotomy-keyindex` or multiple key inputs to the new surrogate-key flow.
29
+
23
30
  The core of Cotomy is `CotomyElement`, which is constructed as a wrapper for `Element`.
24
31
  By passing HTML and CSS strings to the constructor, it is possible to generate Element designs with a limited scope.
25
32
 
@@ -2524,31 +2524,34 @@ class CotomyApiForm extends CotomyForm {
2524
2524
  }
2525
2525
  }
2526
2526
  class CotomyEntityApiForm extends CotomyApiForm {
2527
+ get entityKey() {
2528
+ return this.attribute("data-cotomy-entity-key") ?? undefined;
2529
+ }
2530
+ get requiresEntityKey() {
2531
+ return this.attribute("data-cotomy-identify") !== "false";
2532
+ }
2533
+ get hasEntityKey() {
2534
+ return !!this.entityKey;
2535
+ }
2527
2536
  actionUrl() {
2528
- const base = this.attribute("action");
2529
- const keys = this.externalKey ? [this.externalKey] : this.pathKeyInputs.map(e => encodeURIComponent(e.value));
2530
- return `${base}/${keys.join("/")}`;
2537
+ const action = this.attribute("action");
2538
+ const normalized = action.replace(/\/+$/, "");
2539
+ if (!this.entityKey) {
2540
+ return action.endsWith("/") ? action : `${normalized}/`;
2541
+ }
2542
+ return `${normalized}/${encodeURIComponent(this.entityKey)}`;
2531
2543
  }
2532
2544
  method() {
2533
2545
  if (this.hasAttribute("method") && this.attribute("method") !== "") {
2534
2546
  return this.attribute("method");
2535
2547
  }
2536
- if (this.externalKey)
2537
- return "put";
2538
- if (this.pathKeyInputs.length > 0 && this.pathKeyInputs.every(e => e.readonly))
2539
- return "put";
2540
- if (this.keyInputs.length > 0 && this.keyInputs.every(e => e.readonly))
2541
- return "put";
2542
- return "post";
2548
+ return this.hasEntityKey ? "put" : "post";
2543
2549
  }
2544
- get externalKey() {
2545
- return this.attribute("data-cotomy-key") || undefined;
2546
- }
2547
- setExternalKey(response) {
2548
- if (this.requiresExternalKey && response.status === StatusCodes.CREATED) {
2549
- if (this.hasExternalKey) {
2550
+ setEntityKey(response) {
2551
+ if (this.requiresEntityKey && response.status === StatusCodes.CREATED) {
2552
+ if (this.hasEntityKey) {
2550
2553
  if (CotomyDebugSettings.isEnabled(CotomyDebugFeature.FormLoad)) {
2551
- console.warn("External key already exists, but server responded with 201 Created. Possible duplicate POST.");
2554
+ console.warn("Entity key already exists, but server responded with 201 Created. Possible duplicate POST.");
2552
2555
  }
2553
2556
  return;
2554
2557
  }
@@ -2577,49 +2580,19 @@ class CotomyEntityApiForm extends CotomyApiForm {
2577
2580
  }
2578
2581
  const addedParts = locationParts.slice(actionParts.length).filter(Boolean);
2579
2582
  if (addedParts.length === 1 && addedParts[0]) {
2580
- this.attribute("data-cotomy-key", addedParts[0]);
2583
+ this.attribute("data-cotomy-entity-key", addedParts[0]);
2581
2584
  }
2582
2585
  else {
2583
- const msg = `Location does not contain a single external key segment.
2586
+ const msg = `Location does not contain a single entity key segment.
2584
2587
  action="${baseAction}", location="${locPath}", added=["${addedParts.join('","')}"]`;
2585
2588
  throw new Error(msg);
2586
2589
  }
2587
2590
  }
2588
2591
  }
2589
- get requiresExternalKey() {
2590
- return this.attribute("data-cotomy-identify") !== "false" && this.keyInputs.length == 0 && this.pathKeyInputs.length == 0;
2591
- }
2592
- get hasExternalKey() {
2593
- return !!this.externalKey;
2594
- }
2595
- get pathKeyInputs() {
2596
- return this.find("[data-cotomy-keyindex]").sort((a, b) => {
2597
- const aIndex = parseInt(a.attribute("data-cotomy-keyindex") ?? "0");
2598
- const bIndex = parseInt(b.attribute("data-cotomy-keyindex") ?? "0");
2599
- return aIndex - bIndex;
2600
- });
2601
- }
2602
- get keyInputs() {
2603
- return this.contains("[data-cotomy-key]") ? this.find("[data-cotomy-key]").sort((a, b) => {
2604
- const aIndex = parseInt(a.attribute("data-cotomy-key") ?? "0");
2605
- const bIndex = parseInt(b.attribute("data-cotomy-key") ?? "0");
2606
- return aIndex - bIndex;
2607
- }) : this.find("[name][data-cotomy-key]");
2608
- }
2609
- get usePathKey() {
2610
- const use = this.pathKeyInputs.length > 0 || this.requiresExternalKey;
2611
- if (use && this.hasExternalKey && CotomyDebugSettings.isEnabled(CotomyDebugFeature.FormLoad)) {
2612
- console.warn("Both externalKey and pathKeyInputs are present. Using externalKey and ignoring pathKeyInputs.");
2613
- }
2614
- return use;
2615
- }
2616
- get pathKeyString() {
2617
- return this.externalKey || this.pathKeyInputs.map(e => e.value).join("/");
2618
- }
2619
2592
  async submitToApiAsync(formData) {
2620
2593
  const response = await super.submitToApiAsync(formData);
2621
- if (this.requiresExternalKey && response.status === StatusCodes.CREATED) {
2622
- this.setExternalKey(response);
2594
+ if (this.requiresEntityKey && response.status === StatusCodes.CREATED) {
2595
+ this.setEntityKey(response);
2623
2596
  }
2624
2597
  return response;
2625
2598
  }
@@ -2684,9 +2657,7 @@ class CotomyEntityFillApiForm extends CotomyEntityApiForm {
2684
2657
  return new CotomyViewRenderer(this, this.bindNameGenerator());
2685
2658
  }
2686
2659
  async loadAsync() {
2687
- const hasPathKeys = this.pathKeyInputs.length > 0 && this.pathKeyInputs.every(e => !!e.value);
2688
- const hasKeyInputs = this.keyInputs.length > 0 && this.keyInputs.every(e => !!e.value);
2689
- if (!this.hasExternalKey && !hasPathKeys && !hasKeyInputs) {
2660
+ if (!this.hasEntityKey) {
2690
2661
  return new CotomyApiResponse();
2691
2662
  }
2692
2663
  const api = this.apiClient();
@@ -2738,8 +2709,6 @@ class CotomyEntityFillApiForm extends CotomyEntityApiForm {
2738
2709
  await this.fillObjectAsync(this.bindNameGenerator(), await response.objectAsync());
2739
2710
  await this.renderer().applyAsync(response);
2740
2711
  }
2741
- this.pathKeyInputs.forEach(e => e.readonly = true);
2742
- this.keyInputs.forEach(e => e.readonly = true);
2743
2712
  this.find("textarea").forEach(e => e.input());
2744
2713
  }
2745
2714
  }