@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.mjs CHANGED
@@ -409,60 +409,13 @@ else obj[key] = value;
409
409
  var SemanticPrimitiveNode = class SemanticPrimitiveNode extends SemanticNode {
410
410
  applyAnnotations() {
411
411
  this.annotations = [];
412
- if (this.type === "string" || this.type === "array") {
413
- if (typeof this.config.expect?.minLength === "number") this.annotations.push({
414
- name: "expect.minLength",
415
- token: dummyToken,
416
- args: [num(this.config.expect.minLength)]
417
- });
418
- if (typeof this.config.expect?.maxLength === "number") this.annotations.push({
419
- name: "expect.maxLength",
420
- token: dummyToken,
421
- args: [num(this.config.expect.maxLength)]
422
- });
423
- }
424
- if (this.type === "string") {
425
- if (this.config.expect?.required === true) this.annotations.push({
426
- name: "meta.required",
427
- token: dummyToken,
428
- args: []
429
- });
430
- if (this.config.expect?.pattern !== undefined) {
431
- const patterns = Array.isArray(this.config.expect.pattern) ? this.config.expect.pattern : [this.config.expect.pattern];
432
- for (const p of patterns) {
433
- const args = typeof p === "string" ? [text$1(p)] : [text$1(p.source), text$1(p.flags)];
434
- if (this.config.expect.message) args[2] = text$1(this.config.expect.message);
435
- this.annotations.push({
436
- name: "expect.pattern",
437
- token: dummyToken,
438
- args
439
- });
440
- }
441
- }
442
- }
443
- if (this.type === "number") {
444
- if (typeof this.config.expect?.min === "number") this.annotations.push({
445
- name: "expect.min",
446
- token: dummyToken,
447
- args: [num(this.config.expect.min)]
448
- });
449
- if (typeof this.config.expect?.max === "number") this.annotations.push({
450
- name: "expect.max",
451
- token: dummyToken,
452
- args: [num(this.config.expect.max)]
453
- });
454
- if (this.config.expect?.int === true) this.annotations.push({
455
- name: "expect.int",
456
- token: dummyToken,
457
- args: []
458
- });
459
- }
460
- if (this.type === "boolean") {
461
- if (this.config.expect?.required === true) this.annotations.push({
462
- name: "meta.required",
463
- token: dummyToken,
464
- args: []
465
- });
412
+ for (const [name, value] of Object.entries(this.config.annotations || {})) {
413
+ const spec = this.annotationTree ? resolveAnnotation(name, this.annotationTree) : undefined;
414
+ const isMultiple = spec?.config.multiple ?? false;
415
+ if (Array.isArray(value)) {
416
+ if (isMultiple) for (const item of value) this.annotations.push(toAnnotationTokens(name, item, spec));
417
+ else if (value.length > 0) this.annotations.push(toAnnotationTokens(name, value[0], spec));
418
+ } else this.annotations.push(toAnnotationTokens(name, value, spec));
466
419
  }
467
420
  }
468
421
  get key() {
@@ -491,8 +444,8 @@ var SemanticPrimitiveNode = class SemanticPrimitiveNode extends SemanticNode {
491
444
  s += this.renderChildren();
492
445
  return indent + s.split("\n").join(`\n${indent}`);
493
446
  }
494
- constructor(_id, config, parentKey = "") {
495
- 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;
447
+ constructor(_id, config, parentKey = "", annotationTree) {
448
+ 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;
496
449
  this.props = new Map();
497
450
  this.tags = new Set([_id, ...config?.tags || []]);
498
451
  for (const [ext, def] of Object.entries(config.extensions || {})) {
@@ -501,11 +454,11 @@ var SemanticPrimitiveNode = class SemanticPrimitiveNode extends SemanticNode {
501
454
  documentation: def.documentation ?? config.documentation,
502
455
  extensions: def.extensions,
503
456
  tags: Array.from(new Set([...def.tags || [], ...Array.from(this.tags)])),
504
- expect: {
505
- ...config.expect,
506
- ...def.expect
457
+ annotations: {
458
+ ...config.annotations,
459
+ ...def.annotations
507
460
  }
508
- }, this.key);
461
+ }, this.key, this.annotationTree);
509
462
  this.props.set(ext, node);
510
463
  }
511
464
  if (typeof config.type === "object") this.type = config.type.kind === "final" ? config.type.value : config.type.kind;
@@ -513,6 +466,36 @@ else this.type = config.type;
513
466
  this.applyAnnotations();
514
467
  }
515
468
  };
469
+ function toAnnotationTokens(name, value, spec) {
470
+ if (typeof value === "boolean") return {
471
+ name,
472
+ token: dummyToken,
473
+ args: []
474
+ };
475
+ if (typeof value === "string") return {
476
+ name,
477
+ token: dummyToken,
478
+ args: [text$1(value)]
479
+ };
480
+ if (typeof value === "number") return {
481
+ name,
482
+ token: dummyToken,
483
+ args: [num(value)]
484
+ };
485
+ const argNames = spec?.arguments.map((a) => a.name) ?? Object.keys(value);
486
+ const raw = argNames.map((argName) => {
487
+ const v = value[argName];
488
+ if (v === undefined) return undefined;
489
+ return typeof v === "number" ? num(v) : text$1(String(v));
490
+ });
491
+ while (raw.length > 0 && raw[raw.length - 1] === undefined) raw.pop();
492
+ const args = raw.map((t) => t ?? text$1(""));
493
+ return {
494
+ name,
495
+ token: dummyToken,
496
+ args
497
+ };
498
+ }
516
499
  const dummyToken = new Token({
517
500
  getRange: () => ({
518
501
  end: {
@@ -3024,6 +3007,162 @@ const zeroRange = {
3024
3007
  }
3025
3008
  };
3026
3009
 
3010
+ //#endregion
3011
+ //#region packages/core/src/defaults/db-annotations.ts
3012
+ const dbAnnotations = {
3013
+ table: new AnnotationSpec({
3014
+ 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",
3015
+ nodeType: ["interface"],
3016
+ argument: {
3017
+ optional: true,
3018
+ name: "name",
3019
+ type: "string",
3020
+ description: "Table/collection name. If omitted, derived from interface name."
3021
+ }
3022
+ }),
3023
+ schema: new AnnotationSpec({
3024
+ 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",
3025
+ nodeType: ["interface"],
3026
+ argument: {
3027
+ name: "name",
3028
+ type: "string",
3029
+ description: "Schema/namespace name."
3030
+ }
3031
+ }),
3032
+ index: {
3033
+ plain: new AnnotationSpec({
3034
+ 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",
3035
+ nodeType: ["prop"],
3036
+ multiple: true,
3037
+ mergeStrategy: "append",
3038
+ argument: [{
3039
+ optional: true,
3040
+ name: "name",
3041
+ type: "string",
3042
+ description: "Index name / composite group name."
3043
+ }, {
3044
+ optional: true,
3045
+ name: "sort",
3046
+ type: "string",
3047
+ values: ["asc", "desc"],
3048
+ description: "Sort direction. Defaults to \"asc\"."
3049
+ }]
3050
+ }),
3051
+ unique: new AnnotationSpec({
3052
+ 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",
3053
+ nodeType: ["prop"],
3054
+ multiple: true,
3055
+ mergeStrategy: "append",
3056
+ argument: {
3057
+ optional: true,
3058
+ name: "name",
3059
+ type: "string",
3060
+ description: "Index name / composite group name."
3061
+ }
3062
+ }),
3063
+ fulltext: new AnnotationSpec({
3064
+ 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",
3065
+ nodeType: ["prop"],
3066
+ multiple: true,
3067
+ mergeStrategy: "append",
3068
+ argument: {
3069
+ optional: true,
3070
+ name: "name",
3071
+ type: "string",
3072
+ description: "Index name / composite group name."
3073
+ }
3074
+ })
3075
+ },
3076
+ column: new AnnotationSpec({
3077
+ 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",
3078
+ nodeType: ["prop"],
3079
+ argument: {
3080
+ name: "name",
3081
+ type: "string",
3082
+ description: "The actual column/field name in the DB."
3083
+ }
3084
+ }),
3085
+ default: {
3086
+ value: new AnnotationSpec({
3087
+ 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",
3088
+ nodeType: ["prop"],
3089
+ argument: {
3090
+ name: "value",
3091
+ type: "string",
3092
+ description: "Static default value. Strings used as-is; other types parsed via JSON.parse()."
3093
+ }
3094
+ }),
3095
+ fn: new AnnotationSpec({
3096
+ 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",
3097
+ nodeType: ["prop"],
3098
+ argument: {
3099
+ name: "fn",
3100
+ type: "string",
3101
+ values: [
3102
+ "increment",
3103
+ "uuid",
3104
+ "now"
3105
+ ],
3106
+ description: "Generation function name: \"increment\", \"uuid\", or \"now\"."
3107
+ },
3108
+ validate(token, args, doc) {
3109
+ const errors = [];
3110
+ if (!args[0]) return errors;
3111
+ const fnName = args[0].text;
3112
+ const validFns = [
3113
+ "increment",
3114
+ "uuid",
3115
+ "now"
3116
+ ];
3117
+ if (!validFns.includes(fnName)) {
3118
+ errors.push({
3119
+ message: `Unknown @db.default.fn "${fnName}" — expected "increment", "uuid", or "now"`,
3120
+ severity: 1,
3121
+ range: args[0].range
3122
+ });
3123
+ return errors;
3124
+ }
3125
+ const field = token.parentNode;
3126
+ const definition$1 = field.getDefinition();
3127
+ if (!definition$1 || !isRef(definition$1)) return errors;
3128
+ const unwound = doc.unwindType(definition$1.id, definition$1.chain);
3129
+ if (!unwound || !isPrimitive(unwound.def)) return errors;
3130
+ const baseType = unwound.def.config.type;
3131
+ if (fnName === "increment" && baseType !== "number") errors.push({
3132
+ message: `@db.default.fn "increment" is not compatible with type "${baseType}" — requires number`,
3133
+ severity: 1,
3134
+ range: token.range
3135
+ });
3136
+ else if (fnName === "uuid" && baseType !== "string") errors.push({
3137
+ message: `@db.default.fn "uuid" is not compatible with type "${baseType}" — requires string`,
3138
+ severity: 1,
3139
+ range: token.range
3140
+ });
3141
+ else if (fnName === "now" && !["number", "string"].includes(baseType)) errors.push({
3142
+ message: `@db.default.fn "now" is not compatible with type "${baseType}" — requires number or string`,
3143
+ severity: 1,
3144
+ range: token.range
3145
+ });
3146
+ return errors;
3147
+ }
3148
+ })
3149
+ },
3150
+ ignore: new AnnotationSpec({
3151
+ 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",
3152
+ nodeType: ["prop"],
3153
+ validate(token, args, doc) {
3154
+ const errors = [];
3155
+ const field = token.parentNode;
3156
+ if (field.countAnnotations("meta.id") > 0) errors.push({
3157
+ message: `@db.ignore cannot coexist with @meta.id — a field cannot be both a primary key and excluded from the database`,
3158
+ severity: 1,
3159
+ range: token.range
3160
+ });
3161
+ return errors;
3162
+ }
3163
+ })
3164
+ };
3165
+
3027
3166
  //#endregion
3028
3167
  //#region packages/core/src/defaults/emit-annotations.ts
3029
3168
  const emitAnnotations = { jsonSchema: new AnnotationSpec({
@@ -3200,7 +3339,35 @@ const expectAnnotations = {
3200
3339
  }
3201
3340
  return [];
3202
3341
  }
3203
- })
3342
+ }),
3343
+ array: { key: new AnnotationSpec({
3344
+ 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",
3345
+ nodeType: ["prop", "type"],
3346
+ multiple: false,
3347
+ validate(token, args, doc) {
3348
+ const field = token.parentNode;
3349
+ const errors = [];
3350
+ const isOptional = !!field.token("optional");
3351
+ if (isOptional) errors.push({
3352
+ message: `@expect.array.key can't be optional`,
3353
+ severity: 1,
3354
+ range: field.token("identifier").range
3355
+ });
3356
+ const definition$1 = field.getDefinition();
3357
+ if (!definition$1) return errors;
3358
+ let wrongType = false;
3359
+ if (isRef(definition$1)) {
3360
+ const def = doc.unwindType(definition$1.id, definition$1.chain)?.def;
3361
+ if (isPrimitive(def) && !["string", "number"].includes(def.config.type)) wrongType = true;
3362
+ } else wrongType = true;
3363
+ if (wrongType) errors.push({
3364
+ message: `@expect.array.key must be of type string or number`,
3365
+ severity: 1,
3366
+ range: token.range
3367
+ });
3368
+ return errors;
3369
+ }
3370
+ }) }
3204
3371
  };
3205
3372
 
3206
3373
  //#endregion
@@ -3215,13 +3382,8 @@ const metaAnnotations = {
3215
3382
  }
3216
3383
  }),
