Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DX: Nicer backtracking errors #8673

Merged
merged 1 commit into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/model/src/-private/record-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,15 @@ class Tag {
declare isDirty: boolean;
declare value: any;
declare t: boolean;
declare _debug_base: string;
declare _debug_prop: string;

constructor() {
if (DEBUG) {
const [base, prop] = arguments as unknown as [string, string];
this._debug_base = base;
this._debug_prop = prop;
}
this.rev = 1;
this.isDirty = true;
this.value = undefined;
Expand Down Expand Up @@ -67,7 +74,8 @@ function getTag(record, key) {
tags = Object.create(null);
Tags.set(record, tags);
}
return (tags[key] = tags[key] || new Tag());
// @ts-expect-error
return (tags[key] = tags[key] || (DEBUG ? new Tag(record.constructor.modelName, key) : new Tag()));
}

export function peekTag(record, key) {
Expand Down
2 changes: 2 additions & 0 deletions packages/store/src/-private/cache-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ function maybeUpdateUiObjects<T>(
return document as T;
}
const data = recordArrayManager.createArray({
type: request.url,
identifiers: document.data,
doc: document as CollectionResourceDataDocument,
query: request,
Expand All @@ -95,6 +96,7 @@ function maybeUpdateUiObjects<T>(

if (!managed) {
managed = recordArrayManager.createArray({
type: identifier.lid,
identifiers: document.data,
doc: document as CollectionResourceDataDocument,
});
Expand Down
14 changes: 12 additions & 2 deletions packages/store/src/-private/record-arrays/identifier-array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const NOTIFY = Symbol('#notify');
const IS_COLLECTION = Symbol.for('Collection');

export function notifyArray(arr: IdentifierArray) {
arr[IDENTIFIER_ARRAY_TAG].ref = null;
addToTransaction(arr[IDENTIFIER_ARRAY_TAG]);

if (DEPRECATE_COMPUTED_CHAINS) {
// eslint-disable-next-line
Expand All @@ -90,8 +90,16 @@ class Tag {
* whether this was part of a transaction when last mutated
*/
declare t: boolean;
declare _debug_base: string;
declare _debug_prop: string;

constructor() {
if (DEBUG) {
const [arr, prop] = arguments as unknown as [IdentifierArray, string];

this._debug_base = arr.constructor.name + ':' + String(arr.modelName);
this._debug_prop = prop;
}
this.shouldReset = false;
this.t = false;
}
Expand Down Expand Up @@ -183,7 +191,7 @@ class IdentifierArray {
_updatingPromise: Promise<IdentifierArray> | null = null;

[IS_COLLECTION] = true;
[IDENTIFIER_ARRAY_TAG] = new Tag();
declare [IDENTIFIER_ARRAY_TAG]: Tag;
[SOURCE]: StableRecordIdentifier[];
[NOTIFY]() {
notifyArray(this);
Expand Down Expand Up @@ -235,6 +243,8 @@ class IdentifierArray {
this.store = options.store;
this._manager = options.manager;
this[SOURCE] = options.identifiers;
// @ts-expect-error
this[IDENTIFIER_ARRAY_TAG] = DEBUG ? new Tag(this, 'length') : new Tag();
const store = options.store;
const boundFns = new Map<KeyType, ProxiedMethod>();
const _TAG = this[IDENTIFIER_ARRAY_TAG];
Expand Down
76 changes: 75 additions & 1 deletion packages/tracking/addon-main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,79 @@
const requireModule = require('@ember-data/private-build-infra/src/utilities/require-module');
const getEnv = require('@ember-data/private-build-infra/src/utilities/get-env');
const detectModule = require('@ember-data/private-build-infra/src/utilities/detect-module');

const pkg = require('./package.json');

module.exports = {
name: require('./package.json').name,
name: pkg.name,

options: {
'@embroider/macros': {
setOwnConfig: {},
},
},

_emberDataConfig: null,
configureEmberData() {
if (this._emberDataConfig) {
return this._emberDataConfig;
}
const app = this._findHost();
const isProd = /production/.test(process.env.EMBER_ENV);
const hostOptions = app.options?.emberData || {};
const debugOptions = Object.assign(
{
LOG_PAYLOADS: false,
LOG_OPERATIONS: false,
LOG_MUTATIONS: false,
LOG_NOTIFICATIONS: false,
LOG_REQUESTS: false,
LOG_REQUEST_STATUS: false,
LOG_IDENTIFIERS: false,
LOG_GRAPH: false,
LOG_INSTANCE_CACHE: false,
},
hostOptions.debug || {}
);

const HAS_DEBUG_PACKAGE = detectModule(require, '@ember-data/debug', __dirname, pkg);
const HAS_META_PACKAGE = detectModule(require, 'ember-data', __dirname, pkg);

const includeDataAdapterInProduction =
typeof hostOptions.includeDataAdapterInProduction === 'boolean'
? hostOptions.includeDataAdapterInProduction
: HAS_META_PACKAGE;

const includeDataAdapter = HAS_DEBUG_PACKAGE ? (isProd ? includeDataAdapterInProduction : true) : false;
const DEPRECATIONS = require('@ember-data/private-build-infra/src/deprecations')(hostOptions.compatWith || null);
const FEATURES = require('@ember-data/private-build-infra/src/features')(isProd);

const ALL_PACKAGES = requireModule('@ember-data/private-build-infra/virtual-packages/packages.js');
const MACRO_PACKAGE_FLAGS = Object.assign({}, ALL_PACKAGES.default);
delete MACRO_PACKAGE_FLAGS['HAS_DEBUG_PACKAGE'];

Object.keys(MACRO_PACKAGE_FLAGS).forEach((key) => {
MACRO_PACKAGE_FLAGS[key] = detectModule(require, MACRO_PACKAGE_FLAGS[key], __dirname, pkg);
});

// copy configs forward
const ownConfig = this.options['@embroider/macros'].setOwnConfig;
ownConfig.compatWith = hostOptions.compatWith || null;
ownConfig.debug = debugOptions;
ownConfig.deprecations = Object.assign(DEPRECATIONS, ownConfig.deprecations || {}, hostOptions.deprecations || {});
ownConfig.features = Object.assign({}, FEATURES, ownConfig.features || {}, hostOptions.features || {});
ownConfig.includeDataAdapter = includeDataAdapter;
ownConfig.packages = MACRO_PACKAGE_FLAGS;
ownConfig.env = getEnv(ownConfig);

this._emberDataConfig = ownConfig;
return ownConfig;
},

included() {
this.configureEmberData();
return this._super.included.call(this, ...arguments);
},

treeForVendor() {
return;
Expand Down
13 changes: 13 additions & 0 deletions packages/tracking/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const macros = require('@ember-data/private-build-infra/src/v2-babel-build-pack');

module.exports = {
plugins: [
...macros,
// '@embroider/macros/src/babel/macros-babel-plugin.js',
['@babel/plugin-transform-runtime', { loose: true }],
['@babel/plugin-transform-typescript', { allowDeclareFields: true }],
['@babel/plugin-proposal-decorators', { legacy: true, loose: true }],
['@babel/plugin-proposal-private-methods', { loose: true }],
['@babel/plugin-proposal-class-properties', { loose: true }],
],
};
8 changes: 0 additions & 8 deletions packages/tracking/babel.config.json

This file was deleted.

18 changes: 13 additions & 5 deletions packages/tracking/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@
"volta": {
"extends": "../../package.json"
},
"dependenciesMeta": {
"@ember-data/private-build-infra": {
"injected": true
}
},
"dependencies": {
"ember-cli-babel": "^7.26.11"
"ember-cli-babel": "^7.26.11",
"@ember-data/private-build-infra": "workspace:5.3.0-alpha.2",
"@embroider/macros": "^1.10.0"
},
"files": [
"addon-main.js",
Expand All @@ -47,12 +54,13 @@
"@babel/core": "^7.22.5",
"@babel/cli": "^7.22.5",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.22.5",
"@babel/plugin-transform-runtime": "^7.22.5",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.22.6",
"@babel/plugin-transform-runtime": "^7.22.6",
"@babel/plugin-transform-typescript": "^7.22.5",
"@babel/preset-env": "^7.22.5",
"@babel/preset-env": "^7.22.6",
"@babel/preset-typescript": "^7.22.5",
"@babel/runtime": "^7.22.5",
"@babel/runtime": "^7.22.6",
"@embroider/addon-dev": "^3.1.1",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-node-resolve": "^15.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/tracking/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default {
// You can augment this if you need to.
output: addon.output(),

external: [],
external: ['@embroider/macros'],

plugins: [
// These are the modules that users should be able to import from your
Expand Down
64 changes: 61 additions & 3 deletions packages/tracking/src/-private.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DEBUG } from '@ember-data/env';

/**
* This package provides primitives that allow powerful low-level
* adjustments to change tracking notification behaviors.
Expand Down Expand Up @@ -43,6 +45,62 @@ export function subscribe(obj: Tag): void {
}
}

function updateRef(obj: Tag): void {
if (DEBUG) {
try {
obj.ref = null;
} catch (e: unknown) {
if (e instanceof Error) {
if (e.message.includes('You attempted to update `ref` on `Tag`')) {
e.message = e.message.replace(
'You attempted to update `ref` on `Tag`',
// @ts-expect-error
`You attempted to update <${obj._debug_base}>.${obj._debug_prop}` // eslint-disable-line
);
e.stack = e.stack?.replace(
'You attempted to update `ref` on `Tag`',
// @ts-expect-error
`You attempted to update <${obj._debug_base}>.${obj._debug_prop}` // eslint-disable-line
);

const lines = e.stack?.split(`\n`);
const finalLines: string[] = [];
let lastFile: string | null = null;

lines?.forEach((line) => {
if (line.trim().startsWith('at ')) {
// get the last string in the line which contains the code source location
const location = line.split(' ').at(-1)!;
// remove the line and char offset info

if (location.includes(':')) {
const parts = location.split(':');
parts.pop();
parts.pop();
const file = parts.join(':');
if (file !== lastFile) {
lastFile = file;
finalLines.push('');
}
}
finalLines.push(line);
}
});

const splitstr = '`ref` was first used:';
const parts = e.message.split(splitstr);
parts.splice(1, 0, `Original Stack\n=============\n${finalLines.join(`\n`)}\n\n${splitstr}`);

e.message = parts.join('');
}
}
throw e;
}
} else {
obj.ref = null;
}
}

function flushTransaction() {
let transaction = TRANSACTION!;
TRANSACTION = transaction.parent;
Expand All @@ -52,7 +110,7 @@ function flushTransaction() {
transaction.props.forEach((obj: Tag) => {
// mark this mutation as part of a transaction
obj.t = true;
obj.ref = null;
updateRef(obj);
});
transaction.sub.forEach((obj: Tag) => {
obj.ref;
Expand All @@ -70,15 +128,15 @@ async function untrack() {
transaction.props.forEach((obj: Tag) => {
// mark this mutation as part of a transaction
obj.t = true;
obj.ref = null;
updateRef(obj);
});
}

export function addToTransaction(obj: Tag): void {
if (TRANSACTION) {
TRANSACTION.props.add(obj);
} else {
obj.ref = null;
updateRef(obj);
}
}
export function addTransactionCB(method: OpaqueFn): void {
Expand Down
Loading