interaqt 0.3.1 → 0.4.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/agent/.claude/agents/computation-generation-handler.md +1 -3
- package/agent/.claude/agents/requirements-analysis-handler.md +43 -3
- package/agent/agentspace/knowledge/generator/api-reference.md +538 -101
- package/agent/agentspace/knowledge/generator/computation-analysis.md +22 -20
- package/agent/agentspace/knowledge/generator/computation-implementation.md +62 -38
- package/agent/agentspace/knowledge/generator/data-analysis.md +23 -2
- package/dist/index.js +2977 -2976
- package/dist/index.js.map +1 -1
- package/dist/runtime/ComputationSourceMap.d.ts +11 -21
- package/dist/runtime/ComputationSourceMap.d.ts.map +1 -1
- package/dist/runtime/Controller.d.ts +2 -2
- package/dist/runtime/MonoSystem.d.ts.map +1 -1
- package/dist/runtime/Scheduler.d.ts +6 -6
- package/dist/runtime/Scheduler.d.ts.map +1 -1
- package/dist/runtime/System.d.ts +5 -0
- package/dist/runtime/System.d.ts.map +1 -1
- package/dist/runtime/computations/Computation.d.ts +4 -9
- package/dist/runtime/computations/Computation.d.ts.map +1 -1
- package/dist/runtime/computations/StateMachine.d.ts +4 -7
- package/dist/runtime/computations/StateMachine.d.ts.map +1 -1
- package/dist/runtime/computations/Transform.d.ts +7 -1
- package/dist/runtime/computations/Transform.d.ts.map +1 -1
- package/dist/runtime/computations/TransitionFinder.d.ts +2 -2
- package/dist/runtime/computations/TransitionFinder.d.ts.map +1 -1
- package/dist/shared/StateTransfer.d.ts +15 -5
- package/dist/shared/StateTransfer.d.ts.map +1 -1
- package/dist/shared/Transform.d.ts +17 -3
- package/dist/shared/Transform.d.ts.map +1 -1
- package/package.json +1 -1
- package/agent/.claude/agents/requirements-analysis-handler-bak.md +0 -530
|
@@ -163,14 +163,14 @@ HardDeletionProperty.create(config?: HardDeletionPropertyConfig): PropertyInstan
|
|
|
163
163
|
```
|
|
164
164
|
|
|
165
165
|
**Parameters**
|
|
166
|
-
- `config.name` (string, optional): Property name, defaults to `
|
|
166
|
+
- `config.name` (string, optional): Property name, defaults to `HARD_DELETION_PROPERTY_NAME`
|
|
167
167
|
|
|
168
168
|
**Usage with StateMachine**
|
|
169
169
|
|
|
170
170
|
HardDeletionProperty is typically used with StateMachine to manage record deletion:
|
|
171
171
|
|
|
172
172
|
```typescript
|
|
173
|
-
import { DELETED_STATE, NON_DELETED_STATE } from 'interaqt'
|
|
173
|
+
import { DELETED_STATE, NON_DELETED_STATE, HARD_DELETION_PROPERTY_NAME } from 'interaqt'
|
|
174
174
|
|
|
175
175
|
// Define deletion StateMachine for the HardDeletionProperty
|
|
176
176
|
const deletionStateMachine = StateMachine.create({
|
|
@@ -178,7 +178,12 @@ const deletionStateMachine = StateMachine.create({
|
|
|
178
178
|
defaultState: NON_DELETED_STATE,
|
|
179
179
|
transfers: [
|
|
180
180
|
StateTransfer.create({
|
|
181
|
-
trigger:
|
|
181
|
+
trigger: {
|
|
182
|
+
recordName: InteractionEventEntity.name,
|
|
183
|
+
record: {
|
|
184
|
+
interactionName: DeleteUserInteraction.name
|
|
185
|
+
}
|
|
186
|
+
},
|
|
182
187
|
current: NON_DELETED_STATE,
|
|
183
188
|
next: DELETED_STATE,
|
|
184
189
|
computeTarget: function(event) {
|
|
@@ -208,6 +213,11 @@ const User = Entity.create({
|
|
|
208
213
|
2. The deletion is physical - the record is completely removed from the database
|
|
209
214
|
3. This is different from soft deletion where records remain but are marked as deleted
|
|
210
215
|
|
|
216
|
+
**Note**: When you need to find the HardDeletionProperty after entity creation, use:
|
|
217
|
+
```typescript
|
|
218
|
+
const hardDeletionProp = entity.properties.find(p => p.name === HARD_DELETION_PROPERTY_NAME)
|
|
219
|
+
```
|
|
220
|
+
|
|
211
221
|
**Example: Entity Creation and Deletion Pattern**
|
|
212
222
|
|
|
213
223
|
```typescript
|
|
@@ -217,16 +227,31 @@ const Article = Entity.create({
|
|
|
217
227
|
properties: [
|
|
218
228
|
Property.create({ name: 'title', type: 'string' }),
|
|
219
229
|
Property.create({ name: 'content', type: 'string' }),
|
|
230
|
+
Property.create({ name: 'slug', type: 'string' }),
|
|
220
231
|
HardDeletionProperty.create()
|
|
221
232
|
],
|
|
222
233
|
// Transform creates new articles from interaction events
|
|
223
234
|
computation: Transform.create({
|
|
224
|
-
|
|
225
|
-
|
|
235
|
+
eventDeps: {
|
|
236
|
+
ArticleInteraction: {
|
|
237
|
+
recordName: InteractionEventEntity.name,
|
|
238
|
+
type: 'create'
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
callback: async function(this: Controller, mutationEvent) {
|
|
242
|
+
const event = mutationEvent.record;
|
|
226
243
|
if (event.interactionName === 'CreateArticle') {
|
|
244
|
+
// Use Controller to generate article slug
|
|
245
|
+
const existingCount = await this.system.storage.find('Article',
|
|
246
|
+
undefined,
|
|
247
|
+
undefined,
|
|
248
|
+
['id']
|
|
249
|
+
);
|
|
250
|
+
|
|
227
251
|
return {
|
|
228
252
|
title: event.payload.title,
|
|
229
|
-
content: event.payload.content
|
|
253
|
+
content: event.payload.content,
|
|
254
|
+
slug: `article-${existingCount.length + 1}`
|
|
230
255
|
}
|
|
231
256
|
}
|
|
232
257
|
return null
|
|
@@ -241,7 +266,12 @@ deletionProperty.computation = StateMachine.create({
|
|
|
241
266
|
defaultState: NON_DELETED_STATE,
|
|
242
267
|
transfers: [
|
|
243
268
|
StateTransfer.create({
|
|
244
|
-
trigger:
|
|
269
|
+
trigger: {
|
|
270
|
+
recordName: InteractionEventEntity.name,
|
|
271
|
+
record: {
|
|
272
|
+
interactionName: DeleteArticleInteraction.name
|
|
273
|
+
}
|
|
274
|
+
},
|
|
245
275
|
current: NON_DELETED_STATE,
|
|
246
276
|
next: DELETED_STATE,
|
|
247
277
|
computeTarget: function(event) {
|
|
@@ -755,10 +785,211 @@ Transform.create(config: TransformConfig): TransformInstance
|
|
|
755
785
|
```
|
|
756
786
|
|
|
757
787
|
**Parameters**
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
788
|
+
Transform supports two modes of operation:
|
|
789
|
+
|
|
790
|
+
1. **Entity/Relation Transform Mode**:
|
|
791
|
+
- `config.record` (Entity|Relation, required): Entity or relation to transform from (source collection)
|
|
792
|
+
- `config.attributeQuery` (AttributeQueryData, optional): Attribute query configuration
|
|
793
|
+
- `config.callback` (function, required): Transformation function that converts source data to target data
|
|
794
|
+
- **Context**: `this` is bound to the Controller instance, providing access to system APIs via `this.system.storage`, `this.globals`, etc.
|
|
795
|
+
- **Signature**: `function(this: Controller, record: any): any | any[]`
|
|
796
|
+
|
|
797
|
+
2. **Event-Driven Transform Mode** (Recommended for interaction-based transformations):
|
|
798
|
+
- `config.eventDeps` (EventDeps, required): Event dependencies that trigger the transformation
|
|
799
|
+
- `config.callback` (function, required): Transformation function that processes mutation events
|
|
800
|
+
- **Context**: `this` is bound to the Controller instance, providing access to system APIs via `this.system.storage`, `this.globals`, etc.
|
|
801
|
+
- **Signature**: `function(this: Controller, mutationEvent: MutationEvent): any | any[]`
|
|
802
|
+
|
|
803
|
+
**Event Dependencies (eventDeps)**
|
|
804
|
+
|
|
805
|
+
The `eventDeps` parameter allows Transform to react to specific mutation events (create, update, delete) on entities. This is the recommended approach for transforming data based on interactions or system events.
|
|
806
|
+
|
|
807
|
+
```typescript
|
|
808
|
+
interface EventDep {
|
|
809
|
+
recordName: string; // Entity name to listen to
|
|
810
|
+
type: 'create' | 'update' | 'delete'; // Event type
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Example eventDeps structure
|
|
814
|
+
eventDeps: {
|
|
815
|
+
UserCreate: {
|
|
816
|
+
recordName: 'User',
|
|
817
|
+
type: 'create'
|
|
818
|
+
},
|
|
819
|
+
UserUpdate: {
|
|
820
|
+
recordName: 'User',
|
|
821
|
+
type: 'update'
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
**Examples**
|
|
827
|
+
|
|
828
|
+
1. **Entity-to-Entity Transform** (for deriving new entities from existing ones):
|
|
829
|
+
```typescript
|
|
830
|
+
// Create discounted products from regular products
|
|
831
|
+
const DiscountedProduct = Entity.create({
|
|
832
|
+
name: 'DiscountedProduct',
|
|
833
|
+
properties: [
|
|
834
|
+
Property.create({ name: 'name', type: 'string' }),
|
|
835
|
+
Property.create({ name: 'originalPrice', type: 'number' }),
|
|
836
|
+
Property.create({ name: 'discountedPrice', type: 'number' })
|
|
837
|
+
],
|
|
838
|
+
computation: Transform.create({
|
|
839
|
+
record: Product,
|
|
840
|
+
attributeQuery: ['name', 'price'],
|
|
841
|
+
callback: async function(this: Controller, product) {
|
|
842
|
+
// Access system configuration via Controller
|
|
843
|
+
const discountRate = await this.system.storage.get('config', 'globalDiscountRate', 0.1);
|
|
844
|
+
|
|
845
|
+
return {
|
|
846
|
+
name: product.name,
|
|
847
|
+
originalPrice: product.price,
|
|
848
|
+
discountedPrice: product.price * (1 - discountRate)
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
})
|
|
852
|
+
})
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
2. **Event-Driven Transform** (recommended for interaction-based transformations):
|
|
856
|
+
```typescript
|
|
857
|
+
// Create audit logs from user mutations
|
|
858
|
+
const UserAudit = Entity.create({
|
|
859
|
+
name: 'UserAudit',
|
|
860
|
+
properties: [
|
|
861
|
+
Property.create({ name: 'action', type: 'string' }),
|
|
862
|
+
Property.create({ name: 'userId', type: 'string' }),
|
|
863
|
+
Property.create({ name: 'timestamp', type: 'string' }),
|
|
864
|
+
Property.create({ name: 'changes', type: 'object' })
|
|
865
|
+
],
|
|
866
|
+
computation: Transform.create({
|
|
867
|
+
eventDeps: {
|
|
868
|
+
UserCreate: {
|
|
869
|
+
recordName: 'User',
|
|
870
|
+
type: 'create'
|
|
871
|
+
},
|
|
872
|
+
UserUpdate: {
|
|
873
|
+
recordName: 'User',
|
|
874
|
+
type: 'update'
|
|
875
|
+
},
|
|
876
|
+
UserDelete: {
|
|
877
|
+
recordName: 'User',
|
|
878
|
+
type: 'delete'
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
callback: function(this: Controller, mutationEvent) {
|
|
882
|
+
// Access Controller APIs via 'this'
|
|
883
|
+
console.log('Audit log created by:', this.name); // Controller name
|
|
884
|
+
|
|
885
|
+
return {
|
|
886
|
+
action: mutationEvent.type,
|
|
887
|
+
userId: (mutationEvent.record?.id || mutationEvent.oldRecord?.id),
|
|
888
|
+
timestamp: new Date().toISOString(),
|
|
889
|
+
changes: {
|
|
890
|
+
old: mutationEvent.oldRecord,
|
|
891
|
+
new: mutationEvent.record
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
})
|
|
896
|
+
})
|
|
897
|
+
```
|
|
761
898
|
|
|
899
|
+
3. **Transform from Interaction Events**:
|
|
900
|
+
```typescript
|
|
901
|
+
// Create articles from interactions
|
|
902
|
+
const Article = Entity.create({
|
|
903
|
+
name: 'Article',
|
|
904
|
+
properties: [
|
|
905
|
+
Property.create({ name: 'title', type: 'string' }),
|
|
906
|
+
Property.create({ name: 'content', type: 'string' }),
|
|
907
|
+
Property.create({ name: 'authorId', type: 'string' }),
|
|
908
|
+
Property.create({ name: 'authorName', type: 'string' })
|
|
909
|
+
],
|
|
910
|
+
computation: Transform.create({
|
|
911
|
+
eventDeps: {
|
|
912
|
+
ArticleInteraction: {
|
|
913
|
+
recordName: InteractionEventEntity.name,
|
|
914
|
+
type: 'create'
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
callback: async function(this: Controller, mutationEvent) {
|
|
918
|
+
const event = mutationEvent.record;
|
|
919
|
+
if (event.interactionName === 'CreateArticle') {
|
|
920
|
+
// Use Controller to fetch additional user data
|
|
921
|
+
const author = await this.system.storage.findOne('User',
|
|
922
|
+
this.globals.MatchExp.atom({ key: 'id', value: ['=', event.user.id] }),
|
|
923
|
+
undefined,
|
|
924
|
+
['id', 'name']
|
|
925
|
+
);
|
|
926
|
+
|
|
927
|
+
return {
|
|
928
|
+
title: event.payload.title,
|
|
929
|
+
content: event.payload.content,
|
|
930
|
+
authorId: event.user.id,
|
|
931
|
+
authorName: author?.name || 'Unknown'
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
})
|
|
937
|
+
})
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
4. **One-to-Many Transform**:
|
|
941
|
+
```typescript
|
|
942
|
+
// Create multiple notifications from one order
|
|
943
|
+
const Notification = Entity.create({
|
|
944
|
+
name: 'Notification',
|
|
945
|
+
properties: [
|
|
946
|
+
Property.create({ name: 'type', type: 'string' }),
|
|
947
|
+
Property.create({ name: 'recipient', type: 'string' }),
|
|
948
|
+
Property.create({ name: 'message', type: 'string' })
|
|
949
|
+
],
|
|
950
|
+
computation: Transform.create({
|
|
951
|
+
eventDeps: {
|
|
952
|
+
OrderCreate: {
|
|
953
|
+
recordName: 'Order',
|
|
954
|
+
type: 'create'
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
callback: async function(this: Controller, mutationEvent) {
|
|
958
|
+
const order = mutationEvent.record;
|
|
959
|
+
|
|
960
|
+
// Use Controller to fetch warehouse email from configuration
|
|
961
|
+
const warehouseEmail = await this.system.storage.get('config', 'warehouseEmail', 'warehouse@company.com');
|
|
962
|
+
|
|
963
|
+
// Use Controller to check if customer wants notifications
|
|
964
|
+
const customer = await this.system.storage.findOne('User',
|
|
965
|
+
this.globals.MatchExp.atom({ key: 'email', value: ['=', order.customerEmail] }),
|
|
966
|
+
undefined,
|
|
967
|
+
['id', 'notificationPreferences']
|
|
968
|
+
);
|
|
969
|
+
|
|
970
|
+
const notifications = [];
|
|
971
|
+
|
|
972
|
+
// Only send customer notification if they opted in
|
|
973
|
+
if (customer?.notificationPreferences?.orderUpdates !== false) {
|
|
974
|
+
notifications.push({
|
|
975
|
+
type: 'order_confirmation',
|
|
976
|
+
recipient: order.customerEmail,
|
|
977
|
+
message: `Order ${order.orderNumber} confirmed`
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Always notify warehouse
|
|
982
|
+
notifications.push({
|
|
983
|
+
type: 'warehouse_notification',
|
|
984
|
+
recipient: warehouseEmail,
|
|
985
|
+
message: `New order ${order.orderNumber} to process`
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
return notifications;
|
|
989
|
+
}
|
|
990
|
+
})
|
|
991
|
+
})
|
|
992
|
+
```
|
|
762
993
|
|
|
763
994
|
**Key Points**
|
|
764
995
|
|
|
@@ -767,7 +998,23 @@ Transform.create(config: TransformConfig): TransformInstance
|
|
|
767
998
|
- Array of objects: Creates multiple target records from one source
|
|
768
999
|
- `null`/`undefined`: Creates no records (useful for filtering)
|
|
769
1000
|
|
|
770
|
-
2. **Automatic Updates**:
|
|
1001
|
+
2. **Automatic Updates**:
|
|
1002
|
+
- For entity/relation transforms: When source records are updated or deleted, the transformed records are automatically updated or removed
|
|
1003
|
+
- For event-driven transforms: New records are created in response to mutation events
|
|
1004
|
+
|
|
1005
|
+
3. **Choosing Between Modes**:
|
|
1006
|
+
- Use `record` mode when deriving entities from other entities (e.g., creating views or projections)
|
|
1007
|
+
- Use `eventDeps` mode when creating entities in response to system events or interactions
|
|
1008
|
+
|
|
1009
|
+
4. **MutationEvent Structure** (for eventDeps callbacks):
|
|
1010
|
+
```typescript
|
|
1011
|
+
{
|
|
1012
|
+
type: 'create' | 'update' | 'delete',
|
|
1013
|
+
recordName: string,
|
|
1014
|
+
record?: any, // New/current record (for create/update)
|
|
1015
|
+
oldRecord?: any // Previous record (for update/delete)
|
|
1016
|
+
}
|
|
1017
|
+
```
|
|
771
1018
|
|
|
772
1019
|
### StateMachine.create()
|
|
773
1020
|
|
|
@@ -796,7 +1043,7 @@ const MyStateMachine = StateMachine.create({
|
|
|
796
1043
|
transfers: [...],
|
|
797
1044
|
defaultState: StateNode.create({
|
|
798
1045
|
name: 'pending',
|
|
799
|
-
computeValue: (lastValue) => {
|
|
1046
|
+
computeValue: (lastValue, mutationEvent) => {
|
|
800
1047
|
// Explicitly handle initial value
|
|
801
1048
|
return lastValue || 'initial_state_value';
|
|
802
1049
|
}
|
|
@@ -818,7 +1065,13 @@ const OrderStateMachine = StateMachine.create({
|
|
|
818
1065
|
StateTransfer.create({
|
|
819
1066
|
current: pendingState,
|
|
820
1067
|
next: confirmedState,
|
|
821
|
-
trigger:
|
|
1068
|
+
trigger: {
|
|
1069
|
+
recordName: InteractionEventEntity.name,
|
|
1070
|
+
record: {
|
|
1071
|
+
interactionName: ConfirmOrderInteraction.name
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
1074
|
+
computeTarget: (mutationEvent) => ({ id: mutationEvent.record.payload.orderId })
|
|
822
1075
|
})
|
|
823
1076
|
],
|
|
824
1077
|
defaultState: pendingState
|
|
@@ -837,9 +1090,27 @@ const UserTargetRelation = Relation.create({
|
|
|
837
1090
|
],
|
|
838
1091
|
// Use Transform to create relations
|
|
839
1092
|
computation: Transform.create({
|
|
840
|
-
|
|
841
|
-
|
|
1093
|
+
eventDeps: {
|
|
1094
|
+
RelationInteraction: {
|
|
1095
|
+
recordName: InteractionEventEntity.name,
|
|
1096
|
+
type: 'create'
|
|
1097
|
+
}
|
|
1098
|
+
},
|
|
1099
|
+
callback: async function(this: Controller, mutationEvent) {
|
|
1100
|
+
const event = mutationEvent.record;
|
|
842
1101
|
if (event.interactionName === 'CreateRelation') {
|
|
1102
|
+
// Use Controller to validate the relation
|
|
1103
|
+
const sourceExists = await this.system.storage.findOne('User',
|
|
1104
|
+
MatchExp.atom({ key: 'id', value: ['=', event.payload.sourceUser.id] }),
|
|
1105
|
+
undefined,
|
|
1106
|
+
['id']
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
if (!sourceExists) {
|
|
1110
|
+
console.log('Source user not found, skipping relation creation');
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
843
1114
|
return {
|
|
844
1115
|
source: event.payload.sourceUser,
|
|
845
1116
|
target: event.payload.targetEntity,
|
|
@@ -860,19 +1131,24 @@ relationDeletionProperty.computation = StateMachine.create({
|
|
|
860
1131
|
defaultState: NON_DELETED_STATE,
|
|
861
1132
|
transfers: [
|
|
862
1133
|
StateTransfer.create({
|
|
863
|
-
trigger:
|
|
1134
|
+
trigger: {
|
|
1135
|
+
recordName: InteractionEventEntity.name,
|
|
1136
|
+
record: {
|
|
1137
|
+
interactionName: DeleteRelationInteraction.name
|
|
1138
|
+
}
|
|
1139
|
+
},
|
|
864
1140
|
current: NON_DELETED_STATE,
|
|
865
1141
|
next: DELETED_STATE,
|
|
866
|
-
computeTarget: async function(this: Controller,
|
|
1142
|
+
computeTarget: async function(this: Controller, mutationEvent: any) {
|
|
867
1143
|
const MatchExp = this.globals.MatchExp;
|
|
868
1144
|
const relation = await this.system.storage.findOne(
|
|
869
1145
|
UserTargetRelation.name,
|
|
870
1146
|
MatchExp.atom({
|
|
871
1147
|
key: 'source.id',
|
|
872
|
-
value: ['=',
|
|
1148
|
+
value: ['=', mutationEvent.record.payload.sourceId]
|
|
873
1149
|
}).and({
|
|
874
1150
|
key: 'target.id',
|
|
875
|
-
value: ['=',
|
|
1151
|
+
value: ['=', mutationEvent.record.payload.targetId]
|
|
876
1152
|
}),
|
|
877
1153
|
undefined,
|
|
878
1154
|
['id']
|
|
@@ -1052,8 +1328,8 @@ const nextRecomputeTimeKey = controller.scheduler.getBoundStateName(
|
|
|
1052
1328
|
);
|
|
1053
1329
|
|
|
1054
1330
|
// Read state values
|
|
1055
|
-
const lastRecomputeTime = await system.storage.get(
|
|
1056
|
-
const nextRecomputeTime = await system.storage.get(
|
|
1331
|
+
const lastRecomputeTime = await system.storage.dict.get(lastRecomputeTimeKey);
|
|
1332
|
+
const nextRecomputeTime = await system.storage.dict.get(nextRecomputeTimeKey);
|
|
1057
1333
|
```
|
|
1058
1334
|
|
|
1059
1335
|
### Custom.create()
|
|
@@ -1516,10 +1792,13 @@ const activeUsers = Dictionary.create({
|
|
|
1516
1792
|
computation: Transform.create({
|
|
1517
1793
|
record: User,
|
|
1518
1794
|
attributeQuery: ['id', 'lastLoginTime'],
|
|
1519
|
-
callback: (users)
|
|
1520
|
-
|
|
1795
|
+
callback: async function(this: Controller, users) {
|
|
1796
|
+
// Use Controller to get activity threshold from config
|
|
1797
|
+
const activityThreshold = await this.system.storage.get('config', 'userActivityThreshold', 3600000); // Default 1 hour
|
|
1798
|
+
const cutoffTime = Date.now() - activityThreshold;
|
|
1799
|
+
|
|
1521
1800
|
return users
|
|
1522
|
-
.filter(u => u.lastLoginTime >
|
|
1801
|
+
.filter(u => u.lastLoginTime > cutoffTime)
|
|
1523
1802
|
.map(u => u.id);
|
|
1524
1803
|
}
|
|
1525
1804
|
})
|
|
@@ -1586,13 +1865,13 @@ const pendingState = StateNode.create({ name: 'pending' });
|
|
|
1586
1865
|
// Store timestamp when entering state
|
|
1587
1866
|
const processedState = StateNode.create({
|
|
1588
1867
|
name: 'processed',
|
|
1589
|
-
computeValue: () => new Date().toISOString()
|
|
1868
|
+
computeValue: (lastValue, mutationEvent) => new Date().toISOString()
|
|
1590
1869
|
});
|
|
1591
1870
|
|
|
1592
1871
|
// Store complex object
|
|
1593
1872
|
const activeState = StateNode.create({
|
|
1594
1873
|
name: 'active',
|
|
1595
|
-
computeValue: () => ({
|
|
1874
|
+
computeValue: (lastValue, mutationEvent) => ({
|
|
1596
1875
|
activatedAt: Math.floor(Date.now()/1000), // In seconds
|
|
1597
1876
|
status: 'active',
|
|
1598
1877
|
metadata: { source: 'manual' }
|
|
@@ -1602,7 +1881,7 @@ const activeState = StateNode.create({
|
|
|
1602
1881
|
// Increment value based on previous state
|
|
1603
1882
|
const incrementingState = StateNode.create({
|
|
1604
1883
|
name: 'incrementing',
|
|
1605
|
-
computeValue: (lastValue) => {
|
|
1884
|
+
computeValue: (lastValue, mutationEvent) => {
|
|
1606
1885
|
const baseValue = typeof lastValue === 'number' ? lastValue : 0;
|
|
1607
1886
|
return baseValue + 1;
|
|
1608
1887
|
}
|
|
@@ -1611,7 +1890,7 @@ const incrementingState = StateNode.create({
|
|
|
1611
1890
|
// Preserve previous value
|
|
1612
1891
|
const idleState = StateNode.create({
|
|
1613
1892
|
name: 'idle',
|
|
1614
|
-
computeValue: (lastValue) => {
|
|
1893
|
+
computeValue: (lastValue, mutationEvent) => {
|
|
1615
1894
|
return typeof lastValue === 'number' ? lastValue : 0;
|
|
1616
1895
|
}
|
|
1617
1896
|
});
|
|
@@ -1619,7 +1898,7 @@ const idleState = StateNode.create({
|
|
|
1619
1898
|
// Return null to delete (for entity/relation state machines)
|
|
1620
1899
|
const deletedState = StateNode.create({
|
|
1621
1900
|
name: 'deleted',
|
|
1622
|
-
computeValue: () => null
|
|
1901
|
+
computeValue: (lastValue, mutationEvent) => null
|
|
1623
1902
|
});
|
|
1624
1903
|
|
|
1625
1904
|
// Async computeValue - fetch data from database
|
|
@@ -1644,7 +1923,7 @@ const enrichedState = StateNode.create({
|
|
|
1644
1923
|
// Async computeValue - external API call
|
|
1645
1924
|
const verifiedState = StateNode.create({
|
|
1646
1925
|
name: 'verified',
|
|
1647
|
-
computeValue: async (lastValue) => {
|
|
1926
|
+
computeValue: async (lastValue, mutationEvent) => {
|
|
1648
1927
|
// Simulate external verification
|
|
1649
1928
|
const verificationResult = await someExternalAPI.verify(lastValue.data);
|
|
1650
1929
|
|
|
@@ -1660,9 +1939,9 @@ const verifiedState = StateNode.create({
|
|
|
1660
1939
|
// Using event parameter - access user information
|
|
1661
1940
|
const approvedState = StateNode.create({
|
|
1662
1941
|
name: 'approved',
|
|
1663
|
-
computeValue: (lastValue,
|
|
1942
|
+
computeValue: (lastValue, mutationEvent) => {
|
|
1664
1943
|
// Access user who triggered the approval
|
|
1665
|
-
const approver =
|
|
1944
|
+
const approver = mutationEvent?.record?.user?.name || 'system';
|
|
1666
1945
|
return {
|
|
1667
1946
|
status: 'approved',
|
|
1668
1947
|
approvedAt: Math.floor(Date.now()/1000), // In seconds
|
|
@@ -1674,14 +1953,14 @@ const approvedState = StateNode.create({
|
|
|
1674
1953
|
// Using event parameter - access payload data
|
|
1675
1954
|
const updatedState = StateNode.create({
|
|
1676
1955
|
name: 'updated',
|
|
1677
|
-
computeValue: (lastValue,
|
|
1956
|
+
computeValue: (lastValue, mutationEvent) => {
|
|
1678
1957
|
// Merge payload data with existing value
|
|
1679
|
-
if (
|
|
1958
|
+
if (mutationEvent?.record?.payload) {
|
|
1680
1959
|
return {
|
|
1681
1960
|
...lastValue,
|
|
1682
|
-
...
|
|
1961
|
+
...mutationEvent.record.payload.updates,
|
|
1683
1962
|
lastModifiedAt: Math.floor(Date.now()/1000), // In seconds
|
|
1684
|
-
lastModifiedBy:
|
|
1963
|
+
lastModifiedBy: mutationEvent.record.user?.id
|
|
1685
1964
|
};
|
|
1686
1965
|
}
|
|
1687
1966
|
return lastValue;
|
|
@@ -1691,15 +1970,15 @@ const updatedState = StateNode.create({
|
|
|
1691
1970
|
// Using event parameter - conditional logic based on interaction
|
|
1692
1971
|
const reviewedState = StateNode.create({
|
|
1693
1972
|
name: 'reviewed',
|
|
1694
|
-
computeValue: (lastValue,
|
|
1973
|
+
computeValue: (lastValue, mutationEvent) => {
|
|
1695
1974
|
// Different behavior based on which interaction triggered the transition
|
|
1696
|
-
if (
|
|
1975
|
+
if (mutationEvent?.record?.interactionName === 'approve') {
|
|
1697
1976
|
return { status: 'approved', reviewedAt: Math.floor(Date.now()/1000) }; // In seconds
|
|
1698
|
-
} else if (
|
|
1977
|
+
} else if (mutationEvent?.record?.interactionName === 'reject') {
|
|
1699
1978
|
return {
|
|
1700
1979
|
status: 'rejected',
|
|
1701
1980
|
reviewedAt: Math.floor(Date.now()/1000), // In seconds
|
|
1702
|
-
reason:
|
|
1981
|
+
reason: mutationEvent.record.payload?.reason || 'No reason provided'
|
|
1703
1982
|
};
|
|
1704
1983
|
}
|
|
1705
1984
|
return { status: 'pending_review' };
|
|
@@ -1713,31 +1992,55 @@ StateNodes with `computeValue` are used in StateMachine to compute property valu
|
|
|
1713
1992
|
|
|
1714
1993
|
```typescript
|
|
1715
1994
|
// Counter that increments on interaction
|
|
1995
|
+
const idleState = StateNode.create({
|
|
1996
|
+
name: 'idle',
|
|
1997
|
+
computeValue: (lastValue, mutationEvent) => lastValue || 0
|
|
1998
|
+
});
|
|
1999
|
+
const incrementingState = StateNode.create({
|
|
2000
|
+
name: 'incrementing',
|
|
2001
|
+
computeValue: (lastValue, mutationEvent) => (lastValue || 0) + 1
|
|
2002
|
+
});
|
|
2003
|
+
|
|
1716
2004
|
const CounterStateMachine = StateMachine.create({
|
|
1717
|
-
states: [
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
2005
|
+
states: [idleState, incrementingState],
|
|
2006
|
+
transfers: [
|
|
2007
|
+
StateTransfer.create({
|
|
2008
|
+
trigger: {
|
|
2009
|
+
recordName: InteractionEventEntity.name,
|
|
2010
|
+
record: {
|
|
2011
|
+
interactionName: IncrementInteraction.name
|
|
2012
|
+
}
|
|
2013
|
+
},
|
|
2014
|
+
current: idleState,
|
|
2015
|
+
next: incrementingState,
|
|
2016
|
+
computeTarget: (mutationEvent) => ({ id: mutationEvent.record.payload.counterId })
|
|
1725
2017
|
})
|
|
1726
2018
|
],
|
|
1727
|
-
transfers: [/* ... */],
|
|
1728
2019
|
defaultState: idleState
|
|
1729
2020
|
});
|
|
1730
2021
|
|
|
1731
2022
|
// Timestamp tracking
|
|
2023
|
+
const pendingState = StateNode.create({ name: 'pending' });
|
|
2024
|
+
const processedState = StateNode.create({
|
|
2025
|
+
name: 'processed',
|
|
2026
|
+
computeValue: (lastValue, mutationEvent) => new Date().toISOString()
|
|
2027
|
+
});
|
|
2028
|
+
|
|
1732
2029
|
const ProcessingStateMachine = StateMachine.create({
|
|
1733
|
-
states: [
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
2030
|
+
states: [pendingState, processedState],
|
|
2031
|
+
transfers: [
|
|
2032
|
+
StateTransfer.create({
|
|
2033
|
+
trigger: {
|
|
2034
|
+
recordName: InteractionEventEntity.name,
|
|
2035
|
+
record: {
|
|
2036
|
+
interactionName: ProcessInteraction.name
|
|
2037
|
+
}
|
|
2038
|
+
},
|
|
2039
|
+
current: pendingState,
|
|
2040
|
+
next: processedState,
|
|
2041
|
+
computeTarget: (mutationEvent) => ({ id: mutationEvent.record.payload.itemId })
|
|
1738
2042
|
})
|
|
1739
2043
|
],
|
|
1740
|
-
transfers: [/* ... */],
|
|
1741
2044
|
defaultState: pendingState
|
|
1742
2045
|
});
|
|
1743
2046
|
|
|
@@ -1764,7 +2067,7 @@ StateTransfer.create(config: StateTransferConfig): StateTransferInstance
|
|
|
1764
2067
|
```
|
|
1765
2068
|
|
|
1766
2069
|
**Parameters**
|
|
1767
|
-
- `config.trigger` (
|
|
2070
|
+
- `config.trigger` (RecordMutationEventPattern, required): A partial pattern to match against RecordMutationEvent. Supports deep partial matching of event properties.
|
|
1768
2071
|
- `config.current` (StateNode, required): Current state node
|
|
1769
2072
|
- `config.next` (StateNode, required): Next state node
|
|
1770
2073
|
- `config.computeTarget` (function, optional): Function to compute which records should undergo this state transition. Returns the target record(s) that should be affected by this state change.
|
|
@@ -1785,37 +2088,83 @@ The `computeTarget` function determines which specific records should transition
|
|
|
1785
2088
|
- Async: `async function(this: Controller, event: InteractionEvent) => TargetRecord`
|
|
1786
2089
|
- **Event Parameter**: Contains interaction details including `event.payload` with the interaction's payload data
|
|
1787
2090
|
|
|
2091
|
+
**Trigger Pattern**
|
|
2092
|
+
|
|
2093
|
+
The `trigger` parameter now accepts a RecordMutationEventPattern that allows deep partial matching:
|
|
2094
|
+
|
|
2095
|
+
```typescript
|
|
2096
|
+
// Match interaction events by name
|
|
2097
|
+
trigger: {
|
|
2098
|
+
recordName: InteractionEventEntity.name, // Use the constant, not hardcoded string
|
|
2099
|
+
record: {
|
|
2100
|
+
interactionName: 'approve' // Or better: ApproveInteraction.name
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
// Match data mutation events
|
|
2105
|
+
trigger: {
|
|
2106
|
+
recordName: 'User',
|
|
2107
|
+
type: 'update' // Match only update events
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
// Match with complex patterns
|
|
2111
|
+
trigger: {
|
|
2112
|
+
recordName: InteractionEventEntity.name,
|
|
2113
|
+
record: {
|
|
2114
|
+
interactionName: 'updateStatus',
|
|
2115
|
+
payload: {
|
|
2116
|
+
status: 'active' // Only trigger when status is set to 'active'
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
```
|
|
2121
|
+
|
|
1788
2122
|
**Examples**
|
|
1789
2123
|
```typescript
|
|
1790
2124
|
// Simple state transfer (no computeTarget needed for global state)
|
|
1791
2125
|
const approveTransfer = StateTransfer.create({
|
|
1792
|
-
trigger:
|
|
2126
|
+
trigger: {
|
|
2127
|
+
recordName: InteractionEventEntity.name,
|
|
2128
|
+
record: {
|
|
2129
|
+
interactionName: ApproveInteraction.name
|
|
2130
|
+
}
|
|
2131
|
+
},
|
|
1793
2132
|
current: pendingState,
|
|
1794
2133
|
next: approvedState
|
|
1795
2134
|
});
|
|
1796
2135
|
|
|
1797
2136
|
// Entity state transfer - specify which entity to update
|
|
1798
2137
|
const incrementTransfer = StateTransfer.create({
|
|
1799
|
-
trigger:
|
|
2138
|
+
trigger: {
|
|
2139
|
+
recordName: InteractionEventEntity.name,
|
|
2140
|
+
record: {
|
|
2141
|
+
interactionName: IncrementInteraction.name
|
|
2142
|
+
}
|
|
2143
|
+
},
|
|
1800
2144
|
current: idleState,
|
|
1801
2145
|
next: incrementingState,
|
|
1802
|
-
computeTarget: (
|
|
2146
|
+
computeTarget: (mutationEvent) => {
|
|
1803
2147
|
// Return the counter entity that should transition states
|
|
1804
|
-
return { id:
|
|
2148
|
+
return { id: mutationEvent.record.payload.counter.id };
|
|
1805
2149
|
}
|
|
1806
2150
|
});
|
|
1807
2151
|
|
|
1808
2152
|
// Relation state transfer - create new relation
|
|
1809
2153
|
const assignReviewerTransfer = StateTransfer.create({
|
|
1810
|
-
trigger:
|
|
2154
|
+
trigger: {
|
|
2155
|
+
recordName: InteractionEventEntity.name,
|
|
2156
|
+
record: {
|
|
2157
|
+
interactionName: AssignReviewerInteraction.name
|
|
2158
|
+
}
|
|
2159
|
+
},
|
|
1811
2160
|
current: unassignedState,
|
|
1812
2161
|
next: assignedState,
|
|
1813
|
-
computeTarget: async function(this: Controller,
|
|
2162
|
+
computeTarget: async function(this: Controller, mutationEvent) {
|
|
1814
2163
|
// Find the request entity
|
|
1815
2164
|
const request = await this.system.storage.findOne('Request',
|
|
1816
2165
|
this.globals.MatchExp.atom({
|
|
1817
2166
|
key: 'id',
|
|
1818
|
-
value: ['=',
|
|
2167
|
+
value: ['=', mutationEvent.record.payload.requestId]
|
|
1819
2168
|
}),
|
|
1820
2169
|
undefined,
|
|
1821
2170
|
['id']
|
|
@@ -1824,53 +2173,51 @@ const assignReviewerTransfer = StateTransfer.create({
|
|
|
1824
2173
|
// Return source and target to create/update relation
|
|
1825
2174
|
return {
|
|
1826
2175
|
source: request, // Can be {id: '...'} or full entity
|
|
1827
|
-
target:
|
|
2176
|
+
target: mutationEvent.record.payload.reviewer // From payload
|
|
1828
2177
|
};
|
|
1829
2178
|
}
|
|
1830
2179
|
});
|
|
1831
2180
|
|
|
1832
|
-
//
|
|
1833
|
-
const
|
|
1834
|
-
trigger:
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
2181
|
+
// Trigger on specific payload values
|
|
2182
|
+
const publishTransfer = StateTransfer.create({
|
|
2183
|
+
trigger: {
|
|
2184
|
+
recordName: InteractionEventEntity.name,
|
|
2185
|
+
record: {
|
|
2186
|
+
interactionName: UpdateStatusInteraction.name,
|
|
2187
|
+
payload: {
|
|
2188
|
+
newStatus: 'published' // Only trigger when status changes to 'published'
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
},
|
|
2192
|
+
current: draftState,
|
|
2193
|
+
next: publishedState,
|
|
2194
|
+
computeTarget: (mutationEvent) => ({ id: mutationEvent.record.payload.documentId })
|
|
1841
2195
|
});
|
|
1842
2196
|
|
|
1843
|
-
//
|
|
1844
|
-
const
|
|
1845
|
-
trigger:
|
|
1846
|
-
|
|
2197
|
+
// Trigger on data mutations (non-interaction events)
|
|
2198
|
+
const dataUpdateTransfer = StateTransfer.create({
|
|
2199
|
+
trigger: {
|
|
2200
|
+
recordName: 'Order',
|
|
2201
|
+
type: 'update' // Only trigger on Order updates
|
|
2202
|
+
},
|
|
2203
|
+
current: processingState,
|
|
1847
2204
|
next: updatedState,
|
|
1848
|
-
computeTarget:
|
|
1849
|
-
// Find existing relation
|
|
1850
|
-
const relation = await this.system.storage.findOneRelationByName(UserProjectRelation.name,
|
|
1851
|
-
this.globals.MatchExp.atom({
|
|
1852
|
-
key: 'source.id',
|
|
1853
|
-
value: ['=', event.user.id]
|
|
1854
|
-
}).and({
|
|
1855
|
-
key: 'target.id',
|
|
1856
|
-
value: ['=', event.payload.projectId]
|
|
1857
|
-
}),
|
|
1858
|
-
undefined,
|
|
1859
|
-
['id']
|
|
1860
|
-
);
|
|
1861
|
-
|
|
1862
|
-
return relation; // Return existing relation by id
|
|
1863
|
-
}
|
|
2205
|
+
computeTarget: (mutationEvent) => ({ id: mutationEvent.record.id })
|
|
1864
2206
|
});
|
|
1865
2207
|
|
|
1866
2208
|
// Multiple targets - return array
|
|
1867
2209
|
const bulkApproveTransfer = StateTransfer.create({
|
|
1868
|
-
trigger:
|
|
2210
|
+
trigger: {
|
|
2211
|
+
recordName: InteractionEventEntity.name,
|
|
2212
|
+
record: {
|
|
2213
|
+
interactionName: BulkApproveInteraction.name
|
|
2214
|
+
}
|
|
2215
|
+
},
|
|
1869
2216
|
current: pendingState,
|
|
1870
2217
|
next: approvedState,
|
|
1871
|
-
computeTarget: (
|
|
2218
|
+
computeTarget: (mutationEvent) => {
|
|
1872
2219
|
// Return multiple entities to transition
|
|
1873
|
-
return
|
|
2220
|
+
return mutationEvent.record.payload.items.map(item => ({ id: item.id }));
|
|
1874
2221
|
}
|
|
1875
2222
|
});
|
|
1876
2223
|
```
|
|
@@ -2090,15 +2437,26 @@ const UserPostRelation = Relation.create({
|
|
|
2090
2437
|
source: User,
|
|
2091
2438
|
target: Post,
|
|
2092
2439
|
computation: Transform.create({
|
|
2093
|
-
|
|
2094
|
-
|
|
2440
|
+
eventDeps: {
|
|
2441
|
+
PostInteraction: {
|
|
2442
|
+
recordName: InteractionEventEntity.name,
|
|
2443
|
+
type: 'create'
|
|
2444
|
+
}
|
|
2445
|
+
},
|
|
2446
|
+
callback: async function(this: Controller, mutationEvent) {
|
|
2447
|
+
const event = mutationEvent.record;
|
|
2095
2448
|
if (event.interactionName === 'CreatePost') {
|
|
2449
|
+
// Use Controller to validate and enhance data
|
|
2450
|
+
const now = Math.floor(Date.now() / 1000);
|
|
2451
|
+
|
|
2096
2452
|
// This is where the actual creation logic is
|
|
2097
2453
|
return {
|
|
2098
2454
|
source: event.user.id,
|
|
2099
2455
|
target: {
|
|
2100
2456
|
title: event.payload.title,
|
|
2101
|
-
content: event.payload.content
|
|
2457
|
+
content: event.payload.content,
|
|
2458
|
+
createdAt: now,
|
|
2459
|
+
updatedAt: now
|
|
2102
2460
|
}
|
|
2103
2461
|
};
|
|
2104
2462
|
}
|
|
@@ -2535,6 +2893,50 @@ interface System {
|
|
|
2535
2893
|
|
|
2536
2894
|
Storage layer interface providing data persistence functionality.
|
|
2537
2895
|
|
|
2896
|
+
**Interface Definition**
|
|
2897
|
+
```typescript
|
|
2898
|
+
interface Storage {
|
|
2899
|
+
// Entity mapping
|
|
2900
|
+
map: any
|
|
2901
|
+
|
|
2902
|
+
// Transaction operations
|
|
2903
|
+
beginTransaction: (transactionName?: string) => Promise<any>
|
|
2904
|
+
commitTransaction: (transactionName?: string) => Promise<any>
|
|
2905
|
+
rollbackTransaction: (transactionName?: string) => Promise<any>
|
|
2906
|
+
|
|
2907
|
+
// Dictionary-specific API
|
|
2908
|
+
dict: {
|
|
2909
|
+
get: (key: string) => Promise<any>
|
|
2910
|
+
set: (key: string, value: any) => Promise<void>
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
// General KV storage
|
|
2914
|
+
get: (itemName: string, id: string, initialValue?: any) => Promise<any>
|
|
2915
|
+
set: (itemName: string, id: string, value: any, events?: RecordMutationEvent[]) => Promise<any>
|
|
2916
|
+
|
|
2917
|
+
// Entity/Relation operations
|
|
2918
|
+
setup: (entities: EntityInstance[], relations: RelationInstance[], createTables?: boolean) => any
|
|
2919
|
+
findOne: (entityName: string, ...args: any[]) => Promise<any>
|
|
2920
|
+
find: (entityName: string, ...args: any[]) => Promise<any[]>
|
|
2921
|
+
create: (entityName: string, data: any, events?: RecordMutationEvent[]) => Promise<any>
|
|
2922
|
+
update: (entityName: string, ...args: any[]) => Promise<any>
|
|
2923
|
+
delete: (entityName: string, data: any, events?: RecordMutationEvent[]) => Promise<any>
|
|
2924
|
+
|
|
2925
|
+
// Relation-specific operations
|
|
2926
|
+
findOneRelationByName: (relationName: string, ...args: any[]) => Promise<any>
|
|
2927
|
+
findRelationByName: (relationName: string, ...args: any[]) => Promise<any>
|
|
2928
|
+
updateRelationByName: (relationName: string, matchExpressionData: any, rawData: any, events?: RecordMutationEvent[]) => Promise<any>
|
|
2929
|
+
removeRelationByName: (relationName: string, matchExpressionData: any, events?: RecordMutationEvent[]) => Promise<any>
|
|
2930
|
+
addRelationByNameById: (relationName: string, sourceEntityId: string, targetEntityId: string, rawData?: any, events?: RecordMutationEvent[]) => Promise<any>
|
|
2931
|
+
|
|
2932
|
+
// Utility methods
|
|
2933
|
+
getRelationName: (entityName: string, attributeName: string) => string
|
|
2934
|
+
getEntityName: (entityName: string, attributeName: string) => string
|
|
2935
|
+
listen: (callback: RecordMutationCallback) => void
|
|
2936
|
+
destroy: () => void
|
|
2937
|
+
}
|
|
2938
|
+
```
|
|
2939
|
+
|
|
2538
2940
|
**Main Methods**
|
|
2539
2941
|
|
|
2540
2942
|
#### Transaction Operations
|
|
@@ -2938,10 +3340,43 @@ await storage.removeRelationByName('UserPostRelation', // Don't hardcode!
|
|
|
2938
3340
|
)
|
|
2939
3341
|
```
|
|
2940
3342
|
|
|
3343
|
+
#### Dictionary Operations
|
|
3344
|
+
|
|
3345
|
+
**dict.get(key: string)**
|
|
3346
|
+
Get value from Dictionary storage.
|
|
3347
|
+
|
|
3348
|
+
```typescript
|
|
3349
|
+
// Get dictionary value
|
|
3350
|
+
const userCount = await storage.dict.get('userCount')
|
|
3351
|
+
|
|
3352
|
+
// Returns undefined if key doesn't exist
|
|
3353
|
+
const config = await storage.dict.get('systemConfig')
|
|
3354
|
+
if (config === undefined) {
|
|
3355
|
+
// Handle missing value
|
|
3356
|
+
}
|
|
3357
|
+
```
|
|
3358
|
+
|
|
3359
|
+
**dict.set(key: string, value: any)**
|
|
3360
|
+
Set value in Dictionary storage.
|
|
3361
|
+
|
|
3362
|
+
```typescript
|
|
3363
|
+
// Set dictionary value
|
|
3364
|
+
await storage.dict.set('userCount', 100)
|
|
3365
|
+
|
|
3366
|
+
// Store complex objects
|
|
3367
|
+
await storage.dict.set('systemConfig', {
|
|
3368
|
+
maxUsers: 1000,
|
|
3369
|
+
maintenanceMode: false,
|
|
3370
|
+
features: ['chat', 'notifications']
|
|
3371
|
+
})
|
|
3372
|
+
```
|
|
3373
|
+
|
|
3374
|
+
**Note**: The Dictionary API is specifically designed for storing global state values that are defined as Dictionary instances in the system. It provides a cleaner, more focused API compared to the general KV storage operations.
|
|
3375
|
+
|
|
2941
3376
|
#### KV Storage Operations
|
|
2942
3377
|
|
|
2943
3378
|
**get(itemName: string, id: string, initialValue?: any)**
|
|
2944
|
-
Get value from key-value storage.
|
|
3379
|
+
Get value from general key-value storage.
|
|
2945
3380
|
|
|
2946
3381
|
```typescript
|
|
2947
3382
|
// Get value with default
|
|
@@ -2949,7 +3384,7 @@ const maxUsers = await storage.get('config', 'maxUsers', 100)
|
|
|
2949
3384
|
```
|
|
2950
3385
|
|
|
2951
3386
|
**set(itemName: string, id: string, value: any, events?: RecordMutationEvent[])**
|
|
2952
|
-
Set value in key-value storage.
|
|
3387
|
+
Set value in general key-value storage.
|
|
2953
3388
|
|
|
2954
3389
|
```typescript
|
|
2955
3390
|
// Set configuration value
|
|
@@ -2963,6 +3398,8 @@ await storage.set('cache', 'userPreferences', {
|
|
|
2963
3398
|
})
|
|
2964
3399
|
```
|
|
2965
3400
|
|
|
3401
|
+
**Note**: The general KV storage operations (`get`/`set`) are for arbitrary key-value pairs. For Dictionary entities specifically, prefer using the `dict` API.
|
|
3402
|
+
|
|
2966
3403
|
#### Utility Methods
|
|
2967
3404
|
|
|
2968
3405
|
**getRelationName(entityName: string, attributeName: string)**
|