@nocobase/plugin-field-sequence 3.0.0-alpha.1 → 3.0.0-alpha.12
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/externalVersion.js
CHANGED
|
@@ -9,18 +9,18 @@
|
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
11
|
"react": "18.2.0",
|
|
12
|
-
"@nocobase/client": "3.0.0-alpha.
|
|
13
|
-
"@nocobase/flow-engine": "3.0.0-alpha.
|
|
12
|
+
"@nocobase/client": "3.0.0-alpha.12",
|
|
13
|
+
"@nocobase/flow-engine": "3.0.0-alpha.12",
|
|
14
14
|
"@formily/antd-v5": "1.2.3",
|
|
15
15
|
"@formily/core": "2.3.7",
|
|
16
16
|
"@formily/react": "2.3.7",
|
|
17
|
-
"@nocobase/utils": "3.0.0-alpha.
|
|
17
|
+
"@nocobase/utils": "3.0.0-alpha.12",
|
|
18
18
|
"antd": "5.24.2",
|
|
19
19
|
"react-i18next": "11.18.6",
|
|
20
20
|
"@ant-design/icons": "5.6.1",
|
|
21
|
-
"@nocobase/client-v2": "3.0.0-alpha.
|
|
21
|
+
"@nocobase/client-v2": "3.0.0-alpha.12",
|
|
22
22
|
"lodash": "4.18.1",
|
|
23
|
-
"@nocobase/server": "3.0.0-alpha.
|
|
24
|
-
"@nocobase/database": "3.0.0-alpha.
|
|
23
|
+
"@nocobase/server": "3.0.0-alpha.12",
|
|
24
|
+
"@nocobase/database": "3.0.0-alpha.12",
|
|
25
25
|
"dayjs": "1.11.13"
|
|
26
26
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"cron-parser","version":"4.4.0","description":"Node.js library for parsing crontab instructions","main":"lib/parser.js","types":"index.d.ts","typesVersions":{"<4.1":{"*":["types/ts3/*"]}},"directories":{"test":"test"},"scripts":{"test:tsd":"tsd","test:unit":"TZ=UTC tap ./test/*.js","test:cover":"TZ=UTC tap --coverage-report=html ./test/*.js","lint":"eslint .","lint:fix":"eslint --fix .","test":"npm run lint && npm run test:unit && npm run test:tsd"},"repository":{"type":"git","url":"https://github.com/harrisiirak/cron-parser.git"},"keywords":["cron","crontab","parser"],"author":"Harri Siirak","contributors":["Nicholas Clawson","Daniel Prentis <daniel@salsitasoft.com>","Renault John Lecoultre","Richard Astbury <richard.astbury@gmail.com>","Meaglin Wasabi <Meaglin.wasabi@gmail.com>","Mike Kusold <hello@mikekusold.com>","Alex Kit <alex.kit@atmajs.com>","Santiago Gimeno <santiago.gimeno@gmail.com>","Daniel <darc.tec@gmail.com>","Christian Steininger <christian.steininger.cs@gmail.com>","Mykola Piskovyi <m.piskovyi@gmail.com>","Brian Vaughn <brian.david.vaughn@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Yasuhiroki <yasuhiroki.duck@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Brendan Warkentin <faazshift@gmail.com>","Charlie Fish <fishcharlie.code@gmail.com>","Ian Graves <ian+diskimage@iangrav.es>","Andy Thompson <me@andytson.com>","Regev Brody <regevbr@gmail.com>"],"license":"MIT","dependencies":{"luxon":"^1.28.0"},"devDependencies":{"eslint":"^8.2.0","sinon":"^10.0.0","tap":"^16.0.1","tsd":"^0.19.0"},"engines":{"node":">=0.8"},"browser":{"fs":false},"tap":{"check-coverage":false},"tsd":{"directory":"test","compilerOptions":{"lib":["es2017","dom"]}},"_lastModified":"2026-
|
|
1
|
+
{"name":"cron-parser","version":"4.4.0","description":"Node.js library for parsing crontab instructions","main":"lib/parser.js","types":"index.d.ts","typesVersions":{"<4.1":{"*":["types/ts3/*"]}},"directories":{"test":"test"},"scripts":{"test:tsd":"tsd","test:unit":"TZ=UTC tap ./test/*.js","test:cover":"TZ=UTC tap --coverage-report=html ./test/*.js","lint":"eslint .","lint:fix":"eslint --fix .","test":"npm run lint && npm run test:unit && npm run test:tsd"},"repository":{"type":"git","url":"https://github.com/harrisiirak/cron-parser.git"},"keywords":["cron","crontab","parser"],"author":"Harri Siirak","contributors":["Nicholas Clawson","Daniel Prentis <daniel@salsitasoft.com>","Renault John Lecoultre","Richard Astbury <richard.astbury@gmail.com>","Meaglin Wasabi <Meaglin.wasabi@gmail.com>","Mike Kusold <hello@mikekusold.com>","Alex Kit <alex.kit@atmajs.com>","Santiago Gimeno <santiago.gimeno@gmail.com>","Daniel <darc.tec@gmail.com>","Christian Steininger <christian.steininger.cs@gmail.com>","Mykola Piskovyi <m.piskovyi@gmail.com>","Brian Vaughn <brian.david.vaughn@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Yasuhiroki <yasuhiroki.duck@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Brendan Warkentin <faazshift@gmail.com>","Charlie Fish <fishcharlie.code@gmail.com>","Ian Graves <ian+diskimage@iangrav.es>","Andy Thompson <me@andytson.com>","Regev Brody <regevbr@gmail.com>"],"license":"MIT","dependencies":{"luxon":"^1.28.0"},"devDependencies":{"eslint":"^8.2.0","sinon":"^10.0.0","tap":"^16.0.1","tsd":"^0.19.0"},"engines":{"node":">=0.8"},"browser":{"fs":false},"tap":{"check-coverage":false},"tsd":{"directory":"test","compilerOptions":{"lib":["es2017","dom"]}},"_lastModified":"2026-08-26T01:59:45.989Z"}
|
package/dist/server/Plugin.js
CHANGED
|
@@ -152,15 +152,11 @@ class PluginFieldSequenceServer extends import_server.Plugin {
|
|
|
152
152
|
);
|
|
153
153
|
return;
|
|
154
154
|
}
|
|
155
|
-
const [record] = await collection.model.findAll({
|
|
156
|
-
order: [[(autoIncrementField == null ? void 0 : autoIncrementField.name) ?? (createAtField == null ? void 0 : createAtField.name), "DESC"]],
|
|
157
|
-
limit: 1
|
|
158
|
-
});
|
|
159
|
-
if (!record) {
|
|
160
|
-
app.log.warn(`Collection [${collection.name}] has no records. Skipping sequences repair.`);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
155
|
const sequencesFieldSet = import_lodash.default.uniq(sequencesList.map(({ field }) => field));
|
|
156
|
+
const primaryKeyFields = fields.filter((field) => field.options.primaryKey);
|
|
157
|
+
const sortablePrimaryKeyFields = primaryKeyFields.filter(
|
|
158
|
+
(field) => field.type === "bigInt" || field.type === "snowflakeId"
|
|
159
|
+
);
|
|
164
160
|
for (const sequencesField of sequencesFieldSet) {
|
|
165
161
|
const field = fieldMap[sequencesField];
|
|
166
162
|
app.log.info(
|
|
@@ -178,7 +174,89 @@ class PluginFieldSequenceServer extends import_server.Plugin {
|
|
|
178
174
|
);
|
|
179
175
|
continue;
|
|
180
176
|
}
|
|
181
|
-
|
|
177
|
+
const selectedFields = import_lodash.default.uniq([
|
|
178
|
+
field.name,
|
|
179
|
+
...primaryKeyFields.map((primaryKeyField) => primaryKeyField.name),
|
|
180
|
+
...createAtField ? [createAtField.name] : []
|
|
181
|
+
]);
|
|
182
|
+
const latestRecordOrderField = createAtField ?? autoIncrementField;
|
|
183
|
+
const latestRecordOrder = [
|
|
184
|
+
[latestRecordOrderField.name, "DESC"],
|
|
185
|
+
...sortablePrimaryKeyFields.filter((primaryKeyField) => primaryKeyField !== latestRecordOrderField).map((primaryKeyField) => [primaryKeyField.name, "DESC"])
|
|
186
|
+
];
|
|
187
|
+
const latestRecordFilter = {
|
|
188
|
+
[field.name]: { $ne: null },
|
|
189
|
+
...createAtField ? { [createAtField.name]: { $ne: null } } : {}
|
|
190
|
+
};
|
|
191
|
+
const chunkSize = 1e3;
|
|
192
|
+
const latestRecordQuery = {
|
|
193
|
+
fields: selectedFields,
|
|
194
|
+
filter: latestRecordFilter,
|
|
195
|
+
order: latestRecordOrder
|
|
196
|
+
};
|
|
197
|
+
latestRecordQuery["parseSort"] = false;
|
|
198
|
+
const [latestCandidate] = await collection.repository.find({ ...latestRecordQuery, limit: 1 });
|
|
199
|
+
let latestRecord = latestCandidate && field.match(latestCandidate.get(field.name)) ? latestCandidate : void 0;
|
|
200
|
+
let offset = 1;
|
|
201
|
+
while (!latestRecord && latestCandidate) {
|
|
202
|
+
const records = await collection.repository.find({
|
|
203
|
+
...latestRecordQuery,
|
|
204
|
+
limit: chunkSize,
|
|
205
|
+
offset
|
|
206
|
+
});
|
|
207
|
+
latestRecord = records.find((record) => field.match(record.get(field.name)));
|
|
208
|
+
if (latestRecord || records.length < chunkSize) {
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
offset += chunkSize;
|
|
212
|
+
}
|
|
213
|
+
if (!latestRecord) {
|
|
214
|
+
app.log.warn(
|
|
215
|
+
`Collection [${collection.name}] field [${field.name}] has no valid sequence records. Skipping sequences repair.`
|
|
216
|
+
);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const state = field.createRepairState();
|
|
220
|
+
if (!createAtField) {
|
|
221
|
+
field.collectRepairState(latestRecord, state);
|
|
222
|
+
await field.saveRepairState(state);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const latestCreatedAt = latestRecord.get(createAtField.name);
|
|
226
|
+
if (!(latestCreatedAt instanceof Date) || Number.isNaN(latestCreatedAt.getTime())) {
|
|
227
|
+
app.log.warn(
|
|
228
|
+
`Collection [${collection.name}] field [${field.name}] latest record has an invalid createdAt. Skipping sequences repair.`
|
|
229
|
+
);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const secondStart = new Date(Math.floor(latestCreatedAt.getTime() / 1e3) * 1e3);
|
|
233
|
+
const secondEnd = new Date(secondStart.getTime() + 1e3);
|
|
234
|
+
const latestBatchQuery = {
|
|
235
|
+
fields: selectedFields,
|
|
236
|
+
filter: {
|
|
237
|
+
[field.name]: { $ne: null },
|
|
238
|
+
[createAtField.name]: {
|
|
239
|
+
$gte: secondStart,
|
|
240
|
+
$lt: secondEnd
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const collectRecords = async (records) => {
|
|
245
|
+
for (const record of records) {
|
|
246
|
+
field.collectRepairState(record, state, createAtField.name);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
if (!app.db.inDialect("sqlite") && primaryKeyFields.length && primaryKeyFields.length === sortablePrimaryKeyFields.length) {
|
|
250
|
+
await collection.repository.chunkWithCursor({
|
|
251
|
+
...latestBatchQuery,
|
|
252
|
+
chunkSize,
|
|
253
|
+
callback: collectRecords
|
|
254
|
+
});
|
|
255
|
+
} else {
|
|
256
|
+
latestBatchQuery["parseSort"] = false;
|
|
257
|
+
await collectRecords(await collection.repository.find(latestBatchQuery));
|
|
258
|
+
}
|
|
259
|
+
await field.saveRepairState(state);
|
|
182
260
|
}
|
|
183
261
|
});
|
|
184
262
|
}
|
|
@@ -23,6 +23,13 @@ export interface Pattern {
|
|
|
23
23
|
}): Promise<void>;
|
|
24
24
|
}
|
|
25
25
|
export declare const sequencePatterns: Registry<Pattern>;
|
|
26
|
+
export interface IntegerSequenceRepairState {
|
|
27
|
+
current: bigint;
|
|
28
|
+
lastGeneratedAt: Date;
|
|
29
|
+
cycleStart?: Date;
|
|
30
|
+
cycleEnd?: Date;
|
|
31
|
+
}
|
|
32
|
+
export type SequenceRepairState = Map<number, IntegerSequenceRepairState>;
|
|
26
33
|
interface PatternConfig {
|
|
27
34
|
type: string;
|
|
28
35
|
title?: string;
|
|
@@ -41,6 +48,9 @@ export declare class SequenceField extends Field {
|
|
|
41
48
|
setGroupValue: (instances: Model[], options: any) => Promise<void>;
|
|
42
49
|
cleanHook: (_: any, options: any) => void;
|
|
43
50
|
match(value: any): RegExpMatchArray;
|
|
51
|
+
createRepairState(): SequenceRepairState;
|
|
52
|
+
collectRepairState(instance: Model, state: SequenceRepairState, createdAtField?: string): void;
|
|
53
|
+
saveRepairState(state: SequenceRepairState): Promise<void>;
|
|
44
54
|
update(instance: Model, options: any): Promise<void>;
|
|
45
55
|
bind(): void;
|
|
46
56
|
unbind(): void;
|
|
@@ -64,6 +64,47 @@ function parseBigInt(str, radix = 10) {
|
|
|
64
64
|
}
|
|
65
65
|
return negative ? -result : result;
|
|
66
66
|
}
|
|
67
|
+
function getCycleBounds(recordTime, cycle) {
|
|
68
|
+
const interval = import_cron_parser.default.parseExpression(cycle, { currentDate: recordTime });
|
|
69
|
+
const previous = interval.prev();
|
|
70
|
+
const next = interval.next();
|
|
71
|
+
if (next.getTime() === recordTime.getTime()) {
|
|
72
|
+
return {
|
|
73
|
+
cycleStart: new Date(next.getTime()),
|
|
74
|
+
cycleEnd: new Date(interval.next().getTime())
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
cycleStart: new Date(previous.getTime()),
|
|
79
|
+
cycleEnd: new Date(next.getTime())
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function mergeSequenceRepairState(state, current, recordTime, cycle) {
|
|
83
|
+
if (!state) {
|
|
84
|
+
return {
|
|
85
|
+
current,
|
|
86
|
+
lastGeneratedAt: recordTime,
|
|
87
|
+
...cycle ? getCycleBounds(recordTime, cycle) : {}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (cycle) {
|
|
91
|
+
const cycleBounds = state.cycleStart && state.cycleEnd ? { cycleStart: state.cycleStart, cycleEnd: state.cycleEnd } : getCycleBounds(state.lastGeneratedAt, cycle);
|
|
92
|
+
if (recordTime.getTime() >= cycleBounds.cycleEnd.getTime()) {
|
|
93
|
+
return {
|
|
94
|
+
current,
|
|
95
|
+
lastGeneratedAt: recordTime,
|
|
96
|
+
...getCycleBounds(recordTime, cycle)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (recordTime.getTime() < cycleBounds.cycleStart.getTime()) {
|
|
100
|
+
return state;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (current > state.current) {
|
|
104
|
+
return { ...state, current, lastGeneratedAt: recordTime };
|
|
105
|
+
}
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
67
108
|
sequencePatterns.register("string", {
|
|
68
109
|
validate(options) {
|
|
69
110
|
if (!(options == null ? void 0 : options.value)) {
|
|
@@ -445,6 +486,54 @@ class SequenceField extends import_database.Field {
|
|
|
445
486
|
match(value) {
|
|
446
487
|
return typeof value === "string" ? value.match(this.matcher) : null;
|
|
447
488
|
}
|
|
489
|
+
createRepairState() {
|
|
490
|
+
return /* @__PURE__ */ new Map();
|
|
491
|
+
}
|
|
492
|
+
collectRepairState(instance, state, createdAtField) {
|
|
493
|
+
const { name, patterns } = this.options;
|
|
494
|
+
const matched = this.match(instance.get(name));
|
|
495
|
+
if (!matched) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const recordTime = instance.get(createdAtField ?? "createdAt");
|
|
499
|
+
const generatedAt = recordTime ?? /* @__PURE__ */ new Date();
|
|
500
|
+
patterns.forEach((pattern, index) => {
|
|
501
|
+
if (pattern.type !== "integer") {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const { base = 10, cycle, key } = pattern.options;
|
|
505
|
+
const current = parseBigInt(matched[index + 1], base);
|
|
506
|
+
state.set(key, mergeSequenceRepairState(state.get(key), current, generatedAt, cycle));
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
async saveRepairState(state) {
|
|
510
|
+
const SeqRepo = this.database.getRepository("sequences");
|
|
511
|
+
for (const [key, values] of state) {
|
|
512
|
+
const lastSeq = await SeqRepo.findOne({
|
|
513
|
+
filter: {
|
|
514
|
+
collection: this.collection.name,
|
|
515
|
+
field: this.name,
|
|
516
|
+
key
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
if (lastSeq) {
|
|
520
|
+
await lastSeq.update({
|
|
521
|
+
current: values.current,
|
|
522
|
+
lastGeneratedAt: values.lastGeneratedAt
|
|
523
|
+
});
|
|
524
|
+
} else {
|
|
525
|
+
await SeqRepo.create({
|
|
526
|
+
values: {
|
|
527
|
+
collection: this.collection.name,
|
|
528
|
+
field: this.name,
|
|
529
|
+
key,
|
|
530
|
+
current: values.current,
|
|
531
|
+
lastGeneratedAt: values.lastGeneratedAt
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
448
537
|
async update(instance, options) {
|
|
449
538
|
const { name, patterns } = this.options;
|
|
450
539
|
const matched = this.match(instance.get(name));
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"description": "Automatically generate codes based on configured rules, supporting combinations of dates, numbers, and text.",
|
|
7
7
|
"description.ru-RU": "Автоматически генерирует коды по заданным правилам с поддержкой комбинаций дат, чисел и текста.",
|
|
8
8
|
"description.zh-CN": "根据配置的规则自动生成编码,支持日期、数字、文本的组合。",
|
|
9
|
-
"version": "3.0.0-alpha.
|
|
9
|
+
"version": "3.0.0-alpha.12",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"main": "./dist/server/index.js",
|
|
12
12
|
"homepage": "https://docs.nocobase.com/handbook/field-sequence",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@nocobase/test": "2.x",
|
|
34
34
|
"@nocobase/utils": "2.x"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "2ba035885741a9977d76bbabf8f2caf44b199139",
|
|
37
37
|
"keywords": [
|
|
38
38
|
"Collection fields"
|
|
39
39
|
]
|