3217
3384
  id: new AnnotationSpec({
3218
- description: "Marks a field as a unique identifier across multiple domains (DB, API, UI, logs).\n\n**Example:**```atscript@meta.id \"userId\"id: string```",
3219
- argument: {
3220
- optional: true,
3221
- name: "name",
3222
- type: "string",
3223
- description: "Custom identifier name (defaults to property name if omitted)."
3224
- }
3385
+ 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```",
3386
+ nodeType: ["prop"]
3225
3387
  }),
3226
3388
  description: new AnnotationSpec({
3227
3389
  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```",
@@ -3240,15 +3402,6 @@ const metaAnnotations = {
3240
3402
  description: "A line of documentation text. Multiple annotations can be used to form a full Markdown document."
3241
3403
  }
3242
3404
  }),
3243
- placeholder: new AnnotationSpec({
3244
- description: "Defines a **default placeholder value** for UI input fields.\n\n**Example:**```atscript@meta.placeholder \"Enter your name\"name: string```",
3245
- nodeType: ["prop", "type"],
3246
- argument: {
3247
- name: "text",
3248
- type: "string",
3249
- description: "The placeholder text to display in UI forms."
3250
- }
3251
- }),
3252
3405
  sensitive: new AnnotationSpec({
3253
3406
  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```",
3254
3407
  nodeType: ["prop", "type"],
@@ -3286,34 +3439,6 @@ const metaAnnotations = {
3286
3439
  type: "string",
3287
3440
  description: "The example value. Strings are used as-is; other types are parsed as JSON."
3288
3441
  }
3289
- }),
3290
- isKey: new AnnotationSpec({
3291
- 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",
3292
- nodeType: ["prop", "type"],
3293
- multiple: false,
3294
- validate(token, args, doc) {
3295
- const field = token.parentNode;
3296
- const errors = [];
3297
- const isOptional = !!field.token("optional");
3298
- if (isOptional) errors.push({
3299
- message: `@meta.isKey can't be optional`,
3300
- severity: 1,
3301
- range: field.token("identifier").range
3302
- });
3303
- const definition$1 = field.getDefinition();
3304
- if (!definition$1) return errors;
3305
- let wrongType = false;
3306
- if (isRef(definition$1)) {
3307
- const def = doc.unwindType(definition$1.id, definition$1.chain)?.def;
3308
- if (isPrimitive(def) && !["string", "number"].includes(def.config.type)) wrongType = true;
3309
- } else wrongType = true;
3310
- if (wrongType) errors.push({
3311
- message: `@meta.isKey must be of type string or number`,
3312
- severity: 1,
3313
- range: token.range
3314
- });
3315
- return errors;
3316
- }
3317
3442
  })
