Repository navigation
feat!: prepare Ack 2.0.0-beta.1 release - #220
leoafarias wants to merge 29 commits into
Conversation
…ep-review # Conflicts: # packages/ack/lib/src/constraints/comparison_constraint.dart # packages/ack/lib/src/json_schema/json_schema_compiler.dart # packages/ack/lib/src/schemas/imported_json_schema.dart # packages/ack/test/fixtures/json_schema_import/README.md # packages/ack/test/json_schema_format_import_test.dart # packages/ack/test/json_schema_import_api_test.dart # packages/ack/test/json_schema_import_test.dart # packages/ack_json_schema_builder/test/from_json_schema_builder_test.dart
…way StringSchema normalizers
…and deprecate AckField.schema (#185)
…brary schema detection and simplify 2.0 API
…2.0 and use --offline in build tests
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1ace93ba5c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| @override | ||
| Map<String, Object?> toJsonSchema() { | ||
| final body = {...schema, ..._common.toJson()}; |
There was a problem hiding this comment.
Preserve source allOf when layering conflicting constraints
When an imported schema already contains allOf and a fluent/custom constraint conflicts with another root keyword, _applyConstraints stores the conflicting keyword in extensions['allOf']; this spread then replaces the source document's original allOf. For example, a source containing allOf: [{'maxLength': 10}] and minLength: 2, followed by a constraint exporting minLength: 3, loses the maxLength assertion after toJsonSchema(), so the exported schema accepts values the runtime schema rejects. Merge the generated branch with the source allOf, or preserve it in a separate conjunctive wrapper.
Useful? React with 👍 / 👎.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…-first _<name>Schema
…, and AckSchemaModel hierarchy
…7 lowering, and model codegen
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dcaf31d981
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @@ -9,4 +9,6 @@ const publishableAckPackages = <String>[ | |||
|
|
|||
| /// First releases for packages added after the original workspace baseline. | |||
| /// Earlier versions have no published API to compare against. | |||
| const ackPackageFirstReleases = <String, String>{'ack_mcp_dart': '1.3.0'}; | |||
| const ackPackageFirstReleases = <String, String>{ | |||
| 'ack_mcp_dart': '2.0.0-beta.1', | |||
There was a problem hiding this comment.
Restore the actual ack_mcp_dart first-release version
Keep this value at 1.3.0: ack_mcp_dart was already part of the coordinated 1.3.0–1.6.2 releases, as the repository history and PUBLISHING.md confirm. With 2.0.0-beta.1 here, the API compatibility job skips this package whenever it uses the configured 1.6.2 baseline, so breaking changes in the MCP adapter can pass both PR and release preflight without being examined.
Useful? React with 👍 / 👎.
| return baseFieldsEqual(other) && | ||
| assertFormats == other.assertFormats && | ||
| deepEquals(rawSchema, other.rawSchema); |
There was a problem hiding this comment.
Include referenced resources in imported-schema equality
Compare the compiled resource inputs, not only the root document. Two calls such as Ack.fromJsonSchema({'$ref': 'value.json'}, documents: {valueUri: {'type': 'string'}}) and the same root with valueUri mapped to {'type': 'integer'} currently compare equal and have the same hash even though they accept different values; differing baseUri values can cause the same problem for relative references. This can make schema-keyed caches or collections reuse behavior from a non-equivalent imported schema.
Useful? React with 👍 / 👎.
| if (node['additionalProperties'] == false) { | ||
| node['additionalProperties'] = true; | ||
| widened.add(pointer); |
There was a problem hiding this comment.
Widen unevaluatedProperties in preserve mode
Handle unevaluatedProperties: false as well as additionalProperties: false. For a Draft 2020-12 model that closes an object with unevaluatedProperties, selecting unknown_properties: preserve leaves that assertion unchanged, so generated validated models still reject additive unknown keys despite this option promising to accept and retain them on every object. This is especially relevant for composed schemas, where unevaluatedProperties is the standard way to close properties declared across branches.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5608c4fb2c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (node.allOf case final branches?) { | ||
| for (var i = 0; i < branches.length; i++) { | ||
| final result = _evaluateImportedNode(branches[i], value, currentScope); |
There was a problem hiding this comment.
Honor custom dialects that omit the applicator vocabulary
When a supplied custom dialect declares only the core vocabulary and omits the applicator vocabulary, keywords such as allOf must have no assertion behavior, but this evaluator executes them unconditionally. For example, a schema using that dialect with allOf: [false] rejects every instance even though allOf is unknown under the declared dialect; _configureDialect tracks validation and format vocabularies but never gates applicator or unevaluated keywords. Track those vocabularies and evaluate their keywords only when the dialect includes them.
Useful? React with 👍 / 👎.
| for (final external in _externalRoots) { | ||
| if (external.documentUri != external.baseUri && | ||
| resolved.removeFragment() == external.documentUri) { | ||
| exported[keyword] = external.baseUri |
There was a problem hiding this comment.
Preserve the root retrieval alias during export
When the imported root has a canonical $id different from its baseUri and an external resource refers back to the root through that retrieval URI, this loop rewrites aliases only for _externalRoots. The exported root is identified solely by its canonical $id, while the external resource retains its reference to the now-unregistered root retrieval URI, so Ack.fromJsonSchema(schema.toJsonSchema()) fails with an unresolved reference. Include the root's document/base URI pair in this alias canonicalization as well.
Useful? React with 👍 / 👎.
| final dialect = resources[node.dialectUri]; | ||
| if (dialect == null) { | ||
| _fail( |
There was a problem hiding this comment.
Register the advertised Draft-7 dialect
Every document declaring http(s)://json-schema.org/draft-07/schema(#) now reaches this missing-resource branch because only the 2020-12 meta-schemas are registered. This makes all explicit Draft-7 imports fail with unsupported_dialect, even though this commit's public guide still documents Draft-7 as accepted and the previous importer supported these URI variants. Register or normalize the Draft-7 dialect before performing this lookup.
Useful? React with 👍 / 👎.
| bool _isSupportedJsonSchemaFormat(String format) => | ||
| _jsonSchemaFormats.contains(format); |
There was a problem hiding this comment.
Restore asserted numeric format support
The supported-format lookup no longer contains int32, int64, uint32, uint64, float, or double, although these formats remain documented and were enforced by the previous importer. Consequently, Ack.fromJsonSchema({'type': 'integer', 'format': 'int32'}, assertFormats: true) throws unsupported_format instead of compiling a validator, and the numeric evaluation path has no replacement range checks. Add the numeric formats back to both recognition and numeric validation.
Useful? React with 👍 / 👎.
| return _isFullDate(value.substring(0, 10)) && | ||
| _isFullTime(value.substring(11)); |
There was a problem hiding this comment.
Reject unannounced leap seconds in date-time values
For date-time format assertions, this composition validates only the calendar date and whether :60 maps to UTC minute 23:59; it never checks the bundled announced-leap-second dates. Thus an input such as 2030-06-30T23:59:60Z is now accepted, while the documented behavior and the existing native Ack.string().datetime() validator reject it. Reuse isValidRfc3339DateTime here so imported and native schemas retain the same leap-second semantics.
Useful? React with 👍 / 👎.
| /// | ||
| /// Runs in declaration order with any constraints and refinements chained | ||
| /// before or after this call, on both `parse` and `encode`. | ||
| StringSchema trim() => _withNormalizer(_StringNormalizer.trim); |
There was a problem hiding this comment.
Avoid exporting post-normalization constraints as-is
When a constraint is chained after this normalizer, runtime validation applies it to the normalized value but JSON Schema export still applies the keyword to the original boundary string. For example, Ack.string().trim().minLength(3) rejects ' a ' after trimming it to length 1, while its exported {type: 'string', minLength: 3} accepts the five-character input. This makes schemas advertised to MCP, Firebase, and other JSON Schema consumers disagree with Ack; omit or warn about constraints whose meaning is changed by an earlier normalizer.
Useful? React with 👍 / 👎.
…lidate.notNull and align SDK floor
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b5045ff22b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (final key in mapKeywords) { | ||
| if (rewritten[key] case final Map<String, Object?> children) { | ||
| rewritten[key] = children.map( | ||
| (name, child) => MapEntry(name, visit(child, currentBase)), | ||
| ); |
There was a problem hiding this comment.
Traverse indexed extension-container schemas during relocation
When a nullable imported root reaches a schema through a JSON Pointer in a nonstandard container, this traversal visits only the standard schema-bearing keywords. The nullable rewrite moves that container under /anyOf/0, but nested $ref or $dynamicRef values inside the explicitly indexed schema keep their old root pointers; for example, a catalog entry referring to #/catalog/target makes Ack.fromJsonSchema(schema.nullable().toJsonSchema()) fail with an unresolved reference. Traverse the compiler-indexed schema locations as well.
Useful? React with 👍 / 👎.
| for (final key in [r'$ref', r'$dynamicRef']) { | ||
| final reference = rewritten[key]; | ||
| if (reference is! String) continue; | ||
| final resolved = currentBase.resolve(reference); |
There was a problem hiding this comment.
Preserve dormant malformed references during nullable export
When an imported schema contains a malformed reference in an unreachable $defs entry, the compiler intentionally leaves it dormant and ordinary export preserves it, but adding .nullable() makes this unconditional resolve visit that entry and throw FormatException. Consequently schema.nullable().toJsonSchema() fails for a schema that imported successfully; malformed dormant references should remain unchanged during relocation, as they are in export2020Document.
Useful? React with 👍 / 👎.
| int? integral(Object? value) => | ||
| value is num && value == value.truncate() ? value.toInt() : null; | ||
| if (node['minimum'] case final num n) { | ||
| buffer.write('.min(${integral(n) ?? n.ceil()})'); |
There was a problem hiding this comment.
Fall back before clamping integer bounds
When a .schema.json integer model has a bound outside Dart's integer range, such as minimum: 1e30, toInt() clamps the value instead of reporting that it is unrepresentable. Typed generation therefore emits approximately Ack.integer().min(9223372036854775807), which accepts a representable maximum integer even though the source schema rejects it; fractional bounds can clamp through ceil() or floor() as well. Check representability for all integer bounds and select the validated-value fallback instead.
Useful? React with 👍 / 👎.
| if (keys.contains(r'$ref')) { | ||
| if (keys.length > 1) { | ||
| _fail(pointer, 'Keywords next to "\$ref" are not supported.'); | ||
| return null; | ||
| } | ||
| return _ref(node[r'$ref'], '$pointer/\$ref', owner); |
There was a problem hiding this comment.
Apply use-site descriptions to referenced fields
When a typed .schema.json property uses $ref with a sibling description, this branch returns the referenced schema expression without applying the local describe wrapper. The generated field documentation and toJsonSchema() therefore lose the use-site description (or expose the referenced model's description instead), even though Draft 2020-12 applies annotation siblings alongside $ref; wrap the referenced expression while retaining its dependency metadata.
Useful? React with 👍 / 👎.
|
Superseded by the dedicated |
🎉 Announcing Ack
2.0.0-beta.1Ack 2.0 is a clean-slate architectural release focused on one annotation, one generated part file, bidirectional codecs, full JSON Schema Draft 2020-12 conformance, and zero 1.x baggage.
Every package in the workspace moves together to
2.0.0-beta.1:ack2.0.0-beta.1ack_generator2.0.0-beta.1ack_firebase_ai2.0.0-beta.1ack_json_schema_builder2.0.0-beta.1ack_mcp_dart2.0.0-beta.1✨ What's New in Ack 2.0
1. One Annotation & One Generated Part File (
@Schemable()+.g.dart)ack_generatornow emits models, schemas, value-equality mixins (_$TypeAck), and runtime JSON helpers directly intopart '<file>.g.dart';viasource_gen:combining_builder.json_serializabledependency required:@Schemable()ownsfromJson/toJsongeneration directly against Ack's validated runtime representation, while still coexisting seamlessly in the same.g.dartfile if your library also uses@JsonSerializable()on other classes.@Schemable()everywhere: Whether you write schema-first definitions (@Schemable() final userSchema = Ack.object({...});) or class-first models (@Schemable() final class User with _$UserAck { ... }),@Schemable()is the single entry point._<className>Schema) alongside the<ClassName>Schemafacade so you can bindstatic final schema = _userSchema;directly on your class without repeating the class name.@Schemable(schemas: [...])&@AckField(schema: ...)): Attach customAckSchema<Boundary, Runtime>instances to third-party or value types at the field, class, or library level, or exposestatic final schemaon your own types for automatic discovery.2. Unified
@Validate.*Constraint Namespace (Single Import)@Validate.*namespace exported directly frompackage:ack/ack.dart—no separatepackage:ack/annotations.dartorpackage:ack/format_annotations.dartimport needed, and zero naming collisions withdart:core(Uri,DateTime):@Validate.min(...),@Validate.max(...),@Validate.multipleOf(...),@Validate.positive(),@Validate.negative()@Validate.minLength(...),@Validate.maxLength(...),@Validate.matches(...),@Validate.notEmpty(),@Validate.email(),@Validate.url(),@Validate.uri(),@Validate.uuid(),@Validate.date(),@Validate.dateTime(),@Validate.ip(),@Validate.ipv4(),@Validate.ipv6()@Validate.minItems(...),@Validate.maxItems(...),@Validate.uniqueItems()/@Validate.unique()this.x), and Dart 3.11+ primary constructor parameters (final class User(@Validate.email() final String email) with _$UserAck).3. Bidirectional Codecs & Two-Way String Normalizers
CodecSchema<Boundary, Runtime>replaces one-way transforms as the core type-conversion mechanism, supporting bothparse/safeParse(decode) andencode/safeEncode(encode)..trim(),.toLowerCase(), and.toUpperCase()now stay onStringSchema, execute in declaration order alongside constraints/refinements, round-trip throughencode(), and export cleanly astype: "string"in JSON Schema withoutx-transformed: true.4. Sealed
AckSchema&SchemaErrorHierarchiesAckSchema<Boundary, Runtime>andSchemaError(TypeMismatchError,SchemaConstraintsError,SchemaNestedError,SchemaValidationError,JsonSchemaValidationError) are nowsealed, enabling exhaustiveswitchexpressions across schemas and validation errors without wildcard fallbacks.5. Full JSON Schema Draft 2020-12 Runtime &
.schema.jsonCodegenAck.fromJsonSchema(...)compiles JSON Schema Draft 2020-12 (and Draft-07) documents into executableAckSchemainstances with full support for$id,$anchor,$ref,$dynamicAnchor,$dynamicRef,allOf/anyOf/oneOf/not,if/then/else,dependentSchemas/dependentRequired,prefixItems/items/contains/minContains/maxContains,unevaluatedProperties/unevaluatedItems,propertyNames,patternProperties, and RFC-compliantformatassertions (assertFormats: true).toJsonSchema()&toSchemaModel()preserve Draft 2020-12 by default, with explicittoJsonSchemaDraft7()/toSchemaModelDraft7()helpers when lowering to legacy Draft-07 consumers..schema.jsoncode generator (ack_generator:ack_json_schema&generateAckSchemaLibrary): Compile.schema.jsonassets directly into@Schemable().schema.dartlibraries (whichack_generator:ack_generatorcompiles into.schema.g.dartmodels and schemas).🔥 Breaking Changes & Removals (What Is No Longer Here)
To keep Ack 2.0 lean and unambiguous ("one way to do it"), all deprecated 1.x shims and legacy APIs have been removed:
package:ack_annotations/ack_annotations.dartimport 'package:ack/ack.dart';(re-exports@Schemable,@AckField,@Validate)package:ack/format_annotations.dart@Validate.dateTime(),@Validate.uri(),@Validate.ip(), etc. frompackage:ack/ack.dart@Min,@Max,@Email,@NotEmpty,@MinLength,@MaxLength,@Matches,@Url,@Uuid,@Date,@MinItems,@MaxItems,@UniqueItems,@Positive,@Negative,@MultipleOf@Validate.min(...),@Validate.max(...),@Validate.email(),@Validate.notEmpty(), etc.@JsonKey(name: ...)on@Schemableclasses@AckField(name: '...')@AckModel(),@AckType(),@AckInfer(),@Discriminated()@Schemable()(withdiscriminatorKey/discriminatorValuefor unions)part '<file>.ack.dart';&ack_models/ack_model_jsonbuilderspart '<file>.g.dart';(ack_generator:ack_generatorviasource_gen:combining_builder)SchemaResult.getOrNull()/getOrThrow()/getErrors()result.valueOrNull/switch (result) { case Ok(:final value): ... case Fail(:final error): ... }AckSchema.validate()/validateOrThrow()/tryParse()schema.safeParse(input)/schema.parse(input)Ack.int()/Ack.bool()/Ack.enumString()/Ack.enumCodec()/Ack.discriminated()Ack.integer()/Ack.boolean()/Ack.literal()orAck.enumValues()/Ack.discriminatedObject().nullable(false)/.optional(false)positional booleans &.defaultValue(...).nullable(value: false)/.optional(value: false)&.withDefault(...).strict()/.passthrough()onObjectSchemaAck.object({...}, additionalProperties: false | true)registerAckModelTool(...)inack_mcp_dartregisterAckTool(...)with<Model>.schemaconvertAckSchemaModelToBuilder(...)inack_json_schema_builderschema.toJsonSchemaBuilder()🛠️ Migration Quick-Start (1.x$\rightarrow$ 2.0)
✅ Verification & Consistency Audit
lib/, unused runtime dependencies, and duplicated generator AST/emitter code.fvm dart analyze --fatal-infospasses with 0 issues across all 6 workspace packages (ack,ack_generator,ack_firebase_ai,ack_json_schema_builder,ack_mcp_dart,example).build_runnerintegration suites pass acrossack(1,972 tests),ack_generator(679 tests),ack_json_schema_builder(52 tests),ack_mcp_dart(63 tests),ack_firebase_ai(98 tests),example, andtest/scripts(60 tests).