@shaxpir/duiduidui-models 1.9.31 → 1.10.1

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.
@@ -1,4 +1,5 @@
1
1
  import { CompactDateTime } from "@shaxpir/shaxpir-common";
2
+ import { RecordType } from "./RecordType";
2
3
  export interface BaseCondition {
3
4
  type: string;
4
5
  }
@@ -46,7 +47,15 @@ export interface ComponentCondition extends BaseCondition {
46
47
  components: string[];
47
48
  mode: 'any' | 'all';
48
49
  }
49
- export type AnyCondition = TagCondition | GradeCondition | ThetaCondition | TemporalCondition | CountCondition | StarredCondition | DifficultyCondition | HasTermCondition | ComponentCondition;
50
+ export interface RecordTypeCondition extends BaseCondition {
51
+ type: 'record_type';
52
+ recordType: RecordType;
53
+ }
54
+ export interface ExcludeRecordTypeCondition extends BaseCondition {
55
+ type: 'exclude_record_type';
56
+ recordType: RecordType;
57
+ }
58
+ export type AnyCondition = TagCondition | GradeCondition | ThetaCondition | TemporalCondition | CountCondition | StarredCondition | DifficultyCondition | HasTermCondition | ComponentCondition | RecordTypeCondition | ExcludeRecordTypeCondition;
50
59
  export interface Conditions {
51
60
  all?: AnyCondition[];
52
61
  any?: AnyCondition[];
@@ -87,6 +96,15 @@ export declare const Condition: {
87
96
  hasComponent: (component: string) => ComponentCondition;
88
97
  hasAnyComponent: (components: string[]) => ComponentCondition;
89
98
  hasAllComponents: (components: string[]) => ComponentCondition;
99
+ recordType: (recordType: RecordType) => RecordTypeCondition;
100
+ excludeRecordType: (recordType: RecordType) => ExcludeRecordTypeCondition;
101
+ strokes: () => RecordTypeCondition;
102
+ radicals: () => RecordTypeCondition;
103
+ characters: () => RecordTypeCondition;
104
+ words: () => RecordTypeCondition;
105
+ phrases: () => RecordTypeCondition;
106
+ patterns: () => RecordTypeCondition;
107
+ sentences: () => RecordTypeCondition;
90
108
  /**
91
109
  * Check if starred condition is required (in 'all' section)
92
110
  */
@@ -128,6 +146,31 @@ export declare const Condition: {
128
146
  * Returns an array of error messages, empty if valid
129
147
  */
130
148
  validate: (conditions?: Conditions) => string[];
149
+ /**
150
+ * Extract included record types from conditions.
151
+ * Returns null if no record type conditions are specified (meaning all types are included).
152
+ * Returns an array of included types if record_type conditions are in 'all' or 'any'.
153
+ */
154
+ getIncludedRecordTypes: (conditions?: Conditions) => RecordType[] | null;
155
+ /**
156
+ * Extract excluded record types from conditions.
157
+ * Returns an array of types to exclude based on exclude_record_type conditions in 'all'
158
+ * or record_type conditions in 'none'.
159
+ */
160
+ getExcludedRecordTypes: (conditions?: Conditions) => RecordType[];
161
+ /**
162
+ * Check if a specific record type is required (in 'all' section)
163
+ */
164
+ requiresRecordType: (conditions: Conditions | undefined, recordType: RecordType) => boolean;
165
+ /**
166
+ * Check if a specific record type is excluded
167
+ */
168
+ excludesRecordType: (conditions: Conditions | undefined, recordType: RecordType) => boolean;
169
+ /**
170
+ * Get the effective record types to query based on conditions.
171
+ * Returns the set of types that should be included in a query.
172
+ */
173
+ getEffectiveRecordTypes: (conditions?: Conditions) => RecordType[];
131
174
  hasStarredInAll: (filters?: ConditionFilters) => boolean;
132
175
  hasStarredInAny: (filters?: ConditionFilters) => boolean;
133
176
  hasStarredInNone: (filters?: ConditionFilters) => boolean;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Condition = void 0;
4
+ const RecordType_1 = require("./RecordType");
4
5
  // Factory functions for creating conditions
5
6
  exports.Condition = {
6
7
  // Tag conditions
@@ -45,6 +46,17 @@ exports.Condition = {
45
46
  hasComponent: (component) => ({ type: 'component', components: [component], mode: 'any' }),
46
47
  hasAnyComponent: (components) => ({ type: 'component', components, mode: 'any' }),
47
48
  hasAllComponents: (components) => ({ type: 'component', components, mode: 'all' }),
49
+ // Record type conditions
50
+ recordType: (recordType) => ({ type: 'record_type', recordType }),
51
+ excludeRecordType: (recordType) => ({ type: 'exclude_record_type', recordType }),
52
+ // Convenience methods for common record types
53
+ strokes: () => ({ type: 'record_type', recordType: 'stroke' }),
54
+ radicals: () => ({ type: 'record_type', recordType: 'radical' }),
55
+ characters: () => ({ type: 'record_type', recordType: 'character' }),
56
+ words: () => ({ type: 'record_type', recordType: 'word' }),
57
+ phrases: () => ({ type: 'record_type', recordType: 'phrase' }),
58
+ patterns: () => ({ type: 'record_type', recordType: 'pattern' }),
59
+ sentences: () => ({ type: 'record_type', recordType: 'sentence' }),
48
60
  // Helper methods to check if conditions are present in filters
49
61
  /**
50
62
  * Check if starred condition is required (in 'all' section)
@@ -168,8 +180,118 @@ exports.Condition = {
168
180
  errors.push(`Min difficulty (${min}) must be less than max difficulty (${max})`);
169
181
  }
170
182
  }
183
+ // Check for contradictory record type conditions
184
+ const allRecordTypes = conditions.all
185
+ ?.filter(c => c.type === 'record_type')
186
+ .map(c => c.recordType) || [];
187
+ const noneRecordTypes = conditions.none
188
+ ?.filter(c => c.type === 'record_type')
189
+ .map(c => c.recordType) || [];
190
+ const excludedRecordTypes = conditions.all
191
+ ?.filter(c => c.type === 'exclude_record_type')
192
+ .map(c => c.recordType) || [];
193
+ // Check for multiple required record types (an item can only have one record type)
194
+ if (allRecordTypes.length > 1) {
195
+ errors.push(`Cannot require multiple record types: ${allRecordTypes.join(', ')} - an item can only have one record type`);
196
+ }
197
+ // Check for record_type in 'all' conflicting with same type in 'none'
198
+ allRecordTypes.forEach(rt => {
199
+ if (noneRecordTypes.includes(rt)) {
200
+ errors.push(`Record type "${rt}" cannot be both required and excluded`);
201
+ }
202
+ });
203
+ // Check for record_type in 'all' conflicting with exclude_record_type for same type
204
+ allRecordTypes.forEach(rt => {
205
+ if (excludedRecordTypes.includes(rt)) {
206
+ errors.push(`Record type "${rt}" cannot be both required (record_type) and excluded (exclude_record_type)`);
207
+ }
208
+ });
209
+ // Check if all record types would be excluded (empty result set)
210
+ const effectiveTypes = exports.Condition.getEffectiveRecordTypes(conditions);
211
+ if (effectiveTypes.length === 0) {
212
+ errors.push('All record types are excluded - query would return no results');
213
+ }
171
214
  return errors;
172
215
  },
216
+ /**
217
+ * Extract included record types from conditions.
218
+ * Returns null if no record type conditions are specified (meaning all types are included).
219
+ * Returns an array of included types if record_type conditions are in 'all' or 'any'.
220
+ */
221
+ getIncludedRecordTypes: (conditions) => {
222
+ if (!conditions)
223
+ return null;
224
+ // Check 'all' section for record_type conditions
225
+ const allRecordTypes = conditions.all
226
+ ?.filter(c => c.type === 'record_type')
227
+ .map(c => c.recordType) || [];
228
+ // Check 'any' section for record_type conditions
229
+ const anyRecordTypes = conditions.any
230
+ ?.filter(c => c.type === 'record_type')
231
+ .map(c => c.recordType) || [];
232
+ // If there are record_type conditions in 'all', those are required
233
+ if (allRecordTypes.length > 0) {
234
+ return allRecordTypes;
235
+ }
236
+ // If there are record_type conditions in 'any', those are the allowed types
237
+ if (anyRecordTypes.length > 0) {
238
+ return anyRecordTypes;
239
+ }
240
+ // No record type constraints
241
+ return null;
242
+ },
243
+ /**
244
+ * Extract excluded record types from conditions.
245
+ * Returns an array of types to exclude based on exclude_record_type conditions in 'all'
246
+ * or record_type conditions in 'none'.
247
+ */
248
+ getExcludedRecordTypes: (conditions) => {
249
+ if (!conditions)
250
+ return [];
251
+ const excluded = [];
252
+ // Check 'all' section for exclude_record_type conditions
253
+ const allExclusions = conditions.all
254
+ ?.filter(c => c.type === 'exclude_record_type')
255
+ .map(c => c.recordType) || [];
256
+ excluded.push(...allExclusions);
257
+ // Check 'none' section for record_type conditions (these types should be excluded)
258
+ const noneRecordTypes = conditions.none
259
+ ?.filter(c => c.type === 'record_type')
260
+ .map(c => c.recordType) || [];
261
+ excluded.push(...noneRecordTypes);
262
+ return [...new Set(excluded)]; // Deduplicate
263
+ },
264
+ /**
265
+ * Check if a specific record type is required (in 'all' section)
266
+ */
267
+ requiresRecordType: (conditions, recordType) => {
268
+ return !!(conditions?.all?.some(c => c.type === 'record_type' && c.recordType === recordType));
269
+ },
270
+ /**
271
+ * Check if a specific record type is excluded
272
+ */
273
+ excludesRecordType: (conditions, recordType) => {
274
+ // Check exclude_record_type in 'all'
275
+ const hasExcludeCondition = conditions?.all?.some(c => c.type === 'exclude_record_type' && c.recordType === recordType);
276
+ // Check record_type in 'none'
277
+ const hasNoneCondition = conditions?.none?.some(c => c.type === 'record_type' && c.recordType === recordType);
278
+ return !!(hasExcludeCondition || hasNoneCondition);
279
+ },
280
+ /**
281
+ * Get the effective record types to query based on conditions.
282
+ * Returns the set of types that should be included in a query.
283
+ */
284
+ getEffectiveRecordTypes: (conditions) => {
285
+ const included = exports.Condition.getIncludedRecordTypes(conditions);
286
+ const excluded = exports.Condition.getExcludedRecordTypes(conditions);
287
+ // Start with included types, or all types if none specified
288
+ let types = included ?? [...RecordType_1.RECORD_TYPES];
289
+ // Remove excluded types
290
+ if (excluded.length > 0) {
291
+ types = types.filter(t => !excluded.includes(t));
292
+ }
293
+ return types;
294
+ },
173
295
  // Backward compatibility aliases (deprecated - use requiresStarred/allowsStarred/excludesStarred instead)
174
296
  hasStarredInAll: (filters) => exports.Condition.requiresStarred(filters),
175
297
  hasStarredInAny: (filters) => exports.Condition.allowsStarred(filters),
@@ -1,5 +1,6 @@
1
1
  import { CompactDateTime } from "@shaxpir/shaxpir-common";
2
2
  import { ContentId } from "./Content";
3
+ import { RecordType } from "./RecordType";
3
4
  import { ReviewLike } from "./Review";
4
5
  export interface PhraseExample {
5
6
  id: number;
@@ -8,6 +9,7 @@ export interface PhraseExample {
8
9
  translation: string;
9
10
  difficulty: number;
10
11
  sense_rank: number;
12
+ type?: RecordType;
11
13
  }
12
14
  export interface Phrase {
13
15
  text: string;
@@ -26,10 +28,12 @@ export interface Phrase {
26
28
  }
27
29
  export interface BuiltInPhrase extends Phrase {
28
30
  id: number;
31
+ type: RecordType;
29
32
  }
30
33
  export interface AnnotatedPhrase extends Phrase {
31
34
  id: string;
32
35
  phrase_id?: number;
36
+ type?: RecordType | null;
33
37
  content_id?: ContentId;
34
38
  starred_at: CompactDateTime | null;
35
39
  alpha: number | null;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Record types in the dictionary database.
3
+ *
4
+ * Each record type has its own table and a well-known ID range.
5
+ * This allows determining record type from ID alone.
6
+ */
7
+ export type RecordType = 'stroke' | 'radical' | 'character' | 'word' | 'phrase' | 'pattern' | 'sentence';
8
+ /**
9
+ * Ordered array of all record types.
10
+ * Order reflects linguistic hierarchy from smallest to largest units.
11
+ */
12
+ export declare const RECORD_TYPES: RecordType[];
13
+ /**
14
+ * ID ranges for each record type.
15
+ * Records are assigned IDs in ascending difficulty order within their type's range.
16
+ */
17
+ export declare const ID_RANGES: Record<RecordType, {
18
+ min: number;
19
+ max: number;
20
+ }>;
21
+ /**
22
+ * Determine the record type from an ID based on ID ranges.
23
+ *
24
+ * @param id - The record ID
25
+ * @returns The record type, or null if ID is outside all known ranges
26
+ */
27
+ export declare function getRecordTypeFromId(id: number): RecordType | null;
28
+ /**
29
+ * Check if an ID belongs to a specific record type.
30
+ *
31
+ * @param id - The record ID
32
+ * @param type - The record type to check
33
+ * @returns true if the ID is within the range for the given type
34
+ */
35
+ export declare function isRecordType(id: number, type: RecordType): boolean;
36
+ /**
37
+ * Get the table name for a record type.
38
+ * Table names match record type names exactly.
39
+ *
40
+ * @param type - The record type
41
+ * @returns The table name (same as type)
42
+ */
43
+ export declare function getTableForRecordType(type: RecordType): string;
44
+ /**
45
+ * Get the table name for a record ID.
46
+ *
47
+ * @param id - The record ID
48
+ * @returns The table name, or null if ID is outside all known ranges
49
+ */
50
+ export declare function getTableForId(id: number): string | null;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ID_RANGES = exports.RECORD_TYPES = void 0;
4
+ exports.getRecordTypeFromId = getRecordTypeFromId;
5
+ exports.isRecordType = isRecordType;
6
+ exports.getTableForRecordType = getTableForRecordType;
7
+ exports.getTableForId = getTableForId;
8
+ /**
9
+ * Ordered array of all record types.
10
+ * Order reflects linguistic hierarchy from smallest to largest units.
11
+ */
12
+ exports.RECORD_TYPES = [
13
+ 'stroke',
14
+ 'radical',
15
+ 'character',
16
+ 'word',
17
+ 'phrase',
18
+ 'pattern',
19
+ 'sentence'
20
+ ];
21
+ /**
22
+ * ID ranges for each record type.
23
+ * Records are assigned IDs in ascending difficulty order within their type's range.
24
+ */
25
+ exports.ID_RANGES = {
26
+ stroke: { min: 0, max: 999 },
27
+ radical: { min: 1000, max: 9999 },
28
+ character: { min: 10000, max: 49999 },
29
+ word: { min: 50000, max: 149999 },
30
+ phrase: { min: 150000, max: 239999 },
31
+ pattern: { min: 240000, max: 249999 },
32
+ sentence: { min: 250000, max: 999999 }
33
+ };
34
+ /**
35
+ * Determine the record type from an ID based on ID ranges.
36
+ *
37
+ * @param id - The record ID
38
+ * @returns The record type, or null if ID is outside all known ranges
39
+ */
40
+ function getRecordTypeFromId(id) {
41
+ for (const type of exports.RECORD_TYPES) {
42
+ const range = exports.ID_RANGES[type];
43
+ if (id >= range.min && id <= range.max) {
44
+ return type;
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ /**
50
+ * Check if an ID belongs to a specific record type.
51
+ *
52
+ * @param id - The record ID
53
+ * @param type - The record type to check
54
+ * @returns true if the ID is within the range for the given type
55
+ */
56
+ function isRecordType(id, type) {
57
+ const range = exports.ID_RANGES[type];
58
+ return id >= range.min && id <= range.max;
59
+ }
60
+ /**
61
+ * Get the table name for a record type.
62
+ * Table names match record type names exactly.
63
+ *
64
+ * @param type - The record type
65
+ * @returns The table name (same as type)
66
+ */
67
+ function getTableForRecordType(type) {
68
+ return type;
69
+ }
70
+ /**
71
+ * Get the table name for a record ID.
72
+ *
73
+ * @param id - The record ID
74
+ * @returns The table name, or null if ID is outside all known ranges
75
+ */
76
+ function getTableForId(id) {
77
+ const type = getRecordTypeFromId(id);
78
+ return type;
79
+ }
@@ -12,6 +12,7 @@ import { Conditions } from './Condition';
12
12
  export interface SearchState {
13
13
  query?: string;
14
14
  senseRank?: number;
15
+ phraseId?: number;
15
16
  conditions?: Conditions;
16
17
  }
17
18
  /**
@@ -35,6 +35,9 @@ function areSearchStatesEqual(a, b) {
35
35
  // Compare senseRank
36
36
  if (a.senseRank !== b.senseRank)
37
37
  return false;
38
+ // Compare phraseId
39
+ if (a.phraseId !== b.phraseId)
40
+ return false;
38
41
  // Compare conditions using JSON serialization (with sorted keys for consistency)
39
42
  const conditionsA = a.conditions ? JSON.stringify(sortConditions(a.conditions)) : undefined;
40
43
  const conditionsB = b.conditions ? JSON.stringify(sortConditions(b.conditions)) : undefined;
@@ -4,12 +4,14 @@ import { ShareSync } from '../repo';
4
4
  import { BayesianScore } from './BayesianScore';
5
5
  import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
6
6
  import { BuiltInPhrase, Phrase, PhraseExample } from './Phrase';
7
+ import { RecordType } from './RecordType';
7
8
  import { ImpliedReview, ReviewLike } from "./Review";
8
9
  export interface TermSummary {
9
10
  text: string;
10
11
  sense_rank: number;
11
12
  difficulty: number;
12
13
  phrase_id: number | null;
14
+ type: RecordType;
13
15
  starred_at: CompactDateTime | null;
14
16
  alpha: number;
15
17
  beta: number;
@@ -25,6 +27,7 @@ export interface TermPayload extends BayesianScore {
25
27
  sense_rank: number;
26
28
  difficulty: number;
27
29
  phrase_id: number | null;
30
+ type: RecordType;
28
31
  starred_at: CompactDateTime | null;
29
32
  alpha: number;
30
33
  beta: number;
@@ -57,13 +60,14 @@ export declare class Term extends Content {
57
60
  private _impliedReviewsView;
58
61
  private _userTagsView;
59
62
  constructor(doc: Doc, shouldAcquire: boolean, shareSync: ShareSync);
60
- static forUserPhrase(userId: ContentId, text: string, senseRank: number, difficulty: number, phraseId?: number): Term;
61
- static forUserPhrase(userId: ContentId, phrase: Phrase): Term;
63
+ static forUserPhrase(userId: ContentId, text: string, senseRank: number, difficulty: number, type: RecordType, phraseId?: number): Term;
64
+ static forUserPhrase(userId: ContentId, phrase: Phrase, type: RecordType): Term;
62
65
  static forBuiltinPhrase(userId: ContentId, phrase: BuiltInPhrase): Term;
63
66
  get payload(): TermPayload;
64
67
  get text(): string;
65
68
  get senseRank(): number;
66
69
  get difficulty(): number;
70
+ get type(): RecordType;
67
71
  get alpha(): number;
68
72
  get beta(): number;
69
73
  get theta(): number;
@@ -8,6 +8,7 @@ const BayesianScore_1 = require("./BayesianScore");
8
8
  const Content_1 = require("./Content");
9
9
  const ContentKind_1 = require("./ContentKind");
10
10
  const Operation_1 = require("./Operation");
11
+ const RecordType_1 = require("./RecordType");
11
12
  class Term extends Content_1.Content {
12
13
  static makeTermId(userId, text, senseRank) {
13
14
  return shaxpir_common_1.CachingHasher.makeMd5Base62Hash(`${ContentKind_1.ContentKind.TERM}-${userId}-${text}-${senseRank}`);
@@ -18,11 +19,18 @@ class Term extends Content_1.Content {
18
19
  this._impliedReviewsView = new ArrayView_1.ArrayView(this, ['payload', 'implied_reviews']);
19
20
  this._userTagsView = new ArrayView_1.ArrayView(this, ['payload', 'user_tags']);
20
21
  }
21
- static forUserPhrase(userId, textOrPhrase, senseRank, difficulty, phraseId) {
22
+ static forUserPhrase(userId, textOrPhrase, senseRankOrType, difficultyOrNothing, typeOrPhraseId, phraseId) {
22
23
  const now = shaxpir_common_1.ClockService.getClock().now();
23
24
  // Handle overloaded parameters
24
25
  let phrase;
26
+ let recordType;
27
+ let resolvedPhraseId;
25
28
  if (typeof textOrPhrase === 'string') {
29
+ // First overload: (userId, text, senseRank, difficulty, type, phraseId?)
30
+ const senseRank = senseRankOrType;
31
+ const difficulty = difficultyOrNothing;
32
+ recordType = typeOrPhraseId;
33
+ resolvedPhraseId = phraseId;
26
34
  // Create a minimal Phrase object from individual parameters
27
35
  phrase = {
28
36
  text: textOrPhrase,
@@ -40,9 +48,12 @@ class Term extends Content_1.Content {
40
48
  };
41
49
  }
42
50
  else {
51
+ // Second overload: (userId, phrase, type)
43
52
  phrase = textOrPhrase;
53
+ recordType = senseRankOrType;
54
+ resolvedPhraseId = undefined;
44
55
  }
45
- const termId = Term.makeTermId(userId, phrase.text, senseRank);
56
+ const termId = Term.makeTermId(userId, phrase.text, phrase.sense_rank);
46
57
  return repo_1.ShareSyncFactory.get().createContent({
47
58
  meta: {
48
59
  ref: termId,
@@ -53,7 +64,8 @@ class Term extends Content_1.Content {
53
64
  updated_at: now
54
65
  },
55
66
  payload: {
56
- phrase_id: (typeof textOrPhrase === 'string' && phraseId) ? phraseId : null,
67
+ phrase_id: resolvedPhraseId ?? null,
68
+ type: recordType,
57
69
  text: phrase.text,
58
70
  sense_rank: phrase.sense_rank,
59
71
  hanzi_count: phrase.hanzi_count,
@@ -97,6 +109,7 @@ class Term extends Content_1.Content {
97
109
  sense_rank: phrase.sense_rank,
98
110
  difficulty: phrase.difficulty,
99
111
  phrase_id: phrase.id,
112
+ type: phrase.type ?? (0, RecordType_1.getRecordTypeFromId)(phrase.id), // Use phrase.type if available, else derive from ID
100
113
  starred_at: null,
101
114
  user_tags: [],
102
115
  user_tag_count: 0,
@@ -128,6 +141,10 @@ class Term extends Content_1.Content {
128
141
  this.checkDisposed("Term.difficulty");
129
142
  return this.payload.difficulty;
130
143
  }
144
+ get type() {
145
+ this.checkDisposed("Term.type");
146
+ return this.payload.type;
147
+ }
131
148
  get alpha() {
132
149
  this.checkDisposed("Term.alpha");
133
150
  return this.payload.alpha;
@@ -20,6 +20,7 @@ export * from './Permissions';
20
20
  export * from './Phrase';
21
21
  export * from './Profile';
22
22
  export * from './Progress';
23
+ export * from './RecordType';
23
24
  export * from './Review';
24
25
  export * from './SearchState';
25
26
  export * from './Session';
@@ -37,6 +37,7 @@ __exportStar(require("./Permissions"), exports);
37
37
  __exportStar(require("./Phrase"), exports);
38
38
  __exportStar(require("./Profile"), exports);
39
39
  __exportStar(require("./Progress"), exports);
40
+ __exportStar(require("./RecordType"), exports);
40
41
  __exportStar(require("./Review"), exports);
41
42
  __exportStar(require("./SearchState"), exports);
42
43
  __exportStar(require("./Session"), exports);
@@ -1,5 +1,6 @@
1
1
  import { CompactDateTime } from "@shaxpir/shaxpir-common";
2
2
  import { AnyCondition, Conditions } from '../models/Condition';
3
+ import { RecordType } from '../models/RecordType';
3
4
  /**
4
5
  * Interface for objects that can be matched against Conditions.
5
6
  * This is the minimum set of fields needed for condition matching.
@@ -18,6 +19,8 @@ export interface ConditionMatchable {
18
19
  updated_at?: CompactDateTime;
19
20
  review_count?: number;
20
21
  implied_review_count?: number;
22
+ type?: RecordType | null;
23
+ phrase_id?: number;
21
24
  }
22
25
  /**
23
26
  * Utility for checking if objects match Conditions.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConditionMatcher = void 0;
4
+ const RecordType_1 = require("../models/RecordType");
4
5
  /**
5
6
  * Utility for checking if objects match Conditions.
6
7
  *
@@ -118,6 +119,26 @@ exports.ConditionMatcher = {
118
119
  // suitable for in-memory filtering. Return true (don't filter out).
119
120
  return true;
120
121
  }
122
+ case 'record_type': {
123
+ const rtCond = condition;
124
+ // Try to get type directly, or derive from phrase_id
125
+ const itemType = item.type ?? (item.phrase_id !== undefined ? (0, RecordType_1.getRecordTypeFromId)(item.phrase_id) : null);
126
+ if (itemType === null) {
127
+ // Cannot determine type - user-created term without type, return true (don't filter out)
128
+ return true;
129
+ }
130
+ return itemType === rtCond.recordType;
131
+ }
132
+ case 'exclude_record_type': {
133
+ const ertCond = condition;
134
+ // Try to get type directly, or derive from phrase_id
135
+ const itemType = item.type ?? (item.phrase_id !== undefined ? (0, RecordType_1.getRecordTypeFromId)(item.phrase_id) : null);
136
+ if (itemType === null) {
137
+ // Cannot determine type - user-created term without type, return true (don't filter out)
138
+ return true;
139
+ }
140
+ return itemType !== ertCond.recordType;
141
+ }
121
142
  default:
122
143
  // For unsupported conditions, default to true (don't filter out)
123
144
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shaxpir/duiduidui-models",
3
- "version": "1.9.31",
3
+ "version": "1.10.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/shaxpir/duiduidui-models"