@suss/ir-core 0.34.0 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-WOXA5GBL.js → chunk-A46EUIKC.js} +301 -35
- package/dist/index.cjs +330 -42
- package/dist/index.d.cts +59 -11
- package/dist/index.d.ts +59 -11
- package/dist/index.js +27 -6
- package/dist/{schemas-CghZNwSE.d.cts → schemas-lfg8kvak.d.cts} +6 -0
- package/dist/{schemas-CghZNwSE.d.ts → schemas-lfg8kvak.d.ts} +6 -0
- package/dist/schemas.cjs +299 -37
- package/dist/schemas.d.cts +1 -1
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/package.json +1 -1
|
@@ -514,6 +514,38 @@ function pathAfterOrigin(text) {
|
|
|
514
514
|
function statesAnOrigin(text) {
|
|
515
515
|
return SCHEME_ORIGIN.test(text) || PROTOCOL_RELATIVE_ORIGIN.test(text);
|
|
516
516
|
}
|
|
517
|
+
var PRIVATE_SUFFIXES = [
|
|
518
|
+
"local",
|
|
519
|
+
"internal",
|
|
520
|
+
"localhost",
|
|
521
|
+
"lan",
|
|
522
|
+
"home.arpa",
|
|
523
|
+
"svc",
|
|
524
|
+
"test",
|
|
525
|
+
"example",
|
|
526
|
+
"invalid",
|
|
527
|
+
"example.com",
|
|
528
|
+
"example.net",
|
|
529
|
+
"example.org"
|
|
530
|
+
];
|
|
531
|
+
var PRIVATE_IPV4 = /^(?:127\.|10\.|192\.168\.|169\.254\.|172\.(?:1[6-9]|2\d|3[01])\.|0\.0\.0\.0$)/;
|
|
532
|
+
function hostnameOf(host) {
|
|
533
|
+
const lower = host.toLowerCase();
|
|
534
|
+
const name = lower.startsWith("[") ? lower.slice(0, lower.indexOf("]") + 1) : lower.split(":")[0] ?? lower;
|
|
535
|
+
return name.replace(/\.$/, "");
|
|
536
|
+
}
|
|
537
|
+
function hostCanBeOwn(host) {
|
|
538
|
+
const name = hostnameOf(host);
|
|
539
|
+
if (name === "[::1]" || /^\[f[cd]/.test(name) || PRIVATE_IPV4.test(name)) {
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
if (!name.includes(".")) {
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
return PRIVATE_SUFFIXES.some(
|
|
546
|
+
(suffix) => name === suffix || name.endsWith(`.${suffix}`)
|
|
547
|
+
);
|
|
548
|
+
}
|
|
517
549
|
|
|
518
550
|
// src/semantics/pathPattern.ts
|
|
519
551
|
var HOLE_RANGE_SUFFIX = {
|
|
@@ -580,14 +612,18 @@ function shapedItem(segment) {
|
|
|
580
612
|
return ".*";
|
|
581
613
|
}
|
|
582
614
|
if (part.startsWith("{")) {
|
|
583
|
-
return "[^/]+";
|
|
615
|
+
return /[?*]\}$/.test(part) ? "[^/]*" : "[^/]+";
|
|
584
616
|
}
|
|
585
617
|
return escapedForRegex(part);
|
|
586
618
|
}).join("");
|
|
619
|
+
const holes = segment.match(/\{[^}]*\}|\*/g) ?? [];
|
|
620
|
+
const text = segment.replace(/\{[^}]*\}|\*/g, "");
|
|
587
621
|
return {
|
|
588
622
|
kind: "shaped",
|
|
589
623
|
shape: segment.replace(/\{[^}]*\}/g, "{}"),
|
|
590
|
-
test: new RegExp(`^${source}$`)
|
|
624
|
+
test: new RegExp(`^${source}$`),
|
|
625
|
+
bare: holes.every((hole) => hole === "*" || /[?*]\}$/.test(hole)) ? text : null,
|
|
626
|
+
textLength: text.length
|
|
591
627
|
};
|
|
592
628
|
}
|
|
593
629
|
function itemsOf(segment) {
|
|
@@ -607,10 +643,35 @@ function segmentsOf(alternative) {
|
|
|
607
643
|
const trimmed = alternative.startsWith("/") ? alternative.slice(1) : alternative;
|
|
608
644
|
return trimmed === "" ? [] : trimmed.split("/");
|
|
609
645
|
}
|
|
646
|
+
function patternSegmentsOf(alternative) {
|
|
647
|
+
const segments = segmentsOf(alternative);
|
|
648
|
+
const first = segments[0];
|
|
649
|
+
const opensWithBaseUrl = !alternative.startsWith("/") && first !== void 0 && HOLE_SEGMENT.test(first);
|
|
650
|
+
return opensWithBaseUrl ? segments.slice(1) : segments;
|
|
651
|
+
}
|
|
652
|
+
function pathAfterBaseUrl(path) {
|
|
653
|
+
const segments = segmentsOf(path);
|
|
654
|
+
const first = segments[0];
|
|
655
|
+
if (path.startsWith("/") || first === void 0 || segments.length < 2 || !HOLE_SEGMENT.test(first)) {
|
|
656
|
+
return path;
|
|
657
|
+
}
|
|
658
|
+
return `/${segments.slice(1).join("/")}`;
|
|
659
|
+
}
|
|
660
|
+
var PATTERNS = /* @__PURE__ */ new Map();
|
|
661
|
+
var PATTERN_CACHE_LIMIT = 5e4;
|
|
610
662
|
function patternOf(path) {
|
|
611
|
-
|
|
612
|
-
|
|
663
|
+
const known = PATTERNS.get(path);
|
|
664
|
+
if (known !== void 0) {
|
|
665
|
+
return known;
|
|
666
|
+
}
|
|
667
|
+
const pattern = alternativesOf(path).map(
|
|
668
|
+
(alternative) => patternSegmentsOf(alternative).flatMap(itemsOf)
|
|
613
669
|
);
|
|
670
|
+
if (PATTERNS.size >= PATTERN_CACHE_LIMIT) {
|
|
671
|
+
PATTERNS.clear();
|
|
672
|
+
}
|
|
673
|
+
PATTERNS.set(path, pattern);
|
|
674
|
+
return pattern;
|
|
614
675
|
}
|
|
615
676
|
function literalItemsOf(path) {
|
|
616
677
|
return segmentsOf(path).map((text) => ({ kind: "text", text }));
|
|
@@ -689,17 +750,104 @@ function pathSpansShapes(path) {
|
|
|
689
750
|
function countOf(items, kinds) {
|
|
690
751
|
return items.filter((item) => kinds.includes(item.kind)).length;
|
|
691
752
|
}
|
|
692
|
-
function
|
|
693
|
-
|
|
694
|
-
const ranks = alternatives.map((items) => [
|
|
753
|
+
function readingSpecificity(items) {
|
|
754
|
+
return [
|
|
695
755
|
countOf(items, ["text"]),
|
|
696
756
|
countOf(items, ["shaped"]),
|
|
697
757
|
-countOf(items, ["optional", "star"])
|
|
698
|
-
]
|
|
699
|
-
|
|
700
|
-
|
|
758
|
+
];
|
|
759
|
+
}
|
|
760
|
+
function segmentFit(route, request) {
|
|
761
|
+
if (!meetOnOneSegment(route, request)) {
|
|
762
|
+
return "apart";
|
|
763
|
+
}
|
|
764
|
+
if (request.kind === "text") {
|
|
765
|
+
if (route.kind === "text") {
|
|
766
|
+
return "stated";
|
|
767
|
+
}
|
|
768
|
+
return route.kind === "shaped" ? "shaped" : "free";
|
|
769
|
+
}
|
|
770
|
+
if (absorbsAnything(route)) {
|
|
771
|
+
return "free";
|
|
772
|
+
}
|
|
773
|
+
if (request.kind !== "shaped") {
|
|
774
|
+
return "guess";
|
|
775
|
+
}
|
|
776
|
+
if (route.kind === "shaped") {
|
|
777
|
+
return "free";
|
|
778
|
+
}
|
|
779
|
+
return route.kind === "text" && request.bare === route.text ? "stated" : "guess";
|
|
780
|
+
}
|
|
781
|
+
function shapedText(item) {
|
|
782
|
+
return item.kind === "shaped" ? item.textLength : 0;
|
|
783
|
+
}
|
|
784
|
+
function betterFit(a, b) {
|
|
785
|
+
return a === void 0 || compareRanks(b, a) > 0;
|
|
786
|
+
}
|
|
787
|
+
function bestFit(route, request) {
|
|
788
|
+
const width = request.length + 1;
|
|
789
|
+
const best = new Array((route.length + 1) * width);
|
|
790
|
+
best[0] = [0, 0];
|
|
791
|
+
const offer = (i, j, fit) => {
|
|
792
|
+
const at = i * width + j;
|
|
793
|
+
if (betterFit(best[at], fit)) {
|
|
794
|
+
best[at] = fit;
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
for (let reached = 0; reached <= route.length + request.length; reached += 1) {
|
|
798
|
+
for (let i = Math.max(0, reached - request.length); i <= Math.min(route.length, reached); i += 1) {
|
|
799
|
+
const j = reached - i;
|
|
800
|
+
const fit = best[i * width + j];
|
|
801
|
+
if (fit === void 0) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const left = route[i];
|
|
805
|
+
const right = request[j];
|
|
806
|
+
if (left !== void 0 && skippable(left)) {
|
|
807
|
+
offer(i + 1, j, fit);
|
|
808
|
+
}
|
|
809
|
+
if (right !== void 0 && skippable(right)) {
|
|
810
|
+
offer(i, j + 1, fit);
|
|
811
|
+
}
|
|
812
|
+
if (left === void 0 || right === void 0) {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (left.kind === "star" && right.kind === "star") {
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
const kind = segmentFit(left, right);
|
|
819
|
+
if (kind === "apart" || kind === "guess") {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
offer(
|
|
823
|
+
left.kind === "star" ? i : i + 1,
|
|
824
|
+
right.kind === "star" ? j : j + 1,
|
|
825
|
+
[
|
|
826
|
+
fit[0] + (kind === "stated" ? 1 : 0),
|
|
827
|
+
fit[1] + (kind === "shaped" ? shapedText(left) : 0)
|
|
828
|
+
]
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return best[route.length * width + request.length] ?? null;
|
|
833
|
+
}
|
|
834
|
+
function requestRank(route, request) {
|
|
835
|
+
let found = null;
|
|
836
|
+
for (const routeItems of patternOf(route)) {
|
|
837
|
+
for (const requestItems of patternOf(request)) {
|
|
838
|
+
const fit = bestFit(routeItems, requestItems);
|
|
839
|
+
const rank = fit === null ? null : [...fit, ...readingSpecificity(routeItems)];
|
|
840
|
+
if (rank !== null && (found === null || compareRanks(rank, found) > 0)) {
|
|
841
|
+
found = rank;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return found;
|
|
846
|
+
}
|
|
847
|
+
function isCatchAll(route) {
|
|
848
|
+
return patternOf(route).some(
|
|
849
|
+
(items) => items.length > 0 && items.every(absorbsAnything) && items.some((item) => item.kind === "star")
|
|
701
850
|
);
|
|
702
|
-
return [...loosest, 1 - alternatives.length];
|
|
703
851
|
}
|
|
704
852
|
function compareRanks(a, b) {
|
|
705
853
|
const length = Math.max(a.length, b.length);
|
|
@@ -729,7 +877,18 @@ var RestSemanticsSchema = z7.object({
|
|
|
729
877
|
* here so the pairing layer can see them without unwrapping any
|
|
730
878
|
* metadata. Absent or empty for an inferred source.
|
|
731
879
|
*/
|
|
732
|
-
declaredResponses: z7.array(z7.number()).optional()
|
|
880
|
+
declaredResponses: z7.array(z7.number()).optional(),
|
|
881
|
+
/**
|
|
882
|
+
* The host a client wrote in an absolute URL, with its port
|
|
883
|
+
* (`api.example.com:8443`). A piece suss could not read is written
|
|
884
|
+
* `{name}`. Absent for a relative URL and for a route.
|
|
885
|
+
*/
|
|
886
|
+
host: z7.string().min(1).optional(),
|
|
887
|
+
/**
|
|
888
|
+
* True for a route the framework also serves with `.{format}` after
|
|
889
|
+
* its path, the way Rails serves `/search.json` from `get "search"`.
|
|
890
|
+
*/
|
|
891
|
+
optionalFormat: z7.boolean().optional()
|
|
733
892
|
});
|
|
734
893
|
var OPENING_HOLE = /^\{([^{}]+)\}(.*)$/;
|
|
735
894
|
function baseUrlReference(semantics) {
|
|
@@ -737,7 +896,10 @@ function baseUrlReference(semantics) {
|
|
|
737
896
|
return label === void 0 ? null : referenceFromName(patternHole(label));
|
|
738
897
|
}
|
|
739
898
|
function normalizePath(path) {
|
|
740
|
-
let normalized = path.replace(
|
|
899
|
+
let normalized = withoutParameterConstraints(path).replace(
|
|
900
|
+
/:([a-zA-Z_]\w*)([?+*]?)/g,
|
|
901
|
+
"{$1$2}"
|
|
902
|
+
);
|
|
741
903
|
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
742
904
|
normalized = normalized.slice(0, -1);
|
|
743
905
|
}
|
|
@@ -747,6 +909,46 @@ function normalizePath(path) {
|
|
|
747
909
|
);
|
|
748
910
|
return normalized;
|
|
749
911
|
}
|
|
912
|
+
function withoutParameterConstraints(path) {
|
|
913
|
+
let out = "";
|
|
914
|
+
let i = 0;
|
|
915
|
+
while (i < path.length) {
|
|
916
|
+
const name = /^:[a-zA-Z_]\w*\(/.exec(path.slice(i));
|
|
917
|
+
if (name === null) {
|
|
918
|
+
out += path[i];
|
|
919
|
+
i += 1;
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
const open = i + name[0].length - 1;
|
|
923
|
+
const close = closingParenthesis(path, open);
|
|
924
|
+
if (close === -1) {
|
|
925
|
+
return out + path.slice(i);
|
|
926
|
+
}
|
|
927
|
+
out += path.slice(i, open);
|
|
928
|
+
i = close + 1;
|
|
929
|
+
}
|
|
930
|
+
return out;
|
|
931
|
+
}
|
|
932
|
+
function closingParenthesis(text, open) {
|
|
933
|
+
let depth = 0;
|
|
934
|
+
for (let i = open; i < text.length; i += 1) {
|
|
935
|
+
const char = text[i];
|
|
936
|
+
if (char === "\\") {
|
|
937
|
+
i += 1;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
if (char === "(") {
|
|
941
|
+
depth += 1;
|
|
942
|
+
}
|
|
943
|
+
if (char === ")") {
|
|
944
|
+
depth -= 1;
|
|
945
|
+
if (depth === 0) {
|
|
946
|
+
return i;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return -1;
|
|
951
|
+
}
|
|
750
952
|
function pathShape(path) {
|
|
751
953
|
return normalizePath(path).replace(/\{[^{}]*?([?+*]?)\}/g, "{$1}");
|
|
752
954
|
}
|
|
@@ -762,6 +964,40 @@ function routePathAdmits(declaredPath, requestPath) {
|
|
|
762
964
|
function routePathsMeet(a, b) {
|
|
763
965
|
return pathsMeet(normalizePath(a), normalizePath(b));
|
|
764
966
|
}
|
|
967
|
+
function readHost(semantics) {
|
|
968
|
+
const host = semantics.host;
|
|
969
|
+
return host === void 0 || /[{(]/.test(host) ? void 0 : host;
|
|
970
|
+
}
|
|
971
|
+
function hostsAgree(a, b) {
|
|
972
|
+
return readHost(a) === readHost(b);
|
|
973
|
+
}
|
|
974
|
+
function hostUnread(semantics) {
|
|
975
|
+
if (semantics.host !== void 0) {
|
|
976
|
+
return readHost(semantics) === void 0;
|
|
977
|
+
}
|
|
978
|
+
return baseUrlReference(semantics) !== null;
|
|
979
|
+
}
|
|
980
|
+
function servedPath(semantics, path) {
|
|
981
|
+
const normalized = normalizePath(path);
|
|
982
|
+
return semantics.optionalFormat === true ? `${normalized}(|.{format})` : normalized;
|
|
983
|
+
}
|
|
984
|
+
function withoutHost(semantics) {
|
|
985
|
+
const { host: _host, ...rest } = semantics;
|
|
986
|
+
return rest;
|
|
987
|
+
}
|
|
988
|
+
function groundedBaseUrl(semantics, deployment) {
|
|
989
|
+
const reference = baseUrlReference(semantics);
|
|
990
|
+
if (reference === null) {
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
const base = deployment.setTo(reference);
|
|
994
|
+
if (base === null) {
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
997
|
+
const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
|
|
998
|
+
const grounded = pathAfterOrigin(`${base}${rest}`);
|
|
999
|
+
return { ...semantics, path: grounded === "" ? "/" : grounded };
|
|
1000
|
+
}
|
|
765
1001
|
var restSemantics = defineBoundarySemantics({
|
|
766
1002
|
name: "rest",
|
|
767
1003
|
schema: RestSemanticsSchema,
|
|
@@ -800,19 +1036,46 @@ var restSemantics = defineBoundarySemantics({
|
|
|
800
1036
|
if (semantics.method === null || semantics.path === null) {
|
|
801
1037
|
return null;
|
|
802
1038
|
}
|
|
803
|
-
|
|
1039
|
+
const shape = pathShape(pathAfterBaseUrl(normalizePath(semantics.path)));
|
|
1040
|
+
const host = readHost(semantics);
|
|
1041
|
+
return host === void 0 ? `rest ${shape}` : `rest //${host}${shape}`;
|
|
1042
|
+
},
|
|
1043
|
+
/**
|
|
1044
|
+
* A spec writes `/teams/{team_id}` where the route that serves it
|
|
1045
|
+
* writes `/teams/:id`. The method stays in, so a `"*"` handler is
|
|
1046
|
+
* never compared with the document for one method.
|
|
1047
|
+
*/
|
|
1048
|
+
operationKey(semantics) {
|
|
1049
|
+
if (semantics.method === null || semantics.path === null) {
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
return `${semantics.method.toUpperCase()} ${pathShape(semantics.path)}`;
|
|
804
1053
|
},
|
|
805
1054
|
sidesAgree(a, b) {
|
|
806
|
-
return methodsAgree(a.method, b.method);
|
|
1055
|
+
return methodsAgree(a.method, b.method) && hostsAgree(a, b);
|
|
807
1056
|
},
|
|
808
1057
|
spansBuckets(semantics) {
|
|
809
|
-
return semantics.path !== null && pathSpansShapes(normalizePath(semantics.path));
|
|
1058
|
+
return semantics.path !== null && (semantics.optionalFormat === true || pathSpansShapes(normalizePath(semantics.path)));
|
|
810
1059
|
},
|
|
811
1060
|
bucketsMeet(a, b) {
|
|
812
|
-
return a.path !== null && b.path !== null &&
|
|
1061
|
+
return a.path !== null && b.path !== null && hostsAgree(a, b) && pathsMeet(servedPath(a, a.path), servedPath(b, b.path));
|
|
813
1062
|
},
|
|
814
|
-
|
|
815
|
-
|
|
1063
|
+
/**
|
|
1064
|
+
* A catch-all route never takes a call whose host suss could not
|
|
1065
|
+
* read, since nothing else ties that call to this app.
|
|
1066
|
+
*/
|
|
1067
|
+
pairRank(provider, consumer) {
|
|
1068
|
+
if (provider.path === null || consumer.path === null) {
|
|
1069
|
+
return [];
|
|
1070
|
+
}
|
|
1071
|
+
const route = normalizePath(provider.path);
|
|
1072
|
+
if (hostUnread(consumer) && isCatchAll(route)) {
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
return requestRank(
|
|
1076
|
+
servedPath(provider, provider.path),
|
|
1077
|
+
normalizePath(consumer.path)
|
|
1078
|
+
);
|
|
816
1079
|
},
|
|
817
1080
|
/**
|
|
818
1081
|
* The identity key, with a missing half still readable: `ANY` when
|
|
@@ -853,27 +1116,29 @@ var restSemantics = defineBoundarySemantics({
|
|
|
853
1116
|
},
|
|
854
1117
|
/**
|
|
855
1118
|
* A call whose base URL the deployment fills in, resolved to the
|
|
856
|
-
* path it reaches
|
|
1119
|
+
* path it reaches, and a call to a host that can be the project's
|
|
1120
|
+
* own, with the host left out so it meets the route.
|
|
857
1121
|
*
|
|
858
|
-
* The source cannot settle
|
|
859
|
-
* the hole in. `API_BASE` could be `http://backend.internal`,
|
|
860
|
-
* then the path is `/orders`. It could equally be `/api/v2`, and
|
|
1122
|
+
* The source cannot settle a base URL on its own, so the adapter
|
|
1123
|
+
* leaves the hole in. `API_BASE` could be `http://backend.internal`,
|
|
1124
|
+
* and then the path is `/orders`. It could equally be `/api/v2`, and
|
|
861
1125
|
* then the path is `/api/v2/orders`. Filling in the deployed value
|
|
862
|
-
* and reading the path back out gets the right one either way
|
|
863
|
-
*
|
|
1126
|
+
* and reading the path back out gets the right one either way, and
|
|
1127
|
+
* a base the deployment sets counts as the app's own.
|
|
864
1128
|
*/
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
}
|
|
870
|
-
const base = deployment.setTo(reference);
|
|
871
|
-
if (base === null) {
|
|
1129
|
+
/** A listed host matches with or without the port the call gives. */
|
|
1130
|
+
withoutOwnHost(semantics, ownHosts) {
|
|
1131
|
+
const host = semantics.host?.toLowerCase();
|
|
1132
|
+
if (host === void 0) {
|
|
872
1133
|
return null;
|
|
873
1134
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1135
|
+
return ownHosts.has(host) || ownHosts.has(host.replace(/:\d+$/, "")) ? withoutHost(semantics) : null;
|
|
1136
|
+
},
|
|
1137
|
+
groundName(semantics, deployment) {
|
|
1138
|
+
const based = groundedBaseUrl(semantics, deployment);
|
|
1139
|
+
const grounded = based ?? semantics;
|
|
1140
|
+
const host = readHost(grounded);
|
|
1141
|
+
return host !== void 0 && hostCanBeOwn(host) ? withoutHost(grounded) : based;
|
|
877
1142
|
},
|
|
878
1143
|
nameReference: baseUrlReference,
|
|
879
1144
|
ruleBoundary: {
|
|
@@ -1316,6 +1581,7 @@ export {
|
|
|
1316
1581
|
referenceFromName,
|
|
1317
1582
|
pathAfterOrigin,
|
|
1318
1583
|
statesAnOrigin,
|
|
1584
|
+
hostCanBeOwn,
|
|
1319
1585
|
rangedHole,
|
|
1320
1586
|
setPiece,
|
|
1321
1587
|
compareRanks,
|