@atscript/core 0.1.26 → 0.1.28

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/index.cjs CHANGED
@@ -433,60 +433,13 @@ else obj[key] = value;
433
433
  var SemanticPrimitiveNode = class SemanticPrimitiveNode extends SemanticNode {
434
434
  applyAnnotations() {
435
435
  this.annotations = [];
436
- if (this.type === "string" || this.type === "array") {
437
- if (typeof this.config.expect?.minLength === "number") this.annotations.push({
438
- name: "expect.minLength",
439
- token: dummyToken,
440
- args: [num(this.config.expect.minLength)]
441
- });
442
- if (typeof this.config.expect?.maxLength === "number") this.annotations.push({
443
- name: "expect.maxLength",
444
- token: dummyToken,
445
- args: [num(this.config.expect.maxLength)]
446
- });
447
- }
448
- if (this.type === "string") {
449
- if (this.config.expect?.required === true) this.annotations.push({
450
- name: "meta.required",
451
- token: dummyToken,
452
- args: []
453
- });
454
- if (this.config.expect?.pattern !== undefined) {
455
- const patterns = Array.isArray(this.config.expect.pattern) ? this.config.expect.pattern : [this.config.expect.pattern];
456
- for (const p of patterns) {
457
- const args = typeof p === "string" ? [text$1(p)] : [text$1(p.source), text$1(p.flags)];
458
- if (this.config.expect.message) args[2] = text$1(this.config.expect.message);
459
- this.annotations.push({
460
- name: "expect.pattern",
461
- token: dummyToken,
462
- args
463
- });
464
- }
465
- }
466
- }
467
- if (this.type === "number") {
468
- if (typeof this.config.expect?.min === "number") this.annotations.push({
469
- name: "expect.min",
470
- token: dummyToken,
471
- args: [num(this.config.expect.min)]
472
- });
473
- if (typeof this.config.expect?.max === "number") this.annotations.push({
474
- name: "expect.max",
475
- token: dummyToken,
476
- args: [num(this.config.expect.max)]
477
- });
478
- if (this.config.expect?.int === true) this.annotations.push({
479
- name: "expect.int",
480
- token: dummyToken,
481
- args: []
482
- });
483
- }
484
- if (this.type === "boolean") {
485
- if (this.config.expect?.required === true) this.annotations.push({
486
- name: "meta.required",
487
- token: dummyToken,
488
- args: []
489
- });
436
+ for (const [name, value] of Object.entries(this.config.annotations || {})) {
437
+ const spec = this.annotationTree ? resolveAnnotation(name, this.annotationTree) : undefined;
438
+ const isMultiple = spec?.config.multiple ?? false;
439
+ if (Array.isArray(value)) {
440
+ if (isMultiple) for (const item of value) this.annotations.push(toAnnotationTokens(name, item, spec));
441
+ else if (value.length > 0) this.annotations.push(toAnnotationTokens(name, value[0], spec));
442
+ } else this.annotations.push(toAnnotationTokens(name, value, spec));
490
443
  }
491
444
  }
492
445
  get key() {
@@ -515,8 +468,8 @@ var SemanticPrimitiveNode = class SemanticPrimitiveNode extends SemanticNode {
515
468
  s += this.renderChildren();
516
469
  return indent + s.split("\n").join(`\n${indent}`);
517
470
  }
518
- constructor(_id, config, parentKey = "") {
519
- super("primitive"), _define_property$11(this, "_id", void 0), _define_property$11(this, "config", void 0), _define_property$11(this, "parentKey", void 0), _define_property$11(this, "type", void 0), _define_property$11(this, "props", void 0), _define_property$11(this, "tags", void 0), this._id = _id, this.config = config, this.parentKey = parentKey;
471
+ constructor(_id, config, parentKey = "", annotationTree) {
472
+ super("primitive"), _define_property$11(this, "_id", void 0), _define_property$11(this, "config", void 0), _define_property$11(this, "parentKey", void 0), _define_property$11(this, "annotationTree", void 0), _define_property$11(this, "type", void 0), _define_property$11(this, "props", void 0), _define_property$11(this, "tags", void 0), this._id = _id, this.config = config, this.parentKey = parentKey, this.annotationTree = annotationTree;
520
473
  this.props = new Map();
521
474
  this.tags = new Set([_id, ...config?.tags || []]);
522
475
  for (const [ext, def] of Object.entries(config.extensions || {})) {
@@ -525,11 +478,11 @@ var SemanticPrimitiveNode = class SemanticPrimitiveNode extends SemanticNode {
525
478
  documentation: def.documentation ?? config.documentation,
526
479
  extensions: def.extensions,
527
480
  tags: Array.from(new Set([...def.tags || [], ...Array.from(this.tags)])),
528
- expect: {
529
- ...config.expect,
530
- ...def.expect
481
+ annotations: {
482
+ ...config.annotations,
483
+ ...def.annotations
531
484
  }
532
- }, this.key);
485
+ }, this.key, this.annotationTree);
533
486
  this.props.set(ext, node);
534
487
  }
535
488
  if (typeof config.type === "object") this.type = config.type.kind === "final" ? config.type.value : config.type.kind;
@@ -537,6 +490,36 @@ else this.type = config.type;
537
490
  this.applyAnnotations();
538
491
  }
539
492
  };
493
+ function toAnnotationTokens(name, value, spec) {
494
+ if (typeof value === "boolean") return {
495
+ name,
496
+ token: dummyToken,
497
+ args: []
498
+ };
499
+ if (typeof value === "string") return {
500
+ name,
501
+ token: dummyToken,
502
+ args: [text$1(value)]
503
+ };
504
+ if (typeof value === "number") return {
505
+ name,
506
+ token: dummyToken,
507
+ args: [num(value)]
508
+ };
509
+ const argNames = spec?.arguments.map((a) => a.name) ?? Object.keys(value);
510
+ const raw = argNames.map((argName) => {
511
+ const v = value[argName];
512
+ if (v === undefined) return undefined;
513
+ return typeof v === "number" ? num(v) : text$1(String(v));
514
+ });
515
+ while (raw.length > 0 && raw[raw.length - 1] === undefined) raw.pop();
516
+ const args = raw.map((t) => t ?? text$1(""));
517
+ return {
518
+ name,
519
+ token: dummyToken,
520
+ args
521
+ };
522
+ }
540
523
  const dummyToken = new Token({
541
524
  getRange: () => ({
542
525
  end: {
@@ -3048,6 +3031,162 @@ const zeroRange = {
3048
3031
  }
3049
3032
  };
3050
3033
 
3034
+ //#endregion
3035
+ //#region packages/core/src/defaults/db-annotations.ts
3036
+ const dbAnnotations = {
3037
+ table: new AnnotationSpec({
3038
+ description: "Marks an interface as a database-persisted entity (table in SQL, collection in MongoDB). If the name argument is omitted, the adapter derives the table name from the interface name.\n\n**Example:**\n```atscript\n@db.table \"users\"\nexport interface User { ... }\n```\n",
3039
+ nodeType: ["interface"],
3040
+ argument: {
3041
+ optional: true,
3042
+ name: "name",
3043
+ type: "string",
3044
+ description: "Table/collection name. If omitted, derived from interface name."
3045
+ }
3046
+ }),
3047
+ schema: new AnnotationSpec({
3048
+ description: "Assigns the entity to a database schema/namespace.\n\n**Example:**\n```atscript\n@db.table \"users\"\n@db.schema \"auth\"\nexport interface User { ... }\n```\n",
3049
+ nodeType: ["interface"],
3050
+ argument: {
3051
+ name: "name",
3052
+ type: "string",
3053
+ description: "Schema/namespace name."
3054
+ }
3055
+ }),
3056
+ index: {
3057
+ plain: new AnnotationSpec({
3058
+ description: "Standard (non-unique) index for query performance. Fields sharing the same index name form a composite index.\n\n**Example:**\n```atscript\n@db.index.plain \"idx_timeline\", \"desc\"\ncreatedAt: number.timestamp\n```\n",
3059
+ nodeType: ["prop"],
3060
+ multiple: true,
3061
+ mergeStrategy: "append",
3062
+ argument: [{
3063
+ optional: true,
3064
+ name: "name",
3065
+ type: "string",
3066
+ description: "Index name / composite group name."
3067
+ }, {
3068
+ optional: true,
3069
+ name: "sort",
3070
+ type: "string",
3071
+ values: ["asc", "desc"],
3072
+ description: "Sort direction. Defaults to \"asc\"."
3073
+ }]
3074
+ }),
3075
+ unique: new AnnotationSpec({
3076
+ description: "Unique index — ensures no two rows/documents have the same value(s). Fields sharing the same index name form a composite unique constraint.\n\n**Example:**\n```atscript\n@db.index.unique \"tenant_email\"\nemail: string.email\n```\n",
3077
+ nodeType: ["prop"],
3078
+ multiple: true,
3079
+ mergeStrategy: "append",
3080
+ argument: {
3081
+ optional: true,
3082
+ name: "name",
3083
+ type: "string",
3084
+ description: "Index name / composite group name."
3085
+ }
3086
+ }),
3087
+ fulltext: new AnnotationSpec({
3088
+ description: "Full-text search index. Fields sharing the same index name form a composite full-text index.\n\n**Example:**\n```atscript\n@db.index.fulltext \"ft_content\"\ntitle: string\n```\n",
3089
+ nodeType: ["prop"],
3090
+ multiple: true,
3091
+ mergeStrategy: "append",
3092
+ argument: {
3093
+ optional: true,
3094
+ name: "name",
3095
+ type: "string",
3096
+ description: "Index name / composite group name."
3097
+ }
3098
+ })
3099
+ },
3100
+ column: new AnnotationSpec({
3101
+ description: "Overrides the physical column/document-field name in the database.\n\n**Example:**\n```atscript\n@db.column \"first_name\"\nfirstName: string\n```\n",
3102
+ nodeType: ["prop"],
3103
+ argument: {
3104
+ name: "name",
3105
+ type: "string",
3106
+ description: "The actual column/field name in the DB."
3107
+ }
3108
+ }),
3109
+ default: {
3110
+ value: new AnnotationSpec({
3111
+ description: "Sets a static DB-level default value (used in DDL DEFAULT clause). For string fields the value is used as-is; for other types it is parsed as JSON.\n\n**Example:**\n```atscript\n@db.default.value \"active\"\nstatus: string\n```\n",
3112
+ nodeType: ["prop"],
3113
+ argument: {
3114
+ name: "value",
3115
+ type: "string",
3116
+ description: "Static default value. Strings used as-is; other types parsed via JSON.parse()."
3117
+ }
3118
+ }),
3119
+ fn: new AnnotationSpec({
3120
+ description: "Sets a DB-level generated default. The function name is portable — each adapter maps it to the appropriate DB mechanism.\n\n**Example:**\n```atscript\n@db.default.fn \"increment\"\nid: number\n```\n",
3121
+ nodeType: ["prop"],
3122
+ argument: {
3123
+ name: "fn",
3124
+ type: "string",
3125
+ values: [
3126
+ "increment",
3127
+ "uuid",
3128
+ "now"
3129
+ ],
3130
+ description: "Generation function name: \"increment\", \"uuid\", or \"now\"."
3131
+ },
3132
+ validate(token, args, doc) {
3133
+ const errors = [];
3134
+ if (!args[0]) return errors;
3135
+ const fnName = args[0].text;
3136
+ const validFns = [
3137
+ "increment",
3138
+ "uuid",
3139
+ "now"
3140
+ ];
3141
+ if (!validFns.includes(fnName)) {
3142
+ errors.push({
3143
+ message: `Unknown @db.default.fn "${fnName}" — expected "increment", "uuid", or "now"`,
3144
+ severity: 1,
3145
+ range: args[0].range
3146
+ });
3147
+ return errors;
3148
+ }
3149
+ const field = token.parentNode;
3150
+ const definition$1 = field.getDefinition();
3151
+ if (!definition$1 || !isRef(definition$1)) return errors;
3152
+ const unwound = doc.unwindType(definition$1.id, definition$1.chain);
3153
+ if (!unwound || !isPrimitive(unwound.def)) return errors;
3154
+ const baseType = unwound.def.config.type;
3155
+ if (fnName === "increment" && baseType !== "number") errors.push({
3156
+ message: `@db.default.fn "increment" is not compatible with type "${baseType}" — requires number`,
3157
+ severity: 1,
3158
+ range: token.range
3159
+ });
3160
+ else if (fnName === "uuid" && baseType !== "string") errors.push({
3161
+ message: `@db.default.fn "uuid" is not compatible with type "${baseType}" — requires string`,
3162
+ severity: 1,
3163
+ range: token.range
3164
+ });
3165
+ else if (fnName === "now" && !["number", "string"].includes(baseType)) errors.push({
3166
+ message: `@db.default.fn "now" is not compatible with type "${baseType}" — requires number or string`,
3167
+ severity: 1,
3168
+ range: token.range
3169
+ });
3170
+ return errors;
3171
+ }
3172
+ })
3173
+ },
3174
+ ignore: new AnnotationSpec({
3175
+ description: "Excludes a field from the database schema. The field exists in the Atscript type but has no column in the DB.\n\n**Example:**\n```atscript\n@db.ignore\ndisplayName: string\n```\n",
3176
+ nodeType: ["prop"],
3177
+ validate(token, args, doc) {
3178
+ const errors = [];
3179
+ const field = token.parentNode;
3180
+ if (field.countAnnotations("meta.id") > 0) errors.push({
3181
+ message: `@db.ignore cannot coexist with @meta.id — a field cannot be both a primary key and excluded from the database`,
3182
+ severity: 1,
3183
+ range: token.range
3184
+ });
3185
+ return errors;
3186
+ }
3187
+ })
3188
+ };
3189
+
3051
3190
  //#endregion
3052
3191
  //#region packages/core/src/defaults/emit-annotations.ts
3053
3192
  const emitAnnotations = { jsonSchema: new AnnotationSpec({
@@ -3224,7 +3363,35 @@ const expectAnnotations = {
3224
3363
  }
3225
3364
  return [];
3226
3365
  }
3227
- })
3366
+ }),
3367
+ array: { key: new AnnotationSpec({
3368
+ description: "Marks a **key field** inside an array. This annotation is used to identify unique fields within an array that can be used as **lookup keys**.\n\n\n\n**Example:**\n```atscript\nexport interface User {\n id: string\n profiles: {\n @expect.array.key\n profileId: string\n name: string\n }[]\n}\n```\n",
3369
+ nodeType: ["prop", "type"],
3370
+ multiple: false,
3371
+ validate(token, args, doc) {
3372
+ const field = token.parentNode;
3373
+ const errors = [];
3374
+ const isOptional = !!field.token("optional");
3375
+ if (isOptional) errors.push({
3376
+ message: `@expect.array.key can't be optional`,
3377
+ severity: 1,
3378
+ range: field.token("identifier").range
3379
+ });
3380
+ const definition$1 = field.getDefinition();
3381
+ if (!definition$1) return errors;
3382
+ let wrongType = false;
3383
+ if (isRef(definition$1)) {
3384
+ const def = doc.unwindType(definition$1.id, definition$1.chain)?.def;
3385
+ if (isPrimitive(def) && !["string", "number"].includes(def.config.type)) wrongType = true;
3386
+ } else wrongType = true;
3387
+ if (wrongType) errors.push({
3388
+ message: `@expect.array.key must be of type string or number`,
3389
+ severity: 1,
3390
+ range: token.range
3391
+ });
3392
+ return errors;
3393
+ }
3394
+ }) }
3228
3395
  };
3229
3396
 
3230
3397
  //#endregion
@@ -3239,13 +3406,8 @@ const metaAnnotations = {
3239
3406
  }
3240
3407
  }),
