@nocobase/plugin-flow-engine 2.1.0-alpha.7 → 2.1.0-alpha.8
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/ai/docs/runjs/context/block-model.md +35 -35
- package/dist/ai/docs/runjs/context/collection-field.md +53 -51
- package/dist/ai/docs/runjs/context/collection.md +39 -39
- package/dist/ai/docs/runjs/context/data-source-manager.md +40 -30
- package/dist/ai/docs/runjs/context/data-source.md +52 -44
- package/dist/ai/docs/runjs/context/element.md +44 -38
- package/dist/ai/docs/runjs/context/exit-all.md +37 -35
- package/dist/ai/docs/runjs/context/exit.md +38 -35
- package/dist/ai/docs/runjs/context/filter-manager.md +36 -30
- package/dist/ai/docs/runjs/context/form.md +57 -57
- package/dist/ai/docs/runjs/context/get-model.md +22 -21
- package/dist/ai/docs/runjs/context/get-value.md +20 -19
- package/dist/ai/docs/runjs/context/get-var.md +61 -55
- package/dist/ai/docs/runjs/context/i18n.md +17 -14
- package/dist/ai/docs/runjs/context/import-async.md +333 -45
- package/dist/ai/docs/runjs/context/init-resource.md +20 -20
- package/dist/ai/docs/runjs/context/libs.md +31 -31
- package/dist/ai/docs/runjs/context/location.md +34 -31
- package/dist/ai/docs/runjs/context/logger.md +41 -40
- package/dist/ai/docs/runjs/context/make-resource.md +27 -26
- package/dist/ai/docs/runjs/context/message.md +42 -41
- package/dist/ai/docs/runjs/context/modal.md +44 -44
- package/dist/ai/docs/runjs/context/model.md +36 -33
- package/dist/ai/docs/runjs/context/notification.md +41 -40
- package/dist/ai/docs/runjs/context/off.md +14 -14
- package/dist/ai/docs/runjs/context/on.md +30 -29
- package/dist/ai/docs/runjs/context/open-view.md +40 -40
- package/dist/ai/docs/runjs/context/render.md +37 -32
- package/dist/ai/docs/runjs/context/request.md +46 -45
- package/dist/ai/docs/runjs/context/require-async.md +28 -25
- package/dist/ai/docs/runjs/context/resource.md +34 -34
- package/dist/ai/docs/runjs/context/route.md +36 -34
- package/dist/ai/docs/runjs/context/router.md +43 -31
- package/dist/ai/docs/runjs/context/set-value.md +18 -17
- package/dist/ai/docs/runjs/context/sql.md +7 -15
- package/dist/ai/docs/runjs/context/t.md +20 -17
- package/dist/ai/docs/runjs/context/view.md +49 -46
- package/dist/ai/docs/runjs/document.md +1 -0
- package/dist/ai/docs/runjs/import-modules.md +32 -32
- package/dist/ai/docs/runjs/index.md +13 -13
- package/dist/ai/docs/runjs/jsx.md +19 -19
- package/dist/ai/docs/runjs/model/form-block-model.md +1 -3
- package/dist/ai/docs/runjs/render.md +15 -15
- package/dist/ai/docs/runjs/resource/api-resource.md +53 -53
- package/dist/ai/docs/runjs/resource/multi-record-resource.md +64 -64
- package/dist/ai/docs/runjs/resource/single-record-resource.md +55 -55
- package/dist/ai/docs/runjs/resource/sql-resource.md +57 -57
- package/dist/ai/docs/runjs/window.md +5 -5
- package/dist/externalVersion.js +10 -10
- package/dist/node_modules/ses/package.json +1 -1
- package/dist/node_modules/zod/package.json +1 -1
- package/dist/server/collections/flowsql.js +1 -0
- package/package.json +2 -2
|
@@ -1,83 +1,83 @@
|
|
|
1
1
|
# SingleRecordResource
|
|
2
2
|
|
|
3
|
-
Resource
|
|
3
|
+
A Resource oriented towards a **single record**: data is a single object, supporting retrieval by primary key, creation/updating (save), and deletion. It is suitable for "single record" scenarios such as details and forms. Unlike [MultiRecordResource](./multi-record-resource.md), the `getData()` method of `SingleRecordResource` returns a single object. You specify the primary key via `setFilterByTk(id)`, and `save()` will automatically call `create` or `update` based on the `isNewRecord` state.
|
|
4
4
|
|
|
5
5
|
**Inheritance**: FlowResource → APIResource → BaseRecordResource → SingleRecordResource.
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Creation**: `ctx.makeResource('SingleRecordResource')` or `ctx.initResource('SingleRecordResource')`. You must call `setResourceName('collectionName')` before use. When performing operations by primary key, call `setFilterByTk(id)`. In RunJS, `ctx.api` is injected by the runtime environment.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
## Use
|
|
11
|
+
## Use Scenarios
|
|
12
12
|
|
|
13
13
|
| Scenario | Description |
|
|
14
|
-
|
|
15
|
-
| **
|
|
16
|
-
| **Form
|
|
17
|
-
| **JSBlock
|
|
18
|
-
| **Association
|
|
14
|
+
|------|------|
|
|
15
|
+
| **Details Block** | The Details block uses `SingleRecordResource` by default to load a single record by its primary key. |
|
|
16
|
+
| **Form Block** | Create/Edit forms use `SingleRecordResource`, where `save()` automatically distinguishes between `create` and `update`. |
|
|
17
|
+
| **JSBlock Details** | Load a single user, order, etc., in a JSBlock and customize the display. |
|
|
18
|
+
| **Association Resources** | Load associated single records using the format `users.profile`, requiring `setSourceId(parentRecordID)`. |
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
## Data
|
|
22
|
+
## Data Format
|
|
23
23
|
|
|
24
|
-
- `getData()` returns a **single record object**,
|
|
25
|
-
- `getMeta()` returns metadata (if
|
|
24
|
+
- `getData()` returns a **single record object**, which corresponds to the `data` field of the `get` API response.
|
|
25
|
+
- `getMeta()` returns metadata (if available).
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
-
## Resource
|
|
29
|
+
## Resource Name and Primary Key
|
|
30
30
|
|
|
31
31
|
| Method | Description |
|
|
32
|
-
|
|
33
|
-
| `setResourceName(name)` / `getResourceName()` | Resource name, e.g
|
|
34
|
-
| `setSourceId(id)` / `getSourceId()` |
|
|
35
|
-
| `setDataSourceKey(key)` / `getDataSourceKey()` | Data source
|
|
36
|
-
| `setFilterByTk(tk)` / `getFilterByTk()` |
|
|
32
|
+
|------|------|
|
|
33
|
+
| `setResourceName(name)` / `getResourceName()` | Resource name, e.g., `'users'`, `'users.profile'` (association resource). |
|
|
34
|
+
| `setSourceId(id)` / `getSourceId()` | The parent record ID for association resources (e.g., `users.profile` requires the primary key of the `users` record). |
|
|
35
|
+
| `setDataSourceKey(key)` / `getDataSourceKey()` | Data source identifier (used in multi-data source environments). |
|
|
36
|
+
| `setFilterByTk(tk)` / `getFilterByTk()` | The primary key of the current record; once set, `isNewRecord` becomes `false`. |
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
40
|
## State
|
|
41
41
|
|
|
42
42
|
| Property/Method | Description |
|
|
43
|
-
|
|
44
|
-
| `isNewRecord` | Whether in "
|
|
43
|
+
|----------|------|
|
|
44
|
+
| `isNewRecord` | Whether it is in a "New" state (true if `filterByTk` is not set or if it was just created). |
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
48
|
-
## Request
|
|
48
|
+
## Request Parameters (Filter / Fields)
|
|
49
49
|
|
|
50
50
|
| Method | Description |
|
|
51
|
-
|
|
52
|
-
| `setFilter(filter)` / `getFilter()` | Filter (when not
|
|
53
|
-
| `setFields(fields)` / `getFields()` | Requested fields |
|
|
54
|
-
| `setAppends(appends)` / `getAppends()` / `addAppends` / `removeAppends` | Association
|
|
51
|
+
|------|------|
|
|
52
|
+
| `setFilter(filter)` / `getFilter()` | Filter (available when not in "New" state). |
|
|
53
|
+
| `setFields(fields)` / `getFields()` | Requested fields. |
|
|
54
|
+
| `setAppends(appends)` / `getAppends()` / `addAppends` / `removeAppends` | Association loading (appends). |
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
58
|
## CRUD
|
|
59
59
|
|
|
60
60
|
| Method | Description |
|
|
61
|
-
|
|
62
|
-
| `refresh()` |
|
|
63
|
-
| `save(data, options?)` |
|
|
64
|
-
| `destroy(options?)` |
|
|
65
|
-
| `runAction(actionName, options)` |
|
|
61
|
+
|------|------|
|
|
62
|
+
| `refresh()` | Requests `get` based on the current `filterByTk` and updates `getData()`; does nothing in "New" state. |
|
|
63
|
+
| `save(data, options?)` | Calls `create` when in "New" state, otherwise calls `update`; optional `{ refresh: false }` prevents automatic refreshing. |
|
|
64
|
+
| `destroy(options?)` | Deletes the record based on the current `filterByTk` and clears local data. |
|
|
65
|
+
| `runAction(actionName, options)` | Calls any resource action. |
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
-
##
|
|
69
|
+
## Configuration and Events
|
|
70
70
|
|
|
71
71
|
| Method | Description |
|
|
72
|
-
|
|
73
|
-
| `setSaveActionOptions(options)` | Request
|
|
74
|
-
| `on('refresh', fn)` / `on('saved', fn)` |
|
|
72
|
+
|------|------|
|
|
73
|
+
| `setSaveActionOptions(options)` | Request configuration for the `save` action. |
|
|
74
|
+
| `on('refresh', fn)` / `on('saved', fn)` | Triggered after refresh is complete or after saving. |
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
78
78
|
## Examples
|
|
79
79
|
|
|
80
|
-
### Basic
|
|
80
|
+
### Basic Retrieval and Update
|
|
81
81
|
|
|
82
82
|
```js
|
|
83
83
|
ctx.initResource('SingleRecordResource');
|
|
@@ -87,27 +87,27 @@ await ctx.resource.refresh();
|
|
|
87
87
|
const user = ctx.resource.getData();
|
|
88
88
|
|
|
89
89
|
// Update
|
|
90
|
-
await ctx.resource.save({ name: '
|
|
90
|
+
await ctx.resource.save({ name: 'John Doe' });
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
### Create
|
|
93
|
+
### Create New Record
|
|
94
94
|
|
|
95
95
|
```js
|
|
96
96
|
const newRes = ctx.makeResource('SingleRecordResource');
|
|
97
97
|
newRes.setResourceName('users');
|
|
98
|
-
await newRes.save({ name: '
|
|
98
|
+
await newRes.save({ name: 'Jane Smith', email: 'janesmith@example.com' });
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
### Delete
|
|
101
|
+
### Delete Record
|
|
102
102
|
|
|
103
103
|
```js
|
|
104
104
|
ctx.resource.setResourceName('users');
|
|
105
105
|
ctx.resource.setFilterByTk(1);
|
|
106
106
|
await ctx.resource.destroy();
|
|
107
|
-
// After destroy, getData()
|
|
107
|
+
// After destroy, getData() returns null
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
### Association
|
|
110
|
+
### Association Loading and Fields
|
|
111
111
|
|
|
112
112
|
```js
|
|
113
113
|
ctx.resource.setResourceName('users');
|
|
@@ -118,25 +118,25 @@ await ctx.resource.refresh();
|
|
|
118
118
|
const user = ctx.resource.getData();
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
### Association
|
|
121
|
+
### Association Resources (e.g., users.profile)
|
|
122
122
|
|
|
123
123
|
```js
|
|
124
124
|
const res = ctx.makeResource('SingleRecordResource');
|
|
125
125
|
res.setResourceName('users.profile');
|
|
126
126
|
res.setSourceId(ctx.record?.id); // Parent record primary key
|
|
127
|
-
res.setFilterByTk(profileId); //
|
|
127
|
+
res.setFilterByTk(profileId); // filterByTk can be omitted if profile is a hasOne relationship
|
|
128
128
|
await res.refresh();
|
|
129
129
|
const profile = res.getData();
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
###
|
|
132
|
+
### Save Without Auto-Refresh
|
|
133
133
|
|
|
134
134
|
```js
|
|
135
135
|
await ctx.resource.save({ status: 'active' }, { refresh: false });
|
|
136
|
-
//
|
|
136
|
+
// getData() retains the old value as refresh is not triggered after saving
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
###
|
|
139
|
+
### Listening to refresh / saved Events
|
|
140
140
|
|
|
141
141
|
```js
|
|
142
142
|
ctx.resource?.on?.('refresh', () => {
|
|
@@ -144,7 +144,7 @@ ctx.resource?.on?.('refresh', () => {
|
|
|
144
144
|
ctx.render(<div>User: {data?.nickname}</div>);
|
|
145
145
|
});
|
|
146
146
|
ctx.resource?.on?.('saved', (savedData) => {
|
|
147
|
-
ctx.message.success('Saved');
|
|
147
|
+
ctx.message.success('Saved successfully');
|
|
148
148
|
});
|
|
149
149
|
await ctx.resource?.refresh?.();
|
|
150
150
|
```
|
|
@@ -153,17 +153,17 @@ await ctx.resource?.refresh?.();
|
|
|
153
153
|
|
|
154
154
|
## Notes
|
|
155
155
|
|
|
156
|
-
- **setResourceName
|
|
157
|
-
- **filterByTk and isNewRecord**:
|
|
158
|
-
- **Association
|
|
159
|
-
- **getData
|
|
156
|
+
- **setResourceName is Required**: You must call `setResourceName('collectionName')` before use, otherwise the request URL cannot be constructed.
|
|
157
|
+
- **filterByTk and isNewRecord**: If `setFilterByTk` is not called, `isNewRecord` is `true`, and `refresh()` will not initiate a request; `save()` will execute a `create` action.
|
|
158
|
+
- **Association Resources**: When the resource name is in `parent.child` format (e.g., `users.profile`), you must call `setSourceId(parentPrimaryKey)` first.
|
|
159
|
+
- **getData Returns an Object**: The `data` returned by single-record APIs is a record object; `getData()` returns this object directly. It becomes `null` after `destroy()`.
|
|
160
160
|
|
|
161
161
|
---
|
|
162
162
|
|
|
163
163
|
## Related
|
|
164
164
|
|
|
165
|
-
- [ctx.resource](../context/resource.md) -
|
|
166
|
-
- [ctx.initResource()](../context/init-resource.md) - Initialize and bind to ctx.resource
|
|
167
|
-
- [ctx.makeResource()](../context/make-resource.md) - Create resource instance without binding
|
|
168
|
-
- [APIResource](./api-resource.md) -
|
|
169
|
-
- [MultiRecordResource](./multi-record-resource.md) -
|
|
165
|
+
- [ctx.resource](../context/resource.md) - The resource instance in the current context
|
|
166
|
+
- [ctx.initResource()](../context/init-resource.md) - Initialize and bind to `ctx.resource`
|
|
167
|
+
- [ctx.makeResource()](../context/make-resource.md) - Create a new resource instance without binding
|
|
168
|
+
- [APIResource](./api-resource.md) - General API resource requested by URL
|
|
169
|
+
- [MultiRecordResource](./multi-record-resource.md) - Oriented towards collections/lists, supporting CRUD and pagination
|
|
@@ -1,95 +1,95 @@
|
|
|
1
1
|
# SQLResource
|
|
2
2
|
|
|
3
|
-
Resource
|
|
3
|
+
A Resource for executing queries based on **saved SQL configurations** or **dynamic SQL**, with data sourced from interfaces such as `flowSql:run` / `flowSql:runById`. It is suitable for reports, statistics, custom SQL lists, and other scenarios. Unlike [MultiRecordResource](./multi-record-resource.md), SQLResource does not depend on collections; it executes SQL queries directly and supports pagination, parameter binding, template variables (`{{ctx.xxx}}`), and result type control.
|
|
4
4
|
|
|
5
5
|
**Inheritance**: FlowResource → APIResource → BaseRecordResource → SQLResource.
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Creation**: `ctx.makeResource('SQLResource')` or `ctx.initResource('SQLResource')`. To execute based on a saved configuration, use `setFilterByTk(uid)` (the UID of the SQL template). For debugging, use `setDebug(true)` + `setSQL(sql)` to execute SQL directly. In RunJS, `ctx.api` is injected by the runtime environment.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
## Use
|
|
11
|
+
## Use Cases
|
|
12
12
|
|
|
13
13
|
| Scenario | Description |
|
|
14
|
-
|
|
15
|
-
| **Reports /
|
|
16
|
-
| **JSBlock
|
|
17
|
-
| **Chart
|
|
18
|
-
| **
|
|
14
|
+
|------|------|
|
|
15
|
+
| **Reports / Statistics** | Complex aggregations, cross-table queries, and custom statistical metrics. |
|
|
16
|
+
| **JSBlock Custom Lists** | Implementing special filtering, sorting, or associations using SQL with custom rendering. |
|
|
17
|
+
| **Chart Blocks** | Driving chart data sources with saved SQL templates, supporting pagination. |
|
|
18
|
+
| **Choosing between SQLResource and ctx.sql** | Use SQLResource when pagination, events, or reactive data are required; use `ctx.sql.run()` / `ctx.sql.runById()` for simple one-off queries. |
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
## Data
|
|
22
|
+
## Data Format
|
|
23
23
|
|
|
24
24
|
- `getData()` returns different formats based on `setSQLType()`:
|
|
25
|
-
- `selectRows` (default): **
|
|
26
|
-
- `selectRow`: **
|
|
27
|
-
- `selectVar`: **
|
|
28
|
-
- `getMeta()` returns pagination
|
|
25
|
+
- `selectRows` (default): **Array**, multiple row results.
|
|
26
|
+
- `selectRow`: **Single object**.
|
|
27
|
+
- `selectVar`: **Scalar value** (e.g., COUNT, SUM).
|
|
28
|
+
- `getMeta()` returns metadata such as pagination: `page`, `pageSize`, `count`, `totalPage`, etc.
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
-
## SQL
|
|
32
|
+
## SQL Configuration and Execution Modes
|
|
33
33
|
|
|
34
34
|
| Method | Description |
|
|
35
|
-
|
|
36
|
-
| `setFilterByTk(uid)` | SQL template
|
|
37
|
-
| `setSQL(sql)` |
|
|
38
|
-
| `setSQLType(type)` | Result type: `'selectVar'` / `'selectRow'` / `'selectRows'
|
|
39
|
-
| `setDebug(enabled)` | When true
|
|
40
|
-
| `run()` | Calls `runBySQL()` or `runById()`
|
|
41
|
-
| `runBySQL()` |
|
|
42
|
-
| `runById()` |
|
|
35
|
+
|------|------|
|
|
36
|
+
| `setFilterByTk(uid)` | Sets the UID of the SQL template to execute (corresponds to `runById`; must be saved in the admin interface first). |
|
|
37
|
+
| `setSQL(sql)` | Sets the raw SQL (used for `runBySQL` only when debug mode `setDebug(true)` is enabled). |
|
|
38
|
+
| `setSQLType(type)` | Result type: `'selectVar'` / `'selectRow'` / `'selectRows'`. |
|
|
39
|
+
| `setDebug(enabled)` | When set to `true`, `refresh` calls `runBySQL()`; otherwise, it calls `runById()`. |
|
|
40
|
+
| `run()` | Calls `runBySQL()` or `runById()` based on the debug state. |
|
|
41
|
+
| `runBySQL()` | Executes using the SQL defined in `setSQL` (requires `setDebug(true)`). |
|
|
42
|
+
| `runById()` | Executes the saved SQL template using the current UID. |
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
-
##
|
|
46
|
+
## Parameters and Context
|
|
47
47
|
|
|
48
48
|
| Method | Description |
|
|
49
|
-
|
|
50
|
-
| `setBind(bind)` |
|
|
51
|
-
| `setLiquidContext(ctx)` | Template context (Liquid)
|
|
52
|
-
| `setFilter(filter)` |
|
|
53
|
-
| `setDataSourceKey(key)` | Data source
|
|
49
|
+
|------|------|
|
|
50
|
+
| `setBind(bind)` | Binds variables. Use an object for `:name` placeholders or an array for `?` placeholders. |
|
|
51
|
+
| `setLiquidContext(ctx)` | Template context (Liquid), used to parse `{{ctx.xxx}}`. |
|
|
52
|
+
| `setFilter(filter)` | Additional filter conditions (passed into the request data). |
|
|
53
|
+
| `setDataSourceKey(key)` | Data source identifier (used for multi-data source environments). |
|
|
54
54
|
|
|
55
55
|
---
|
|
56
56
|
|
|
57
57
|
## Pagination
|
|
58
58
|
|
|
59
59
|
| Method | Description |
|
|
60
|
-
|
|
61
|
-
| `setPage(page)` / `getPage()` | Current page (default 1) |
|
|
62
|
-
| `setPageSize(size)` / `getPageSize()` |
|
|
63
|
-
| `next()` / `previous()` / `goto(page)` |
|
|
60
|
+
|------|------|
|
|
61
|
+
| `setPage(page)` / `getPage()` | Current page (default is 1). |
|
|
62
|
+
| `setPageSize(size)` / `getPageSize()` | Items per page (default is 20). |
|
|
63
|
+
| `next()` / `previous()` / `goto(page)` | Navigates pages and triggers `refresh`. |
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
In SQL, you can use `{{ctx.limit}}` and `{{ctx.offset}}` to reference pagination parameters. SQLResource injects `limit` and `offset` into the context automatically.
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
-
## Data
|
|
69
|
+
## Data Fetching and Events
|
|
70
70
|
|
|
71
71
|
| Method | Description |
|
|
72
|
-
|
|
73
|
-
| `refresh()` |
|
|
74
|
-
| `runAction(actionName, options)` |
|
|
75
|
-
| `on('refresh', fn)` / `on('loading', fn)` |
|
|
72
|
+
|------|------|
|
|
73
|
+
| `refresh()` | Executes the SQL (`runById` or `runBySQL`), writes the result to `setData(data)`, updates meta, and triggers the `'refresh'` event. |
|
|
74
|
+
| `runAction(actionName, options)` | Calls underlying actions (e.g., `getBind`, `run`, `runById`). |
|
|
75
|
+
| `on('refresh', fn)` / `on('loading', fn)` | Triggered when refreshing is complete or when loading starts. |
|
|
76
76
|
|
|
77
77
|
---
|
|
78
78
|
|
|
79
79
|
## Examples
|
|
80
80
|
|
|
81
|
-
###
|
|
81
|
+
### Executing via Saved Template (runById)
|
|
82
82
|
|
|
83
83
|
```js
|
|
84
84
|
ctx.initResource('SQLResource');
|
|
85
|
-
ctx.resource.setFilterByTk('active-users-report'); //
|
|
85
|
+
ctx.resource.setFilterByTk('active-users-report'); // UID of the saved SQL template
|
|
86
86
|
ctx.resource.setBind({ status: 'active' });
|
|
87
87
|
await ctx.resource.refresh();
|
|
88
88
|
const data = ctx.resource.getData();
|
|
89
89
|
const meta = ctx.resource.getMeta(); // page, pageSize, count, etc.
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
### Debug
|
|
92
|
+
### Debug Mode: Executing SQL Directly (runBySQL)
|
|
93
93
|
|
|
94
94
|
```js
|
|
95
95
|
const res = ctx.makeResource('SQLResource');
|
|
@@ -100,20 +100,20 @@ await res.refresh();
|
|
|
100
100
|
const data = res.getData();
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
### Pagination and
|
|
103
|
+
### Pagination and Navigation
|
|
104
104
|
|
|
105
105
|
```js
|
|
106
106
|
ctx.resource.setFilterByTk('user-list-sql');
|
|
107
107
|
ctx.resource.setPageSize(20);
|
|
108
108
|
await ctx.resource.refresh();
|
|
109
109
|
|
|
110
|
-
//
|
|
110
|
+
// Navigation
|
|
111
111
|
await ctx.resource.next();
|
|
112
112
|
await ctx.resource.previous();
|
|
113
113
|
await ctx.resource.goto(3);
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
### Result
|
|
116
|
+
### Result Types
|
|
117
117
|
|
|
118
118
|
```js
|
|
119
119
|
// Multiple rows (default)
|
|
@@ -124,12 +124,12 @@ const rows = ctx.resource.getData(); // [{...}, {...}]
|
|
|
124
124
|
ctx.resource.setSQLType('selectRow');
|
|
125
125
|
const row = ctx.resource.getData(); // {...}
|
|
126
126
|
|
|
127
|
-
// Single value (e.g
|
|
127
|
+
// Single value (e.g., COUNT)
|
|
128
128
|
ctx.resource.setSQLType('selectVar');
|
|
129
129
|
const total = ctx.resource.getData(); // 42
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
###
|
|
132
|
+
### Using Template Variables
|
|
133
133
|
|
|
134
134
|
```js
|
|
135
135
|
ctx.defineProperty('minId', { get: () => 10 });
|
|
@@ -139,7 +139,7 @@ res.setSQL('SELECT * FROM users WHERE id > {{ctx.minId}} LIMIT {{ctx.limit}}');
|
|
|
139
139
|
await res.refresh();
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
###
|
|
142
|
+
### Listening to the refresh Event
|
|
143
143
|
|
|
144
144
|
```js
|
|
145
145
|
ctx.resource?.on?.('refresh', () => {
|
|
@@ -153,18 +153,18 @@ await ctx.resource?.refresh?.();
|
|
|
153
153
|
|
|
154
154
|
## Notes
|
|
155
155
|
|
|
156
|
-
- **runById requires
|
|
157
|
-
- **Debug mode
|
|
158
|
-
- **
|
|
159
|
-
- **Parameter
|
|
156
|
+
- **runById requires saving the template first**: The UID used in `setFilterByTk(uid)` must be a SQL template ID already saved in the admin interface. You can save it via `ctx.sql.save({ uid, sql })`.
|
|
157
|
+
- **Debug mode requires permissions**: `setDebug(true)` uses `flowSql:run`, which requires the current role to have SQL configuration permissions. `runById` only requires the user to be logged in.
|
|
158
|
+
- **Refresh Debouncing**: Multiple calls to `refresh()` within the same event loop will only execute the last one to avoid redundant requests.
|
|
159
|
+
- **Parameter Binding for Injection Prevention**: Use `setBind()` with `:name` or `?` placeholders instead of string concatenation to prevent SQL injection.
|
|
160
160
|
|
|
161
161
|
---
|
|
162
162
|
|
|
163
163
|
## Related
|
|
164
164
|
|
|
165
|
-
- [ctx.sql](../context/sql.md) - SQL execution and management; `ctx.sql.runById` for simple one-off queries
|
|
166
|
-
- [ctx.resource](../context/resource.md) -
|
|
167
|
-
- [ctx.initResource()](../context/init-resource.md) -
|
|
168
|
-
- [ctx.makeResource()](../context/make-resource.md) -
|
|
169
|
-
- [APIResource](./api-resource.md) -
|
|
170
|
-
- [MultiRecordResource](./multi-record-resource.md) -
|
|
165
|
+
- [ctx.sql](../context/sql.md) - SQL execution and management; `ctx.sql.runById` is suitable for simple one-off queries.
|
|
166
|
+
- [ctx.resource](../context/resource.md) - The resource instance in the current context.
|
|
167
|
+
- [ctx.initResource()](../context/init-resource.md) - Initializes and binds to `ctx.resource`.
|
|
168
|
+
- [ctx.makeResource()](../context/make-resource.md) - Creates a new resource instance without binding.
|
|
169
|
+
- [APIResource](./api-resource.md) - General API resource.
|
|
170
|
+
- [MultiRecordResource](./multi-record-resource.md) - Designed for collections and lists.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# window
|
|
2
2
|
|
|
3
|
-
The following
|
|
3
|
+
The following properties can be accessed directly via `window`:
|
|
4
4
|
|
|
5
5
|
* `setTimeout` / `clearTimeout`
|
|
6
6
|
* `setInterval` / `clearInterval`
|
|
@@ -9,12 +9,12 @@ The following are available on `window`:
|
|
|
9
9
|
* `Date`
|
|
10
10
|
* `FormData`
|
|
11
11
|
* `addEventListener`
|
|
12
|
-
* `open` (
|
|
13
|
-
* `location` (
|
|
12
|
+
* `open` (Only `http:`, `https:`, or `about:blank` are allowed)
|
|
13
|
+
* `location` (Read-only, supports secure navigation)
|
|
14
14
|
* `navigator`
|
|
15
15
|
|
|
16
|
-
Only basic
|
|
16
|
+
Only basic and secure DOM query and creation capabilities are supported:
|
|
17
17
|
|
|
18
18
|
* `createElement(tagName)`
|
|
19
19
|
* `querySelector(selectors)`
|
|
20
|
-
* `querySelectorAll(selectors)`
|
|
20
|
+
* `querySelectorAll(selectors)`
|
package/dist/externalVersion.js
CHANGED
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
|
-
"@nocobase/client": "2.1.0-alpha.
|
|
11
|
+
"@nocobase/client": "2.1.0-alpha.8",
|
|
12
12
|
"lodash": "4.17.21",
|
|
13
|
-
"@nocobase/database": "2.1.0-alpha.
|
|
14
|
-
"@nocobase/data-source-manager": "2.1.0-alpha.
|
|
15
|
-
"@nocobase/resourcer": "2.1.0-alpha.
|
|
16
|
-
"@nocobase/server": "2.1.0-alpha.
|
|
17
|
-
"@nocobase/utils": "2.1.0-alpha.
|
|
18
|
-
"@nocobase/cache": "2.1.0-alpha.
|
|
19
|
-
"@nocobase/plugin-localization": "2.1.0-alpha.
|
|
20
|
-
"@nocobase/ai": "2.1.0-alpha.
|
|
21
|
-
"@nocobase/actions": "2.1.0-alpha.
|
|
13
|
+
"@nocobase/database": "2.1.0-alpha.8",
|
|
14
|
+
"@nocobase/data-source-manager": "2.1.0-alpha.8",
|
|
15
|
+
"@nocobase/resourcer": "2.1.0-alpha.8",
|
|
16
|
+
"@nocobase/server": "2.1.0-alpha.8",
|
|
17
|
+
"@nocobase/utils": "2.1.0-alpha.8",
|
|
18
|
+
"@nocobase/cache": "2.1.0-alpha.8",
|
|
19
|
+
"@nocobase/plugin-localization": "2.1.0-alpha.8",
|
|
20
|
+
"@nocobase/ai": "2.1.0-alpha.8",
|
|
21
|
+
"@nocobase/actions": "2.1.0-alpha.8"
|
|
22
22
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"ses","version":"1.14.0","description":"Hardened JavaScript for Fearless Cooperation","keywords":["lockdown","harden","Compartment","assert","security","confinement","isolation","object capabilities","ocaps","secure execution","third-party code","prototype pollution","supply-chain attack","plugin"],"author":"Agoric","license":"Apache-2.0","homepage":"https://github.com/Agoric/SES-shim/tree/master/packages/ses#readme","repository":{"type":"git","url":"git+https://github.com/endojs/endo.git","directory":"packages/ses"},"bugs":{"url":"https://github.com/endojs/endo/issues"},"type":"module","main":"./dist/ses.cjs","module":"./index.js","unpkg":"./dist/ses.umd.js","types":"./types.d.ts","exports":{".":{"import":{"types":"./types.d.ts","xs":"./src-xs/index.js","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./lockdown":{"import":{"types":"./types.d.ts","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./hermes":{"require":{"types":"./dist/types.d.cts","default":"./dist/ses-hermes.cjs"}},"./tools.js":"./tools.js","./assert-shim.js":"./assert-shim.js","./lockdown-shim.js":{"xs":"./src-xs/lockdown-shim.js","default":"./lockdown-shim.js"},"./compartment-shim.js":{"xs":"./src-xs/compartment-shim.js","default":"./compartment-shim.js"},"./console-shim.js":"./console-shim.js","./package.json":"./package.json"},"scripts":{"build:vanilla":"node scripts/bundle.js","build:hermes":"node scripts/bundle.js hermes","build":"yarn build:vanilla && yarn build:hermes","clean":"rm -rf dist","cover":"c8 ava","demo":"python3 -m http.server","lint":"yarn lint:types && yarn lint:eslint","lint-fix":"eslint --fix .","lint:eslint":"eslint .","lint:types":"tsc","prepare":"npm run clean && npm run build","qt":"ava","test":"tsd && ava","test:hermes":"./scripts/hermes-test.sh","test:xs":"xst dist/ses.umd.js test/_lockdown-safe.js && node scripts/generate-test-xs.js && xst tmp/test-xs.js && rm -rf tmp","postpack":"git clean -fX \"*.d.ts*\" \"*.d.cts*\" \"*.d.mts*\" \"*.tsbuildinfo\""},"dependencies":{"@endo/cache-map":"^1.1.0","@endo/env-options":"^1.1.11","@endo/immutable-arraybuffer":"^1.1.2"},"devDependencies":{"@babel/generator":"^7.26.3","@babel/parser":"~7.26.2","@babel/traverse":"~7.25.9","@babel/types":"~7.26.0","@endo/compartment-mapper":"^1.6.3","@endo/module-source":"^1.3.3","@endo/test262-runner":"^0.1.48","@types/babel__traverse":"^7.20.5","ava":"^6.1.3","babel-eslint":"^10.1.0","c8":"^7.14.0","core-js":"^3.31.0","eslint":"^8.57.1","eslint-config-airbnb-base":"^15.0.0","eslint-config-prettier":"^9.1.0","eslint-plugin-eslint-comments":"^3.2.0","eslint-plugin-import":"^2.31.0","hermes-engine-cli":"^0.12.0","prettier":"^3.5.3","terser":"^5.16.6","tsd":"^0.31.2","typescript":"~5.8.3"},"files":["./*.d.ts","./*.js","./*.map","LICENSE*","SECURITY*","dist","lib","src","tools"],"publishConfig":{"access":"public"},"eslintConfig":{"extends":["plugin:@endo/ses"]},"ava":{"files":["test/**/*.test.*"],"timeout":"2m"},"typeCoverage":{"atLeast":81.17},"gitHead":"9815aea9541f241389d2135c6097a7442bdffa17","_lastModified":"2026-
|
|
1
|
+
{"name":"ses","version":"1.14.0","description":"Hardened JavaScript for Fearless Cooperation","keywords":["lockdown","harden","Compartment","assert","security","confinement","isolation","object capabilities","ocaps","secure execution","third-party code","prototype pollution","supply-chain attack","plugin"],"author":"Agoric","license":"Apache-2.0","homepage":"https://github.com/Agoric/SES-shim/tree/master/packages/ses#readme","repository":{"type":"git","url":"git+https://github.com/endojs/endo.git","directory":"packages/ses"},"bugs":{"url":"https://github.com/endojs/endo/issues"},"type":"module","main":"./dist/ses.cjs","module":"./index.js","unpkg":"./dist/ses.umd.js","types":"./types.d.ts","exports":{".":{"import":{"types":"./types.d.ts","xs":"./src-xs/index.js","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./lockdown":{"import":{"types":"./types.d.ts","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./hermes":{"require":{"types":"./dist/types.d.cts","default":"./dist/ses-hermes.cjs"}},"./tools.js":"./tools.js","./assert-shim.js":"./assert-shim.js","./lockdown-shim.js":{"xs":"./src-xs/lockdown-shim.js","default":"./lockdown-shim.js"},"./compartment-shim.js":{"xs":"./src-xs/compartment-shim.js","default":"./compartment-shim.js"},"./console-shim.js":"./console-shim.js","./package.json":"./package.json"},"scripts":{"build:vanilla":"node scripts/bundle.js","build:hermes":"node scripts/bundle.js hermes","build":"yarn build:vanilla && yarn build:hermes","clean":"rm -rf dist","cover":"c8 ava","demo":"python3 -m http.server","lint":"yarn lint:types && yarn lint:eslint","lint-fix":"eslint --fix .","lint:eslint":"eslint .","lint:types":"tsc","prepare":"npm run clean && npm run build","qt":"ava","test":"tsd && ava","test:hermes":"./scripts/hermes-test.sh","test:xs":"xst dist/ses.umd.js test/_lockdown-safe.js && node scripts/generate-test-xs.js && xst tmp/test-xs.js && rm -rf tmp","postpack":"git clean -fX \"*.d.ts*\" \"*.d.cts*\" \"*.d.mts*\" \"*.tsbuildinfo\""},"dependencies":{"@endo/cache-map":"^1.1.0","@endo/env-options":"^1.1.11","@endo/immutable-arraybuffer":"^1.1.2"},"devDependencies":{"@babel/generator":"^7.26.3","@babel/parser":"~7.26.2","@babel/traverse":"~7.25.9","@babel/types":"~7.26.0","@endo/compartment-mapper":"^1.6.3","@endo/module-source":"^1.3.3","@endo/test262-runner":"^0.1.48","@types/babel__traverse":"^7.20.5","ava":"^6.1.3","babel-eslint":"^10.1.0","c8":"^7.14.0","core-js":"^3.31.0","eslint":"^8.57.1","eslint-config-airbnb-base":"^15.0.0","eslint-config-prettier":"^9.1.0","eslint-plugin-eslint-comments":"^3.2.0","eslint-plugin-import":"^2.31.0","hermes-engine-cli":"^0.12.0","prettier":"^3.5.3","terser":"^5.16.6","tsd":"^0.31.2","typescript":"~5.8.3"},"files":["./*.d.ts","./*.js","./*.map","LICENSE*","SECURITY*","dist","lib","src","tools"],"publishConfig":{"access":"public"},"eslintConfig":{"extends":["plugin:@endo/ses"]},"ava":{"files":["test/**/*.test.*"],"timeout":"2m"},"typeCoverage":{"atLeast":81.17},"gitHead":"9815aea9541f241389d2135c6097a7442bdffa17","_lastModified":"2026-03-12T00:54:45.690Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"zod","version":"4.3.5","type":"module","license":"MIT","author":"Colin McDonnell <zod@colinhacks.com>","description":"TypeScript-first schema declaration and validation library with static type inference","homepage":"https://zod.dev","llms":"https://zod.dev/llms.txt","llmsFull":"https://zod.dev/llms-full.txt","mcpServer":"https://mcp.inkeep.com/zod/mcp","funding":"https://github.com/sponsors/colinhacks","sideEffects":false,"files":["src","**/*.js","**/*.mjs","**/*.cjs","**/*.d.ts","**/*.d.mts","**/*.d.cts","**/package.json"],"keywords":["typescript","schema","validation","type","inference"],"main":"./index.cjs","types":"./index.d.cts","module":"./index.js","zshy":{"exports":{"./package.json":"./package.json",".":"./src/index.ts","./mini":"./src/mini/index.ts","./locales":"./src/locales/index.ts","./v3":"./src/v3/index.ts","./v4":"./src/v4/index.ts","./v4-mini":"./src/v4-mini/index.ts","./v4/mini":"./src/v4/mini/index.ts","./v4/core":"./src/v4/core/index.ts","./v4/locales":"./src/v4/locales/index.ts","./v4/locales/*":"./src/v4/locales/*"},"conditions":{"@zod/source":"src"}},"exports":{"./package.json":"./package.json",".":{"@zod/source":"./src/index.ts","types":"./index.d.cts","import":"./index.js","require":"./index.cjs"},"./mini":{"@zod/source":"./src/mini/index.ts","types":"./mini/index.d.cts","import":"./mini/index.js","require":"./mini/index.cjs"},"./locales":{"@zod/source":"./src/locales/index.ts","types":"./locales/index.d.cts","import":"./locales/index.js","require":"./locales/index.cjs"},"./v3":{"@zod/source":"./src/v3/index.ts","types":"./v3/index.d.cts","import":"./v3/index.js","require":"./v3/index.cjs"},"./v4":{"@zod/source":"./src/v4/index.ts","types":"./v4/index.d.cts","import":"./v4/index.js","require":"./v4/index.cjs"},"./v4-mini":{"@zod/source":"./src/v4-mini/index.ts","types":"./v4-mini/index.d.cts","import":"./v4-mini/index.js","require":"./v4-mini/index.cjs"},"./v4/mini":{"@zod/source":"./src/v4/mini/index.ts","types":"./v4/mini/index.d.cts","import":"./v4/mini/index.js","require":"./v4/mini/index.cjs"},"./v4/core":{"@zod/source":"./src/v4/core/index.ts","types":"./v4/core/index.d.cts","import":"./v4/core/index.js","require":"./v4/core/index.cjs"},"./v4/locales":{"@zod/source":"./src/v4/locales/index.ts","types":"./v4/locales/index.d.cts","import":"./v4/locales/index.js","require":"./v4/locales/index.cjs"},"./v4/locales/*":{"@zod/source":"./src/v4/locales/*","types":"./v4/locales/*","import":"./v4/locales/*","require":"./v4/locales/*"}},"repository":{"type":"git","url":"git+https://github.com/colinhacks/zod.git"},"bugs":{"url":"https://github.com/colinhacks/zod/issues"},"support":{"backing":{"npm-funding":true}},"scripts":{"clean":"git clean -xdf . -e node_modules","build":"zshy --project tsconfig.build.json","postbuild":"tsx ../../scripts/write-stub-package-jsons.ts && pnpm biome check --write .","test:watch":"pnpm vitest","test":"pnpm vitest run","prepublishOnly":"tsx ../../scripts/check-versions.ts"},"_lastModified":"2026-
|
|
1
|
+
{"name":"zod","version":"4.3.5","type":"module","license":"MIT","author":"Colin McDonnell <zod@colinhacks.com>","description":"TypeScript-first schema declaration and validation library with static type inference","homepage":"https://zod.dev","llms":"https://zod.dev/llms.txt","llmsFull":"https://zod.dev/llms-full.txt","mcpServer":"https://mcp.inkeep.com/zod/mcp","funding":"https://github.com/sponsors/colinhacks","sideEffects":false,"files":["src","**/*.js","**/*.mjs","**/*.cjs","**/*.d.ts","**/*.d.mts","**/*.d.cts","**/package.json"],"keywords":["typescript","schema","validation","type","inference"],"main":"./index.cjs","types":"./index.d.cts","module":"./index.js","zshy":{"exports":{"./package.json":"./package.json",".":"./src/index.ts","./mini":"./src/mini/index.ts","./locales":"./src/locales/index.ts","./v3":"./src/v3/index.ts","./v4":"./src/v4/index.ts","./v4-mini":"./src/v4-mini/index.ts","./v4/mini":"./src/v4/mini/index.ts","./v4/core":"./src/v4/core/index.ts","./v4/locales":"./src/v4/locales/index.ts","./v4/locales/*":"./src/v4/locales/*"},"conditions":{"@zod/source":"src"}},"exports":{"./package.json":"./package.json",".":{"@zod/source":"./src/index.ts","types":"./index.d.cts","import":"./index.js","require":"./index.cjs"},"./mini":{"@zod/source":"./src/mini/index.ts","types":"./mini/index.d.cts","import":"./mini/index.js","require":"./mini/index.cjs"},"./locales":{"@zod/source":"./src/locales/index.ts","types":"./locales/index.d.cts","import":"./locales/index.js","require":"./locales/index.cjs"},"./v3":{"@zod/source":"./src/v3/index.ts","types":"./v3/index.d.cts","import":"./v3/index.js","require":"./v3/index.cjs"},"./v4":{"@zod/source":"./src/v4/index.ts","types":"./v4/index.d.cts","import":"./v4/index.js","require":"./v4/index.cjs"},"./v4-mini":{"@zod/source":"./src/v4-mini/index.ts","types":"./v4-mini/index.d.cts","import":"./v4-mini/index.js","require":"./v4-mini/index.cjs"},"./v4/mini":{"@zod/source":"./src/v4/mini/index.ts","types":"./v4/mini/index.d.cts","import":"./v4/mini/index.js","require":"./v4/mini/index.cjs"},"./v4/core":{"@zod/source":"./src/v4/core/index.ts","types":"./v4/core/index.d.cts","import":"./v4/core/index.js","require":"./v4/core/index.cjs"},"./v4/locales":{"@zod/source":"./src/v4/locales/index.ts","types":"./v4/locales/index.d.cts","import":"./v4/locales/index.js","require":"./v4/locales/index.cjs"},"./v4/locales/*":{"@zod/source":"./src/v4/locales/*","types":"./v4/locales/*","import":"./v4/locales/*","require":"./v4/locales/*"}},"repository":{"type":"git","url":"git+https://github.com/colinhacks/zod.git"},"bugs":{"url":"https://github.com/colinhacks/zod/issues"},"support":{"backing":{"npm-funding":true}},"scripts":{"clean":"git clean -xdf . -e node_modules","build":"zshy --project tsconfig.build.json","postbuild":"tsx ../../scripts/write-stub-package-jsons.ts && pnpm biome check --write .","test:watch":"pnpm vitest","test":"pnpm vitest run","prepublishOnly":"tsx ../../scripts/check-versions.ts"},"_lastModified":"2026-03-12T00:54:45.189Z"}
|
|
@@ -32,6 +32,7 @@ module.exports = __toCommonJS(flowsql_exports);
|
|
|
32
32
|
var import_database = require("@nocobase/database");
|
|
33
33
|
var flowsql_default = (0, import_database.defineCollection)({
|
|
34
34
|
name: "flowSql",
|
|
35
|
+
filterTargetKey: "uid",
|
|
35
36
|
migrationRules: ["overwrite", "schema-only"],
|
|
36
37
|
fields: [
|
|
37
38
|
{
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"displayName.zh-CN": "前端流引擎",
|
|
5
5
|
"description": "",
|
|
6
6
|
"description.zh-CN": "",
|
|
7
|
-
"version": "2.1.0-alpha.
|
|
7
|
+
"version": "2.1.0-alpha.8",
|
|
8
8
|
"main": "./dist/server/index.js",
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"devDependencies": {
|
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"@nocobase/test": "2.x",
|
|
25
25
|
"@nocobase/utils": "2.x"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "eda3bfb9df40d4394905e178f1c5331adbec4e76"
|
|
28
28
|
}
|