@antelopejs/interface-dms-saas 0.4.1 → 0.5.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/README.md CHANGED
@@ -17,7 +17,7 @@ The AntelopeJS interface to extend the DMS SaaS module: billing, plans, registra
17
17
  - Builds plan projections and catalogues, and normalizes prices.
18
18
  - Extends registration and the provisioning of a new workspace.
19
19
  - Adds workspace settings categories and blocks to the tenant billing page.
20
- - Listens to workspace lifecycle events and operator actions, and exposes the canonical SaaS tables and models (`/db`).
20
+ - Listens to tenant lifecycle events and operator actions, and exposes the canonical SaaS tables and models (`/db`).
21
21
 
22
22
  A module that extends [`@antelopejs/dms-saas`](https://www.npmjs.com/package/@antelopejs/dms-saas) depends on this package, never on the runtime.
23
23
 
@@ -29,13 +29,13 @@ pnpm add @antelopejs/interface-dms-saas
29
29
 
30
30
  ## Quick start
31
31
 
32
- The root entry holds every extension point except `db`, with `invoiceLineItems` and `workspaceLifecycle` as namespaces:
32
+ The root entry holds every extension point except `db`, with `invoiceLineItems` and `tenantLifecycle` as namespaces:
33
33
 
34
34
  ```ts docs-check
35
- import { invoiceLineItems, workspaceLifecycle } from "@antelopejs/interface-dms-saas";
35
+ import { invoiceLineItems, tenantLifecycle } from "@antelopejs/interface-dms-saas";
36
36
 
37
37
  export const usageKey = invoiceLineItems.buildInvoiceLineKey("example.compute", "minutes");
38
- export const isProvisioningCommitted = workspaceLifecycle.IsWorkspaceProvisioningCommitted;
38
+ export const isProvisioningCommitted = tenantLifecycle.IsTenantProvisioningCommitted;
39
39
  ```
40
40
 
41
41
  Use the [entry-point reference](./docs/1.index.md#entry-points) for supported
package/dist/index.d.ts CHANGED
@@ -5,4 +5,4 @@ export * from "./plans";
5
5
  export * from "./provisioning";
6
6
  export * from "./registration";
7
7
  export * as invoiceLineItems from "./invoice-line-items";
8
- export * as workspaceLifecycle from "./workspace-lifecycle";
8
+ export * as tenantLifecycle from "./tenant-lifecycle";
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  };
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.workspaceLifecycle = exports.invoiceLineItems = void 0;
39
+ exports.tenantLifecycle = exports.invoiceLineItems = void 0;
40
40
  __exportStar(require("./billing"), exports);
41
41
  __exportStar(require("./data-api"), exports);
42
42
  __exportStar(require("./pages"), exports);
@@ -44,5 +44,5 @@ __exportStar(require("./plans"), exports);
44
44
  __exportStar(require("./provisioning"), exports);
45
45
  __exportStar(require("./registration"), exports);
46
46
  exports.invoiceLineItems = __importStar(require("./invoice-line-items"));
47
- exports.workspaceLifecycle = __importStar(require("./workspace-lifecycle"));
47
+ exports.tenantLifecycle = __importStar(require("./tenant-lifecycle"));
48
48
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWA,4CAA0B;AAC1B,6CAA2B;AAC3B,0CAAwB;AACxB,0CAAwB;AACxB,iDAA+B;AAC/B,iDAA+B;AAC/B,yEAAyD;AACzD,4EAA4D"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWA,4CAA0B;AAC1B,6CAA2B;AAC3B,0CAAwB;AACxB,0CAAwB;AACxB,iDAA+B;AAC/B,iDAA+B;AAC/B,yEAAyD;AACzD,sEAAsD"}
@@ -0,0 +1,27 @@
1
+ import { RegisteringProxy } from "@antelopejs/interface-core";
2
+ export declare const TENANT_LIFECYCLE_TRANSITIONS: readonly ["created", "suspended", "reactivation_requested"];
3
+ export type TenantLifecycleTransition = (typeof TENANT_LIFECYCLE_TRANSITIONS)[number];
4
+ export interface TenantLifecycleMessage {
5
+ tenantId: string;
6
+ operationId: string;
7
+ transition: TenantLifecycleTransition;
8
+ requestedAt: Date;
9
+ }
10
+ export interface TenantLifecycleReceipt {
11
+ receiptId: string;
12
+ effectiveAt?: Date;
13
+ }
14
+ export interface TenantLifecycleConsumer {
15
+ name: string;
16
+ transitions: readonly TenantLifecycleTransition[];
17
+ consume(message: TenantLifecycleMessage): Promise<TenantLifecycleReceipt>;
18
+ }
19
+ export interface TenantLifecycleConsumerRegistration {
20
+ unregister(): void;
21
+ }
22
+ export declare const IsTenantProvisioningCommitted: (tenantId: string) => Promise<boolean>;
23
+ export declare namespace internal {
24
+ const RegisterTenantLifecycleConsumer: RegisteringProxy<(name: string, consumer: TenantLifecycleConsumer) => void>;
25
+ }
26
+ export declare function UnregisterTenantLifecycleConsumer(name: string): void;
27
+ export declare function RegisterTenantLifecycleConsumer(consumer: TenantLifecycleConsumer): TenantLifecycleConsumerRegistration;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.internal = exports.IsTenantProvisioningCommitted = exports.TENANT_LIFECYCLE_TRANSITIONS = void 0;
4
+ exports.UnregisterTenantLifecycleConsumer = UnregisterTenantLifecycleConsumer;
5
+ exports.RegisterTenantLifecycleConsumer = RegisterTenantLifecycleConsumer;
6
+ const interface_core_1 = require("@antelopejs/interface-core");
7
+ const CONSUMER_NAME_PATTERN = /^[a-z][a-z0-9._-]{2,63}$/;
8
+ exports.TENANT_LIFECYCLE_TRANSITIONS = [
9
+ "created",
10
+ "suspended",
11
+ "reactivation_requested",
12
+ ];
13
+ exports.IsTenantProvisioningCommitted = (0, interface_core_1.InterfaceFunction)();
14
+ var internal;
15
+ (function (internal) {
16
+ internal.RegisterTenantLifecycleConsumer = new interface_core_1.RegisteringProxy();
17
+ })(internal || (exports.internal = internal = {}));
18
+ function UnregisterTenantLifecycleConsumer(name) {
19
+ internal.RegisterTenantLifecycleConsumer.unregister(name);
20
+ }
21
+ function RegisterTenantLifecycleConsumer(consumer) {
22
+ if (!CONSUMER_NAME_PATTERN.test(consumer.name)) {
23
+ throw new Error("Invalid workspace lifecycle consumer name");
24
+ }
25
+ internal.RegisterTenantLifecycleConsumer.register(consumer.name, consumer);
26
+ return {
27
+ unregister: () => UnregisterTenantLifecycleConsumer(consumer.name),
28
+ };
29
+ }
30
+ //# sourceMappingURL=tenant-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-lifecycle.js","sourceRoot":"","sources":["../src/tenant-lifecycle.ts"],"names":[],"mappings":";;;AA+DA,8EAEC;AAWD,0EAUC;AAtFD,+DAGoC;AAEpC,MAAM,qBAAqB,GAAG,0BAA0B,CAAC;AAG5C,QAAA,4BAA4B,GAAG;IAC1C,SAAS;IACT,WAAW;IACX,wBAAwB;CAChB,CAAC;AAwCE,QAAA,6BAA6B,GACxC,IAAA,kCAAiB,GAA0C,CAAC;AAG9D,IAAiB,QAAQ,CAIxB;AAJD,WAAiB,QAAQ;IACV,wCAA+B,GAAG,IAAI,iCAAgB,EAEhE,CAAC;AACN,CAAC,EAJgB,QAAQ,wBAAR,QAAQ,QAIxB;AAGD,SAAgB,iCAAiC,CAAC,IAAY;IAC5D,QAAQ,CAAC,+BAA+B,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AAC5D,CAAC;AAWD,SAAgB,+BAA+B,CAC7C,QAAiC;IAEjC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IACD,QAAQ,CAAC,+BAA+B,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC3E,OAAO;QACL,UAAU,EAAE,GAAG,EAAE,CAAC,iCAAiC,CAAC,QAAQ,CAAC,IAAI,CAAC;KACnE,CAAC;AACJ,CAAC"}
package/docs/1.index.md CHANGED
@@ -11,26 +11,26 @@ Consumers depend on `@antelopejs/interface-dms-saas`, not the runtime's private
11
11
 
12
12
  | Import after `@antelopejs/interface-dms-saas` | Contract |
13
13
  | --- | --- |
14
- | Root | Billing, data API, pages, plans, provisioning and registration exports; `invoiceLineItems` and `workspaceLifecycle` namespaces |
14
+ | Root | Billing, data API, pages, plans, provisioning and registration exports; `invoiceLineItems` and `tenantLifecycle` namespaces |
15
15
  | `/billing` | [Balances, previews and billing-state recomputation](./2.billing.md) |
16
16
  | `/invoice-line-items` | [Usage providers, line types and key helpers](./3.lifecycle-and-usage.md#invoice-line-items) |
17
- | `/workspace-lifecycle` | [Durable lifecycle consumers and provisioning gate](./3.lifecycle-and-usage.md#workspace-lifecycle) |
17
+ | `/tenant-lifecycle` | [Durable lifecycle consumers and provisioning gate](./3.lifecycle-and-usage.md#tenant-lifecycle) |
18
18
  | `/provisioning`, `/registration` | [Provisioning payload and registration limits](./3.lifecycle-and-usage.md#registration-and-provisioning) |
19
19
  | `/pages`, `/page-definitions` | [Page descriptors and extension registration](./4.pages-plans-data.md#page-extensions) |
20
20
  | `/plans` | [Plan projections, localization and pricing helpers](./4.pages-plans-data.md#plan-helpers) |
21
21
  | `/data-api`, `/hidden-filter` | [HiddenStringFilter](./4.pages-plans-data.md#hiddenstringfilter) |
22
22
  | `/db`, `/db/models`, `/db/tables` | Canonical SaaS models and table classes; see the data boundary below |
23
23
 
24
- `invoiceLineItems` and `workspaceLifecycle` are namespaces at the root, not flat exports. Use their direct subpaths for individual functions. Their `internal` proxy namespaces are implementation plumbing, not consumer APIs.
24
+ `invoiceLineItems` and `tenantLifecycle` are namespaces at the root, not flat exports. Use their direct subpaths for individual functions. Their `internal` proxy namespaces are implementation plumbing, not consumer APIs.
25
25
 
26
26
  ```ts docs-check
27
- import { invoiceLineItems, workspaceLifecycle } from "@antelopejs/interface-dms-saas";
27
+ import { invoiceLineItems, tenantLifecycle } from "@antelopejs/interface-dms-saas";
28
28
 
29
29
  export const usageKey = invoiceLineItems.buildInvoiceLineKey("example.compute", "minutes");
30
30
 
31
31
  export async function mayProvision(tenantId: string): Promise<boolean> {
32
32
  // Errors propagate: unavailable storage must not grant eligibility.
33
- return workspaceLifecycle.IsWorkspaceProvisioningCommitted(tenantId);
33
+ return tenantLifecycle.IsTenantProvisioningCommitted(tenantId);
34
34
  }
35
35
  ```
36
36
 
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  title: Lifecycle and usage
3
- description: Registration extras, durable workspace lifecycle consumers and idempotent invoice usage providers.
3
+ description: Registration extras, durable tenant lifecycle consumers and idempotent invoice usage providers.
4
4
  ---
5
5
 
6
6
  # Lifecycle and usage
7
7
 
8
- Use the direct subpaths below or the root's `invoiceLineItems` and `workspaceLifecycle` namespaces. Register consumers/providers from the owning module's lifecycle, not a request handler. Registrations replay when SaaS attaches or reloads and are released when their owner unloads.
8
+ Use the direct subpaths below or the root's `invoiceLineItems` and `tenantLifecycle` namespaces. Register consumers/providers from the owning module's lifecycle, not a request handler. Registrations replay when SaaS attaches or reloads and are released when their owner unloads.
9
9
 
10
10
  ## Invoice line items
11
11
 
@@ -49,42 +49,44 @@ Preview calls set `isPreview: true`, use a synthetic `upcoming_<subscription id>
49
49
 
50
50
  `selectInvoiceLineItemsToCreate(providerId: string, items: readonly InvoiceLineItem[], alreadyInjectedKeys: ReadonlySet<string>): InvoiceLineItemSelection` is synchronous and has no Stripe side effects. It returns `accepted: PreparedInvoiceLineItem[]` (`{ lineKey, item }`, with trimmed item keys) and `skipped: SkippedInvoiceLineItem[]` (`{ lineKey, reason }`). `SkippedLineItemReason` is `already_invoiced`, `duplicate_key`, `zero_amount`, or `invalid`. The set must contain full provider-prefixed keys. This selection helper does not itself provide transactional idempotency.
51
51
 
52
- ## Workspace lifecycle
52
+ ## Tenant lifecycle
53
53
 
54
- Import from `/workspace-lifecycle`. `WORKSPACE_LIFECYCLE_TRANSITIONS` contains `created`, `suspended` and `reactivation_requested`; `WorkspaceLifecycleTransition` is their union.
54
+ Import from `/tenant-lifecycle`. `TENANT_LIFECYCLE_TRANSITIONS` contains `created`, `suspended` and `reactivation_requested`; `TenantLifecycleTransition` is their union.
55
+
56
+ **Breaking migration:** Update `@antelopejs/interface-dms-saas` and `@antelopejs/dms-saas` together. The old `RegisterWorkspaceLifecycleConsumer`, `UnregisterWorkspaceLifecycleConsumer`, `WorkspaceLifecycle*` types, `WORKSPACE_LIFECYCLE_TRANSITIONS`, `IsWorkspaceProvisioningCommitted`, `/workspace-lifecycle` subpath and root `workspaceLifecycle` namespace are removed, without compatibility aliases. Use the tenant-named API below and root `tenantLifecycle` namespace instead. Persisted consumer IDs, transition values and operation IDs remain unchanged; keep existing consumer names and deduplication keys.
55
57
 
56
58
  ```ts [Contract]
57
- import type { WorkspaceLifecycleTransition } from "@antelopejs/interface-dms-saas/workspace-lifecycle";
59
+ import type { TenantLifecycleTransition } from "@antelopejs/interface-dms-saas/tenant-lifecycle";
58
60
 
59
- interface WorkspaceLifecycleMessage {
61
+ interface TenantLifecycleMessage {
60
62
  tenantId: string;
61
63
  operationId: string;
62
- transition: WorkspaceLifecycleTransition;
64
+ transition: TenantLifecycleTransition;
63
65
  requestedAt: Date;
64
66
  }
65
- interface WorkspaceLifecycleReceipt {
67
+ interface TenantLifecycleReceipt {
66
68
  receiptId: string;
67
69
  effectiveAt?: Date;
68
70
  }
69
- interface WorkspaceLifecycleConsumer {
71
+ interface TenantLifecycleConsumer {
70
72
  name: string;
71
- transitions: readonly WorkspaceLifecycleTransition[];
72
- consume(message: WorkspaceLifecycleMessage): Promise<WorkspaceLifecycleReceipt>;
73
+ transitions: readonly TenantLifecycleTransition[];
74
+ consume(message: TenantLifecycleMessage): Promise<TenantLifecycleReceipt>;
73
75
  }
74
- interface WorkspaceLifecycleConsumerRegistration { unregister(): void; }
76
+ interface TenantLifecycleConsumerRegistration { unregister(): void; }
75
77
 
76
- declare function RegisterWorkspaceLifecycleConsumer(
77
- consumer: WorkspaceLifecycleConsumer,
78
- ): WorkspaceLifecycleConsumerRegistration;
79
- declare function UnregisterWorkspaceLifecycleConsumer(name: string): void;
80
- declare function IsWorkspaceProvisioningCommitted(tenantId: string): Promise<boolean>;
78
+ declare function RegisterTenantLifecycleConsumer(
79
+ consumer: TenantLifecycleConsumer,
80
+ ): TenantLifecycleConsumerRegistration;
81
+ declare function UnregisterTenantLifecycleConsumer(name: string): void;
82
+ declare function IsTenantProvisioningCommitted(tenantId: string): Promise<boolean>;
81
83
  ```
82
84
 
83
85
  Names must match `/^[a-z][a-z0-9._-]{2,63}$/`; invalid names throw synchronously. Pick a stable unique name and explicitly select transitions. Unregister via the returned handle or by name when the module stops.
84
86
 
85
87
  Delivery is **at least once**. Deduplicate by `operationId`, durably link provisioned resources, then return a durable receipt. A rejection is not an acknowledgement. Suspension closes SaaS access before delivery; reactivation keeps access closed until every registered required consumer returns a receipt. `created` arrives after ownership, billing and pre-provisioning hooks succeed and the first invoice is paid, or no payment is required.
86
88
 
87
- `IsWorkspaceProvisioningCommitted` returns false for preparing, unpaid or cancelled self-service workspaces; true for committed creations and legacy/bootstrap tenants without a marker. It is not an existence, membership or access check. Storage and missing-implementation errors propagate; fail closed instead of treating an unavailable check as eligibility.
89
+ `IsTenantProvisioningCommitted` returns false for preparing, unpaid or cancelled self-service workspaces; true for committed creations and legacy/bootstrap tenants without a marker. It is not an existence, membership or access check. Storage and missing-implementation errors propagate; fail closed instead of treating an unavailable check as eligibility.
88
90
 
89
91
  ## Registration and provisioning
90
92
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/interface-dms-saas",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "AntelopeJS interface for the DMS SaaS module: billing, plans, registration, workspace pages and lifecycle extension points.",
5
5
  "keywords": [
6
6
  "antelopejs",
@@ -1,27 +0,0 @@
1
- import { RegisteringProxy } from "@antelopejs/interface-core";
2
- export declare const WORKSPACE_LIFECYCLE_TRANSITIONS: readonly ["created", "suspended", "reactivation_requested"];
3
- export type WorkspaceLifecycleTransition = (typeof WORKSPACE_LIFECYCLE_TRANSITIONS)[number];
4
- export interface WorkspaceLifecycleMessage {
5
- tenantId: string;
6
- operationId: string;
7
- transition: WorkspaceLifecycleTransition;
8
- requestedAt: Date;
9
- }
10
- export interface WorkspaceLifecycleReceipt {
11
- receiptId: string;
12
- effectiveAt?: Date;
13
- }
14
- export interface WorkspaceLifecycleConsumer {
15
- name: string;
16
- transitions: readonly WorkspaceLifecycleTransition[];
17
- consume(message: WorkspaceLifecycleMessage): Promise<WorkspaceLifecycleReceipt>;
18
- }
19
- export interface WorkspaceLifecycleConsumerRegistration {
20
- unregister(): void;
21
- }
22
- export declare const IsWorkspaceProvisioningCommitted: (tenantId: string) => Promise<boolean>;
23
- export declare namespace internal {
24
- const RegisterWorkspaceLifecycleConsumer: RegisteringProxy<(name: string, consumer: WorkspaceLifecycleConsumer) => void>;
25
- }
26
- export declare function UnregisterWorkspaceLifecycleConsumer(name: string): void;
27
- export declare function RegisterWorkspaceLifecycleConsumer(consumer: WorkspaceLifecycleConsumer): WorkspaceLifecycleConsumerRegistration;
@@ -1,30 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.internal = exports.IsWorkspaceProvisioningCommitted = exports.WORKSPACE_LIFECYCLE_TRANSITIONS = void 0;
4
- exports.UnregisterWorkspaceLifecycleConsumer = UnregisterWorkspaceLifecycleConsumer;
5
- exports.RegisterWorkspaceLifecycleConsumer = RegisterWorkspaceLifecycleConsumer;
6
- const interface_core_1 = require("@antelopejs/interface-core");
7
- const CONSUMER_NAME_PATTERN = /^[a-z][a-z0-9._-]{2,63}$/;
8
- exports.WORKSPACE_LIFECYCLE_TRANSITIONS = [
9
- "created",
10
- "suspended",
11
- "reactivation_requested",
12
- ];
13
- exports.IsWorkspaceProvisioningCommitted = (0, interface_core_1.InterfaceFunction)();
14
- var internal;
15
- (function (internal) {
16
- internal.RegisterWorkspaceLifecycleConsumer = new interface_core_1.RegisteringProxy();
17
- })(internal || (exports.internal = internal = {}));
18
- function UnregisterWorkspaceLifecycleConsumer(name) {
19
- internal.RegisterWorkspaceLifecycleConsumer.unregister(name);
20
- }
21
- function RegisterWorkspaceLifecycleConsumer(consumer) {
22
- if (!CONSUMER_NAME_PATTERN.test(consumer.name)) {
23
- throw new Error("Invalid workspace lifecycle consumer name");
24
- }
25
- internal.RegisterWorkspaceLifecycleConsumer.register(consumer.name, consumer);
26
- return {
27
- unregister: () => UnregisterWorkspaceLifecycleConsumer(consumer.name),
28
- };
29
- }
30
- //# sourceMappingURL=workspace-lifecycle.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"workspace-lifecycle.js","sourceRoot":"","sources":["../src/workspace-lifecycle.ts"],"names":[],"mappings":";;;AAiEA,oFAEC;AAWD,gFAUC;AAxFD,+DAGoC;AAEpC,MAAM,qBAAqB,GAAG,0BAA0B,CAAC;AAG5C,QAAA,+BAA+B,GAAG;IAC7C,SAAS;IACT,WAAW;IACX,wBAAwB;CAChB,CAAC;AA0CE,QAAA,gCAAgC,GAC3C,IAAA,kCAAiB,GAA0C,CAAC;AAG9D,IAAiB,QAAQ,CAIxB;AAJD,WAAiB,QAAQ;IACV,2CAAkC,GAAG,IAAI,iCAAgB,EAEnE,CAAC;AACN,CAAC,EAJgB,QAAQ,wBAAR,QAAQ,QAIxB;AAGD,SAAgB,oCAAoC,CAAC,IAAY;IAC/D,QAAQ,CAAC,kCAAkC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC;AAWD,SAAgB,kCAAkC,CAChD,QAAoC;IAEpC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IACD,QAAQ,CAAC,kCAAkC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC9E,OAAO;QACL,UAAU,EAAE,GAAG,EAAE,CAAC,oCAAoC,CAAC,QAAQ,CAAC,IAAI,CAAC;KACtE,CAAC;AACJ,CAAC"}