3241
3408
  id: new AnnotationSpec({
3242
- description: "Marks a field as a unique identifier across multiple domains (DB, API, UI, logs).\n\n**Example:**```atscript@meta.id \"userId\"id: string```",
3243
- argument: {
3244
- optional: true,
3245
- name: "name",
3246
- type: "string",
3247
- description: "Custom identifier name (defaults to property name if omitted)."
3248
- }
3409
+ description: "Marks a field as a unique identifier across multiple domains (DB, API, UI, logs). Multiple fields can be annotated with `@meta.id` to form a composite primary key.\n\n**Example:**```atscript@meta.idid: string```",
3410
+ nodeType: ["prop"]
3249
3411
  }),
3250
3412
  description: new AnnotationSpec({
3251
3413
  description: "Provides a **detailed description** of a field or entity, often used in documentation.\n\n**Example:**```atscript@meta.description \"Stores the user email address\"email: string```",
@@ -3264,15 +3426,6 @@ const metaAnnotations = {
3264
3426
  description: "A line of documentation text. Multiple annotations can be used to form a full Markdown document."
3265
3427
  }
3266
3428
  }),
3267
- placeholder: new AnnotationSpec({
3268
- description: "Defines a **default placeholder value** for UI input fields.\n\n**Example:**```atscript@meta.placeholder \"Enter your name\"name: string```",
3269
- nodeType: ["prop", "type"],
3270
- argument: {
3271
- name: "text",
3272
- type: "string",
3273
- description: "The placeholder text to display in UI forms."
3274
- }
3275
- }),
3276
3429
  sensitive: new AnnotationSpec({
3277
3430
  description: "Marks a field as **sensitive** (e.g., passwords, API keys), ensuring it is hidden in logs and UI.\n\n**Example:**```atscript@meta.sensitivepassword: string```",
3278
3431
  nodeType: ["prop", "type"],
@@ -3310,34 +3463,6 @@ const metaAnnotations = {
3310
3463
  type: "string",
3311
3464
  description: "The example value. Strings are used as-is; other types are parsed as JSON."
3312
3465
  }
3313
- }),
3314
- isKey: new AnnotationSpec({
3315
- description: "Marks a **key field** inside an array. This annotation is used to identify unique fields within an array that can be used as **lookup keys**.\n\n\n\n**Example:**\n```atscript\nexport interface User {\n id: string\n profiles: {\n @meta.isKey\n profileId: string\n name: string\n }[]\n}\n```\n",
3316
- nodeType: ["prop", "type"],
3317
- multiple: false,
3318
- validate(token, args, doc) {
3319
- const field = token.parentNode;
3320
- const errors = [];
3321
- const isOptional = !!field.token("optional");
3322
- if (isOptional) errors.push({
3323
- message: `@meta.isKey can't be optional`,
3324
- severity: 1,
3325
- range: field.token("identifier").range
3326
- });
3327
- const definition$1 = field.getDefinition();
3328
- if (!definition$1) return errors;
3329
- let wrongType = false;
3330
- if (isRef(definition$1)) {
3331
- const def = doc.unwindType(definition$1.id, definition$1.chain)?.def;
3332
- if (isPrimitive(def) && !["string", "number"].includes(def.config.type)) wrongType = true;
3333
- } else wrongType = true;
3334
- if (wrongType) errors.push({
3335
- message: `@meta.isKey must be of type string or number`,
3336
- severity: 1,
3337
- range: token.range
3338
- });
3339
- return errors;
3340
- }
3341
3466
  })
