@smplkit/sdk 1.8.3 → 1.9.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.
- package/dist/index.cjs +752 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +335 -17
- package/dist/index.d.ts +335 -17
- package/dist/index.js +741 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16614,7 +16614,7 @@ var require_pino = __commonJS({
|
|
|
16614
16614
|
});
|
|
16615
16615
|
|
|
16616
16616
|
// src/client.ts
|
|
16617
|
-
import
|
|
16617
|
+
import createClient5 from "openapi-fetch";
|
|
16618
16618
|
|
|
16619
16619
|
// src/config/client.ts
|
|
16620
16620
|
import createClient from "openapi-fetch";
|
|
@@ -17846,7 +17846,7 @@ var FlagsClient = class {
|
|
|
17846
17846
|
/** Management API — CRUD operations on Flag models. */
|
|
17847
17847
|
management;
|
|
17848
17848
|
/** @internal */
|
|
17849
|
-
constructor(apiKey, ensureWs, timeout, flagsBaseUrl, appBaseUrl) {
|
|
17849
|
+
constructor(apiKey, ensureWs, timeout, flagsBaseUrl, appBaseUrl, contextBuffer) {
|
|
17850
17850
|
this._apiKey = apiKey;
|
|
17851
17851
|
this._ensureWs = ensureWs;
|
|
17852
17852
|
const resolvedBaseUrl = flagsBaseUrl ?? FLAGS_BASE_URL;
|
|
@@ -17883,6 +17883,9 @@ var FlagsClient = class {
|
|
|
17883
17883
|
},
|
|
17884
17884
|
fetch: fetchWithTimeout
|
|
17885
17885
|
});
|
|
17886
|
+
if (contextBuffer !== void 0) {
|
|
17887
|
+
this._contextBuffer = contextBuffer;
|
|
17888
|
+
}
|
|
17886
17889
|
this.management = new FlagsManagement(this);
|
|
17887
17890
|
}
|
|
17888
17891
|
// ------------------------------------------------------------------
|
|
@@ -18257,25 +18260,6 @@ var FlagsClient = class {
|
|
|
18257
18260
|
}
|
|
18258
18261
|
}
|
|
18259
18262
|
// ------------------------------------------------------------------
|
|
18260
|
-
// Runtime: context registration
|
|
18261
|
-
// ------------------------------------------------------------------
|
|
18262
|
-
/**
|
|
18263
|
-
* Register context(s) with the server.
|
|
18264
|
-
*
|
|
18265
|
-
* Accepts a single Context or an array. Works before `initialize()` is called.
|
|
18266
|
-
*/
|
|
18267
|
-
register(context) {
|
|
18268
|
-
if (Array.isArray(context)) {
|
|
18269
|
-
this._contextBuffer.observe(context);
|
|
18270
|
-
} else {
|
|
18271
|
-
this._contextBuffer.observe([context]);
|
|
18272
|
-
}
|
|
18273
|
-
}
|
|
18274
|
-
/** Flush pending context registrations to the server. */
|
|
18275
|
-
async flushContexts() {
|
|
18276
|
-
await this._flushContexts();
|
|
18277
|
-
}
|
|
18278
|
-
// ------------------------------------------------------------------
|
|
18279
18263
|
// Runtime: Tier 1 evaluate
|
|
18280
18264
|
// ------------------------------------------------------------------
|
|
18281
18265
|
/**
|
|
@@ -18573,7 +18557,7 @@ var FlagsClient = class {
|
|
|
18573
18557
|
return new Flag(this, {
|
|
18574
18558
|
id: resource.id ?? null,
|
|
18575
18559
|
name: attrs.name,
|
|
18576
|
-
type: attrs.type
|
|
18560
|
+
type: attrs.type,
|
|
18577
18561
|
default: attrs.default,
|
|
18578
18562
|
values: attrs.values ? attrs.values.map((v) => ({ name: v.name, value: v.value })) : null,
|
|
18579
18563
|
description: attrs.description ?? null,
|
|
@@ -18844,6 +18828,16 @@ var LoggingManagement = class {
|
|
|
18844
18828
|
async deleteGroup(id) {
|
|
18845
18829
|
return this._client._mgDeleteGroup(id);
|
|
18846
18830
|
}
|
|
18831
|
+
/**
|
|
18832
|
+
* Bulk-register explicit logger sources with the logging service.
|
|
18833
|
+
*
|
|
18834
|
+
* Unlike `start()`, which auto-discovers loggers from the current
|
|
18835
|
+
* process, this method accepts explicit `service` and `environment`
|
|
18836
|
+
* overrides — useful for sample-data seeding and test fixtures.
|
|
18837
|
+
*/
|
|
18838
|
+
async registerSources(sources) {
|
|
18839
|
+
return this._client._mgRegisterSources(sources);
|
|
18840
|
+
}
|
|
18847
18841
|
};
|
|
18848
18842
|
var LoggingClient = class {
|
|
18849
18843
|
/** @internal */
|
|
@@ -19022,6 +19016,26 @@ var LoggingClient = class {
|
|
|
19022
19016
|
return data.data.map((r) => this._groupToModel(r));
|
|
19023
19017
|
}
|
|
19024
19018
|
/** @internal */
|
|
19019
|
+
async _mgRegisterSources(sources) {
|
|
19020
|
+
if (sources.length === 0) return;
|
|
19021
|
+
const loggers = sources.map((src) => ({
|
|
19022
|
+
id: src.name,
|
|
19023
|
+
level: src.level ?? void 0,
|
|
19024
|
+
resolved_level: src.resolvedLevel,
|
|
19025
|
+
service: src.service,
|
|
19026
|
+
environment: src.environment
|
|
19027
|
+
}));
|
|
19028
|
+
try {
|
|
19029
|
+
const result = await this._http.POST("/api/v1/loggers/bulk", {
|
|
19030
|
+
body: { loggers }
|
|
19031
|
+
});
|
|
19032
|
+
if (result.error !== void 0)
|
|
19033
|
+
await checkError3(result.response, "Failed to register sources");
|
|
19034
|
+
} catch (err) {
|
|
19035
|
+
wrapFetchError3(err);
|
|
19036
|
+
}
|
|
19037
|
+
}
|
|
19038
|
+
/** @internal */
|
|
19025
19039
|
async _mgDeleteGroup(id) {
|
|
19026
19040
|
const group = await this.management.getGroup(id);
|
|
19027
19041
|
try {
|
|
@@ -19549,10 +19563,11 @@ var LoggingClient = class {
|
|
|
19549
19563
|
// ------------------------------------------------------------------
|
|
19550
19564
|
_loggerToModel(resource) {
|
|
19551
19565
|
const attrs = resource.attributes;
|
|
19566
|
+
const rawLevel = attrs.level ?? null;
|
|
19552
19567
|
return new Logger(this, {
|
|
19553
19568
|
id: resource.id ?? null,
|
|
19554
19569
|
name: attrs.name,
|
|
19555
|
-
level:
|
|
19570
|
+
level: rawLevel,
|
|
19556
19571
|
group: attrs.group ?? null,
|
|
19557
19572
|
managed: attrs.managed ?? false,
|
|
19558
19573
|
sources: [],
|
|
@@ -19563,11 +19578,12 @@ var LoggingClient = class {
|
|
|
19563
19578
|
}
|
|
19564
19579
|
_groupToModel(resource) {
|
|
19565
19580
|
const attrs = resource.attributes;
|
|
19581
|
+
const rawLevel = attrs.level ?? null;
|
|
19566
19582
|
return new LogGroup(this, {
|
|
19567
19583
|
id: resource.id ?? null,
|
|
19568
19584
|
key: attrs.key ?? null,
|
|
19569
19585
|
name: attrs.name,
|
|
19570
|
-
level:
|
|
19586
|
+
level: rawLevel,
|
|
19571
19587
|
group: attrs.parent_id ?? null,
|
|
19572
19588
|
environments: attrs.environments ?? {},
|
|
19573
19589
|
createdAt: attrs.created_at ?? null,
|
|
@@ -19576,6 +19592,666 @@ var LoggingClient = class {
|
|
|
19576
19592
|
}
|
|
19577
19593
|
};
|
|
19578
19594
|
|
|
19595
|
+
// src/management/client.ts
|
|
19596
|
+
import createClient4 from "openapi-fetch";
|
|
19597
|
+
|
|
19598
|
+
// src/management/types.ts
|
|
19599
|
+
var EnvironmentClassification = /* @__PURE__ */ ((EnvironmentClassification2) => {
|
|
19600
|
+
EnvironmentClassification2["STANDARD"] = "STANDARD";
|
|
19601
|
+
EnvironmentClassification2["AD_HOC"] = "AD_HOC";
|
|
19602
|
+
return EnvironmentClassification2;
|
|
19603
|
+
})(EnvironmentClassification || {});
|
|
19604
|
+
|
|
19605
|
+
// src/management/models.ts
|
|
19606
|
+
var Environment = class {
|
|
19607
|
+
/** Unique slug identifier (e.g. `"production"`). */
|
|
19608
|
+
id;
|
|
19609
|
+
/** Human-readable display name. */
|
|
19610
|
+
name;
|
|
19611
|
+
/** Hex color code, or null. */
|
|
19612
|
+
color;
|
|
19613
|
+
/** Whether this is a STANDARD or AD_HOC environment. */
|
|
19614
|
+
classification;
|
|
19615
|
+
/** When the environment was created. */
|
|
19616
|
+
createdAt;
|
|
19617
|
+
/** When the environment was last updated. */
|
|
19618
|
+
updatedAt;
|
|
19619
|
+
/** @internal */
|
|
19620
|
+
_client;
|
|
19621
|
+
/** @internal */
|
|
19622
|
+
constructor(client, fields) {
|
|
19623
|
+
this._client = client;
|
|
19624
|
+
this.id = fields.id;
|
|
19625
|
+
this.name = fields.name;
|
|
19626
|
+
this.color = fields.color;
|
|
19627
|
+
this.classification = fields.classification;
|
|
19628
|
+
this.createdAt = fields.createdAt;
|
|
19629
|
+
this.updatedAt = fields.updatedAt;
|
|
19630
|
+
}
|
|
19631
|
+
/** Persist this environment to the server (creates if new, updates if existing). */
|
|
19632
|
+
async save() {
|
|
19633
|
+
if (this._client === null) {
|
|
19634
|
+
throw new Error("Environment was constructed without a client; cannot save");
|
|
19635
|
+
}
|
|
19636
|
+
if (this.createdAt === null) {
|
|
19637
|
+
const saved = await this._client._create(this);
|
|
19638
|
+
this._apply(saved);
|
|
19639
|
+
} else {
|
|
19640
|
+
const saved = await this._client._update(this);
|
|
19641
|
+
this._apply(saved);
|
|
19642
|
+
}
|
|
19643
|
+
}
|
|
19644
|
+
/** @internal */
|
|
19645
|
+
_apply(other) {
|
|
19646
|
+
this.id = other.id;
|
|
19647
|
+
this.name = other.name;
|
|
19648
|
+
this.color = other.color;
|
|
19649
|
+
this.classification = other.classification;
|
|
19650
|
+
this.createdAt = other.createdAt;
|
|
19651
|
+
this.updatedAt = other.updatedAt;
|
|
19652
|
+
}
|
|
19653
|
+
toString() {
|
|
19654
|
+
return `Environment(id=${this.id}, name=${this.name}, classification=${this.classification})`;
|
|
19655
|
+
}
|
|
19656
|
+
};
|
|
19657
|
+
var ContextType = class {
|
|
19658
|
+
/** Unique slug identifier (e.g. `"user"`). */
|
|
19659
|
+
id;
|
|
19660
|
+
/** Human-readable display name. */
|
|
19661
|
+
name;
|
|
19662
|
+
/** Known attribute keys with metadata objects. */
|
|
19663
|
+
attributes;
|
|
19664
|
+
/** When the context type was created. */
|
|
19665
|
+
createdAt;
|
|
19666
|
+
/** When the context type was last updated. */
|
|
19667
|
+
updatedAt;
|
|
19668
|
+
/** @internal */
|
|
19669
|
+
_client;
|
|
19670
|
+
/** @internal */
|
|
19671
|
+
constructor(client, fields) {
|
|
19672
|
+
this._client = client;
|
|
19673
|
+
this.id = fields.id;
|
|
19674
|
+
this.name = fields.name;
|
|
19675
|
+
this.attributes = { ...fields.attributes };
|
|
19676
|
+
this.createdAt = fields.createdAt;
|
|
19677
|
+
this.updatedAt = fields.updatedAt;
|
|
19678
|
+
}
|
|
19679
|
+
/** Add a known-attribute slot. Local; call `save()` to persist. */
|
|
19680
|
+
addAttribute(name, metadata = {}) {
|
|
19681
|
+
this.attributes = { ...this.attributes, [name]: metadata };
|
|
19682
|
+
}
|
|
19683
|
+
/** Remove a known-attribute slot. Local; call `save()` to persist. */
|
|
19684
|
+
removeAttribute(name) {
|
|
19685
|
+
const attrs = { ...this.attributes };
|
|
19686
|
+
delete attrs[name];
|
|
19687
|
+
this.attributes = attrs;
|
|
19688
|
+
}
|
|
19689
|
+
/** Replace a known-attribute slot's metadata. Local; call `save()` to persist. */
|
|
19690
|
+
updateAttribute(name, metadata) {
|
|
19691
|
+
this.attributes = { ...this.attributes, [name]: metadata };
|
|
19692
|
+
}
|
|
19693
|
+
/** Persist this context type to the server (creates if new, updates if existing). */
|
|
19694
|
+
async save() {
|
|
19695
|
+
if (this._client === null) {
|
|
19696
|
+
throw new Error("ContextType was constructed without a client; cannot save");
|
|
19697
|
+
}
|
|
19698
|
+
if (this.createdAt === null) {
|
|
19699
|
+
const saved = await this._client._create(this);
|
|
19700
|
+
this._apply(saved);
|
|
19701
|
+
} else {
|
|
19702
|
+
const saved = await this._client._update(this);
|
|
19703
|
+
this._apply(saved);
|
|
19704
|
+
}
|
|
19705
|
+
}
|
|
19706
|
+
/** @internal */
|
|
19707
|
+
_apply(other) {
|
|
19708
|
+
this.id = other.id;
|
|
19709
|
+
this.name = other.name;
|
|
19710
|
+
this.attributes = { ...other.attributes };
|
|
19711
|
+
this.createdAt = other.createdAt;
|
|
19712
|
+
this.updatedAt = other.updatedAt;
|
|
19713
|
+
}
|
|
19714
|
+
toString() {
|
|
19715
|
+
return `ContextType(id=${this.id}, name=${this.name})`;
|
|
19716
|
+
}
|
|
19717
|
+
};
|
|
19718
|
+
var ContextEntity = class {
|
|
19719
|
+
/** Context type key (e.g. `"user"`). */
|
|
19720
|
+
type;
|
|
19721
|
+
/** Entity key (e.g. `"user-123"`). */
|
|
19722
|
+
key;
|
|
19723
|
+
/** Human-readable display name, or null. */
|
|
19724
|
+
name;
|
|
19725
|
+
/** Observed attributes. */
|
|
19726
|
+
attributes;
|
|
19727
|
+
/** When the context was created. */
|
|
19728
|
+
createdAt;
|
|
19729
|
+
/** When the context was last updated. */
|
|
19730
|
+
updatedAt;
|
|
19731
|
+
/** @internal */
|
|
19732
|
+
constructor(fields) {
|
|
19733
|
+
this.type = fields.type;
|
|
19734
|
+
this.key = fields.key;
|
|
19735
|
+
this.name = fields.name;
|
|
19736
|
+
this.attributes = { ...fields.attributes };
|
|
19737
|
+
this.createdAt = fields.createdAt;
|
|
19738
|
+
this.updatedAt = fields.updatedAt;
|
|
19739
|
+
}
|
|
19740
|
+
/** Composite `"type:key"` identifier. */
|
|
19741
|
+
get id() {
|
|
19742
|
+
return `${this.type}:${this.key}`;
|
|
19743
|
+
}
|
|
19744
|
+
toString() {
|
|
19745
|
+
return `ContextEntity(type=${this.type}, key=${this.key})`;
|
|
19746
|
+
}
|
|
19747
|
+
};
|
|
19748
|
+
var AccountSettings = class {
|
|
19749
|
+
/** @internal */
|
|
19750
|
+
_data;
|
|
19751
|
+
/** @internal */
|
|
19752
|
+
_client;
|
|
19753
|
+
/** @internal */
|
|
19754
|
+
constructor(client, data) {
|
|
19755
|
+
this._client = client;
|
|
19756
|
+
this._data = { ...data };
|
|
19757
|
+
}
|
|
19758
|
+
/** The full settings dict. Direct mutations are reflected in `save()`. */
|
|
19759
|
+
get raw() {
|
|
19760
|
+
return this._data;
|
|
19761
|
+
}
|
|
19762
|
+
set raw(value) {
|
|
19763
|
+
this._data = { ...value };
|
|
19764
|
+
}
|
|
19765
|
+
/** Canonical ordering of STANDARD environments. Empty array if unset. */
|
|
19766
|
+
get environmentOrder() {
|
|
19767
|
+
const val = this._data["environment_order"];
|
|
19768
|
+
return Array.isArray(val) ? [...val] : [];
|
|
19769
|
+
}
|
|
19770
|
+
set environmentOrder(value) {
|
|
19771
|
+
this._data["environment_order"] = [...value];
|
|
19772
|
+
}
|
|
19773
|
+
/** Persist the settings to the server. */
|
|
19774
|
+
async save() {
|
|
19775
|
+
if (this._client === null) {
|
|
19776
|
+
throw new Error("AccountSettings was constructed without a client; cannot save");
|
|
19777
|
+
}
|
|
19778
|
+
const saved = await this._client._save(this._data);
|
|
19779
|
+
this._apply(saved);
|
|
19780
|
+
}
|
|
19781
|
+
/** @internal */
|
|
19782
|
+
_apply(other) {
|
|
19783
|
+
this._data = { ...other._data };
|
|
19784
|
+
}
|
|
19785
|
+
/** @internal — expose raw data for _save(). */
|
|
19786
|
+
get _rawData() {
|
|
19787
|
+
return this._data;
|
|
19788
|
+
}
|
|
19789
|
+
toString() {
|
|
19790
|
+
return `AccountSettings(${JSON.stringify(this._data)})`;
|
|
19791
|
+
}
|
|
19792
|
+
};
|
|
19793
|
+
|
|
19794
|
+
// src/management/client.ts
|
|
19795
|
+
function splitContextId(idOrType, key) {
|
|
19796
|
+
if (key === void 0) {
|
|
19797
|
+
if (!idOrType.includes(":")) {
|
|
19798
|
+
throw new Error(
|
|
19799
|
+
`context id must be 'type:key' (got ${JSON.stringify(idOrType)}); alternatively pass type and key as separate args`
|
|
19800
|
+
);
|
|
19801
|
+
}
|
|
19802
|
+
const colonIdx = idOrType.indexOf(":");
|
|
19803
|
+
return [idOrType.slice(0, colonIdx), idOrType.slice(colonIdx + 1)];
|
|
19804
|
+
}
|
|
19805
|
+
return [idOrType, key];
|
|
19806
|
+
}
|
|
19807
|
+
async function checkError4(response, _context) {
|
|
19808
|
+
const body = await response.text().catch(() => "");
|
|
19809
|
+
throwForStatus(response.status, body);
|
|
19810
|
+
}
|
|
19811
|
+
function wrapFetchError4(err) {
|
|
19812
|
+
if (err instanceof SmplError) {
|
|
19813
|
+
throw err;
|
|
19814
|
+
}
|
|
19815
|
+
if (err instanceof TypeError) {
|
|
19816
|
+
throw new SmplConnectionError(`Network error: ${err.message}`);
|
|
19817
|
+
}
|
|
19818
|
+
throw new SmplConnectionError(
|
|
19819
|
+
`Request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
19820
|
+
);
|
|
19821
|
+
}
|
|
19822
|
+
function envFromResource(resource, client) {
|
|
19823
|
+
const attrs = resource.attributes ?? {};
|
|
19824
|
+
return new Environment(client, {
|
|
19825
|
+
id: resource.id ?? null,
|
|
19826
|
+
name: attrs.name ?? "",
|
|
19827
|
+
color: attrs.color ?? null,
|
|
19828
|
+
classification: attrs.classification === "AD_HOC" ? "AD_HOC" /* AD_HOC */ : "STANDARD" /* STANDARD */,
|
|
19829
|
+
createdAt: attrs.created_at ?? null,
|
|
19830
|
+
updatedAt: attrs.updated_at ?? null
|
|
19831
|
+
});
|
|
19832
|
+
}
|
|
19833
|
+
function ctFromResource(resource, client) {
|
|
19834
|
+
const attrs = resource.attributes ?? {};
|
|
19835
|
+
const rawMeta = attrs.attributes;
|
|
19836
|
+
const attributeMetadata = {};
|
|
19837
|
+
if (rawMeta && typeof rawMeta === "object") {
|
|
19838
|
+
for (const [k, v] of Object.entries(rawMeta)) {
|
|
19839
|
+
attributeMetadata[k] = typeof v === "object" && v !== null ? v : {};
|
|
19840
|
+
}
|
|
19841
|
+
}
|
|
19842
|
+
return new ContextType(client, {
|
|
19843
|
+
id: resource.id ?? null,
|
|
19844
|
+
name: attrs.name ?? "",
|
|
19845
|
+
attributes: attributeMetadata,
|
|
19846
|
+
createdAt: attrs.created_at ?? null,
|
|
19847
|
+
updatedAt: attrs.updated_at ?? null
|
|
19848
|
+
});
|
|
19849
|
+
}
|
|
19850
|
+
function ctxEntityFromResource(resource) {
|
|
19851
|
+
const compositeId = resource.id ?? "";
|
|
19852
|
+
const colonIdx = compositeId.indexOf(":");
|
|
19853
|
+
const ctxType = colonIdx >= 0 ? compositeId.slice(0, colonIdx) : compositeId;
|
|
19854
|
+
const ctxKey = colonIdx >= 0 ? compositeId.slice(colonIdx + 1) : "";
|
|
19855
|
+
const attrs = resource.attributes ?? {};
|
|
19856
|
+
const rawAttrs = attrs.attributes;
|
|
19857
|
+
const attrDict = rawAttrs && typeof rawAttrs === "object" ? { ...rawAttrs } : {};
|
|
19858
|
+
return new ContextEntity({
|
|
19859
|
+
type: ctxType,
|
|
19860
|
+
key: ctxKey,
|
|
19861
|
+
name: attrs.name ?? null,
|
|
19862
|
+
attributes: attrDict,
|
|
19863
|
+
createdAt: attrs.created_at ?? null,
|
|
19864
|
+
updatedAt: attrs.updated_at ?? null
|
|
19865
|
+
});
|
|
19866
|
+
}
|
|
19867
|
+
var EnvironmentsClient = class {
|
|
19868
|
+
/** @internal */
|
|
19869
|
+
constructor(_http) {
|
|
19870
|
+
this._http = _http;
|
|
19871
|
+
}
|
|
19872
|
+
/**
|
|
19873
|
+
* Return an unsaved `Environment`. Call `.save()` to persist.
|
|
19874
|
+
*/
|
|
19875
|
+
new(id, options) {
|
|
19876
|
+
return new Environment(this, {
|
|
19877
|
+
id,
|
|
19878
|
+
name: options.name,
|
|
19879
|
+
color: options.color ?? null,
|
|
19880
|
+
classification: options.classification ?? "STANDARD" /* STANDARD */,
|
|
19881
|
+
createdAt: null,
|
|
19882
|
+
updatedAt: null
|
|
19883
|
+
});
|
|
19884
|
+
}
|
|
19885
|
+
/** List all environments. */
|
|
19886
|
+
async list() {
|
|
19887
|
+
let data;
|
|
19888
|
+
try {
|
|
19889
|
+
const result = await this._http.GET("/api/v1/environments", {});
|
|
19890
|
+
if (!result.response.ok) await checkError4(result.response, "Failed to list environments");
|
|
19891
|
+
data = result.data;
|
|
19892
|
+
} catch (err) {
|
|
19893
|
+
wrapFetchError4(err);
|
|
19894
|
+
}
|
|
19895
|
+
const items = data?.data ?? [];
|
|
19896
|
+
return items.map((r) => envFromResource(r, this));
|
|
19897
|
+
}
|
|
19898
|
+
/** Fetch an environment by id. */
|
|
19899
|
+
async get(id) {
|
|
19900
|
+
let data;
|
|
19901
|
+
try {
|
|
19902
|
+
const result = await this._http.GET("/api/v1/environments/{id}", {
|
|
19903
|
+
params: { path: { id } }
|
|
19904
|
+
});
|
|
19905
|
+
if (!result.response.ok) await checkError4(result.response, `Environment '${id}' not found`);
|
|
19906
|
+
data = result.data;
|
|
19907
|
+
} catch (err) {
|
|
19908
|
+
wrapFetchError4(err);
|
|
19909
|
+
}
|
|
19910
|
+
if (!data?.data) throw new SmplNotFoundError(`Environment with id '${id}' not found`);
|
|
19911
|
+
return envFromResource(data.data, this);
|
|
19912
|
+
}
|
|
19913
|
+
/** Delete an environment by id. */
|
|
19914
|
+
async delete(id) {
|
|
19915
|
+
try {
|
|
19916
|
+
const result = await this._http.DELETE("/api/v1/environments/{id}", {
|
|
19917
|
+
params: { path: { id } }
|
|
19918
|
+
});
|
|
19919
|
+
if (!result.response.ok && result.response.status !== 204)
|
|
19920
|
+
await checkError4(result.response, `Failed to delete environment '${id}'`);
|
|
19921
|
+
} catch (err) {
|
|
19922
|
+
wrapFetchError4(err);
|
|
19923
|
+
}
|
|
19924
|
+
}
|
|
19925
|
+
/** @internal — called by Environment.save() for new resources. */
|
|
19926
|
+
async _create(env) {
|
|
19927
|
+
const body = {
|
|
19928
|
+
data: {
|
|
19929
|
+
id: env.id,
|
|
19930
|
+
type: "environment",
|
|
19931
|
+
attributes: {
|
|
19932
|
+
name: env.name,
|
|
19933
|
+
color: env.color,
|
|
19934
|
+
classification: env.classification
|
|
19935
|
+
}
|
|
19936
|
+
}
|
|
19937
|
+
};
|
|
19938
|
+
let data;
|
|
19939
|
+
try {
|
|
19940
|
+
const result = await this._http.POST("/api/v1/environments", { body });
|
|
19941
|
+
if (!result.response.ok) await checkError4(result.response, "Failed to create environment");
|
|
19942
|
+
data = result.data;
|
|
19943
|
+
} catch (err) {
|
|
19944
|
+
wrapFetchError4(err);
|
|
19945
|
+
}
|
|
19946
|
+
if (!data?.data) throw new SmplValidationError("Failed to create environment");
|
|
19947
|
+
return envFromResource(data.data, this);
|
|
19948
|
+
}
|
|
19949
|
+
/** @internal — called by Environment.save() for existing resources. */
|
|
19950
|
+
async _update(env) {
|
|
19951
|
+
if (!env.id) throw new Error("Cannot update an Environment with no id");
|
|
19952
|
+
const body = {
|
|
19953
|
+
data: {
|
|
19954
|
+
id: env.id,
|
|
19955
|
+
type: "environment",
|
|
19956
|
+
attributes: {
|
|
19957
|
+
name: env.name,
|
|
19958
|
+
color: env.color,
|
|
19959
|
+
classification: env.classification
|
|
19960
|
+
}
|
|
19961
|
+
}
|
|
19962
|
+
};
|
|
19963
|
+
let data;
|
|
19964
|
+
try {
|
|
19965
|
+
const result = await this._http.PUT("/api/v1/environments/{id}", {
|
|
19966
|
+
params: { path: { id: env.id } },
|
|
19967
|
+
body
|
|
19968
|
+
});
|
|
19969
|
+
if (!result.response.ok)
|
|
19970
|
+
await checkError4(result.response, `Failed to update environment ${env.id}`);
|
|
19971
|
+
data = result.data;
|
|
19972
|
+
} catch (err) {
|
|
19973
|
+
wrapFetchError4(err);
|
|
19974
|
+
}
|
|
19975
|
+
if (!data?.data) throw new SmplValidationError(`Failed to update environment ${env.id}`);
|
|
19976
|
+
return envFromResource(data.data, this);
|
|
19977
|
+
}
|
|
19978
|
+
};
|
|
19979
|
+
var ContextTypesClient = class {
|
|
19980
|
+
/** @internal */
|
|
19981
|
+
constructor(_http) {
|
|
19982
|
+
this._http = _http;
|
|
19983
|
+
}
|
|
19984
|
+
/**
|
|
19985
|
+
* Return an unsaved `ContextType`. Call `.save()` to persist.
|
|
19986
|
+
*/
|
|
19987
|
+
new(id, options = {}) {
|
|
19988
|
+
return new ContextType(this, {
|
|
19989
|
+
id,
|
|
19990
|
+
name: options.name ?? id,
|
|
19991
|
+
attributes: options.attributes ?? {},
|
|
19992
|
+
createdAt: null,
|
|
19993
|
+
updatedAt: null
|
|
19994
|
+
});
|
|
19995
|
+
}
|
|
19996
|
+
/** List all context types. */
|
|
19997
|
+
async list() {
|
|
19998
|
+
let data;
|
|
19999
|
+
try {
|
|
20000
|
+
const result = await this._http.GET("/api/v1/context_types", {});
|
|
20001
|
+
if (!result.response.ok) await checkError4(result.response, "Failed to list context types");
|
|
20002
|
+
data = result.data;
|
|
20003
|
+
} catch (err) {
|
|
20004
|
+
wrapFetchError4(err);
|
|
20005
|
+
}
|
|
20006
|
+
const items = data?.data ?? [];
|
|
20007
|
+
return items.map((r) => ctFromResource(r, this));
|
|
20008
|
+
}
|
|
20009
|
+
/** Fetch a context type by id. */
|
|
20010
|
+
async get(id) {
|
|
20011
|
+
let data;
|
|
20012
|
+
try {
|
|
20013
|
+
const result = await this._http.GET("/api/v1/context_types/{id}", {
|
|
20014
|
+
params: { path: { id } }
|
|
20015
|
+
});
|
|
20016
|
+
if (!result.response.ok) await checkError4(result.response, `ContextType '${id}' not found`);
|
|
20017
|
+
data = result.data;
|
|
20018
|
+
} catch (err) {
|
|
20019
|
+
wrapFetchError4(err);
|
|
20020
|
+
}
|
|
20021
|
+
if (!data?.data) throw new SmplNotFoundError(`ContextType with id '${id}' not found`);
|
|
20022
|
+
return ctFromResource(data.data, this);
|
|
20023
|
+
}
|
|
20024
|
+
/** Delete a context type by id. */
|
|
20025
|
+
async delete(id) {
|
|
20026
|
+
try {
|
|
20027
|
+
const result = await this._http.DELETE("/api/v1/context_types/{id}", {
|
|
20028
|
+
params: { path: { id } }
|
|
20029
|
+
});
|
|
20030
|
+
if (!result.response.ok && result.response.status !== 204)
|
|
20031
|
+
await checkError4(result.response, `Failed to delete context type '${id}'`);
|
|
20032
|
+
} catch (err) {
|
|
20033
|
+
wrapFetchError4(err);
|
|
20034
|
+
}
|
|
20035
|
+
}
|
|
20036
|
+
/** @internal — called by ContextType.save() for new resources. */
|
|
20037
|
+
async _create(ct) {
|
|
20038
|
+
const body = {
|
|
20039
|
+
data: {
|
|
20040
|
+
id: ct.id,
|
|
20041
|
+
type: "context_type",
|
|
20042
|
+
attributes: {
|
|
20043
|
+
id: ct.id,
|
|
20044
|
+
name: ct.name,
|
|
20045
|
+
attributes: ct.attributes
|
|
20046
|
+
}
|
|
20047
|
+
}
|
|
20048
|
+
};
|
|
20049
|
+
let data;
|
|
20050
|
+
try {
|
|
20051
|
+
const result = await this._http.POST("/api/v1/context_types", { body });
|
|
20052
|
+
if (!result.response.ok) await checkError4(result.response, "Failed to create context type");
|
|
20053
|
+
data = result.data;
|
|
20054
|
+
} catch (err) {
|
|
20055
|
+
wrapFetchError4(err);
|
|
20056
|
+
}
|
|
20057
|
+
if (!data?.data) throw new SmplValidationError("Failed to create context type");
|
|
20058
|
+
return ctFromResource(data.data, this);
|
|
20059
|
+
}
|
|
20060
|
+
/** @internal — called by ContextType.save() for existing resources. */
|
|
20061
|
+
async _update(ct) {
|
|
20062
|
+
if (!ct.id) throw new Error("Cannot update a ContextType with no id");
|
|
20063
|
+
const body = {
|
|
20064
|
+
data: {
|
|
20065
|
+
id: ct.id,
|
|
20066
|
+
type: "context_type",
|
|
20067
|
+
attributes: {
|
|
20068
|
+
id: ct.id,
|
|
20069
|
+
name: ct.name,
|
|
20070
|
+
attributes: ct.attributes
|
|
20071
|
+
}
|
|
20072
|
+
}
|
|
20073
|
+
};
|
|
20074
|
+
let data;
|
|
20075
|
+
try {
|
|
20076
|
+
const result = await this._http.PUT("/api/v1/context_types/{id}", {
|
|
20077
|
+
params: { path: { id: ct.id } },
|
|
20078
|
+
body
|
|
20079
|
+
});
|
|
20080
|
+
if (!result.response.ok)
|
|
20081
|
+
await checkError4(result.response, `Failed to update context type ${ct.id}`);
|
|
20082
|
+
data = result.data;
|
|
20083
|
+
} catch (err) {
|
|
20084
|
+
wrapFetchError4(err);
|
|
20085
|
+
}
|
|
20086
|
+
if (!data?.data) throw new SmplValidationError(`Failed to update context type ${ct.id}`);
|
|
20087
|
+
return ctFromResource(data.data, this);
|
|
20088
|
+
}
|
|
20089
|
+
};
|
|
20090
|
+
var ContextsClient = class {
|
|
20091
|
+
/** @internal */
|
|
20092
|
+
constructor(_http, _buffer) {
|
|
20093
|
+
this._http = _http;
|
|
20094
|
+
this._buffer = _buffer;
|
|
20095
|
+
}
|
|
20096
|
+
/**
|
|
20097
|
+
* Buffer context(s) for registration; optionally flush immediately.
|
|
20098
|
+
*
|
|
20099
|
+
* When `flush` is false (default), contexts are queued for the SDK's
|
|
20100
|
+
* background flush — right for high-frequency observation from a live
|
|
20101
|
+
* request handler. When `flush` is true the call awaits the round-trip
|
|
20102
|
+
* — right for IaC scripts.
|
|
20103
|
+
*/
|
|
20104
|
+
async register(items, options = {}) {
|
|
20105
|
+
const batch = Array.isArray(items) ? items : [items];
|
|
20106
|
+
this._buffer.observe(batch);
|
|
20107
|
+
if (options.flush) {
|
|
20108
|
+
await this.flush();
|
|
20109
|
+
}
|
|
20110
|
+
}
|
|
20111
|
+
/** Send any pending context observations to the server. */
|
|
20112
|
+
async flush() {
|
|
20113
|
+
const batch = this._buffer.drain();
|
|
20114
|
+
if (batch.length === 0) return;
|
|
20115
|
+
try {
|
|
20116
|
+
const result = await this._http.POST("/api/v1/contexts/bulk", {
|
|
20117
|
+
body: {
|
|
20118
|
+
contexts: batch.map((ctx) => ({
|
|
20119
|
+
type: ctx.type,
|
|
20120
|
+
key: ctx.key,
|
|
20121
|
+
attributes: ctx.attributes
|
|
20122
|
+
}))
|
|
20123
|
+
}
|
|
20124
|
+
});
|
|
20125
|
+
if (!result.response.ok) await checkError4(result.response, "Failed to flush contexts");
|
|
20126
|
+
} catch (err) {
|
|
20127
|
+
wrapFetchError4(err);
|
|
20128
|
+
}
|
|
20129
|
+
}
|
|
20130
|
+
/** List all contexts of a given type. */
|
|
20131
|
+
async list(type) {
|
|
20132
|
+
let data;
|
|
20133
|
+
try {
|
|
20134
|
+
const result = await this._http.GET("/api/v1/contexts", {
|
|
20135
|
+
params: { query: { "filter[context_type]": type } }
|
|
20136
|
+
});
|
|
20137
|
+
if (!result.response.ok) await checkError4(result.response, "Failed to list contexts");
|
|
20138
|
+
data = result.data;
|
|
20139
|
+
} catch (err) {
|
|
20140
|
+
wrapFetchError4(err);
|
|
20141
|
+
}
|
|
20142
|
+
const items = data?.data ?? [];
|
|
20143
|
+
return items.map(ctxEntityFromResource);
|
|
20144
|
+
}
|
|
20145
|
+
/** Fetch a context by composite id (`"type:key"`) or by separate type and key. */
|
|
20146
|
+
async get(idOrType, key) {
|
|
20147
|
+
const [ctxType, ctxKey] = splitContextId(idOrType, key);
|
|
20148
|
+
const composite = `${ctxType}:${ctxKey}`;
|
|
20149
|
+
let data;
|
|
20150
|
+
try {
|
|
20151
|
+
const result = await this._http.GET("/api/v1/contexts/{id}", {
|
|
20152
|
+
params: { path: { id: composite } }
|
|
20153
|
+
});
|
|
20154
|
+
if (!result.response.ok)
|
|
20155
|
+
await checkError4(result.response, `Context '${composite}' not found`);
|
|
20156
|
+
data = result.data;
|
|
20157
|
+
} catch (err) {
|
|
20158
|
+
wrapFetchError4(err);
|
|
20159
|
+
}
|
|
20160
|
+
if (!data?.data) throw new SmplNotFoundError(`Context with id '${composite}' not found`);
|
|
20161
|
+
return ctxEntityFromResource(data.data);
|
|
20162
|
+
}
|
|
20163
|
+
/** Delete a context by composite id (`"type:key"`) or by separate type and key. */
|
|
20164
|
+
async delete(idOrType, key) {
|
|
20165
|
+
const [ctxType, ctxKey] = splitContextId(idOrType, key);
|
|
20166
|
+
const composite = `${ctxType}:${ctxKey}`;
|
|
20167
|
+
try {
|
|
20168
|
+
const result = await this._http.DELETE("/api/v1/contexts/{id}", {
|
|
20169
|
+
params: { path: { id: composite } }
|
|
20170
|
+
});
|
|
20171
|
+
if (!result.response.ok && result.response.status !== 204)
|
|
20172
|
+
await checkError4(result.response, `Failed to delete context '${composite}'`);
|
|
20173
|
+
} catch (err) {
|
|
20174
|
+
wrapFetchError4(err);
|
|
20175
|
+
}
|
|
20176
|
+
}
|
|
20177
|
+
};
|
|
20178
|
+
var AccountSettingsClient = class {
|
|
20179
|
+
/** @internal */
|
|
20180
|
+
constructor(_appBaseUrl, apiKey) {
|
|
20181
|
+
this._appBaseUrl = _appBaseUrl;
|
|
20182
|
+
this._headers = {
|
|
20183
|
+
Authorization: `Bearer ${apiKey}`,
|
|
20184
|
+
"Content-Type": "application/json",
|
|
20185
|
+
Accept: "application/json"
|
|
20186
|
+
};
|
|
20187
|
+
}
|
|
20188
|
+
_headers;
|
|
20189
|
+
/** Fetch the current account settings. */
|
|
20190
|
+
async get() {
|
|
20191
|
+
const url = `${this._appBaseUrl}/api/v1/accounts/current/settings`;
|
|
20192
|
+
let resp;
|
|
20193
|
+
try {
|
|
20194
|
+
resp = await fetch(url, { headers: this._headers });
|
|
20195
|
+
} catch (err) {
|
|
20196
|
+
throw new SmplConnectionError(
|
|
20197
|
+
`Network error: ${err instanceof Error ? err.message : String(err)}`
|
|
20198
|
+
);
|
|
20199
|
+
}
|
|
20200
|
+
if (!resp.ok) {
|
|
20201
|
+
const body = await resp.text().catch(() => "");
|
|
20202
|
+
throwForStatus(resp.status, body);
|
|
20203
|
+
}
|
|
20204
|
+
const data = await resp.json();
|
|
20205
|
+
return new AccountSettings(this, data ?? {});
|
|
20206
|
+
}
|
|
20207
|
+
/** @internal — called by AccountSettings.save(). */
|
|
20208
|
+
async _save(data) {
|
|
20209
|
+
const url = `${this._appBaseUrl}/api/v1/accounts/current/settings`;
|
|
20210
|
+
let resp;
|
|
20211
|
+
try {
|
|
20212
|
+
resp = await fetch(url, {
|
|
20213
|
+
method: "PUT",
|
|
20214
|
+
headers: this._headers,
|
|
20215
|
+
body: JSON.stringify(data)
|
|
20216
|
+
});
|
|
20217
|
+
} catch (err) {
|
|
20218
|
+
throw new SmplConnectionError(
|
|
20219
|
+
`Network error: ${err instanceof Error ? err.message : String(err)}`
|
|
20220
|
+
);
|
|
20221
|
+
}
|
|
20222
|
+
if (!resp.ok) {
|
|
20223
|
+
const body = await resp.text().catch(() => "");
|
|
20224
|
+
throwForStatus(resp.status, body);
|
|
20225
|
+
}
|
|
20226
|
+
const saved = await resp.json();
|
|
20227
|
+
return new AccountSettings(this, saved ?? {});
|
|
20228
|
+
}
|
|
20229
|
+
};
|
|
20230
|
+
var ManagementClient = class {
|
|
20231
|
+
/** CRUD for environments. */
|
|
20232
|
+
environments;
|
|
20233
|
+
/** Registration, list, get, and delete for context instances. */
|
|
20234
|
+
contexts;
|
|
20235
|
+
/** CRUD for context types (entity schemas). */
|
|
20236
|
+
context_types;
|
|
20237
|
+
/** Get/save for account-level settings. */
|
|
20238
|
+
account_settings;
|
|
20239
|
+
/** @internal */
|
|
20240
|
+
constructor(options) {
|
|
20241
|
+
const http = createClient4({
|
|
20242
|
+
baseUrl: options.appBaseUrl,
|
|
20243
|
+
headers: {
|
|
20244
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
20245
|
+
Accept: "application/json"
|
|
20246
|
+
}
|
|
20247
|
+
});
|
|
20248
|
+
this.environments = new EnvironmentsClient(http);
|
|
20249
|
+
this.contexts = new ContextsClient(http, options.buffer);
|
|
20250
|
+
this.context_types = new ContextTypesClient(http);
|
|
20251
|
+
this.account_settings = new AccountSettingsClient(options.appBaseUrl, options.apiKey);
|
|
20252
|
+
}
|
|
20253
|
+
};
|
|
20254
|
+
|
|
19579
20255
|
// src/ws.ts
|
|
19580
20256
|
import WebSocket from "ws";
|
|
19581
20257
|
var BACKOFF_MS = [1e3, 2e3, 4e3, 8e3, 16e3, 32e3, 6e4];
|
|
@@ -20013,6 +20689,8 @@ var SmplClient = class {
|
|
|
20013
20689
|
flags;
|
|
20014
20690
|
/** Client for logging management and runtime. */
|
|
20015
20691
|
logging;
|
|
20692
|
+
/** Client for app-plane management (environments, contexts, context types, account settings). */
|
|
20693
|
+
management;
|
|
20016
20694
|
_wsManager = null;
|
|
20017
20695
|
_apiKey;
|
|
20018
20696
|
/** @internal */
|
|
@@ -20043,7 +20721,7 @@ var SmplClient = class {
|
|
|
20043
20721
|
"lifecycle",
|
|
20044
20722
|
`SmplClient created (api_key=${maskedKey}, environment=${cfg.environment}, service=${cfg.service})`
|
|
20045
20723
|
);
|
|
20046
|
-
this._appHttp =
|
|
20724
|
+
this._appHttp = createClient5({
|
|
20047
20725
|
baseUrl: appBaseUrl,
|
|
20048
20726
|
headers: {
|
|
20049
20727
|
Authorization: `Bearer ${cfg.apiKey}`,
|
|
@@ -20058,13 +20736,15 @@ var SmplClient = class {
|
|
|
20058
20736
|
appBaseUrl
|
|
20059
20737
|
});
|
|
20060
20738
|
}
|
|
20739
|
+
const sharedContextBuffer = new ContextRegistrationBuffer();
|
|
20061
20740
|
this.config = new ConfigClient(cfg.apiKey, this._timeout, configBaseUrl);
|
|
20062
20741
|
this.flags = new FlagsClient(
|
|
20063
20742
|
cfg.apiKey,
|
|
20064
20743
|
() => this._ensureWs(),
|
|
20065
20744
|
this._timeout,
|
|
20066
20745
|
flagsBaseUrl,
|
|
20067
|
-
appBaseUrl
|
|
20746
|
+
appBaseUrl,
|
|
20747
|
+
sharedContextBuffer
|
|
20068
20748
|
);
|
|
20069
20749
|
this.logging = new LoggingClient(
|
|
20070
20750
|
cfg.apiKey,
|
|
@@ -20072,6 +20752,11 @@ var SmplClient = class {
|
|
|
20072
20752
|
this._timeout,
|
|
20073
20753
|
loggingBaseUrl
|
|
20074
20754
|
);
|
|
20755
|
+
this.management = new ManagementClient({
|
|
20756
|
+
appBaseUrl,
|
|
20757
|
+
apiKey: cfg.apiKey,
|
|
20758
|
+
buffer: sharedContextBuffer
|
|
20759
|
+
});
|
|
20075
20760
|
this.config._getSharedWs = () => this._ensureWs();
|
|
20076
20761
|
this.flags._parent = this;
|
|
20077
20762
|
this.config._parent = this;
|
|
@@ -20197,6 +20882,25 @@ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
|
20197
20882
|
LogLevel2["SILENT"] = "SILENT";
|
|
20198
20883
|
return LogLevel2;
|
|
20199
20884
|
})(LogLevel || {});
|
|
20885
|
+
var LoggerSource = class {
|
|
20886
|
+
/** Logger name (e.g. `"sqlalchemy.engine"`). */
|
|
20887
|
+
name;
|
|
20888
|
+
/** Service name this source belongs to. */
|
|
20889
|
+
service;
|
|
20890
|
+
/** Environment name this source belongs to. */
|
|
20891
|
+
environment;
|
|
20892
|
+
/** Effective log level for this source. */
|
|
20893
|
+
resolvedLevel;
|
|
20894
|
+
/** Explicit (configured) log level, if different from `resolvedLevel`. */
|
|
20895
|
+
level;
|
|
20896
|
+
constructor(name, options) {
|
|
20897
|
+
this.name = name;
|
|
20898
|
+
this.service = options.service;
|
|
20899
|
+
this.environment = options.environment;
|
|
20900
|
+
this.resolvedLevel = options.resolved_level;
|
|
20901
|
+
this.level = options.level ?? null;
|
|
20902
|
+
}
|
|
20903
|
+
};
|
|
20200
20904
|
|
|
20201
20905
|
// src/logging/adapters/winston.ts
|
|
20202
20906
|
var SMPLKIT_TO_WINSTON = {
|
|
@@ -20422,11 +21126,20 @@ var PinoAdapter = class {
|
|
|
20422
21126
|
}
|
|
20423
21127
|
};
|
|
20424
21128
|
export {
|
|
21129
|
+
AccountSettings,
|
|
21130
|
+
AccountSettingsClient,
|
|
20425
21131
|
BooleanFlag,
|
|
20426
21132
|
Config,
|
|
20427
21133
|
ConfigClient,
|
|
20428
21134
|
ConfigManagement,
|
|
20429
21135
|
Context,
|
|
21136
|
+
ContextEntity,
|
|
21137
|
+
ContextType,
|
|
21138
|
+
ContextTypesClient,
|
|
21139
|
+
ContextsClient,
|
|
21140
|
+
Environment,
|
|
21141
|
+
EnvironmentClassification,
|
|
21142
|
+
EnvironmentsClient,
|
|
20430
21143
|
Flag,
|
|
20431
21144
|
FlagChangeEvent,
|
|
20432
21145
|
FlagStats,
|
|
@@ -20437,8 +21150,10 @@ export {
|
|
|
20437
21150
|
LogGroup,
|
|
20438
21151
|
LogLevel,
|
|
20439
21152
|
Logger,
|
|
21153
|
+
LoggerSource,
|
|
20440
21154
|
LoggingClient,
|
|
20441
21155
|
LoggingManagement,
|
|
21156
|
+
ManagementClient,
|
|
20442
21157
|
NumberFlag,
|
|
20443
21158
|
PinoAdapter,
|
|
20444
21159
|
Rule,
|