@svadmin/core 0.53.0 → 0.55.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/package.json +31 -1
- package/src/approval-contract.ts +131 -0
- package/src/dashboard-contract.ts +72 -0
- package/src/date-time.ts +55 -0
- package/src/enterprise-maturity.ts +54 -0
- package/src/export-format.ts +44 -0
- package/src/export-hooks.svelte.ts +169 -8
- package/src/filter-values.ts +42 -0
- package/src/gantt.ts +84 -0
- package/src/i18n.svelte.ts +306 -2
- package/src/import-contract.ts +94 -6
- package/src/import-hooks.svelte.ts +13 -5
- package/src/index.ts +38 -2
- package/src/pivot.ts +367 -0
- package/src/query-session.svelte.ts +6 -1
- package/src/resource-contract.ts +111 -2
- package/src/schema-form.ts +204 -0
- package/src/schema.ts +7 -0
- package/src/spreadsheet-clipboard.ts +45 -0
- package/src/spreadsheet-workbook.ts +64 -0
- package/src/spreadsheet-xlsx.ts +181 -0
- package/src/task-hooks.svelte.ts +115 -46
- package/src/types.ts +18 -0
package/src/i18n.svelte.ts
CHANGED
|
@@ -4,6 +4,25 @@ import { createContext } from 'svelte';
|
|
|
4
4
|
|
|
5
5
|
const baseLocales: Record<string, Record<string, string>> = {
|
|
6
6
|
'zh-CN': {
|
|
7
|
+
'dashboard.title': '仪表盘',
|
|
8
|
+
'dashboard.metrics': '指标概览',
|
|
9
|
+
'dashboard.empty': '暂无仪表盘数据',
|
|
10
|
+
'dashboard.load': '无法加载仪表盘数据。',
|
|
11
|
+
'dashboard.invalid': '仪表盘数据无效。',
|
|
12
|
+
'approval.title': '审批中心',
|
|
13
|
+
'approval.pending': '待办',
|
|
14
|
+
'approval.history': '审批历史',
|
|
15
|
+
'approval.search': '搜索审批',
|
|
16
|
+
'approval.empty': '暂无审批记录',
|
|
17
|
+
'approval.select': '请选择审批记录。',
|
|
18
|
+
'approval.applicant': '申请人:{name}',
|
|
19
|
+
'approval.attachments': '附件',
|
|
20
|
+
'approval.comment': '意见',
|
|
21
|
+
'approval.target': '转交目标',
|
|
22
|
+
'approval.conflict': '审批记录已变更,请刷新后再操作。',
|
|
23
|
+
'approval.invalid': '审批数据无效。',
|
|
24
|
+
'approval.load': '无法加载审批数据。',
|
|
25
|
+
'approval.action': '无法提交审批操作,请检查输入并确认操作结果。',
|
|
7
26
|
'common.list': '列表',
|
|
8
27
|
'common.prev': '上一页',
|
|
9
28
|
'common.pageOf': '第 {page} 页,共 {total} 页',
|
|
@@ -12,6 +31,7 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
12
31
|
'common.refresh': '刷新',
|
|
13
32
|
'common.importData': '导入数据',
|
|
14
33
|
'common.upload': '上传',
|
|
34
|
+
'common.processing': '处理中...',
|
|
15
35
|
'common.returnHome': '返回首页',
|
|
16
36
|
// Common
|
|
17
37
|
'common.add': '添加',
|
|
@@ -20,10 +40,121 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
20
40
|
'common.treeSelect': '树形选择',
|
|
21
41
|
'tree.toggleChildren': '展开或折叠子节点',
|
|
22
42
|
'tree.loadFailed': '子节点加载失败',
|
|
43
|
+
'tree.selectionFailed': '无法完成整组选择,请重试或缩小选择范围',
|
|
44
|
+
'tree.removeSelection': '移除 {label}',
|
|
45
|
+
'virtualTable.label': '数据表格',
|
|
46
|
+
'virtualTable.invalidKeys': '记录标识无效或重复,请检查数据。',
|
|
47
|
+
'virtualTable.totalRows': '共 {count} 行',
|
|
48
|
+
'treeTable.label': '树形表格',
|
|
49
|
+
'pivot.title': '透视分析',
|
|
50
|
+
'pivot.total': '合计',
|
|
51
|
+
'pivot.grandTotal': '总计',
|
|
52
|
+
'pivot.drilldown': '钻取明细',
|
|
53
|
+
'pivot.drilldownFailed': '无法打开明细,请重试。',
|
|
54
|
+
'pivot.sum': '求和',
|
|
55
|
+
'pivot.count': '记录数',
|
|
56
|
+
'pivot.avg': '平均值',
|
|
57
|
+
'pivot.min': '最小值',
|
|
58
|
+
'pivot.max': '最大值',
|
|
59
|
+
'pivot.invalidData': '透视表配置或维度无效。',
|
|
60
|
+
'pivot.limit': '透视表超过数据量或维度限制,请缩小查询范围。',
|
|
61
|
+
'pivot.invalidNumber': '统计字段包含非法数值,请先清理数据。',
|
|
62
|
+
'pivot.overflow': '聚合结果超出数值范围,请调整统计单位或使用服务端精确计算。',
|
|
63
|
+
'gantt.title': '项目甘特图',
|
|
64
|
+
'import.limit': '导入文件或记录数量超过限制。',
|
|
65
|
+
'import.invalidRow': '第 {row} 条记录不符合资源创建规则,请检查字段映射和原始数据。',
|
|
66
|
+
'import.invalidLimits': '导入容量配置无效。',
|
|
67
|
+
'import.taskConfiguration': '服务端导入需要任务 Provider、任务名称和幂等键。',
|
|
68
|
+
'import.taskPayloadLimit': '导入任务载荷超过配置的字节限制。',
|
|
69
|
+
'import.taskUnconfirmed': '任务提交结果未确认。请先查询任务中心,重试时必须使用相同的幂等键和载荷。',
|
|
70
|
+
'import.retryFailedRows': '重试可恢复失败行',
|
|
71
|
+
'dateInput.presets': '快捷范围',
|
|
72
|
+
'dateInput.to': '至',
|
|
73
|
+
'dateInput.unavailable': '该日期或时间不可选。',
|
|
74
|
+
'import.invalidTaskResult': '导入任务返回的结果不符合协议。',
|
|
75
|
+
'import.stopped': '已停止继续提交',
|
|
76
|
+
'import.stoppedHint': '已发出的请求可能已写入,结果尚未确认。重新导入前请核对服务端记录。',
|
|
77
|
+
'kanban.label': '看板',
|
|
78
|
+
'kanban.invalidData': '看板列或卡片数据无效。',
|
|
79
|
+
'kanban.limit': '看板超过列或卡片数量限制。',
|
|
80
|
+
'kanban.moveFailed': '移动未确认,已撤销本地预览;请核对服务端记录。',
|
|
81
|
+
'kanban.addFailed': '新增未确认,请核对服务端记录后再试。',
|
|
82
|
+
'kanban.addTo': '向 {column} 新增卡片',
|
|
83
|
+
'kanban.move': '移动 {card}',
|
|
84
|
+
'kanban.cardTitle': '卡片标题',
|
|
85
|
+
'gantt.task': '任务',
|
|
86
|
+
'gantt.noData': '暂无排期任务',
|
|
87
|
+
'gantt.invalidData': '甘特图任务或时间范围无效。',
|
|
88
|
+
'gantt.limit': '甘特图任务数量超过限制,请缩小范围。',
|
|
89
|
+
'gantt.dependency': '甘特图任务依赖无效、缺失或存在循环。',
|
|
90
|
+
'gantt.milestone': '里程碑,第 {day} 天',
|
|
91
|
+
'gantt.dependsOn': '前置任务:{tasks}',
|
|
92
|
+
'gantt.summary': '{tasks} 个任务 / {days} 天',
|
|
93
|
+
'gantt.range': '第 {start} 天至第 {end} 天',
|
|
94
|
+
'gantt.progress': '进度 {progress}%',
|
|
95
|
+
'gantt.status': '状态',
|
|
96
|
+
'gantt.planned': '已计划',
|
|
97
|
+
'gantt.in_progress': '进行中',
|
|
98
|
+
'gantt.completed': '已完成',
|
|
99
|
+
'gantt.delayed': '已延期',
|
|
100
|
+
'treeTable.invalidData': '树形数据或列定义无效,或超过节点/层级限制。',
|
|
101
|
+
'treeTable.expandAll': '全部展开',
|
|
102
|
+
'treeTable.collapseAll': '全部折叠',
|
|
103
|
+
'treeTable.expand': '展开',
|
|
104
|
+
'treeTable.collapse': '折叠',
|
|
105
|
+
'treeTable.previousPage': '上一页',
|
|
106
|
+
'treeTable.nextPage': '下一页',
|
|
107
|
+
'treeTable.sortColumn': '排序:{column}',
|
|
108
|
+
'treeTable.loadFailed': '子节点加载失败,请重试。',
|
|
109
|
+
'upload.selectOrDrop': '选择或拖放文件',
|
|
110
|
+
'upload.selectedCount': '已选择 {count}/{max} 个文件',
|
|
111
|
+
'upload.cleanupPending': '正在回收临时文件',
|
|
112
|
+
'upload.cleanupFailed': '临时文件回收失败',
|
|
113
|
+
'upload.rejectedFiles': '被拒绝的文件',
|
|
114
|
+
'spreadsheet.title': '电子表格',
|
|
115
|
+
'spreadsheet.row': '行',
|
|
116
|
+
'spreadsheet.column': '列',
|
|
117
|
+
'spreadsheet.export': '导出 CSV',
|
|
118
|
+
'spreadsheet.exportWorkbook': '导出工作簿',
|
|
119
|
+
'spreadsheet.exportXlsx': '导出 XLSX',
|
|
120
|
+
'spreadsheet.importWorkbook': '导入工作簿',
|
|
121
|
+
'spreadsheet.confirmImport': '确认导入',
|
|
122
|
+
'spreadsheet.replaceWorkbook': '替换当前工作簿?现有工作表将被完整替换。',
|
|
123
|
+
'spreadsheet.importRejected': '工作簿无效、过大或超出当前限制,未导入任何数据。',
|
|
124
|
+
'spreadsheet.bold': '粗体',
|
|
125
|
+
'spreadsheet.italic': '斜体',
|
|
126
|
+
'spreadsheet.numberFormat': '数字格式',
|
|
127
|
+
'spreadsheet.decimalPlaces': '小数位数',
|
|
128
|
+
'spreadsheet.general': '常规',
|
|
129
|
+
'spreadsheet.number': '数字',
|
|
130
|
+
'spreadsheet.percent': '百分比',
|
|
131
|
+
'spreadsheet.align': '对齐',
|
|
132
|
+
'spreadsheet.left': '左对齐',
|
|
133
|
+
'spreadsheet.center': '居中',
|
|
134
|
+
'spreadsheet.right': '右对齐',
|
|
135
|
+
'spreadsheet.formula': '单元格值或公式',
|
|
136
|
+
'spreadsheet.newSheet': '新建工作表',
|
|
137
|
+
'spreadsheet.undo': '撤销',
|
|
138
|
+
'spreadsheet.redo': '重做',
|
|
139
|
+
'spreadsheet.clear': '清空选区',
|
|
140
|
+
'spreadsheet.fill': '填充选区',
|
|
141
|
+
'spreadsheet.pasteRejected': '无法粘贴:数据格式无效、超出选定工作表或超过容量限制。',
|
|
142
|
+
'spreadsheet.sheetName': '工作表 {number}',
|
|
143
|
+
'spreadsheet.invalid': '工作表数据无效或超出安全限制,无法显示或导出。',
|
|
144
|
+
'spreadsheet.empty': '暂无工作表',
|
|
23
145
|
'common.cascader': '级联选择',
|
|
24
146
|
'common.transfer': '穿梭框',
|
|
25
147
|
'common.filterBuilder': '高级筛选',
|
|
26
148
|
'filter.incomplete': '请补全筛选条件后再应用。',
|
|
149
|
+
'filter.in': '属于集合',
|
|
150
|
+
'filter.nin': '不属于集合',
|
|
151
|
+
'filter.between': '位于范围内',
|
|
152
|
+
'filter.nbetween': '位于范围外',
|
|
153
|
+
'filter.rangeStart': '下界',
|
|
154
|
+
'filter.rangeEnd': '上界',
|
|
155
|
+
'filter.collectionItem': '集合值 {number}',
|
|
156
|
+
'filter.addValue': '添加集合值',
|
|
157
|
+
'filter.removeValue': '删除集合值',
|
|
27
158
|
'filter.readonly': '只读筛选条件',
|
|
28
159
|
'common.addRule': '添加筛选条件',
|
|
29
160
|
'common.sourceList': '源列表',
|
|
@@ -48,6 +179,9 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
48
179
|
'common.pageUnit': '页',
|
|
49
180
|
'common.all': '全部',
|
|
50
181
|
'common.search': '搜索...',
|
|
182
|
+
'select.loadFailed': '无法加载选项。',
|
|
183
|
+
'select.noResults': '无匹配结果。',
|
|
184
|
+
'select.loadMore': '加载更多',
|
|
51
185
|
'common.confirm': '确定',
|
|
52
186
|
'common.loading': '加载中...',
|
|
53
187
|
'common.noData': '暂无数据',
|
|
@@ -56,6 +190,7 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
56
190
|
'common.total': '共 {total} 条',
|
|
57
191
|
'common.page': '{current} / {total}',
|
|
58
192
|
'common.export': '导出',
|
|
193
|
+
'common.download': '下载',
|
|
59
194
|
'common.import': '导入',
|
|
60
195
|
'common.chooseFile': '选择文件',
|
|
61
196
|
'common.noFileChosen': '未选择文件',
|
|
@@ -86,11 +221,14 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
86
221
|
'common.autoSaving': '自动保存中...',
|
|
87
222
|
'common.autoSaved': '已自动保存',
|
|
88
223
|
'common.selectAll': '全选',
|
|
224
|
+
'common.selectAllOnPage': '全选当前页',
|
|
225
|
+
'common.selectAllMatching': '选择全部匹配记录 ({count})',
|
|
226
|
+
'common.allMatchingSelected': '已选择全部匹配记录 ({count})',
|
|
89
227
|
'common.selectRow': '选择记录 {id}',
|
|
90
228
|
'common.batchDelete': '批量删除 ({count})',
|
|
91
229
|
'common.selectedCount': '已选择 {count} 条记录',
|
|
92
|
-
'common.processing': '处理中...',
|
|
93
230
|
'common.openFullDetail': '完整详情',
|
|
231
|
+
'common.additionalDetails': '补充详情',
|
|
94
232
|
'common.unsavedChanges': '有未保存的修改,确定离开吗?',
|
|
95
233
|
'common.unsaved': '未保存',
|
|
96
234
|
'common.deleteConfirm': '确定要删除这条记录吗?此操作不可撤销。',
|
|
@@ -126,6 +264,7 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
126
264
|
'common.reset': '重置',
|
|
127
265
|
'common.welcome': '欢迎使用 {title}',
|
|
128
266
|
'common.dashboardHint': '从侧边栏选择一个资源开始。',
|
|
267
|
+
'common.dashboard': '工作台',
|
|
129
268
|
'common.redirecting': '正在跳转到登录页...',
|
|
130
269
|
'common.expand': '展开',
|
|
131
270
|
'common.collapse': '收起',
|
|
@@ -142,6 +281,19 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
142
281
|
'common.currentView': '当前未保存视图',
|
|
143
282
|
'common.viewName': '视图名称',
|
|
144
283
|
'common.saveView': '保存',
|
|
284
|
+
'common.viewSource': '视图归属',
|
|
285
|
+
'common.personalView': '个人',
|
|
286
|
+
'common.teamView': '团队',
|
|
287
|
+
'common.systemView': '系统',
|
|
288
|
+
'common.setDefaultView': '设为默认视图',
|
|
289
|
+
'common.unsetDefaultView': '取消默认视图',
|
|
290
|
+
'common.viewAccess': '视图可见范围',
|
|
291
|
+
'common.accessOrganization': '组织',
|
|
292
|
+
'common.accessRestricted': '指定成员',
|
|
293
|
+
'common.accessSubjects': '成员 ID(逗号分隔)',
|
|
294
|
+
'common.saveAccess': '保存可见范围',
|
|
295
|
+
'common.viewConflict': '视图已被修改,请刷新后重新确认。当前查询未被覆盖。',
|
|
296
|
+
'common.formErrors': '请修正以下错误',
|
|
145
297
|
'common.perPage': '每页',
|
|
146
298
|
'common.copyId': '复制 ID',
|
|
147
299
|
'common.moreActions': '更多操作',
|
|
@@ -714,6 +866,7 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
714
866
|
'common.refresh': 'Refresh',
|
|
715
867
|
'common.importData': 'Import data',
|
|
716
868
|
'common.upload': 'Upload',
|
|
869
|
+
'common.processing': 'Processing...',
|
|
717
870
|
'common.returnHome': 'Return to Home',
|
|
718
871
|
// Common
|
|
719
872
|
'common.add': 'Add',
|
|
@@ -722,13 +875,143 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
722
875
|
'common.treeSelect': 'Tree Select',
|
|
723
876
|
'tree.toggleChildren': 'Expand or collapse children',
|
|
724
877
|
'tree.loadFailed': 'Failed to load child nodes',
|
|
878
|
+
'tree.selectionFailed': 'Could not select the entire group. Retry or select a smaller branch.',
|
|
879
|
+
'tree.removeSelection': 'Remove {label}',
|
|
880
|
+
'virtualTable.label': 'Data table',
|
|
881
|
+
'virtualTable.invalidKeys': 'Record keys are invalid or duplicated. Check the data.',
|
|
882
|
+
'virtualTable.totalRows': 'Total {count} rows',
|
|
883
|
+
'treeTable.label': 'Tree table',
|
|
884
|
+
'pivot.title': 'Pivot Analysis',
|
|
885
|
+
'pivot.total': 'Total',
|
|
886
|
+
'pivot.grandTotal': 'Grand Total',
|
|
887
|
+
'pivot.drilldown': 'Drill down',
|
|
888
|
+
'pivot.drilldownFailed': 'Unable to open details. Please retry.',
|
|
889
|
+
'pivot.sum': 'Sum',
|
|
890
|
+
'pivot.count': 'Record count',
|
|
891
|
+
'pivot.avg': 'Average',
|
|
892
|
+
'pivot.min': 'Minimum',
|
|
893
|
+
'pivot.max': 'Maximum',
|
|
894
|
+
'pivot.invalidData': 'Invalid pivot configuration or dimensions.',
|
|
895
|
+
'pivot.limit': 'Pivot data or dimensions exceed the limit. Narrow the query.',
|
|
896
|
+
'pivot.invalidNumber': 'The measure contains invalid numbers. Clean the data first.',
|
|
897
|
+
'pivot.overflow': 'The aggregate exceeds the numeric range. Change units or use exact server-side calculation.',
|
|
898
|
+
'gantt.title': 'Project Gantt Schedule',
|
|
899
|
+
'import.limit': 'Import file or record count exceeds the limit.',
|
|
900
|
+
'import.invalidRow': 'Row {row} does not match the resource create schema. Check the mapping and source data.',
|
|
901
|
+
'import.invalidLimits': 'Invalid import capacity configuration.',
|
|
902
|
+
'import.taskConfiguration': 'Server import requires a task provider, task name and idempotency key.',
|
|
903
|
+
'import.taskPayloadLimit': 'The import task payload exceeds the configured byte limit.',
|
|
904
|
+
'import.taskUnconfirmed': 'Task submission is unconfirmed. Check the task center before retrying with the same idempotency key and payload.',
|
|
905
|
+
'import.retryFailedRows': 'Retry recoverable failed rows',
|
|
906
|
+
'dateInput.presets': 'Quick ranges',
|
|
907
|
+
'dateInput.to': 'to',
|
|
908
|
+
'dateInput.unavailable': 'This date or time is unavailable.',
|
|
909
|
+
'import.invalidTaskResult': 'The import task returned an invalid result.',
|
|
910
|
+
'import.stopped': 'Further submission stopped',
|
|
911
|
+
'import.stoppedHint': 'Requests already sent may have been committed. Check server records before importing again.',
|
|
912
|
+
'kanban.label': 'Kanban board',
|
|
913
|
+
'kanban.invalidData': 'Invalid Kanban column or card data.',
|
|
914
|
+
'kanban.limit': 'The Kanban board exceeds its column or card limit.',
|
|
915
|
+
'kanban.moveFailed': 'Move not confirmed. Local preview reverted; check server records.',
|
|
916
|
+
'kanban.addFailed': 'Creation not confirmed. Check server records before retrying.',
|
|
917
|
+
'kanban.addTo': 'Add card to {column}',
|
|
918
|
+
'kanban.move': 'Move {card}',
|
|
919
|
+
'kanban.cardTitle': 'Card title',
|
|
920
|
+
'gantt.task': 'Task',
|
|
921
|
+
'gantt.noData': 'No schedule tasks available',
|
|
922
|
+
'gantt.invalidData': 'Invalid Gantt task or timeline.',
|
|
923
|
+
'gantt.limit': 'The number of Gantt tasks exceeds the limit. Narrow the range.',
|
|
924
|
+
'gantt.dependency': 'Gantt task dependencies are missing, cyclic, or out of order.',
|
|
925
|
+
'gantt.milestone': 'Milestone, Day {day}',
|
|
926
|
+
'gantt.dependsOn': 'Depends on: {tasks}',
|
|
927
|
+
'gantt.summary': '{tasks} tasks / {days} days',
|
|
928
|
+
'gantt.range': 'Day {start} through day {end}',
|
|
929
|
+
'gantt.progress': 'Progress {progress}%',
|
|
930
|
+
'gantt.status': 'Status',
|
|
931
|
+
'gantt.planned': 'Planned',
|
|
932
|
+
'gantt.in_progress': 'In progress',
|
|
933
|
+
'gantt.completed': 'Completed',
|
|
934
|
+
'gantt.delayed': 'Delayed',
|
|
935
|
+
'treeTable.invalidData': 'Invalid tree data or columns, or node/depth limits exceeded.',
|
|
936
|
+
'treeTable.expandAll': 'Expand All',
|
|
937
|
+
'treeTable.collapseAll': 'Collapse All',
|
|
938
|
+
'treeTable.expand': 'Expand',
|
|
939
|
+
'treeTable.collapse': 'Collapse',
|
|
940
|
+
'treeTable.previousPage': 'Previous page',
|
|
941
|
+
'treeTable.nextPage': 'Next page',
|
|
942
|
+
'treeTable.sortColumn': 'Sort: {column}',
|
|
943
|
+
'treeTable.loadFailed': 'Unable to load children. Please retry.',
|
|
944
|
+
'upload.selectOrDrop': 'Select or drop files',
|
|
945
|
+
'upload.selectedCount': '{count}/{max} files selected',
|
|
946
|
+
'upload.cleanupPending': 'Cleanup pending',
|
|
947
|
+
'upload.cleanupFailed': 'Cleanup failed',
|
|
948
|
+
'upload.rejectedFiles': 'Rejected files',
|
|
949
|
+
'spreadsheet.title': 'Spreadsheet',
|
|
950
|
+
'spreadsheet.row': 'Row',
|
|
951
|
+
'spreadsheet.column': 'Column',
|
|
952
|
+
'spreadsheet.export': 'Export CSV',
|
|
953
|
+
'spreadsheet.exportWorkbook': 'Export workbook',
|
|
954
|
+
'spreadsheet.exportXlsx': 'Export XLSX',
|
|
955
|
+
'spreadsheet.importWorkbook': 'Import workbook',
|
|
956
|
+
'spreadsheet.confirmImport': 'Confirm import',
|
|
957
|
+
'spreadsheet.replaceWorkbook': 'Replace the current workbook? All existing sheets will be replaced.',
|
|
958
|
+
'spreadsheet.importRejected': 'The workbook is invalid, too large, or exceeds current limits. Nothing was imported.',
|
|
959
|
+
'spreadsheet.bold': 'Bold',
|
|
960
|
+
'spreadsheet.italic': 'Italic',
|
|
961
|
+
'spreadsheet.numberFormat': 'Number format',
|
|
962
|
+
'spreadsheet.decimalPlaces': 'Decimal places',
|
|
963
|
+
'spreadsheet.general': 'General',
|
|
964
|
+
'spreadsheet.number': 'Number',
|
|
965
|
+
'spreadsheet.percent': 'Percent',
|
|
966
|
+
'spreadsheet.align': 'Alignment',
|
|
967
|
+
'spreadsheet.left': 'Left',
|
|
968
|
+
'spreadsheet.center': 'Center',
|
|
969
|
+
'spreadsheet.right': 'Right',
|
|
970
|
+
'spreadsheet.formula': 'Cell value or formula',
|
|
971
|
+
'spreadsheet.newSheet': 'New Sheet',
|
|
972
|
+
'spreadsheet.undo': 'Undo',
|
|
973
|
+
'spreadsheet.redo': 'Redo',
|
|
974
|
+
'spreadsheet.clear': 'Clear selection',
|
|
975
|
+
'spreadsheet.fill': 'Fill selection',
|
|
976
|
+
'spreadsheet.pasteRejected': 'Cannot paste: invalid data, outside the current sheet, or capacity exceeded.',
|
|
977
|
+
'spreadsheet.sheetName': 'Sheet {number}',
|
|
978
|
+
'spreadsheet.invalid': 'Sheet data is invalid or exceeds safety limits. Display and export are unavailable.',
|
|
979
|
+
'spreadsheet.empty': 'No sheets',
|
|
725
980
|
'common.cascader': 'Cascader',
|
|
726
981
|
'common.transfer': 'Transfer',
|
|
727
982
|
'common.filterBuilder': 'Filter Builder',
|
|
728
983
|
'filter.incomplete': 'Complete the filter conditions before applying.',
|
|
984
|
+
'filter.in': 'In set',
|
|
985
|
+
'filter.nin': 'Not in set',
|
|
986
|
+
'filter.between': 'Between',
|
|
987
|
+
'filter.nbetween': 'Not between',
|
|
988
|
+
'filter.rangeStart': 'Lower bound',
|
|
989
|
+
'filter.rangeEnd': 'Upper bound',
|
|
990
|
+
'filter.collectionItem': 'Set value {number}',
|
|
991
|
+
'filter.addValue': 'Add set value',
|
|
992
|
+
'filter.removeValue': 'Remove set value',
|
|
729
993
|
'filter.readonly': 'Read-only filter condition',
|
|
730
994
|
'common.addRule': 'Add Filter Rule',
|
|
731
995
|
'common.sourceList': 'Source',
|
|
996
|
+
'dashboard.title': 'Dashboard',
|
|
997
|
+
'dashboard.metrics': 'Metrics overview',
|
|
998
|
+
'dashboard.empty': 'No dashboard data',
|
|
999
|
+
'dashboard.load': 'Unable to load dashboard data.',
|
|
1000
|
+
'dashboard.invalid': 'Dashboard data is invalid.',
|
|
1001
|
+
'approval.title': 'Approval center',
|
|
1002
|
+
'approval.pending': 'Pending',
|
|
1003
|
+
'approval.history': 'Approval history',
|
|
1004
|
+
'approval.search': 'Search approvals',
|
|
1005
|
+
'approval.empty': 'No approval requests',
|
|
1006
|
+
'approval.select': 'Select an approval request.',
|
|
1007
|
+
'approval.applicant': 'Applicant: {name}',
|
|
1008
|
+
'approval.attachments': 'Attachments',
|
|
1009
|
+
'approval.comment': 'Comment',
|
|
1010
|
+
'approval.target': 'Target',
|
|
1011
|
+
'approval.conflict': 'This request changed. Refresh before acting.',
|
|
1012
|
+
'approval.invalid': 'Approval data is invalid.',
|
|
1013
|
+
'approval.load': 'Unable to load approval data.',
|
|
1014
|
+
'approval.action': 'Unable to submit the action. Check the input and confirm the outcome.',
|
|
732
1015
|
'common.targetList': 'Target',
|
|
733
1016
|
'common.selectLeafOnly': 'Select leaf only',
|
|
734
1017
|
'common.save': 'Save',
|
|
@@ -750,6 +1033,9 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
750
1033
|
'common.pageUnit': 'Page',
|
|
751
1034
|
'common.all': 'All',
|
|
752
1035
|
'common.search': 'Search...',
|
|
1036
|
+
'select.loadFailed': 'Unable to load options.',
|
|
1037
|
+
'select.noResults': 'No results.',
|
|
1038
|
+
'select.loadMore': 'Load more',
|
|
753
1039
|
'common.confirm': 'Confirm',
|
|
754
1040
|
'common.loading': 'Loading...',
|
|
755
1041
|
'common.noData': 'No data',
|
|
@@ -758,6 +1044,7 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
758
1044
|
'common.total': 'Total: {total}',
|
|
759
1045
|
'common.page': '{current} / {total}',
|
|
760
1046
|
'common.export': 'Export',
|
|
1047
|
+
'common.download': 'Download',
|
|
761
1048
|
'common.import': 'Import',
|
|
762
1049
|
'common.chooseFile': 'Choose file',
|
|
763
1050
|
'common.noFileChosen': 'No file chosen',
|
|
@@ -788,11 +1075,14 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
788
1075
|
'common.autoSaving': 'Auto-saving...',
|
|
789
1076
|
'common.autoSaved': 'Saved',
|
|
790
1077
|
'common.selectAll': 'Select All',
|
|
1078
|
+
'common.selectAllOnPage': 'Select all on this page',
|
|
1079
|
+
'common.selectAllMatching': 'Select all matching records ({count})',
|
|
1080
|
+
'common.allMatchingSelected': 'All matching records selected ({count})',
|
|
791
1081
|
'common.selectRow': 'Select record {id}',
|
|
792
1082
|
'common.batchDelete': 'Batch Delete ({count})',
|
|
793
1083
|
'common.selectedCount': '{count} records selected',
|
|
794
|
-
'common.processing': 'Processing...',
|
|
795
1084
|
'common.openFullDetail': 'Full details',
|
|
1085
|
+
'common.additionalDetails': 'Additional details',
|
|
796
1086
|
'common.unsavedChanges': 'You have unsaved changes. Leave anyway?',
|
|
797
1087
|
'common.unsaved': 'Unsaved',
|
|
798
1088
|
'common.deleteConfirm': 'Are you sure you want to delete this record? This cannot be undone.',
|
|
@@ -828,6 +1118,7 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
828
1118
|
'common.reset': 'Reset',
|
|
829
1119
|
'common.welcome': 'Welcome to {title}',
|
|
830
1120
|
'common.dashboardHint': 'Select a resource from the sidebar to get started.',
|
|
1121
|
+
'common.dashboard': 'Dashboard',
|
|
831
1122
|
'common.redirecting': 'Redirecting to login...',
|
|
832
1123
|
'common.expand': 'Expand',
|
|
833
1124
|
'common.collapse': 'Collapse',
|
|
@@ -844,6 +1135,19 @@ const baseLocales: Record<string, Record<string, string>> = {
|
|
|
844
1135
|
'common.currentView': 'Current unsaved view',
|
|
845
1136
|
'common.viewName': 'View name',
|
|
846
1137
|
'common.saveView': 'Save',
|
|
1138
|
+
'common.viewSource': 'View ownership',
|
|
1139
|
+
'common.personalView': 'Personal',
|
|
1140
|
+
'common.teamView': 'Team',
|
|
1141
|
+
'common.systemView': 'System',
|
|
1142
|
+
'common.setDefaultView': 'Set as default view',
|
|
1143
|
+
'common.unsetDefaultView': 'Unset default view',
|
|
1144
|
+
'common.viewAccess': 'View visibility',
|
|
1145
|
+
'common.accessOrganization': 'Organization',
|
|
1146
|
+
'common.accessRestricted': 'Specific members',
|
|
1147
|
+
'common.accessSubjects': 'Member IDs (comma separated)',
|
|
1148
|
+
'common.saveAccess': 'Save visibility',
|
|
1149
|
+
'common.viewConflict': 'The view has changed. Refresh before trying again. Your current query is unchanged.',
|
|
1150
|
+
'common.formErrors': 'Please fix the following errors',
|
|
847
1151
|
'common.perPage': 'per page',
|
|
848
1152
|
'common.copyId': 'Copy ID',
|
|
849
1153
|
'common.moreActions': 'More actions',
|
package/src/import-contract.ts
CHANGED
|
@@ -7,12 +7,65 @@ import { parseContractId, parseContractRecord, type ContractSchemas, type Resour
|
|
|
7
7
|
import { decodeOneResult, decodeManyResult, rejectProviderResponse } from './record-decoder';
|
|
8
8
|
|
|
9
9
|
export type ImportFormat = 'auto' | 'csv' | 'json';
|
|
10
|
+
export const IMPORT_LIMITS = Object.freeze({ maxRows: 10_000, maxBytes: 50 * 1024 * 1024 });
|
|
11
|
+
export interface ImportArtifact {
|
|
12
|
+
artifactId: string;
|
|
13
|
+
fileName: string;
|
|
14
|
+
size: number;
|
|
15
|
+
contentType?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ImportArtifactProvider {
|
|
18
|
+
upload: (input: {
|
|
19
|
+
file: File; fileName: string; size: number; resource: string;
|
|
20
|
+
idempotencyKey: string; signal: AbortSignal;
|
|
21
|
+
}) => Promise<unknown>;
|
|
22
|
+
}
|
|
10
23
|
const progressSchema = Type.Object({
|
|
11
24
|
totalAmount: Type.Integer({ minimum: 0 }),
|
|
12
25
|
attemptedAmount: Type.Integer({ minimum: 0 }),
|
|
13
26
|
processedAmount: Type.Integer({ minimum: 0 }),
|
|
14
27
|
}, { additionalProperties: false });
|
|
15
28
|
export type ImportOperationProgress = Static<typeof progressSchema>;
|
|
29
|
+
const importTaskResultSchema = Type.Object({
|
|
30
|
+
succeeded: Type.Integer({ minimum: 0, maximum: IMPORT_LIMITS.maxRows }),
|
|
31
|
+
failed: Type.Integer({ minimum: 0, maximum: IMPORT_LIMITS.maxRows }),
|
|
32
|
+
failedRows: Type.Optional(Type.Array(Type.Object({
|
|
33
|
+
row: Type.Integer({ minimum: 1, maximum: IMPORT_LIMITS.maxRows }),
|
|
34
|
+
error: Type.String({ maxLength: 2000 }),
|
|
35
|
+
}, { additionalProperties: false }), { maxItems: IMPORT_LIMITS.maxRows })),
|
|
36
|
+
retry: Type.Optional(Type.Object({
|
|
37
|
+
receiptId: Type.String({ minLength: 1, maxLength: 200 }),
|
|
38
|
+
rows: Type.Array(Type.Integer({ minimum: 1, maximum: IMPORT_LIMITS.maxRows }), {
|
|
39
|
+
minItems: 1, maxItems: IMPORT_LIMITS.maxRows, uniqueItems: true,
|
|
40
|
+
}),
|
|
41
|
+
}, { additionalProperties: false })),
|
|
42
|
+
}, { additionalProperties: false });
|
|
43
|
+
export type ImportTaskResult = Static<typeof importTaskResultSchema>;
|
|
44
|
+
|
|
45
|
+
export function snapshotImportArtifact(value: unknown): ImportArtifact | undefined {
|
|
46
|
+
try {
|
|
47
|
+
const candidate = snapshotPlainData(value);
|
|
48
|
+
if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate)) return;
|
|
49
|
+
const artifactId = candidate['artifactId'];
|
|
50
|
+
const fileName = candidate['fileName'];
|
|
51
|
+
const size = candidate['size'];
|
|
52
|
+
const contentType = candidate['contentType'];
|
|
53
|
+
if (typeof artifactId !== 'string' || !artifactId.trim() || artifactId.length > 200
|
|
54
|
+
|| typeof fileName !== 'string' || !fileName.trim() || fileName.length > 255
|
|
55
|
+
|| typeof size !== 'number') return;
|
|
56
|
+
if (!Number.isSafeInteger(size) || size < 0 || size > IMPORT_LIMITS.maxBytes
|
|
57
|
+
|| Object.keys(candidate).some(key => !['artifactId', 'fileName', 'size', 'contentType'].includes(key))
|
|
58
|
+
|| (contentType !== undefined && (typeof contentType !== 'string' || contentType.length > 200))) return;
|
|
59
|
+
return {
|
|
60
|
+
artifactId,
|
|
61
|
+
fileName,
|
|
62
|
+
size,
|
|
63
|
+
...(contentType === undefined ? {} : { contentType }),
|
|
64
|
+
};
|
|
65
|
+
} catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
16
69
|
|
|
17
70
|
export function newImportProgress(): ImportOperationProgress {
|
|
18
71
|
return { totalAmount: 0, attemptedAmount: 0, processedAmount: 0 };
|
|
@@ -26,6 +79,22 @@ export function snapshotImportProgress(value: unknown): ImportOperationProgress
|
|
|
26
79
|
return progress;
|
|
27
80
|
}
|
|
28
81
|
|
|
82
|
+
export function snapshotImportTaskResult(value: unknown): ImportTaskResult | undefined {
|
|
83
|
+
try {
|
|
84
|
+
const snapshot = snapshotPlainData(value);
|
|
85
|
+
if (!checkExact(importTaskResultSchema, snapshot) ||
|
|
86
|
+
snapshot.succeeded + snapshot.failed > IMPORT_LIMITS.maxRows ||
|
|
87
|
+
(snapshot.failedRows?.length ?? 0) > snapshot.failed ||
|
|
88
|
+
new Set(snapshot.failedRows?.map(row => row.row)).size !== (snapshot.failedRows?.length ?? 0)) return undefined;
|
|
89
|
+
const failedRowIds = new Set(snapshot.failedRows?.map(row => row.row));
|
|
90
|
+
if (snapshot.retry && (!snapshot.retry.receiptId.trim() ||
|
|
91
|
+
snapshot.retry.rows.some(row => !failedRowIds.has(row)))) return undefined;
|
|
92
|
+
return snapshot;
|
|
93
|
+
} catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
29
98
|
export function invalidImportInput(row?: number): never {
|
|
30
99
|
throw new HttpError('Invalid import input', 422, undefined, {
|
|
31
100
|
code: 'INVALID_RESOURCE_INPUT',
|
|
@@ -33,6 +102,12 @@ export function invalidImportInput(row?: number): never {
|
|
|
33
102
|
});
|
|
34
103
|
}
|
|
35
104
|
|
|
105
|
+
export function importLimitExceeded(): never {
|
|
106
|
+
throw new HttpError('Import exceeds the configured limit', 413, undefined, {
|
|
107
|
+
code: 'IMPORT_LIMIT_EXCEEDED', details: { phase: 'input', writeMayHaveSucceeded: false },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
36
111
|
/** Files are the only non-JSON input; do not invoke caller-supplied getters or text methods. */
|
|
37
112
|
export function snapshotImportFile(value: unknown) {
|
|
38
113
|
try {
|
|
@@ -46,8 +121,10 @@ export function snapshotImportFile(value: unknown) {
|
|
|
46
121
|
if (ownName && !('value' in ownName)) return invalidImportInput();
|
|
47
122
|
const name: unknown = ownName ? ownName.value : Object.getOwnPropertyDescriptor(File.prototype, 'name')?.get?.call(file);
|
|
48
123
|
if (typeof name !== 'string') return invalidImportInput();
|
|
124
|
+
const size: unknown = Object.getOwnPropertyDescriptor(Blob.prototype, 'size')?.get?.call(file);
|
|
125
|
+
if (typeof size !== 'number' || !Number.isSafeInteger(size) || size < 0) return invalidImportInput();
|
|
49
126
|
const read = Blob.prototype.text.bind(file);
|
|
50
|
-
return { file, name, read };
|
|
127
|
+
return { file, name, size, read };
|
|
51
128
|
} catch {
|
|
52
129
|
return invalidImportInput();
|
|
53
130
|
}
|
|
@@ -57,27 +134,38 @@ const rowsSchema = Type.Array(Type.Record(Type.String(), Type.Unknown()));
|
|
|
57
134
|
const csvSchema = Type.Array(Type.Array(Type.String()));
|
|
58
135
|
|
|
59
136
|
/** CSV values stay strings; only an explicit mapper may supply business conversions. */
|
|
60
|
-
export function parseImportRows(text: string, name: string, format: ImportFormat): Record<string, unknown>[] {
|
|
137
|
+
export function parseImportRows(text: string, name: string, format: ImportFormat, maxRows: number = IMPORT_LIMITS.maxRows): Record<string, unknown>[] {
|
|
61
138
|
try {
|
|
139
|
+
if (!Number.isSafeInteger(maxRows) || maxRows < 1 || maxRows > IMPORT_LIMITS.maxRows) return invalidImportInput();
|
|
140
|
+
if (text.length > IMPORT_LIMITS.maxBytes) return importLimitExceeded();
|
|
62
141
|
const source = text.charCodeAt(0) === 0xFEFF ? text.slice(1) : text;
|
|
63
142
|
const selected = format === 'auto' ? name.toLowerCase().endsWith('.json') ? 'json'
|
|
64
143
|
: name.toLowerCase().endsWith('.csv') ? 'csv' : undefined : format;
|
|
65
144
|
if (selected === 'json') {
|
|
66
|
-
const
|
|
67
|
-
|
|
145
|
+
const parsed: unknown = JSON.parse(source);
|
|
146
|
+
if (Array.isArray(parsed) && parsed.length > maxRows) return importLimitExceeded();
|
|
147
|
+
const data = snapshotPlainData(parsed);
|
|
148
|
+
if (!checkExact(rowsSchema, data)) return invalidImportInput();
|
|
149
|
+
if (data.length > maxRows) return importLimitExceeded();
|
|
150
|
+
return data;
|
|
68
151
|
}
|
|
69
152
|
if (selected !== 'csv') return invalidImportInput();
|
|
70
|
-
const parsed = Papa.parse<unknown>(source, {
|
|
153
|
+
const parsed = Papa.parse<unknown>(source, {
|
|
154
|
+
delimiter: ',', dynamicTyping: false, skipEmptyLines: true, preview: maxRows + 2,
|
|
155
|
+
});
|
|
156
|
+
if (parsed.data.length > maxRows + 1) return importLimitExceeded();
|
|
71
157
|
const data = snapshotPlainData(parsed.data);
|
|
72
158
|
if (parsed.errors.length || !checkExact(csvSchema, data)) return invalidImportInput();
|
|
73
159
|
const [headers, ...rows] = data;
|
|
74
160
|
if (!headers) return [];
|
|
75
161
|
if (headers.some(header => header.trim() === '') || new Set(headers).size !== headers.length) return invalidImportInput();
|
|
162
|
+
if (rows.length > maxRows) return importLimitExceeded();
|
|
76
163
|
return rows.map((row, index) => {
|
|
77
164
|
if (row.length !== headers.length) return invalidImportInput(index + 1);
|
|
78
165
|
return Object.fromEntries(headers.map((header, column) => [header, row[column]]));
|
|
79
166
|
});
|
|
80
|
-
} catch {
|
|
167
|
+
} catch (cause) {
|
|
168
|
+
if (cause instanceof HttpError && cause.code === 'IMPORT_LIMIT_EXCEEDED') throw cause;
|
|
81
169
|
return invalidImportInput();
|
|
82
170
|
}
|
|
83
171
|
}
|
|
@@ -11,7 +11,7 @@ import { invalidateOwnedQueries } from './query-invalidation';
|
|
|
11
11
|
import { captureAuthLiveScope, clearAuthQueries, handleAuthError } from './auth-hooks.svelte';
|
|
12
12
|
import { HttpError } from './types';
|
|
13
13
|
import { decodeImportReceipt, invalidImportInput, parseImportRows, snapshotImportFile,
|
|
14
|
-
newImportProgress, snapshotImportProgress, type ImportOperationProgress, type ImportFormat } from './import-contract';
|
|
14
|
+
importLimitExceeded, newImportProgress, snapshotImportProgress, IMPORT_LIMITS, type ImportOperationProgress, type ImportFormat } from './import-contract';
|
|
15
15
|
|
|
16
16
|
export type ImportProgress = Pick<ImportOperationProgress, 'totalAmount' | 'processedAmount'>;
|
|
17
17
|
export interface ImportResult<S extends ContractSchemas> {
|
|
@@ -23,6 +23,8 @@ export interface UseImportOptions<S extends ContractSchemas> {
|
|
|
23
23
|
format?: ImportFormat;
|
|
24
24
|
mapData?: (item: Record<string, unknown>) => NoInfer<ContractCreateInput<S>>;
|
|
25
25
|
batchSize?: number;
|
|
26
|
+
maxRows?: number;
|
|
27
|
+
maxBytes?: number;
|
|
26
28
|
enabled?: boolean;
|
|
27
29
|
meta?: Record<string, unknown>;
|
|
28
30
|
dataProviderName?: string;
|
|
@@ -50,10 +52,11 @@ function importFailure(cause: unknown, dispatched = false): HttpError {
|
|
|
50
52
|
const input = code === 'INVALID_RESOURCE_INPUT' && !dispatched;
|
|
51
53
|
const contract = code === 'INVALID_RESOURCE_CONTRACT' && !dispatched;
|
|
52
54
|
const response = code === 'INVALID_PROVIDER_RESPONSE';
|
|
53
|
-
|
|
55
|
+
const limit = code === 'IMPORT_LIMIT_EXCEEDED' && !dispatched;
|
|
56
|
+
return new HttpError(limit ? 'Import exceeds the configured limit' : input ? 'Invalid import input' : contract ? 'A create schema is required'
|
|
54
57
|
: response ? 'Invalid provider response' : 'Import failed', input ? 422 : contract ? 400 : response ? 502
|
|
55
|
-
: status === 401 || status === 403 ? status : 500, undefined, {
|
|
56
|
-
code: input ? 'INVALID_RESOURCE_INPUT' : contract ? 'INVALID_RESOURCE_CONTRACT'
|
|
58
|
+
: limit ? 413 : status === 401 || status === 403 ? status : 500, undefined, {
|
|
59
|
+
code: limit ? 'IMPORT_LIMIT_EXCEEDED' : input ? 'INVALID_RESOURCE_INPUT' : contract ? 'INVALID_RESOURCE_CONTRACT'
|
|
57
60
|
: response ? 'INVALID_PROVIDER_RESPONSE' : 'IMPORT_FAILED',
|
|
58
61
|
details: { phase: dispatched ? response ? 'response' : 'request' : 'input', writeMayHaveSucceeded: dispatched },
|
|
59
62
|
});
|
|
@@ -197,13 +200,18 @@ export function useImport<S extends ContractSchemas>(options: UseImportOptions<S
|
|
|
197
200
|
}
|
|
198
201
|
requireContractCreateSchema(scope.contract);
|
|
199
202
|
const batchSize = options.batchSize ?? 1;
|
|
203
|
+
const maxRows = options.maxRows ?? IMPORT_LIMITS.maxRows;
|
|
204
|
+
const maxBytes = options.maxBytes ?? IMPORT_LIMITS.maxBytes;
|
|
200
205
|
const format = options.format ?? 'auto';
|
|
201
206
|
const mapData = options.mapData;
|
|
202
207
|
const onFinish = options.onFinish;
|
|
203
208
|
const onProgress = options.onProgress;
|
|
204
209
|
if (!Number.isSafeInteger(batchSize) || batchSize < 1 || !['auto', 'csv', 'json'].includes(format) ||
|
|
210
|
+
!Number.isSafeInteger(maxRows) || maxRows < 1 || maxRows > IMPORT_LIMITS.maxRows ||
|
|
211
|
+
!Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > IMPORT_LIMITS.maxBytes ||
|
|
205
212
|
(options.enabled !== undefined && typeof options.enabled !== 'boolean') ||
|
|
206
213
|
[mapData, onFinish, onProgress].some(callback => callback !== undefined && typeof callback !== 'function')) invalidImportInput();
|
|
214
|
+
if (file.size > maxBytes) importLimitExceeded();
|
|
207
215
|
if (!originCurrent(origin)) return Promise.reject(cancelled());
|
|
208
216
|
latestToken = token;
|
|
209
217
|
latest = Object.freeze({ token, origin, scope });
|
|
@@ -219,7 +227,7 @@ export function useImport<S extends ContractSchemas>(options: UseImportOptions<S
|
|
|
219
227
|
ensureActive();
|
|
220
228
|
const text = await file.read();
|
|
221
229
|
ensureActive();
|
|
222
|
-
const records = parseImportRows(text, file.name, format).map((row, index) => {
|
|
230
|
+
const records = parseImportRows(text, file.name, format, maxRows).map((row, index) => {
|
|
223
231
|
ensureActive();
|
|
224
232
|
try {
|
|
225
233
|
const input = mapData ? mapData(decodeBaseRecord(snapshotPlainData(row))) : row;
|