3342
3467
  };
3343
3468
 
@@ -3345,11 +3470,11 @@ const metaAnnotations = {
3345
3470
  //#region packages/core/src/defaults/primitives.ts
3346
3471
  const positive = {
3347
3472
  documentation: "Number that greater than or equal to zero.",
3348
- expect: { min: 0 }
3473
+ annotations: { "expect.min": 0 }
3349
3474
  };
3350
3475
  const negative = {
3351
3476
  documentation: "Number that less than or equal to zero.",
3352
- expect: { max: 0 }
3477
+ annotations: { "expect.max": 0 }
3353
3478
  };
3354
3479
  const positiveOrNegative = {
3355
3480
  positive,
@@ -3363,47 +3488,60 @@ const primitives = {
3363
3488
  extensions: {
3364
3489
  email: {
3365
3490
  documentation: "Represents an email address.",
3366
- expect: {
3367
- pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
3491
+ annotations: { "expect.pattern": {
3492
+ pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$",
3368
3493
  message: "Invalid email format."
3369
- }
3494
+ } }
3370
3495
  },
3371
3496
  phone: {
3372
3497
  documentation: "Represents an phone number.",
3373
- expect: {
3374
- pattern: /^\+?[0-9\s-]{10,15}$/,
3498
+ annotations: { "expect.pattern": {
3499
+ pattern: "^\\+?[0-9\\s-]{10,15}$",
3375
3500
  message: "Invalid phone number format."
3376
- }
3501
+ } }
3377
3502
  },
3378
3503
  date: {
3379
3504
  documentation: "Represents a date string.",
3380
- expect: {
3381
- pattern: [
3382
- /^\d{4}-\d{2}-\d{2}$/,
3383
- /^\d{2}\/\d{2}\/\d{4}$/,
3384
- /^\d{2}-\d{2}-\d{4}$/,
3385
- /^\d{1,2} [A-Za-z]+ \d{4}$/
3386
- ],
3387
- message: "Invalid date format."
3388
- }
3505
+ annotations: { "expect.pattern": [
3506
+ {
3507
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
3508
+ message: "Invalid date format."
3509
+ },
3510
+ {
3511
+ pattern: "^\\d{2}/\\d{2}/\\d{4}$",
3512
+ message: "Invalid date format."
3513
+ },
3514
+ {
3515
+ pattern: "^\\d{2}-\\d{2}-\\d{4}$",
3516
+ message: "Invalid date format."
3517
+ },
3518
+ {
3519
+ pattern: "^\\d{1,2} [A-Za-z]+ \\d{4}$",
3520
+ message: "Invalid date format."
3521
+ }
3522
+ ] }
3389
3523
  },
3390
3524
  isoDate: {
3391
3525
  documentation: "Represents a date string in ISO format.",
3392
- expect: {
3393
- pattern: [/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?([+-]\d{2}:\d{2})$/],
3526
+ annotations: { "expect.pattern": [{
3527
+ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z$",
3394
3528
  message: "Invalid ISO date format."
3395
- }
3529
+ }, {
3530
+ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?([+-]\\d{2}:\\d{2})$",
3531
+ message: "Invalid ISO date format."
3532
+ }] }
3396
3533
  },
3397
3534
  uuid: {
3398
3535
  documentation: "Represents a UUID.",
3399
- expect: {
3400
- pattern: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
3536
+ annotations: { "expect.pattern": {
3537
+ pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
3538
+ flags: "i",
3401
3539
  message: "Invalid UUID format."
3402
- }
3540
+ } }
3403
3541
  },
3404
3542
  required: {
3405
3543
  documentation: "Non-empty string that contains at least one non-whitespace character.",
3406
- expect: { required: true }
3544
+ annotations: { "meta.required": true }
3407
3545
  }
3408
3546
  }
3409
3547
  },
@@ -3423,11 +3561,22 @@ const primitives = {
3423
3561
  int: {
3424
3562
  extensions: positiveOrNegative,
3425
3563
  documentation: "Represents an integer number.",
3426
- expect: { int: true }
3564
+ annotations: { "expect.int": true }
3427
3565
  },
3428
3566
  timestamp: {
3429
3567
  documentation: "Represents a timestamp.",
3430
- expect: { int: true }
3568
+ annotations: { "expect.int": true },
3569
+ extensions: {
3570
+ created: {
3571
+ documentation: "Timestamp auto-set on creation. Auto-applies @db.default.fn \"now\".",
3572
+ tags: ["created"],
3573
+ annotations: { "db.default.fn": "now" }
3574
+ },
3575
+ updated: {
3576
+ documentation: "Timestamp auto-updated on every write. DB adapters read the \"updated\" tag.",
3577
+ tags: ["updated"]
3578
+ }
3579
+ }
3431
3580
  }
3432
3581
  }
3433
3582
  },
@@ -3437,7 +3586,7 @@ const primitives = {
3437
3586
  extensions: {
3438
3587
  required: {
3439
3588
  documentation: "Boolean that must be true. Useful for checkboxes like \"accept terms\".",
3440
- expect: { required: true }
3589
+ annotations: { "meta.required": true }
3441
3590
  },
3442
3591
  true: { documentation: "Represents a true value." },
3443
3592
  false: { documentation: "Represents a false value." }
@@ -3461,6 +3610,148 @@ const primitives = {
3461
3610
  }
3462
3611
  };
3463
3612
 
3613
+ //#endregion
3614
+ //#region packages/core/src/defaults/ui-annotations.ts
3615
+ const uiAnnotations = {
3616
+ placeholder: new AnnotationSpec({
3617
+ description: "Defines **placeholder text** for UI input fields.\n\n**Example:**\n```atscript\n@ui.placeholder \"Enter your name\"\nname: string\n```\n",
3618
+ nodeType: ["prop", "type"],
3619
+ argument: {
3620
+ name: "text",
3621
+ type: "string",
3622
+ description: "The placeholder text to display in UI input fields."
3623
+ }
3624
+ }),
3625
+ component: new AnnotationSpec({
3626
+ description: "Hints which **UI component** to use when rendering this field.\n\n**Example:**\n```atscript\n@ui.component \"select\"\ncountry: string\n```\n",
3627
+ nodeType: ["prop", "type"],
3628
+ argument: {
3629
+ name: "name",
3630
+ type: "string",
3631
+ description: "The component name or type to use for rendering."
3632
+ }
3633
+ }),
3634
+ hidden: new AnnotationSpec({
3635
+ description: "Hides this field or entity from **UI forms and tables** entirely.\n\n**Example:**\n```atscript\n@ui.hidden\ninternalId: string\n```\n",
3636
+ nodeType: [
3637
+ "prop",
3638
+ "type",
3639
+ "interface"
3640
+ ]
3641
+ }),
3642
+ group: new AnnotationSpec({
3643
+ description: "Groups fields into **form sections**. Fields sharing the same group name are rendered together.\n\n**Example:**\n```atscript\n@ui.group \"personal\"\nfirstName: string\n\n@ui.group \"personal\"\nlastName: string\n\n@ui.group \"contact\"\nemail: string.email\n```\n",
3644
+ nodeType: ["prop"],
3645
+ argument: {
3646
+ name: "name",
3647
+ type: "string",
3648
+ description: "The section/group name to place this field in."
3649
+ }
3650
+ }),
3651
+ order: new AnnotationSpec({
3652
+ description: "Sets the **display order** of a field in auto-generated forms. Lower numbers appear first.\n\n**Example:**\n```atscript\n@ui.order 1\nname: string\n\n@ui.order 2\nemail: string.email\n```\n",
3653
+ nodeType: ["prop"],
3654
+ argument: {
3655
+ name: "order",
3656
+ type: "number",
3657
+ description: "Display order number. Lower values appear first."
3658
+ }
3659
+ }),
3660
+ width: new AnnotationSpec({
3661
+ description: "Provides a **layout width hint** for the field in auto-generated forms.\n\n**Example:**\n```atscript\n@ui.width \"half\"\nfirstName: string\n```\n",
3662
+ nodeType: ["prop", "type"],
3663
+ argument: {
3664
+ name: "width",
3665
+ type: "string",
3666
+ description: "Layout width hint (e.g., \"half\", \"full\", \"third\", \"quarter\")."
3667
+ }
3668
+ }),
3669
+ icon: new AnnotationSpec({
3670
+ description: "Provides an **icon hint** for the field or entity.\n\n**Example:**\n```atscript\n@ui.icon \"mail\"\nemail: string.email\n```\n",
3671
+ nodeType: [
3672
+ "prop",
3673
+ "type",
3674
+ "interface"
3675
+ ],
3676
+ argument: {
3677
+ name: "name",
3678
+ type: "string",
3679
+ description: "Icon name or identifier."
3680
+ }
3681
+ }),
3682
+ hint: new AnnotationSpec({
3683
+ description: "Provides **help text or tooltip** displayed near the field in UI forms.\n\n**Example:**\n```atscript\n@ui.hint \"Must be a valid business email\"\nemail: string.email\n```\n",
3684
+ nodeType: ["prop", "type"],
3685
+ argument: {
3686
+ name: "text",
3687
+ type: "string",
3688
+ description: "Help text or tooltip content."
3689
+ }
3690
+ }),
3691
+ disabled: new AnnotationSpec({
3692
+ description: "Marks a field as **disabled** (rendered but non-interactive) in UI forms.\n\n**Example:**\n```atscript\n@ui.disabled\nreferralCode: string\n```\n",
3693
+ nodeType: ["prop", "type"]
3694
+ }),
3695
+ type: new AnnotationSpec({
3696
+ description: "Specifies the **input type** for the field in UI forms. Maps to HTML input types or framework-specific equivalents.\n\n**Example:**\n```atscript\n@ui.type \"textarea\"\nbio: string\n\n@ui.type \"password\"\nsecret: string\n```\n",
3697
+ nodeType: ["prop", "type"],
3698
+ argument: {
3699
+ name: "type",
3700
+ type: "string",
3701
+ description: "Input type (e.g., \"text\", \"textarea\", \"password\", \"number\", \"date\", \"color\", \"range\")."
3702
+ }
3703
+ }),
3704
+ attr: new AnnotationSpec({
3705
+ description: "Passes an arbitrary **HTML/component attribute** to the rendered field. Multiple `@ui.attr` annotations can be used on the same field.\n\n**Example:**\n```atscript\n@ui.attr \"rows\", \"5\"\n@ui.attr \"autocomplete\", \"off\"\nbio: string\n```\n",
3706
+ nodeType: [
3707
+ "prop",
3708
+ "type",
3709
+ "interface"
3710
+ ],
3711
+ multiple: true,
3712
+ mergeStrategy: "append",
3713
+ argument: [{
3714
+ name: "key",
3715
+ type: "string",
3716
+ description: "Attribute name."
3717
+ }, {
3718
+ name: "value",
3719
+ type: "string",
3720
+ description: "Attribute value."
3721
+ }]
3722
+ }),
3723
+ class: new AnnotationSpec({
3724
+ description: "Adds **CSS class names** to the rendered field or entity. Multiple `@ui.class` annotations are appended.\n\n**Example:**\n```atscript\n@ui.class \"text-bold\"\n@ui.class \"mt-4\"\ntitle: string\n```\n",
3725
+ nodeType: [
3726
+ "prop",
3727
+ "type",
3728
+ "interface"
3729
+ ],
3730
+ multiple: true,
3731
+ mergeStrategy: "append",
3732
+ argument: {
3733
+ name: "names",
3734
+ type: "string",
3735
+ description: "One or more CSS class names (space-separated)."
3736
+ }
3737
+ }),
3738
+ style: new AnnotationSpec({
3739
+ description: "Adds **inline CSS styles** to the rendered field or entity. Multiple `@ui.style` annotations are appended.\n\n**Example:**\n```atscript\n@ui.style \"color: red\"\n@ui.style \"font-weight: bold\"\nwarning: string\n```\n",
3740
+ nodeType: [
3741
+ "prop",
3742
+ "type",
3743
+ "interface"
3744
+ ],
3745
+ multiple: true,
3746
+ mergeStrategy: "append",
3747
+ argument: {
3748
+ name: "css",
3749
+ type: "string",
3750
+ description: "CSS style declarations (semicolon-separated)."
3751
+ }
3752
+ })
3753
+ };
3754
+
3464
3755
  //#endregion
3465
3756
  //#region packages/core/src/default-atscript-config.ts
3466
3757
  function getDefaultAtscriptConfig() {
@@ -3469,7 +3760,9 @@ function getDefaultAtscriptConfig() {
3469
3760
  annotations: {
3470
3761
  meta: { ...metaAnnotations },
3471
3762
  expect: { ...expectAnnotations },
3472
- emit: { ...emitAnnotations }
3763
+ emit: { ...emitAnnotations },
3764
+ db: { ...dbAnnotations },
3765
+ ui: { ...uiAnnotations }
3473
3766
  }
3474
3767
  };
3475
3768
  return defaulTAtscriptConfig;
@@ -3497,7 +3790,7 @@ var PluginManager = class {
3497
3790
  this._docConfig = {};
3498
3791
  if (raw?.primitives) {
3499
3792
  this._docConfig.primitives = this._docConfig.primitives || new Map();
3500
- for (const [key, value] of Object.entries(raw.primitives)) this._docConfig.primitives.set(key, new SemanticPrimitiveNode(key, value));
3793
+ for (const [key, value] of Object.entries(raw.primitives)) this._docConfig.primitives.set(key, new SemanticPrimitiveNode(key, value, "", raw.annotations));
3501
3794
  }
3502
3795
  if (raw?.annotations) {
3503
3796
  this._docConfig.annotations = this._docConfig.annotations || {};