Skip to content

feat: Add ember-resources integration via resource factories#3

Draft
johanrd wants to merge 2 commits intov4from
ember-resources/resource-factory-wrappers
Draft

feat: Add ember-resources integration via resource factories#3
johanrd wants to merge 2 commits intov4from
ember-resources/resource-factory-wrappers

Conversation

@johanrd
Copy link
Owner

@johanrd johanrd commented Feb 10, 2026

Summary

  • Add queryResource, mutationResource, and subscriptionResource factory
    functions exported from glimmer-apollo/resource-factories
  • Enable Apollo operations in template-only components via {{#let}} and
    in class-based components via the @use decorator from ember-resources
  • ember-resources is an optional peer dependency — existing usage of
    useQuery, useMutation, and useSubscription is completely unchanged

Motivation

Today, using Apollo with glimmer-apollo requires a backing class for every
component that fetches data. The ember-resources ecosystem provides a
resource() / resourceFactory() primitive that enables reactive data
patterns directly in templates. This PR bridges glimmer-apollo's existing
Resource classes into that ecosystem without modifying any existing code.

Design

A single wrapResource() helper wraps any glimmer-apollo Resource subclass
(QueryResource, MutationResource, SubscriptionResource) into an
ember-resources resource(). Lifecycle (setup/update/teardown) is derived
from the prototype, mirroring ResourceManager.createHelper. A Proxy
entangles property access with autotracking, matching the existing
useResource pattern.

Each factory supports two calling conventions:

Convention Syntax Use case
Direct args queryResource(QUERY, opts) {{#let}} in templates
Thunk queryResource(() => [QUERY, opts]) @use decorator

Alternative approach

Full integration with ember-resources, where QueryResource, MutationResource, and SubscriptionResource are rewritten as native resource() functions instead of wrapping the existing class-based Resources. This would give native @use support and composability (via ember-resources' use() hook) without needing the Proxy bridge layer. However, it would also change the ergonomics and APIs, maybe not even a good fit for all current glimmer-apollo-resources.

Test plan

  • pnpm test passes in test-app (unit + integration)
  • Build succeeds (pnpm build in glimmer-apollo)
  • Verify import from glimmer-apollo/resource-factories resolves
  • Verify no impact when ember-resources is not installed (existing
    imports from glimmer-apollo still work)

Cowritten by claude

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional ember-resources integration layer to glimmer-apollo by introducing resource factory entrypoints that wrap existing QueryResource, MutationResource, and SubscriptionResource classes for use in templates and via the @use decorator.

Changes:

  • Introduces queryResource / mutationResource / subscriptionResource plus curried create*Resource helpers under glimmer-apollo/resource-factories.
  • Exposes the new subpath export and Rollup public entrypoint, with ember-resources as an optional peer dependency.
  • Adds unit + integration tests and end-user docs for resource factory usage patterns.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test-app/tests/unit/resource-factory-test.ts Adds unit coverage for the new resource factories (query/mutation/subscription + curried variants).
test-app/tests/unit/query-test.ts Adds a regression test around refetch() behavior when a query starts skipped.
test-app/tests/integration/components/resource-factory-test.gts Adds rendering tests proving template + @use decorator usage and subscription teardown behavior.
test-app/package.json Adds ember-resources to the test app to exercise the integration.
pnpm-lock.yaml Locks ember-resources dependency resolution.
glimmer-apollo/src/resource-factories.ts Adds the public entrypoint re-exporting the factories from a private module.
glimmer-apollo/src/-private/resource-factories.ts Implements wrapResource + factory APIs using ember-resources and glimmer-apollo Resource classes.
glimmer-apollo/rollup.config.mjs Marks resource-factories.js as a public entrypoint for the addon build.
glimmer-apollo/package.json Adds the ./resource-factories export plus optional peer dep metadata for ember-resources.
docs/fetching/resource-factories.md Adds documentation for factory usage in templates and with @use.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +61 to +66
on.cleanup(() => {
if (hasTeardown) instance.teardown!();
destroy(instance);
});

let updateCache: ReturnType<typeof createCache> | undefined;
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateCache created via createCache() is never destroyed on cleanup. If a consumer holds onto the returned proxy past teardown (or if caches retain tracking tags), the cache can keep references alive and may call instance.update() on a destroyed instance via the proxy's get trap. Consider destroying the cache in on.cleanup (and clearing the reference) before/alongside destroying the instance.

Suggested change
on.cleanup(() => {
if (hasTeardown) instance.teardown!();
destroy(instance);
});
let updateCache: ReturnType<typeof createCache> | undefined;
let updateCache: ReturnType<typeof createCache> | undefined;
on.cleanup(() => {
if (updateCache) {
destroy(updateCache);
updateCache = undefined;
}
if (hasTeardown) instance.teardown!();
destroy(instance);
});

Copilot uses AI. Check for mistakes.
</template>
```

When `@userId` changes, the resource is re-created with the new variables.
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs say the resource is "re-created" when @userId changes, but the implementation for query/subscription uses an internal createCache to call instance.update() and keep a single instance alive. To avoid misleading readers, consider rewording this to say the query re-executes/updates when the variables change (without implying a new resource instance is created).

Suggested change
When `@userId` changes, the resource is re-created with the new variables.
When `@userId` changes, the query re-executes and the resource updates with the new variables.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants