@cadenza.io/service 2.0.0 → 2.0.2

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.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _cadenza_io_core from '@cadenza.io/core';
2
- import { Task, ThrottleTagGetter, SchemaDefinition as SchemaDefinition$1, GraphContext, AnyObject, TaskResult, SignalBroker, GraphRunner, GraphRegistry, CadenzaMode, GraphRoutine, TaskOptions, TaskFunction, DebounceOptions, DebounceTask, EphemeralTaskOptions, EphemeralTask } from '@cadenza.io/core';
2
+ import { Task, ThrottleTagGetter, SchemaDefinition as SchemaDefinition$1, GraphContext, AnyObject, TaskResult, GraphRoutine, SignalBroker, GraphRunner, GraphRegistry, CadenzaMode, TaskOptions, TaskFunction, DebounceOptions, DebounceTask, EphemeralTaskOptions, EphemeralTask } from '@cadenza.io/core';
3
3
  export { AnyObject, DebounceOptions, DebounceTask, EphemeralTask, EphemeralTaskOptions, GraphRoutine, Task, TaskFunction, TaskOptions, ThrottleTagGetter } from '@cadenza.io/core';
4
4
 
5
5
  /**
@@ -190,7 +190,7 @@ declare class ServiceRegistry {
190
190
  handleGlobalSignalRegistrationTask: Task;
191
191
  getRemoteSignalsTask: Task;
192
192
  handleSocketStatusUpdateTask: Task;
193
- fullSyncTask: Task;
193
+ fullSyncTask: GraphRoutine;
194
194
  getAllInstances: Task;
195
195
  doForEachInstance: Task;
196
196
  deleteInstance: Task;
@@ -22087,7 +22087,7 @@ declare class CadenzaService {
22087
22087
  * @param {TaskOptions} [options={}] - A set of optional parameters to further configure the task.
22088
22088
  * @return {SignalTransmissionTask} A new instance of SignalTransmissionTask configured with the given parameters.
22089
22089
  */
22090
- static createSignalTransmissionTask(signalName: string, serviceName: string, options?: TaskOptions): SignalTransmissionTask;
22090
+ static createSignalTransmissionTask(signalName: string, serviceName: string, options?: TaskOptions): SignalTransmissionTask | undefined;
22091
22091
  /**
22092
22092
  * Creates and configures a database task that performs an operation on a specified table.
22093
22093
  *
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _cadenza_io_core from '@cadenza.io/core';
2
- import { Task, ThrottleTagGetter, SchemaDefinition as SchemaDefinition$1, GraphContext, AnyObject, TaskResult, SignalBroker, GraphRunner, GraphRegistry, CadenzaMode, GraphRoutine, TaskOptions, TaskFunction, DebounceOptions, DebounceTask, EphemeralTaskOptions, EphemeralTask } from '@cadenza.io/core';
2
+ import { Task, ThrottleTagGetter, SchemaDefinition as SchemaDefinition$1, GraphContext, AnyObject, TaskResult, GraphRoutine, SignalBroker, GraphRunner, GraphRegistry, CadenzaMode, TaskOptions, TaskFunction, DebounceOptions, DebounceTask, EphemeralTaskOptions, EphemeralTask } from '@cadenza.io/core';
3
3
  export { AnyObject, DebounceOptions, DebounceTask, EphemeralTask, EphemeralTaskOptions, GraphRoutine, Task, TaskFunction, TaskOptions, ThrottleTagGetter } from '@cadenza.io/core';
4
4
 
5
5
  /**
@@ -190,7 +190,7 @@ declare class ServiceRegistry {
190
190
  handleGlobalSignalRegistrationTask: Task;
191
191
  getRemoteSignalsTask: Task;
192
192
  handleSocketStatusUpdateTask: Task;
193
- fullSyncTask: Task;
193
+ fullSyncTask: GraphRoutine;
194
194
  getAllInstances: Task;
195
195
  doForEachInstance: Task;
196
196
  deleteInstance: Task;
@@ -22087,7 +22087,7 @@ declare class CadenzaService {
22087
22087
  * @param {TaskOptions} [options={}] - A set of optional parameters to further configure the task.
22088
22088
  * @return {SignalTransmissionTask} A new instance of SignalTransmissionTask configured with the given parameters.
22089
22089
  */
22090
- static createSignalTransmissionTask(signalName: string, serviceName: string, options?: TaskOptions): SignalTransmissionTask;
22090
+ static createSignalTransmissionTask(signalName: string, serviceName: string, options?: TaskOptions): SignalTransmissionTask | undefined;
22091
22091
  /**
22092
22092
  * Creates and configures a database task that performs an operation on a specified table.
22093
22093
  *
package/dist/index.js CHANGED
@@ -116,7 +116,7 @@ var DeputyTask = class extends import_core.Task {
116
116
  (responseCtx) => {
117
117
  console.log(
118
118
  "Resolving deputy",
119
- remoteRoutineName,
119
+ context.__localTaskName,
120
120
  responseCtx.errored ? responseCtx.__error : ""
121
121
  );
122
122
  if (responseCtx?.errored) {
@@ -382,35 +382,38 @@ var ServiceRegistry = class _ServiceRegistry {
382
382
  this.handleGlobalSignalRegistrationTask = CadenzaService.createMetaTask(
383
383
  "Handle global Signal Registration",
384
384
  (ctx) => {
385
- const { signalToTaskMap } = ctx;
386
- const sortedSignalToTaskMap = signalToTaskMap.sort((a, b) => {
387
- if (a.deleted && !b.deleted) return -1;
388
- if (!a.deleted && b.deleted) return 1;
389
- return 0;
390
- });
385
+ const { signalToTaskMaps } = ctx;
386
+ const sortedSignalToTaskMap = signalToTaskMaps.sort(
387
+ (a, b) => {
388
+ if (a.deleted && !b.deleted) return -1;
389
+ if (!a.deleted && b.deleted) return 1;
390
+ return 0;
391
+ }
392
+ );
393
+ console.log("signalToTaskMap", sortedSignalToTaskMap);
391
394
  const locallyEmittedSignals = CadenzaService.broker.listEmittedSignals().filter((s) => s.startsWith("global."));
392
395
  for (const map of sortedSignalToTaskMap) {
393
396
  if (map.deleted) {
394
- this.remoteSignals.get(map.taskServiceName)?.delete(map.signal);
395
- if (!this.remoteSignals.get(map.taskServiceName)?.size) {
396
- this.remoteSignals.delete(map.taskServiceName);
397
+ this.remoteSignals.get(map.serviceName)?.delete(map.signalName);
398
+ if (!this.remoteSignals.get(map.serviceName)?.size) {
399
+ this.remoteSignals.delete(map.serviceName);
397
400
  }
398
401
  CadenzaService.get(
399
- `Transmit signal: ${map.signal} to ${map.taskServiceName}`
402
+ `Transmit signal: ${map.signalName} to ${map.serviceName}`
400
403
  )?.destroy();
401
404
  continue;
402
405
  }
403
- if (locallyEmittedSignals.includes(map.signal)) {
404
- if (!this.remoteSignals.get(map.taskServiceName)) {
405
- this.remoteSignals.set(map.taskServiceName, /* @__PURE__ */ new Set());
406
+ if (locallyEmittedSignals.includes(map.signalName)) {
407
+ if (!this.remoteSignals.get(map.serviceName)) {
408
+ this.remoteSignals.set(map.serviceName, /* @__PURE__ */ new Set());
406
409
  }
407
- if (!this.remoteSignals.get(map.taskServiceName)?.has(map.signal)) {
410
+ if (!this.remoteSignals.get(map.serviceName)?.has(map.signalName)) {
408
411
  CadenzaService.createSignalTransmissionTask(
409
- map.signal,
410
- map.taskServiceName
412
+ map.signalName,
413
+ map.serviceName
411
414
  );
412
415
  }
413
- this.remoteSignals.get(map.taskServiceName)?.add(map.signal);
416
+ this.remoteSignals.get(map.serviceName)?.add(map.signalName);
414
417
  }
415
418
  }
416
419
  return true;
@@ -507,45 +510,43 @@ var ServiceRegistry = class _ServiceRegistry {
507
510
  },
508
511
  "Handles status update from socket broadcast"
509
512
  ).doOn("meta.socket_client.status_received");
510
- this.fullSyncTask = CadenzaService.createCadenzaDBQueryTask("service_instance", {
511
- filter: {
512
- deleted: false,
513
- is_active: true,
514
- is_non_responsive: false,
515
- is_blocked: false
516
- },
517
- fields: [
518
- "uuid",
519
- "address",
520
- "port",
521
- "service_name",
522
- "is_active",
523
- "is_non_responsive",
524
- "is_blocked",
525
- "health",
526
- "exposed",
527
- "created",
528
- "is_frontend"
529
- ]
530
- }).doOn("meta.sync_requested").emits("meta.service_registry.synced_instances").then(
531
- CadenzaService.createMetaTask(
532
- "Split service instances",
533
- function* (ctx) {
534
- const { serviceInstances } = ctx;
535
- if (!serviceInstances) {
536
- CadenzaService.log(
537
- "SyncFailed: No service instances found",
538
- ctx,
539
- "error"
540
- );
541
- return;
542
- }
543
- for (const serviceInstance of serviceInstances) {
544
- yield { serviceInstance };
545
- }
546
- }
547
- ).then(this.handleInstanceUpdateTask).doOn("meta.signal_controller.signal_map_added")
548
- );
513
+ const mergeSyncDataTask = CadenzaService.createUniqueMetaTask(
514
+ "Merge sync data",
515
+ (ctx) => {
516
+ let joinedContext = {};
517
+ ctx.joinedContexts.forEach((ctx2) => {
518
+ joinedContext = { ...joinedContext, ...ctx2 };
519
+ });
520
+ console.log("Full sync joinedContext", joinedContext);
521
+ return joinedContext;
522
+ }
523
+ ).then(this.handleGlobalSignalRegistrationTask);
524
+ this.fullSyncTask = CadenzaService.createMetaRoutine("Full sync", [
525
+ CadenzaService.createCadenzaDBQueryTask("signal_to_task_map", {
526
+ fields: ["signal_name", "service_name", "deleted"]
527
+ }).then(mergeSyncDataTask),
528
+ CadenzaService.createCadenzaDBQueryTask("service_instance", {
529
+ filter: {
530
+ deleted: false,
531
+ is_active: true,
532
+ is_non_responsive: false,
533
+ is_blocked: false
534
+ },
535
+ fields: [
536
+ "uuid",
537
+ "address",
538
+ "port",
539
+ "service_name",
540
+ "is_active",
541
+ "is_non_responsive",
542
+ "is_blocked",
543
+ "health",
544
+ "exposed",
545
+ "created",
546
+ "is_frontend"
547
+ ]
548
+ }).then(mergeSyncDataTask)
549
+ ]).doOn("meta.sync_requested");
549
550
  this.getInstanceById = CadenzaService.createMetaTask(
550
551
  "Get instance by id",
551
552
  (context) => {
@@ -761,15 +762,6 @@ var ServiceRegistry = class _ServiceRegistry {
761
762
  retryDelayMax: 6e4,
762
763
  retryDelayFactor: 1.3
763
764
  }
764
- ).then(
765
- CadenzaService.createMetaTask(
766
- "Set service name",
767
- ({ __serviceName }) => {
768
- this.serviceName = __serviceName;
769
- return true;
770
- },
771
- "Sets service name after insertion"
772
- )
773
765
  ).emits("meta.service_registry.service_inserted").emitsOnFail("meta.service_registry.service_insertion_failed");
774
766
  this.insertServiceInstanceTask = CadenzaService.createCadenzaDBInsertTask(
775
767
  "serviceInstance",
@@ -859,7 +851,7 @@ var ServiceRegistry = class _ServiceRegistry {
859
851
  if (isBrowser) {
860
852
  CadenzaService.createMetaTask("Prepare for signal sync", () => {
861
853
  return {};
862
- }).doAfter(this.fullSyncTask).then(
854
+ }).then(
863
855
  CadenzaService.createCadenzaDBQueryTask("signal_registry", {
864
856
  fields: ["name"],
865
857
  filter: {
@@ -2806,7 +2798,7 @@ var DatabaseController = class _DatabaseController {
2806
2798
  if (field.generated)
2807
2799
  def += ` GENERATED ALWAYS AS ${field.generated.toUpperCase()} STORED`;
2808
2800
  if (field.references)
2809
- def += ` REFERENCES ${field.references} ON DELETE ${field.onDelete || "DO NOTHING"}`;
2801
+ def += ` REFERENCES ${field.references} ON DELETE ${field.onDelete || "CASCADE"}`;
2810
2802
  if (field.encrypted) def += " ENCRYPTED";
2811
2803
  if (field.constraints?.check) {
2812
2804
  def += ` CHECK (${field.constraints.check})`;
@@ -3356,6 +3348,7 @@ var DatabaseController = class _DatabaseController {
3356
3348
  if (!data || Array.isArray(data) && data.length === 0) {
3357
3349
  return { errored: true, __error: "No data provided for insert" };
3358
3350
  }
3351
+ let resultContext = {};
3359
3352
  const client = transaction ? await this.getClient() : this.dbClient;
3360
3353
  try {
3361
3354
  if (transaction) await client.query("BEGIN");
@@ -3408,24 +3401,32 @@ var DatabaseController = class _DatabaseController {
3408
3401
  );
3409
3402
  if (transaction) await client.query("COMMIT");
3410
3403
  const resultRows = this.toCamelCase(result.rows);
3411
- return {
3404
+ resultContext = {
3412
3405
  [`${(0, import_lodash_es.camelCase)(tableName)}${isBatch ? "s" : ""}`]: isBatch ? resultRows : resultRows[0],
3413
3406
  rowCount: result.rowCount,
3414
3407
  __success: true
3415
3408
  };
3416
3409
  } catch (error) {
3417
- if (transaction) await client.query("ROLLBACK");
3418
- return {
3419
- ...context,
3420
- errored: true,
3421
- __error: `Insert failed: ${error.message}`,
3422
- __success: false
3423
- };
3410
+ if (error.message.includes("violates unique constraint")) {
3411
+ resultContext = {
3412
+ [`${(0, import_lodash_es.camelCase)(tableName)}`]: null,
3413
+ __success: false
3414
+ };
3415
+ } else {
3416
+ if (transaction) await client.query("ROLLBACK");
3417
+ resultContext = {
3418
+ ...context,
3419
+ errored: true,
3420
+ __error: `Insert failed: ${error.message}`,
3421
+ __success: false
3422
+ };
3423
+ }
3424
3424
  } finally {
3425
3425
  if (transaction && client) {
3426
3426
  client.release();
3427
3427
  }
3428
3428
  }
3429
+ return resultContext;
3429
3430
  }
3430
3431
  /**
3431
3432
  * Updates a database table with the provided data and filter conditions.
@@ -3447,6 +3448,7 @@ var DatabaseController = class _DatabaseController {
3447
3448
  if (!data || Object.keys(data).length === 0) {
3448
3449
  return { errored: true, __error: "No data provided for update" };
3449
3450
  }
3451
+ let resultContext = {};
3450
3452
  const client = transaction ? await this.getClient() : this.dbClient;
3451
3453
  try {
3452
3454
  if (transaction) await client.query("BEGIN");
@@ -3479,19 +3481,20 @@ var DatabaseController = class _DatabaseController {
3479
3481
  if (transaction) await client.query("COMMIT");
3480
3482
  const rows = this.toCamelCase(result.rows);
3481
3483
  if (rows.length === 0) {
3482
- return {
3484
+ resultContext = {
3483
3485
  sql,
3484
3486
  params,
3485
3487
  __success: false
3486
3488
  };
3489
+ } else {
3490
+ resultContext = {
3491
+ [`${(0, import_lodash_es.camelCase)(tableName)}`]: rows[0],
3492
+ __success: true
3493
+ };
3487
3494
  }
3488
- return {
3489
- [`${(0, import_lodash_es.camelCase)(tableName)}`]: rows[0],
3490
- __success: true
3491
- };
3492
3495
  } catch (error) {
3493
3496
  if (transaction) await client.query("ROLLBACK");
3494
- return {
3497
+ resultContext = {
3495
3498
  ...context,
3496
3499
  errored: true,
3497
3500
  __error: `Update failed: ${error.message}`,
@@ -3502,6 +3505,7 @@ var DatabaseController = class _DatabaseController {
3502
3505
  client.release();
3503
3506
  }
3504
3507
  }
3508
+ return resultContext;
3505
3509
  }
3506
3510
  /**
3507
3511
  * Deletes a record from the specified database table based on the given filter criteria.
@@ -3518,6 +3522,7 @@ var DatabaseController = class _DatabaseController {
3518
3522
  if (Object.keys(filter).length === 0) {
3519
3523
  return { errored: true, __error: "No filter provided for delete" };
3520
3524
  }
3525
+ let resultContext = {};
3521
3526
  const client = transaction ? await this.getClient() : this.dbClient;
3522
3527
  try {
3523
3528
  if (transaction) await client.query("BEGIN");
@@ -3527,13 +3532,13 @@ var DatabaseController = class _DatabaseController {
3527
3532
  const result = await client.query(sql, params);
3528
3533
  if (transaction) await client.query("COMMIT");
3529
3534
  const rows = this.toCamelCase(result.rows);
3530
- return {
3535
+ resultContext = {
3531
3536
  [`${(0, import_lodash_es.camelCase)(tableName)}`]: rows[0],
3532
3537
  __success: true
3533
3538
  };
3534
3539
  } catch (error) {
3535
3540
  if (transaction) await client.query("ROLLBACK");
3536
- return {
3541
+ resultContext = {
3537
3542
  errored: true,
3538
3543
  __error: `Delete failed: ${error.message}`,
3539
3544
  __errors: { delete: error.message },
@@ -3544,6 +3549,7 @@ var DatabaseController = class _DatabaseController {
3544
3549
  client.release();
3545
3550
  }
3546
3551
  }
3552
+ return resultContext;
3547
3553
  }
3548
3554
  /**
3549
3555
  * Constructs a SQL WHERE clause based on the provided filter object.
@@ -3636,9 +3642,7 @@ var DatabaseController = class _DatabaseController {
3636
3642
  let result;
3637
3643
  if (op.subOperation === "insert") {
3638
3644
  const resolvedData = await this.resolveNestedData(op.data, op.table);
3639
- const sql = `INSERT INTO ${op.table} (${Object.keys(resolvedData).join(", ")}) VALUES (${Object.values(
3640
- resolvedData
3641
- ).map((_, i) => `$${i + 1}`).join(", ")}) ON CONFLICT DO NOTHING RETURNING ${op.return ?? "*"}`;
3645
+ const sql = `INSERT INTO ${op.table} (${Object.keys(resolvedData).map((k) => (0, import_lodash_es.snakeCase)(k)).join(", ")}) VALUES (${Object.values(resolvedData).map((_, i) => `$${i + 1}`).join(", ")}) ON CONFLICT DO NOTHING RETURNING ${op.return ?? "*"}`;
3642
3646
  result = await client.query(sql, Object.values(resolvedData));
3643
3647
  result = result.rows[0]?.[op.return ?? "uuid"];
3644
3648
  if (!result) {
@@ -3805,6 +3809,7 @@ var GraphSyncController = class _GraphSyncController {
3805
3809
  this.splitRoutinesTask = CadenzaService.createMetaTask(
3806
3810
  "Split routines for registration",
3807
3811
  (ctx, emit) => {
3812
+ console.log("SPLITTING ROUTINES FOR REGISTRATION");
3808
3813
  const { routines } = ctx;
3809
3814
  if (!routines) return;
3810
3815
  for (const routine of routines) {
@@ -3846,6 +3851,7 @@ var GraphSyncController = class _GraphSyncController {
3846
3851
  uuid: CadenzaService.serviceRegistry.serviceInstanceId
3847
3852
  }
3848
3853
  });
3854
+ CadenzaService.log("Synced resources...");
3849
3855
  }
3850
3856
  ).attachSignal(
3851
3857
  "global.meta.sync_controller.routine_added",
@@ -3869,8 +3875,7 @@ var GraphSyncController = class _GraphSyncController {
3869
3875
  isGlobal,
3870
3876
  domain,
3871
3877
  action,
3872
- isMeta,
3873
- serviceName: CadenzaService.serviceRegistry.serviceName
3878
+ isMeta
3874
3879
  }
3875
3880
  };
3876
3881
  }
@@ -3895,6 +3900,7 @@ var GraphSyncController = class _GraphSyncController {
3895
3900
  this.splitTasksForRegistration = CadenzaService.createMetaTask(
3896
3901
  "Split tasks for registration",
3897
3902
  function* (ctx) {
3903
+ console.log("SPLITTING TASKS FOR REGISTRATION");
3898
3904
  const tasks = ctx.tasks;
3899
3905
  for (const task of tasks) {
3900
3906
  if (task.registered) continue;
@@ -4161,7 +4167,9 @@ var GraphSyncController = class _GraphSyncController {
4161
4167
  )
4162
4168
  );
4163
4169
  CadenzaService.throttle("sync_controller.sync_tick", { __syncing: true }, 12e4);
4164
- CadenzaService.schedule("meta.sync_requested", { __syncing: true }, 2e3);
4170
+ if (this.isCadenzaDBReady) {
4171
+ CadenzaService.schedule("meta.sync_requested", { __syncing: true }, 2e3);
4172
+ }
4165
4173
  }
4166
4174
  };
4167
4175
 
@@ -4513,7 +4521,8 @@ var CadenzaService = class {
4513
4521
  options.isMeta = true;
4514
4522
  const name = `Transmit signal: ${signalName} to ${serviceName}`;
4515
4523
  if (this.get(name)) {
4516
- throw new Error(`Task '${name}' already exists`);
4524
+ console.log("Signal transmission task already exists", name);
4525
+ return;
4517
4526
  }
4518
4527
  return new SignalTransmissionTask(
4519
4528
  name,
@@ -4779,13 +4788,19 @@ var CadenzaService = class {
4779
4788
  __isDatabase: options.isDatabase
4780
4789
  };
4781
4790
  if (options.cadenzaDB?.connect) {
4782
- this.createEphemeralMetaTask("Create service", async (_, emit) => {
4791
+ this.createEphemeralMetaTask("Create service", async (context, emit) => {
4783
4792
  emit("meta.create_service_requested", initContext);
4784
4793
  }).doOn("meta.fetch.handshake_complete");
4785
4794
  } else {
4786
4795
  this.emit("meta.create_service_requested", initContext);
4796
+ this.createMetaTask("Create signal transmission for sync", (ctx) => {
4797
+ this.createSignalTransmissionTask(
4798
+ "global.meta.cadenza_db.gathered_sync_data",
4799
+ ctx.serviceName
4800
+ );
4801
+ }).doOn("meta.rest.handshake");
4787
4802
  }
4788
- this.createEphemeralMetaTask("Handle service setup completion", () => {
4803
+ this.createMetaTask("Handle service setup completion", () => {
4789
4804
  GraphMetadataController.instance;
4790
4805
  GraphSyncController.instance.isCadenzaDBReady = !!options.cadenzaDB?.connect;
4791
4806
  GraphSyncController.instance.init();
@@ -4852,24 +4867,9 @@ var CadenzaService = class {
4852
4867
  databaseName: options.databaseName,
4853
4868
  options
4854
4869
  });
4855
- this.createEphemeralMetaTask("Set database connection", () => {
4856
- if (options.cadenzaDB?.connect) {
4857
- this.createEphemeralMetaTask("Insert database service", (_, emit) => {
4858
- emit("global.meta.created_database_service", {
4859
- data: {
4860
- service_name: name,
4861
- description,
4862
- schema,
4863
- is_meta: options.isMeta
4864
- }
4865
- });
4866
- this.log("Database service created", {
4867
- name,
4868
- isMeta: options.isMeta
4869
- });
4870
- }).doOn("meta.service_registry.service_inserted");
4871
- } else {
4872
- this.emit("global.meta.created_database_service", {
4870
+ this.createMetaTask("Set database connection", () => {
4871
+ this.createMetaTask("Insert database service", (_, emit) => {
4872
+ emit("global.meta.created_database_service", {
4873
4873
  data: {
4874
4874
  service_name: name,
4875
4875
  description,
@@ -4881,7 +4881,7 @@ var CadenzaService = class {
4881
4881
  name,
4882
4882
  isMeta: options.isMeta
4883
4883
  });
4884
- }
4884
+ }).doOn("meta.service_registry.service_inserted");
4885
4885
  this.createCadenzaService(name, description, options);
4886
4886
  }).doOn("meta.database.setup_done");
4887
4887
  }