3318
3443
  };
3319
3444
 
@@ -3321,11 +3446,11 @@ const metaAnnotations = {
3321
3446
  //#region packages/core/src/defaults/primitives.ts
3322
3447
  const positive = {
3323
3448
  documentation: "Number that greater than or equal to zero.",
3324
- expect: { min: 0 }
3449
+ annotations: { "expect.min": 0 }
3325
3450
  };
3326
3451
  const negative = {
3327
3452
  documentation: "Number that less than or equal to zero.",
3328
- expect: { max: 0 }
3453
+ annotations: { "expect.max": 0 }
3329
3454
  };
3330
3455
  const positiveOrNegative = {
3331
3456
  positive,
@@ -3339,47 +3464,60 @@ const primitives = {
3339
3464
  extensions: {
3340
3465
  email: {
3341
3466
  documentation: "Represents an email address.",
3342
- expect: {
3343
- pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
3467
+ annotations: { "expect.pattern": {
3468
+ pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$",
3344
3469
  message: "Invalid email format."
3345
- }
3470
+ } }
3346
3471
  },
3347
3472
  phone: {
3348
3473
  documentation: "Represents an phone number.",
3349
- expect: {
3350
- pattern: /^\+?[0-9\s-]{10,15}$/,
3474
+ annotations: { "expect.pattern": {
3475
+ pattern: "^\\+?[0-9\\s-]{10,15}$",
3351
3476
  message: "Invalid phone number format."
3352
- }
3477
+ } }
3353
3478
  },
3354
3479
  date: {
3355
3480
  documentation: "Represents a date string.",
3356
- expect: {
3357
- pattern: [
3358
- /^\d{4}-\d{2}-\d{2}$/,
3359
- /^\d{2}\/\d{2}\/\d{4}$/,
3360
- /^\d{2}-\d{2}-\d{4}$/,
3361
- /^\d{1,2} [A-Za-z]+ \d{4}$/
3362
- ],
3363
- message: "Invalid date format."
3364
- }
3481
+ annotations: { "expect.pattern": [
3482
+ {
3483
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
3484
+ message: "Invalid date format."
3485
+ },
3486
+ {
3487
+ pattern: "^\\d{2}/\\d{2}/\\d{4}$",
3488
+ message: "Invalid date format."
3489
+ },
3490
+ {
3491
+ pattern: "^\\d{2}-\\d{2}-\\d{4}$",
3492
+ message: "Invalid date format."
3493
+ },
3494
+ {
3495
+ pattern: "^\\d{1,2} [A-Za-z]+ \\d{4}$",
3496
+ message: "Invalid date format."
3497
+ }
3498
+ ] }
3365
3499
  },
3366
3500
  isoDate: {
3367
3501
  documentation: "Represents a date string in ISO format.",
3368
- expect: {
3369
- 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})$/],
3502
+ annotations: { "expect.pattern": [{
3503
+ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z$",
3370
3504
  message: "Invalid ISO date format."
3371
- }
3505
+ }, {
3506
+ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?([+-]\\d{2}:\\d{2})$",
3507
+ message: "Invalid ISO date format."
3508
+ }] }
3372
3509
  },
3373
3510
  uuid: {
3374
3511
  documentation: "Represents a UUID.",
3375
- expect: {
3376
- pattern: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
3512
+ annotations: { "expect.pattern": {
3513
+ pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
3514
+ flags: "i",
3377
3515
  message: "Invalid UUID format."
3378
- }
3516
+ } }
3379
3517
  },
3380
3518
  required: {
3381
3519
  documentation: "Non-empty string that contains at least one non-whitespace character.",
3382
- expect: { required: true }
3520
+ annotations: { "meta.required": true }
3383
3521
  }
3384
3522
  }
3385
3523
  },
@@ -3399,11 +3537,22 @@ const primitives = {
3399
3537
  int: {
3400
3538
  extensions: positiveOrNegative,
3401
3539
  documentation: "Represents an integer number.",
3402
- expect: { int: true }
3540
+ annotations: { "expect.int": true }
3403
3541
  },
3404
3542
  timestamp: {
3405
3543
  documentation: "Represents a timestamp.",
3406
- expect: { int: true }
3544
+ annotations: { "expect.int": true },
3545
+ extensions: {
3546
+ created: {
3547
+ documentation: "Timestamp auto-set on creation. Auto-applies @db.default.fn \"now\".",
3548
+ tags: ["created"],
3549
+ annotations: { "db.default.fn": "now" }
3550
+ },
3551
+ updated: {
3552
+ documentation: "Timestamp auto-updated on every write. DB adapters read the \"updated\" tag.",
3553
+ tags: ["updated"]
3554
+ }
3555
+ }
3407
3556
  }
3408
3557
  }
3409
3558
  },
@@ -3413,7 +3562,7 @@ const primitives = {
3413
3562
  extensions: {
3414
3563
  required: {
3415
3564
  documentation: "Boolean that must be true. Useful for checkboxes like \"accept terms\".",
3416
- expect: { required: true }
3565
+ annotations: { "meta.required": true }
3417
3566
  },
3418
3567
  true: { documentation: "Represents a true value." },
3419
3568
  false: { documentation: "Represents a false value." }
@@ -3437,6 +3586,148 @@ const primitives = {
3437
3586
  }
3438
3587
  };
3439
3588
 
3589
+ //#endregion
3590
+ //#region packages/core/src/defaults/ui-annotations.ts
3591
+ const uiAnnotations = {
3592
+ placeholder: new AnnotationSpec({
3593
+ description: "Defines **placeholder text** for UI input fields.\n\n**Example:**\n```atscript\n@ui.placeholder \"Enter your name\"\nname: string\n```\n",
3594
+ nodeType: ["prop", "type"],
3595
+ argument: {
3596
+ name: "text",
3597
+ type: "string",
3598
+ description: "The placeholder text to display in UI input fields."
3599
+ }
3600
+ }),
3601
+ component: new AnnotationSpec({
3602
+ description: "Hints which **UI component** to use when rendering this field.\n\n**Example:**\n```atscript\n@ui.component \"select\"\ncountry: string\n```\n",
3603
+ nodeType: ["prop", "type"],
3604
+ argument: {
3605
+ name: "name",
3606
+ type: "string",
3607
+ description: "The component name or type to use for rendering."
3608
+ }
3609
+ }),
3610
+ hidden: new AnnotationSpec({
3611
+ description: "Hides this field or entity from **UI forms and tables** entirely.\n\n**Example:**\n```atscript\n@ui.hidden\ninternalId: string\n```\n",
3612
+ nodeType: [
3613
+ "prop",
3614
+ "type",
3615
+ "interface"
3616
+ ]
3617
+ }),
3618
+ group: new AnnotationSpec({
3619
+ 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",
3620
+ nodeType: ["prop"],
3621
+ argument: {
3622
+ name: "name",
3623
+ type: "string",
3624
+ description: "The section/group name to place this field in."
3625
+ }
3626
+ }),
3627
+ order: new AnnotationSpec({
3628
+ 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",
3629
+ nodeType: ["prop"],
3630
+ argument: {
3631
+ name: "order",
3632
+ type: "number",
3633
+ description: "Display order number. Lower values appear first."
3634
+ }
3635
+ }),
3636
+ width: new AnnotationSpec({
3637
+ 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",
3638
+ nodeType: ["prop", "type"],
3639
+ argument: {
3640
+ name: "width",
3641
+ type: "string",
3642
+ description: "Layout width hint (e.g., \"half\", \"full\", \"third\", \"quarter\")."
3643
+ }
3644
+ }),
3645
+ icon: new AnnotationSpec({
3646
+ description: "Provides an **icon hint** for the field or entity.\n\n**Example:**\n```atscript\n@ui.icon \"mail\"\nemail: string.email\n```\n",
3647
+ nodeType: [
3648
+ "prop",
3649
+ "type",
3650
+ "interface"
3651
+ ],
3652
+ argument: {
3653
+ name: "name",
3654
+ type: "string",
3655
+ description: "Icon name or identifier."
3656
+ }
3657
+ }),
3658
+ hint: new AnnotationSpec({
3659
+ 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",
3660
+ nodeType: ["prop", "type"],
3661
+ argument: {
3662
+ name: "text",
3663
+ type: "string",
3664
+ description: "Help text or tooltip content."
3665
+ }
3666
+ }),
3667
+ disabled: new AnnotationSpec({
3668
+ 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",
3669
+ nodeType: ["prop", "type"]
3670
+ }),
3671
+ type: new AnnotationSpec({
3672
+ 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",
3673
+ nodeType: ["prop", "type"],
3674
+ argument: {
3675
+ name: "type",
3676
+ type: "string",
3677
+ description: "Input type (e.g., \"text\", \"textarea\", \"password\", \"number\", \"date\", \"color\", \"range\")."
3678
+ }
3679
+ }),
3680
+ attr: new AnnotationSpec({
3681
+ 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",
3682
+ nodeType: [
3683
+ "prop",
3684
+ "type",
3685
+ "interface"
3686
+ ],
3687
+ multiple: true,
3688
+ mergeStrategy: "append",
3689
+ argument: [{
3690
+ name: "key",
3691
+ type: "string",
3692
+ description: "Attribute name."
3693
+ }, {
3694
+ name: "value",
3695
+ type: "string",
3696
+ description: "Attribute value."
3697
+ }]
3698
+ }),
3699
+ class: new AnnotationSpec({
3700
+ 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",
3701
+ nodeType: [
3702
+ "prop",
3703
+ "type",
3704
+ "interface"
3705
+ ],
3706
+ multiple: true,
3707
+ mergeStrategy: "append",
3708
+ argument: {
3709
+ name: "names",
3710
+ type: "string",
3711
+ description: "One or more CSS class names (space-separated)."
3712
+ }
3713
+ }),
3714
+ style: new AnnotationSpec({
3715
+ 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",
3716
+ nodeType: [
3717
+ "prop",
3718
+ "type",
3719
+ "interface"
3720
+ ],
3721
+ multiple: true,
3722
+ mergeStrategy: "append",
3723
+ argument: {
3724
+ name: "css",
3725
+ type: "string",
3726
+ description: "CSS style declarations (semicolon-separated)."
3727
+ }
3728
+ })
3729
+ };
3730
+
3440
3731
  //#endregion
3441
3732
  //#region packages/core/src/default-atscript-config.ts
3442
3733
  function getDefaultAtscriptConfig() {
@@ -3445,7 +3736,9 @@ function getDefaultAtscriptConfig() {
3445
3736
  annotations: {
3446
3737
  meta: { ...metaAnnotations },
3447
3738
  expect: { ...expectAnnotations },
3448
- emit: { ...emitAnnotations }
3739
+ emit: { ...emitAnnotations },
3740
+ db: { ...dbAnnotations },
3741
+ ui: { ...uiAnnotations }
3449
3742
  }
3450
3743
  };
3451
3744
  return defaulTAtscriptConfig;
@@ -3473,7 +3766,7 @@ var PluginManager = class {
3473
3766
  this._docConfig = {};
3474
3767
  if (raw?.primitives) {
3475
3768
  this._docConfig.primitives = this._docConfig.primitives || new Map();
3476
- for (const [key, value] of Object.entries(raw.primitives)) this._docConfig.primitives.set(key, new SemanticPrimitiveNode(key, value));
3769
+ for (const [key, value] of Object.entries(raw.primitives)) this._docConfig.primitives.set(key, new SemanticPrimitiveNode(key, value, "", raw.annotations));
3477
3770
  }
3478
3771
  if (raw?.annotations) {
3479
3772
  this._docConfig.annotations = this._docConfig.annotations || {};